From fa5c86dc384989dc63a1295b2bee452eb4425398 Mon Sep 17 00:00:00 2001 From: Jozef Kralik Date: Tue, 16 Jul 2024 07:53:01 +0000 Subject: [PATCH 01/31] m2m-oauth-server: define RPC calls --- m2m-oauth-server/Makefile | 19 + m2m-oauth-server/pb/README.md | 206 ++++++ m2m-oauth-server/pb/doc.html | 812 ++++++++++++++++++++++ m2m-oauth-server/pb/service.pb.go | 826 +++++++++++++++++++++++ m2m-oauth-server/pb/service.pb.gw.go | 337 +++++++++ m2m-oauth-server/pb/service.proto | 123 ++++ m2m-oauth-server/pb/service.swagger.json | 312 +++++++++ m2m-oauth-server/pb/service_grpc.pb.go | 189 ++++++ m2m-oauth-server/service/token.go | 12 + m2m-oauth-server/uri/uri.go | 1 + 10 files changed, 2837 insertions(+) create mode 100644 m2m-oauth-server/pb/README.md create mode 100644 m2m-oauth-server/pb/doc.html create mode 100644 m2m-oauth-server/pb/service.pb.go create mode 100644 m2m-oauth-server/pb/service.pb.gw.go create mode 100644 m2m-oauth-server/pb/service.proto create mode 100644 m2m-oauth-server/pb/service.swagger.json create mode 100644 m2m-oauth-server/pb/service_grpc.pb.go diff --git a/m2m-oauth-server/Makefile b/m2m-oauth-server/Makefile index a0ae54877..f43d82fa5 100644 --- a/m2m-oauth-server/Makefile +++ b/m2m-oauth-server/Makefile @@ -6,6 +6,9 @@ ifneq ($(BRANCH_TAG),main) LATEST_TAG = $(BRANCH_TAG) endif VERSION_TAG ?= $(LATEST_TAG)-$(shell git rev-parse --short=7 --verify HEAD) +GOPATH ?= $(shell go env GOPATH) +WORKING_DIRECTORY := $(shell pwd) +REPOSITORY_DIRECTORY := $(shell cd .. && pwd) BUILD_COMMIT_DATE ?= $(shell date -u +%FT%TZ --date=@`git show --format='%ct' HEAD --quiet`) BUILD_SHORT_COMMIT ?= $(shell git show --format=%h HEAD --quiet) BUILD_DATE ?= $(shell date -u +%FT%TZ) @@ -41,6 +44,22 @@ push: build-servicecontainer docker push plgd/$(SERVICE_NAME):$(VERSION_TAG) docker push plgd/$(SERVICE_NAME):$(LATEST_TAG) +GOOGLEAPIS_PATH := $(REPOSITORY_DIRECTORY)/dependency/googleapis +GRPCGATEWAY_MODULE_PATH := $(shell go list -m -f '{{.Dir}}' github.com/grpc-ecosystem/grpc-gateway/v2 | head -1) + proto/generate: + protoc -I=. -I=$(REPOSITORY_DIRECTORY) -I=$(GOPATH)/src -I=$(GOOGLEAPIS_PATH) -I=$(GRPCGATEWAY_MODULE_PATH) --go_out=$(GOPATH)/src $(WORKING_DIRECTORY)/pb/service.proto + protoc-go-inject-tag -remove_tag_comment -input=$(WORKING_DIRECTORY)/pb/service.pb.go + protoc -I=. -I=$(REPOSITORY_DIRECTORY) -I=$(GOPATH)/src -I=$(GOOGLEAPIS_PATH) -I=$(GRPCGATEWAY_MODULE_PATH) --openapiv2_out=$(REPOSITORY_DIRECTORY) \ + --openapiv2_opt logtostderr=true \ + $(WORKING_DIRECTORY)/pb/service.proto + protoc -I=. -I=$(REPOSITORY_DIRECTORY) -I=$(GOPATH)/src -I=$(GOOGLEAPIS_PATH) -I=$(GRPCGATEWAY_MODULE_PATH) --grpc-gateway_out=$(REPOSITORY_DIRECTORY) \ + --grpc-gateway_opt logtostderr=true \ + --grpc-gateway_opt paths=source_relative \ + $(WORKING_DIRECTORY)/pb/service.proto + protoc -I=. -I=$(REPOSITORY_DIRECTORY) -I=$(GOPATH)/src -I=$(GOOGLEAPIS_PATH) -I=$(GRPCGATEWAY_MODULE_PATH) --go-grpc_out=$(GOPATH)/src \ + $(WORKING_DIRECTORY)/pb/service.proto + protoc -I=. -I=$(REPOSITORY_DIRECTORY) -I=$(GOPATH)/src -I=$(GOOGLEAPIS_PATH) -I=$(GRPCGATEWAY_MODULE_PATH) --doc_out=$(WORKING_DIRECTORY)/pb --doc_opt=markdown,README.md $(WORKING_DIRECTORY)/pb/*.proto + protoc -I=. -I=$(REPOSITORY_DIRECTORY) -I=$(GOPATH)/src -I=$(GOOGLEAPIS_PATH) -I=$(GRPCGATEWAY_MODULE_PATH) --doc_out=$(WORKING_DIRECTORY)/pb --doc_opt=html,doc.html $(WORKING_DIRECTORY)/pb/*.proto .PHONY: build-servicecontainer build push proto/generate diff --git a/m2m-oauth-server/pb/README.md b/m2m-oauth-server/pb/README.md new file mode 100644 index 000000000..2d0ea5342 --- /dev/null +++ b/m2m-oauth-server/pb/README.md @@ -0,0 +1,206 @@ +# Protocol Documentation + + +## Table of Contents + +- [m2m-oauth-server/pb/service.proto](#m2m-oauth-server_pb_service-proto) + - [BlacklistTokensRequest](#m2moauthserver-pb-BlacklistTokensRequest) + - [BlacklistTokensResponse](#m2moauthserver-pb-BlacklistTokensResponse) + - [GetBlacklistedTokensRequest](#m2moauthserver-pb-GetBlacklistedTokensRequest) + - [GetBlacklistedTokensResponse](#m2moauthserver-pb-GetBlacklistedTokensResponse) + - [GetBlacklistedTokensResponse.Item](#m2moauthserver-pb-GetBlacklistedTokensResponse-Item) + - [GetTokensRequest](#m2moauthserver-pb-GetTokensRequest) + - [Token](#m2moauthserver-pb-Token) + - [Token.BlackListed](#m2moauthserver-pb-Token-BlackListed) + + - [M2MOAuthServer](#m2moauthserver-pb-M2MOAuthServer) + +- [Scalar Value Types](#scalar-value-types) + + + + +

Top

+ +## m2m-oauth-server/pb/service.proto + + + + + +### BlacklistTokensRequest + + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| id_filter | [string](#string) | repeated | | + + + + + + + + +### BlacklistTokensResponse + + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| id | [string](#string) | repeated | | + + + + + + + + +### GetBlacklistedTokensRequest + + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| version | [uint64](#uint64) | | Returns all blacklisted/revoked not expired tokens of the owner from the given version | + + + + + + + + +### GetBlacklistedTokensResponse + + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| black_list | [GetBlacklistedTokensResponse.Item](#m2moauthserver-pb-GetBlacklistedTokensResponse-Item) | repeated | | +| version | [uint64](#uint64) | | The biggest version of the blacklisted/revoked token over all tokens of owner | + + + + + + + + +### GetBlacklistedTokensResponse.Item + + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| audience | [string](#string) | | | +| id | [string](#string) | | | + + + + + + + + +### GetTokensRequest + + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| id_filter | [string](#string) | repeated | | +| audience_filter | [string](#string) | repeated | | +| include_blacklisted | [bool](#bool) | | | + + + + + + + + +### Token +Tokens are deleted from DB after they are expired and blacklisted/revoked + +driven by resource change event + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| id | [string](#string) | | Token ID / jti | +| version | [uint64](#uint64) | | Incremetal version for update | +| name | [string](#string) | | User-friendly token name | +| owner | [string](#string) | | Owner of the token | +| timestamp | [int64](#int64) | | Unix timestamp in ns when the condition has been created/updated | +| audience | [string](#string) | repeated | Token Audience | +| scopes | [string](#string) | repeated | Token scopes | +| expiration | [int64](#int64) | | Original token expiration | +| original_token_claims | [google.protobuf.Value](#google-protobuf-Value) | | Original token claims | +| blacklisted | [Token.BlackListed](#m2moauthserver-pb-Token-BlackListed) | | Token black list section | + + + + + + + + +### Token.BlackListed + + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| flag | [bool](#bool) | | Blacklisted/revoked enabled flag, if once token has been blacklisted/revoked then it can't be unblacklisted/unrevoked | +| timestamp | [int64](#int64) | | Unix timestamp in ns when the token has been blacklisted/revoked | +| version | [uint64](#uint64) | | Incremental version of the blacklisted/revoked token over all tokens of owner | + + + + + + + + + + + + + + +### M2MOAuthServer + + +| Method Name | Request Type | Response Type | Description | +| ----------- | ------------ | ------------- | ------------| +| GetTokens | [GetTokensRequest](#m2moauthserver-pb-GetTokensRequest) | [Token](#m2moauthserver-pb-Token) | Returns all tokens of the owner | +| GetBlacklistedTokens | [GetBlacklistedTokensRequest](#m2moauthserver-pb-GetBlacklistedTokensRequest) | [GetBlacklistedTokensResponse](#m2moauthserver-pb-GetBlacklistedTokensResponse) | Returns all blacklisted/revoked not expired tokens | +| BlacklistTokens | [BlacklistTokensRequest](#m2moauthserver-pb-BlacklistTokensRequest) | [BlacklistTokensResponse](#m2moauthserver-pb-BlacklistTokensResponse) | Blacklists/revokes tokens | + + + + + +## Scalar Value Types + +| .proto Type | Notes | C++ | Java | Python | Go | C# | PHP | Ruby | +| ----------- | ----- | --- | ---- | ------ | -- | -- | --- | ---- | +| double | | double | double | float | float64 | double | float | Float | +| float | | float | float | float | float32 | float | float | Float | +| int32 | Uses variable-length encoding. Inefficient for encoding negative numbers – if your field is likely to have negative values, use sint32 instead. | int32 | int | int | int32 | int | integer | Bignum or Fixnum (as required) | +| int64 | Uses variable-length encoding. Inefficient for encoding negative numbers – if your field is likely to have negative values, use sint64 instead. | int64 | long | int/long | int64 | long | integer/string | Bignum | +| uint32 | Uses variable-length encoding. | uint32 | int | int/long | uint32 | uint | integer | Bignum or Fixnum (as required) | +| uint64 | Uses variable-length encoding. | uint64 | long | int/long | uint64 | ulong | integer/string | Bignum or Fixnum (as required) | +| sint32 | Uses variable-length encoding. Signed int value. These more efficiently encode negative numbers than regular int32s. | int32 | int | int | int32 | int | integer | Bignum or Fixnum (as required) | +| sint64 | Uses variable-length encoding. Signed int value. These more efficiently encode negative numbers than regular int64s. | int64 | long | int/long | int64 | long | integer/string | Bignum | +| fixed32 | Always four bytes. More efficient than uint32 if values are often greater than 2^28. | uint32 | int | int | uint32 | uint | integer | Bignum or Fixnum (as required) | +| fixed64 | Always eight bytes. More efficient than uint64 if values are often greater than 2^56. | uint64 | long | int/long | uint64 | ulong | integer/string | Bignum | +| sfixed32 | Always four bytes. | int32 | int | int | int32 | int | integer | Bignum or Fixnum (as required) | +| sfixed64 | Always eight bytes. | int64 | long | int/long | int64 | long | integer/string | Bignum | +| bool | | bool | boolean | boolean | bool | bool | boolean | TrueClass/FalseClass | +| string | A string must always contain UTF-8 encoded or 7-bit ASCII text. | string | String | str/unicode | string | string | string | String (UTF-8) | +| bytes | May contain any arbitrary sequence of bytes. | string | ByteString | str | []byte | ByteString | string | String (ASCII-8BIT) | + diff --git a/m2m-oauth-server/pb/doc.html b/m2m-oauth-server/pb/doc.html new file mode 100644 index 000000000..9d227701c --- /dev/null +++ b/m2m-oauth-server/pb/doc.html @@ -0,0 +1,812 @@ + + + + + Protocol Documentation + + + + + + + + + + +

Protocol Documentation

+ +

Table of Contents

+ +
+ +
+ + + +
+

m2m-oauth-server/pb/service.proto

Top +
+

+ + +

BlacklistTokensRequest

+

+ + + + + + + + + + + + + + + + +
FieldTypeLabelDescription
id_filterstringrepeated

+ + + + + +

BlacklistTokensResponse

+

+ + + + + + + + + + + + + + + + +
FieldTypeLabelDescription
idstringrepeated

+ + + + + +

GetBlacklistedTokensRequest

+

+ + + + + + + + + + + + + + + + +
FieldTypeLabelDescription
versionuint64

Returns all blacklisted/revoked not expired tokens of the owner from the given version

+ + + + + +

GetBlacklistedTokensResponse

+

+ + + + + + + + + + + + + + + + + + + + + + + +
FieldTypeLabelDescription
black_listGetBlacklistedTokensResponse.Itemrepeated

versionuint64

The biggest version of the blacklisted/revoked token over all tokens of owner

+ + + + + +

GetBlacklistedTokensResponse.Item

+

+ + + + + + + + + + + + + + + + + + + + + + + +
FieldTypeLabelDescription
audiencestring

idstring

+ + + + + +

GetTokensRequest

+

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldTypeLabelDescription
id_filterstringrepeated

audience_filterstringrepeated

include_blacklistedbool

+ + + + + +

Token

+

Tokens are deleted from DB after they are expired and blacklisted/revoked

driven by resource change event

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldTypeLabelDescription
idstring

Token ID / jti

versionuint64

Incremetal version for update

namestring

User-friendly token name

ownerstring

Owner of the token

timestampint64

Unix timestamp in ns when the condition has been created/updated

audiencestringrepeated

Token Audience

scopesstringrepeated

Token scopes

expirationint64

Original token expiration

original_token_claimsgoogle.protobuf.Value

Original token claims

blacklistedToken.BlackListed

Token black list section

+ + + + + +

Token.BlackListed

+

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldTypeLabelDescription
flagbool

Blacklisted/revoked enabled flag, if once token has been blacklisted/revoked then it can't be unblacklisted/unrevoked

timestampint64

Unix timestamp in ns when the token has been blacklisted/revoked

versionuint64

Incremental version of the blacklisted/revoked token over all tokens of owner

+ + + + + + + + + + + +

M2MOAuthServer

+

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Method NameRequest TypeResponse TypeDescription
GetTokensGetTokensRequestToken

Returns all tokens of the owner

GetBlacklistedTokensGetBlacklistedTokensRequestGetBlacklistedTokensResponse

Returns all blacklisted/revoked not expired tokens

BlacklistTokensBlacklistTokensRequestBlacklistTokensResponse

Blacklists/revokes tokens

+ + + + +

Methods with HTTP bindings

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Method NameMethodPatternBody
GetTokensGET/m2m-oauth-server/api/v1/tokens
GetBlacklistedTokensGET/m2m-oauth-server/api/v1/blacklist
BlacklistTokensPOST/m2m-oauth-server/api/v1/blacklist*
+ + + + +

Scalar Value Types

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
.proto TypeNotesC++JavaPythonGoC#PHPRuby
doubledoubledoublefloatfloat64doublefloatFloat
floatfloatfloatfloatfloat32floatfloatFloat
int32Uses variable-length encoding. Inefficient for encoding negative numbers – if your field is likely to have negative values, use sint32 instead.int32intintint32intintegerBignum or Fixnum (as required)
int64Uses variable-length encoding. Inefficient for encoding negative numbers – if your field is likely to have negative values, use sint64 instead.int64longint/longint64longinteger/stringBignum
uint32Uses variable-length encoding.uint32intint/longuint32uintintegerBignum or Fixnum (as required)
uint64Uses variable-length encoding.uint64longint/longuint64ulonginteger/stringBignum or Fixnum (as required)
sint32Uses variable-length encoding. Signed int value. These more efficiently encode negative numbers than regular int32s.int32intintint32intintegerBignum or Fixnum (as required)
sint64Uses variable-length encoding. Signed int value. These more efficiently encode negative numbers than regular int64s.int64longint/longint64longinteger/stringBignum
fixed32Always four bytes. More efficient than uint32 if values are often greater than 2^28.uint32intintuint32uintintegerBignum or Fixnum (as required)
fixed64Always eight bytes. More efficient than uint64 if values are often greater than 2^56.uint64longint/longuint64ulonginteger/stringBignum
sfixed32Always four bytes.int32intintint32intintegerBignum or Fixnum (as required)
sfixed64Always eight bytes.int64longint/longint64longinteger/stringBignum
boolboolbooleanbooleanboolboolbooleanTrueClass/FalseClass
stringA string must always contain UTF-8 encoded or 7-bit ASCII text.stringStringstr/unicodestringstringstringString (UTF-8)
bytesMay contain any arbitrary sequence of bytes.stringByteStringstr[]byteByteStringstringString (ASCII-8BIT)
+ + + diff --git a/m2m-oauth-server/pb/service.pb.go b/m2m-oauth-server/pb/service.pb.go new file mode 100644 index 000000000..f9f38bc93 --- /dev/null +++ b/m2m-oauth-server/pb/service.pb.go @@ -0,0 +1,826 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.34.1 +// protoc v5.26.1 +// source: m2m-oauth-server/pb/service.proto + +package pb + +import ( + _ "github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2/options" + _ "google.golang.org/genproto/googleapis/api/annotations" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + structpb "google.golang.org/protobuf/types/known/structpb" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +// Tokens are deleted from DB after they are expired and blacklisted/revoked +type Token struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Token ID / jti + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + // Incremetal version for update + Version uint64 `protobuf:"varint,2,opt,name=version,proto3" json:"version,omitempty"` + // User-friendly token name + Name string `protobuf:"bytes,3,opt,name=name,proto3" json:"name,omitempty"` + // Owner of the token + Owner string `protobuf:"bytes,4,opt,name=owner,proto3" json:"owner,omitempty"` + // Unix timestamp in ns when the condition has been created/updated + Timestamp int64 `protobuf:"varint,5,opt,name=timestamp,proto3" json:"timestamp,omitempty"` + // Token Audience + Audience []string `protobuf:"bytes,6,rep,name=audience,proto3" json:"audience,omitempty"` + // Token scopes + Scopes []string `protobuf:"bytes,7,rep,name=scopes,proto3" json:"scopes,omitempty"` + // Original token expiration + Expiration int64 `protobuf:"varint,8,opt,name=expiration,proto3" json:"expiration,omitempty"` + // Original token claims + OriginalTokenClaims *structpb.Value `protobuf:"bytes,9,opt,name=original_token_claims,json=originalTokenClaims,proto3" json:"original_token_claims,omitempty"` + // Token black list section + Blacklisted *Token_BlackListed `protobuf:"bytes,10,opt,name=blacklisted,proto3" json:"blacklisted,omitempty"` +} + +func (x *Token) Reset() { + *x = Token{} + if protoimpl.UnsafeEnabled { + mi := &file_m2m_oauth_server_pb_service_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Token) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Token) ProtoMessage() {} + +func (x *Token) ProtoReflect() protoreflect.Message { + mi := &file_m2m_oauth_server_pb_service_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Token.ProtoReflect.Descriptor instead. +func (*Token) Descriptor() ([]byte, []int) { + return file_m2m_oauth_server_pb_service_proto_rawDescGZIP(), []int{0} +} + +func (x *Token) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *Token) GetVersion() uint64 { + if x != nil { + return x.Version + } + return 0 +} + +func (x *Token) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *Token) GetOwner() string { + if x != nil { + return x.Owner + } + return "" +} + +func (x *Token) GetTimestamp() int64 { + if x != nil { + return x.Timestamp + } + return 0 +} + +func (x *Token) GetAudience() []string { + if x != nil { + return x.Audience + } + return nil +} + +func (x *Token) GetScopes() []string { + if x != nil { + return x.Scopes + } + return nil +} + +func (x *Token) GetExpiration() int64 { + if x != nil { + return x.Expiration + } + return 0 +} + +func (x *Token) GetOriginalTokenClaims() *structpb.Value { + if x != nil { + return x.OriginalTokenClaims + } + return nil +} + +func (x *Token) GetBlacklisted() *Token_BlackListed { + if x != nil { + return x.Blacklisted + } + return nil +} + +type GetBlacklistedTokensRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Returns all blacklisted/revoked not expired tokens of the owner from the given version + Version uint64 `protobuf:"varint,1,opt,name=version,proto3" json:"version,omitempty"` +} + +func (x *GetBlacklistedTokensRequest) Reset() { + *x = GetBlacklistedTokensRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_m2m_oauth_server_pb_service_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetBlacklistedTokensRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetBlacklistedTokensRequest) ProtoMessage() {} + +func (x *GetBlacklistedTokensRequest) ProtoReflect() protoreflect.Message { + mi := &file_m2m_oauth_server_pb_service_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetBlacklistedTokensRequest.ProtoReflect.Descriptor instead. +func (*GetBlacklistedTokensRequest) Descriptor() ([]byte, []int) { + return file_m2m_oauth_server_pb_service_proto_rawDescGZIP(), []int{1} +} + +func (x *GetBlacklistedTokensRequest) GetVersion() uint64 { + if x != nil { + return x.Version + } + return 0 +} + +type GetBlacklistedTokensResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + BlackList []*GetBlacklistedTokensResponse_Item `protobuf:"bytes,1,rep,name=black_list,json=blackList,proto3" json:"black_list,omitempty"` + // The biggest version of the blacklisted/revoked token over all tokens of owner + Version uint64 `protobuf:"varint,2,opt,name=version,proto3" json:"version,omitempty"` +} + +func (x *GetBlacklistedTokensResponse) Reset() { + *x = GetBlacklistedTokensResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_m2m_oauth_server_pb_service_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetBlacklistedTokensResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetBlacklistedTokensResponse) ProtoMessage() {} + +func (x *GetBlacklistedTokensResponse) ProtoReflect() protoreflect.Message { + mi := &file_m2m_oauth_server_pb_service_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetBlacklistedTokensResponse.ProtoReflect.Descriptor instead. +func (*GetBlacklistedTokensResponse) Descriptor() ([]byte, []int) { + return file_m2m_oauth_server_pb_service_proto_rawDescGZIP(), []int{2} +} + +func (x *GetBlacklistedTokensResponse) GetBlackList() []*GetBlacklistedTokensResponse_Item { + if x != nil { + return x.BlackList + } + return nil +} + +func (x *GetBlacklistedTokensResponse) GetVersion() uint64 { + if x != nil { + return x.Version + } + return 0 +} + +type GetTokensRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + IdFilter []string `protobuf:"bytes,1,rep,name=id_filter,json=idFilter,proto3" json:"id_filter,omitempty"` + AudienceFilter []string `protobuf:"bytes,2,rep,name=audience_filter,json=audienceFilter,proto3" json:"audience_filter,omitempty"` + IncludeBlacklisted bool `protobuf:"varint,3,opt,name=include_blacklisted,json=includeBlacklisted,proto3" json:"include_blacklisted,omitempty"` +} + +func (x *GetTokensRequest) Reset() { + *x = GetTokensRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_m2m_oauth_server_pb_service_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetTokensRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetTokensRequest) ProtoMessage() {} + +func (x *GetTokensRequest) ProtoReflect() protoreflect.Message { + mi := &file_m2m_oauth_server_pb_service_proto_msgTypes[3] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetTokensRequest.ProtoReflect.Descriptor instead. +func (*GetTokensRequest) Descriptor() ([]byte, []int) { + return file_m2m_oauth_server_pb_service_proto_rawDescGZIP(), []int{3} +} + +func (x *GetTokensRequest) GetIdFilter() []string { + if x != nil { + return x.IdFilter + } + return nil +} + +func (x *GetTokensRequest) GetAudienceFilter() []string { + if x != nil { + return x.AudienceFilter + } + return nil +} + +func (x *GetTokensRequest) GetIncludeBlacklisted() bool { + if x != nil { + return x.IncludeBlacklisted + } + return false +} + +type BlacklistTokensRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + IdFilter []string `protobuf:"bytes,1,rep,name=id_filter,json=idFilter,proto3" json:"id_filter,omitempty"` +} + +func (x *BlacklistTokensRequest) Reset() { + *x = BlacklistTokensRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_m2m_oauth_server_pb_service_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *BlacklistTokensRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*BlacklistTokensRequest) ProtoMessage() {} + +func (x *BlacklistTokensRequest) ProtoReflect() protoreflect.Message { + mi := &file_m2m_oauth_server_pb_service_proto_msgTypes[4] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use BlacklistTokensRequest.ProtoReflect.Descriptor instead. +func (*BlacklistTokensRequest) Descriptor() ([]byte, []int) { + return file_m2m_oauth_server_pb_service_proto_rawDescGZIP(), []int{4} +} + +func (x *BlacklistTokensRequest) GetIdFilter() []string { + if x != nil { + return x.IdFilter + } + return nil +} + +type BlacklistTokensResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Id []string `protobuf:"bytes,1,rep,name=id,proto3" json:"id,omitempty"` +} + +func (x *BlacklistTokensResponse) Reset() { + *x = BlacklistTokensResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_m2m_oauth_server_pb_service_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *BlacklistTokensResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*BlacklistTokensResponse) ProtoMessage() {} + +func (x *BlacklistTokensResponse) ProtoReflect() protoreflect.Message { + mi := &file_m2m_oauth_server_pb_service_proto_msgTypes[5] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use BlacklistTokensResponse.ProtoReflect.Descriptor instead. +func (*BlacklistTokensResponse) Descriptor() ([]byte, []int) { + return file_m2m_oauth_server_pb_service_proto_rawDescGZIP(), []int{5} +} + +func (x *BlacklistTokensResponse) GetId() []string { + if x != nil { + return x.Id + } + return nil +} + +type Token_BlackListed struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Blacklisted/revoked enabled flag, if once token has been blacklisted/revoked then it can't be unblacklisted/unrevoked + Flag bool `protobuf:"varint,1,opt,name=flag,proto3" json:"flag,omitempty"` + // Unix timestamp in ns when the token has been blacklisted/revoked + Timestamp int64 `protobuf:"varint,2,opt,name=timestamp,proto3" json:"timestamp,omitempty"` + // Incremental version of the blacklisted/revoked token over all tokens of owner + Version uint64 `protobuf:"varint,3,opt,name=version,proto3" json:"version,omitempty"` +} + +func (x *Token_BlackListed) Reset() { + *x = Token_BlackListed{} + if protoimpl.UnsafeEnabled { + mi := &file_m2m_oauth_server_pb_service_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Token_BlackListed) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Token_BlackListed) ProtoMessage() {} + +func (x *Token_BlackListed) ProtoReflect() protoreflect.Message { + mi := &file_m2m_oauth_server_pb_service_proto_msgTypes[6] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Token_BlackListed.ProtoReflect.Descriptor instead. +func (*Token_BlackListed) Descriptor() ([]byte, []int) { + return file_m2m_oauth_server_pb_service_proto_rawDescGZIP(), []int{0, 0} +} + +func (x *Token_BlackListed) GetFlag() bool { + if x != nil { + return x.Flag + } + return false +} + +func (x *Token_BlackListed) GetTimestamp() int64 { + if x != nil { + return x.Timestamp + } + return 0 +} + +func (x *Token_BlackListed) GetVersion() uint64 { + if x != nil { + return x.Version + } + return 0 +} + +type GetBlacklistedTokensResponse_Item struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Audience string `protobuf:"bytes,1,opt,name=audience,proto3" json:"audience,omitempty"` + Id string `protobuf:"bytes,2,opt,name=id,proto3" json:"id,omitempty"` +} + +func (x *GetBlacklistedTokensResponse_Item) Reset() { + *x = GetBlacklistedTokensResponse_Item{} + if protoimpl.UnsafeEnabled { + mi := &file_m2m_oauth_server_pb_service_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetBlacklistedTokensResponse_Item) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetBlacklistedTokensResponse_Item) ProtoMessage() {} + +func (x *GetBlacklistedTokensResponse_Item) ProtoReflect() protoreflect.Message { + mi := &file_m2m_oauth_server_pb_service_proto_msgTypes[7] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetBlacklistedTokensResponse_Item.ProtoReflect.Descriptor instead. +func (*GetBlacklistedTokensResponse_Item) Descriptor() ([]byte, []int) { + return file_m2m_oauth_server_pb_service_proto_rawDescGZIP(), []int{2, 0} +} + +func (x *GetBlacklistedTokensResponse_Item) GetAudience() string { + if x != nil { + return x.Audience + } + return "" +} + +func (x *GetBlacklistedTokensResponse_Item) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +var File_m2m_oauth_server_pb_service_proto protoreflect.FileDescriptor + +var file_m2m_oauth_server_pb_service_proto_rawDesc = []byte{ + 0x0a, 0x21, 0x6d, 0x32, 0x6d, 0x2d, 0x6f, 0x61, 0x75, 0x74, 0x68, 0x2d, 0x73, 0x65, 0x72, 0x76, + 0x65, 0x72, 0x2f, 0x70, 0x62, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x12, 0x11, 0x6d, 0x32, 0x6d, 0x6f, 0x61, 0x75, 0x74, 0x68, 0x73, 0x65, 0x72, + 0x76, 0x65, 0x72, 0x2e, 0x70, 0x62, 0x1a, 0x1c, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1c, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x61, 0x70, 0x69, + 0x2f, 0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x2d, 0x67, 0x65, 0x6e, 0x2d, 0x6f, + 0x70, 0x65, 0x6e, 0x61, 0x70, 0x69, 0x76, 0x32, 0x2f, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, + 0x2f, 0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x22, 0xbc, 0x03, 0x0a, 0x05, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x0e, 0x0a, 0x02, + 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x18, 0x0a, 0x07, + 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x07, 0x76, + 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x6f, 0x77, + 0x6e, 0x65, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6f, 0x77, 0x6e, 0x65, 0x72, + 0x12, 0x1c, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x05, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x12, 0x1a, + 0x0a, 0x08, 0x61, 0x75, 0x64, 0x69, 0x65, 0x6e, 0x63, 0x65, 0x18, 0x06, 0x20, 0x03, 0x28, 0x09, + 0x52, 0x08, 0x61, 0x75, 0x64, 0x69, 0x65, 0x6e, 0x63, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x63, + 0x6f, 0x70, 0x65, 0x73, 0x18, 0x07, 0x20, 0x03, 0x28, 0x09, 0x52, 0x06, 0x73, 0x63, 0x6f, 0x70, + 0x65, 0x73, 0x12, 0x1e, 0x0a, 0x0a, 0x65, 0x78, 0x70, 0x69, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x18, 0x08, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x65, 0x78, 0x70, 0x69, 0x72, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x12, 0x4a, 0x0a, 0x15, 0x6f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x61, 0x6c, 0x5f, 0x74, + 0x6f, 0x6b, 0x65, 0x6e, 0x5f, 0x63, 0x6c, 0x61, 0x69, 0x6d, 0x73, 0x18, 0x09, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x62, 0x75, 0x66, 0x2e, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x13, 0x6f, 0x72, 0x69, 0x67, 0x69, + 0x6e, 0x61, 0x6c, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x43, 0x6c, 0x61, 0x69, 0x6d, 0x73, 0x12, 0x46, + 0x0a, 0x0b, 0x62, 0x6c, 0x61, 0x63, 0x6b, 0x6c, 0x69, 0x73, 0x74, 0x65, 0x64, 0x18, 0x0a, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x6d, 0x32, 0x6d, 0x6f, 0x61, 0x75, 0x74, 0x68, 0x73, 0x65, + 0x72, 0x76, 0x65, 0x72, 0x2e, 0x70, 0x62, 0x2e, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x2e, 0x42, 0x6c, + 0x61, 0x63, 0x6b, 0x4c, 0x69, 0x73, 0x74, 0x65, 0x64, 0x52, 0x0b, 0x62, 0x6c, 0x61, 0x63, 0x6b, + 0x6c, 0x69, 0x73, 0x74, 0x65, 0x64, 0x1a, 0x59, 0x0a, 0x0b, 0x42, 0x6c, 0x61, 0x63, 0x6b, 0x4c, + 0x69, 0x73, 0x74, 0x65, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x66, 0x6c, 0x61, 0x67, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x08, 0x52, 0x04, 0x66, 0x6c, 0x61, 0x67, 0x12, 0x1c, 0x0a, 0x09, 0x74, 0x69, 0x6d, + 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x74, 0x69, + 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, + 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, + 0x6e, 0x22, 0x37, 0x0a, 0x1b, 0x47, 0x65, 0x74, 0x42, 0x6c, 0x61, 0x63, 0x6b, 0x6c, 0x69, 0x73, + 0x74, 0x65, 0x64, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x04, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0xc1, 0x01, 0x0a, 0x1c, 0x47, + 0x65, 0x74, 0x42, 0x6c, 0x61, 0x63, 0x6b, 0x6c, 0x69, 0x73, 0x74, 0x65, 0x64, 0x54, 0x6f, 0x6b, + 0x65, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x53, 0x0a, 0x0a, 0x62, + 0x6c, 0x61, 0x63, 0x6b, 0x5f, 0x6c, 0x69, 0x73, 0x74, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, + 0x34, 0x2e, 0x6d, 0x32, 0x6d, 0x6f, 0x61, 0x75, 0x74, 0x68, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, + 0x2e, 0x70, 0x62, 0x2e, 0x47, 0x65, 0x74, 0x42, 0x6c, 0x61, 0x63, 0x6b, 0x6c, 0x69, 0x73, 0x74, + 0x65, 0x64, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x2e, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x09, 0x62, 0x6c, 0x61, 0x63, 0x6b, 0x4c, 0x69, 0x73, 0x74, + 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x04, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x1a, 0x32, 0x0a, 0x04, 0x49, 0x74, + 0x65, 0x6d, 0x12, 0x1a, 0x0a, 0x08, 0x61, 0x75, 0x64, 0x69, 0x65, 0x6e, 0x63, 0x65, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x61, 0x75, 0x64, 0x69, 0x65, 0x6e, 0x63, 0x65, 0x12, 0x0e, + 0x0a, 0x02, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x22, 0x89, + 0x01, 0x0a, 0x10, 0x47, 0x65, 0x74, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x69, 0x64, 0x5f, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, + 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x08, 0x69, 0x64, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, + 0x12, 0x27, 0x0a, 0x0f, 0x61, 0x75, 0x64, 0x69, 0x65, 0x6e, 0x63, 0x65, 0x5f, 0x66, 0x69, 0x6c, + 0x74, 0x65, 0x72, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0e, 0x61, 0x75, 0x64, 0x69, 0x65, + 0x6e, 0x63, 0x65, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x12, 0x2f, 0x0a, 0x13, 0x69, 0x6e, 0x63, + 0x6c, 0x75, 0x64, 0x65, 0x5f, 0x62, 0x6c, 0x61, 0x63, 0x6b, 0x6c, 0x69, 0x73, 0x74, 0x65, 0x64, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x12, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x42, + 0x6c, 0x61, 0x63, 0x6b, 0x6c, 0x69, 0x73, 0x74, 0x65, 0x64, 0x22, 0x35, 0x0a, 0x16, 0x42, 0x6c, + 0x61, 0x63, 0x6b, 0x6c, 0x69, 0x73, 0x74, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x69, 0x64, 0x5f, 0x66, 0x69, 0x6c, 0x74, 0x65, + 0x72, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x08, 0x69, 0x64, 0x46, 0x69, 0x6c, 0x74, 0x65, + 0x72, 0x22, 0x29, 0x0a, 0x17, 0x42, 0x6c, 0x61, 0x63, 0x6b, 0x6c, 0x69, 0x73, 0x74, 0x54, 0x6f, + 0x6b, 0x65, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x0e, 0x0a, 0x02, + 0x69, 0x64, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x32, 0xe6, 0x03, 0x0a, + 0x0e, 0x4d, 0x32, 0x4d, 0x4f, 0x41, 0x75, 0x74, 0x68, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x12, + 0x7e, 0x0a, 0x09, 0x47, 0x65, 0x74, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x12, 0x23, 0x2e, 0x6d, + 0x32, 0x6d, 0x6f, 0x61, 0x75, 0x74, 0x68, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x70, 0x62, + 0x2e, 0x47, 0x65, 0x74, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x18, 0x2e, 0x6d, 0x32, 0x6d, 0x6f, 0x61, 0x75, 0x74, 0x68, 0x73, 0x65, 0x72, 0x76, + 0x65, 0x72, 0x2e, 0x70, 0x62, 0x2e, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0x32, 0x92, 0x41, 0x08, + 0x0a, 0x06, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x21, 0x12, 0x1f, + 0x2f, 0x6d, 0x32, 0x6d, 0x2d, 0x6f, 0x61, 0x75, 0x74, 0x68, 0x2d, 0x73, 0x65, 0x72, 0x76, 0x65, + 0x72, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x12, + 0xae, 0x01, 0x0a, 0x14, 0x47, 0x65, 0x74, 0x42, 0x6c, 0x61, 0x63, 0x6b, 0x6c, 0x69, 0x73, 0x74, + 0x65, 0x64, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x12, 0x2e, 0x2e, 0x6d, 0x32, 0x6d, 0x6f, 0x61, + 0x75, 0x74, 0x68, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x70, 0x62, 0x2e, 0x47, 0x65, 0x74, + 0x42, 0x6c, 0x61, 0x63, 0x6b, 0x6c, 0x69, 0x73, 0x74, 0x65, 0x64, 0x54, 0x6f, 0x6b, 0x65, 0x6e, + 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2f, 0x2e, 0x6d, 0x32, 0x6d, 0x6f, 0x61, + 0x75, 0x74, 0x68, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x70, 0x62, 0x2e, 0x47, 0x65, 0x74, + 0x42, 0x6c, 0x61, 0x63, 0x6b, 0x6c, 0x69, 0x73, 0x74, 0x65, 0x64, 0x54, 0x6f, 0x6b, 0x65, 0x6e, + 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x35, 0x92, 0x41, 0x08, 0x0a, 0x06, + 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x24, 0x12, 0x22, 0x2f, 0x6d, + 0x32, 0x6d, 0x2d, 0x6f, 0x61, 0x75, 0x74, 0x68, 0x2d, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2f, + 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x62, 0x6c, 0x61, 0x63, 0x6b, 0x6c, 0x69, 0x73, 0x74, + 0x12, 0xa2, 0x01, 0x0a, 0x0f, 0x42, 0x6c, 0x61, 0x63, 0x6b, 0x6c, 0x69, 0x73, 0x74, 0x54, 0x6f, + 0x6b, 0x65, 0x6e, 0x73, 0x12, 0x29, 0x2e, 0x6d, 0x32, 0x6d, 0x6f, 0x61, 0x75, 0x74, 0x68, 0x73, + 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x70, 0x62, 0x2e, 0x42, 0x6c, 0x61, 0x63, 0x6b, 0x6c, 0x69, + 0x73, 0x74, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x2a, 0x2e, 0x6d, 0x32, 0x6d, 0x6f, 0x61, 0x75, 0x74, 0x68, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, + 0x2e, 0x70, 0x62, 0x2e, 0x42, 0x6c, 0x61, 0x63, 0x6b, 0x6c, 0x69, 0x73, 0x74, 0x54, 0x6f, 0x6b, + 0x65, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x38, 0x92, 0x41, 0x08, + 0x0a, 0x06, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x27, 0x3a, 0x01, + 0x2a, 0x22, 0x22, 0x2f, 0x6d, 0x32, 0x6d, 0x2d, 0x6f, 0x61, 0x75, 0x74, 0x68, 0x2d, 0x73, 0x65, + 0x72, 0x76, 0x65, 0x72, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x62, 0x6c, 0x61, 0x63, + 0x6b, 0x6c, 0x69, 0x73, 0x74, 0x42, 0xcb, 0x02, 0x92, 0x41, 0x94, 0x02, 0x12, 0xbc, 0x01, 0x0a, + 0x0c, 0x50, 0x4c, 0x47, 0x44, 0x20, 0x4d, 0x32, 0x4d, 0x20, 0x41, 0x50, 0x49, 0x12, 0x24, 0x41, + 0x50, 0x49, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x74, 0x6f, 0x20, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, + 0x20, 0x6d, 0x32, 0x6d, 0x20, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x20, 0x69, 0x6e, 0x20, 0x50, + 0x4c, 0x47, 0x44, 0x22, 0x3a, 0x0a, 0x08, 0x70, 0x6c, 0x67, 0x64, 0x2e, 0x64, 0x65, 0x76, 0x12, + 0x1f, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, + 0x63, 0x6f, 0x6d, 0x2f, 0x70, 0x6c, 0x67, 0x64, 0x2d, 0x64, 0x65, 0x76, 0x2f, 0x68, 0x75, 0x62, + 0x1a, 0x0d, 0x69, 0x6e, 0x66, 0x6f, 0x40, 0x70, 0x6c, 0x67, 0x64, 0x2e, 0x64, 0x65, 0x76, 0x2a, + 0x45, 0x0a, 0x12, 0x41, 0x70, 0x61, 0x63, 0x68, 0x65, 0x20, 0x4c, 0x69, 0x63, 0x65, 0x6e, 0x73, + 0x65, 0x20, 0x32, 0x2e, 0x30, 0x12, 0x2f, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x67, + 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x70, 0x6c, 0x67, 0x64, 0x2d, 0x64, + 0x65, 0x76, 0x2f, 0x68, 0x75, 0x62, 0x2f, 0x62, 0x6c, 0x6f, 0x62, 0x2f, 0x76, 0x32, 0x2f, 0x4c, + 0x49, 0x43, 0x45, 0x4e, 0x53, 0x45, 0x32, 0x03, 0x31, 0x2e, 0x30, 0x2a, 0x01, 0x02, 0x32, 0x10, + 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x6a, 0x73, 0x6f, 0x6e, + 0x32, 0x15, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x6a, 0x73, 0x6f, 0x6e, 0x3a, 0x10, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x6a, 0x73, 0x6f, 0x6e, 0x3a, 0x15, 0x61, 0x70, 0x70, 0x6c, 0x69, + 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x6a, 0x73, 0x6f, 0x6e, + 0x5a, 0x31, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x70, 0x6c, 0x67, + 0x64, 0x2d, 0x64, 0x65, 0x76, 0x2f, 0x68, 0x75, 0x62, 0x2f, 0x76, 0x32, 0x2f, 0x6d, 0x32, 0x6d, + 0x2d, 0x6f, 0x61, 0x75, 0x74, 0x68, 0x2d, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2f, 0x70, 0x62, + 0x3b, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_m2m_oauth_server_pb_service_proto_rawDescOnce sync.Once + file_m2m_oauth_server_pb_service_proto_rawDescData = file_m2m_oauth_server_pb_service_proto_rawDesc +) + +func file_m2m_oauth_server_pb_service_proto_rawDescGZIP() []byte { + file_m2m_oauth_server_pb_service_proto_rawDescOnce.Do(func() { + file_m2m_oauth_server_pb_service_proto_rawDescData = protoimpl.X.CompressGZIP(file_m2m_oauth_server_pb_service_proto_rawDescData) + }) + return file_m2m_oauth_server_pb_service_proto_rawDescData +} + +var file_m2m_oauth_server_pb_service_proto_msgTypes = make([]protoimpl.MessageInfo, 8) +var file_m2m_oauth_server_pb_service_proto_goTypes = []interface{}{ + (*Token)(nil), // 0: m2moauthserver.pb.Token + (*GetBlacklistedTokensRequest)(nil), // 1: m2moauthserver.pb.GetBlacklistedTokensRequest + (*GetBlacklistedTokensResponse)(nil), // 2: m2moauthserver.pb.GetBlacklistedTokensResponse + (*GetTokensRequest)(nil), // 3: m2moauthserver.pb.GetTokensRequest + (*BlacklistTokensRequest)(nil), // 4: m2moauthserver.pb.BlacklistTokensRequest + (*BlacklistTokensResponse)(nil), // 5: m2moauthserver.pb.BlacklistTokensResponse + (*Token_BlackListed)(nil), // 6: m2moauthserver.pb.Token.BlackListed + (*GetBlacklistedTokensResponse_Item)(nil), // 7: m2moauthserver.pb.GetBlacklistedTokensResponse.Item + (*structpb.Value)(nil), // 8: google.protobuf.Value +} +var file_m2m_oauth_server_pb_service_proto_depIdxs = []int32{ + 8, // 0: m2moauthserver.pb.Token.original_token_claims:type_name -> google.protobuf.Value + 6, // 1: m2moauthserver.pb.Token.blacklisted:type_name -> m2moauthserver.pb.Token.BlackListed + 7, // 2: m2moauthserver.pb.GetBlacklistedTokensResponse.black_list:type_name -> m2moauthserver.pb.GetBlacklistedTokensResponse.Item + 3, // 3: m2moauthserver.pb.M2MOAuthServer.GetTokens:input_type -> m2moauthserver.pb.GetTokensRequest + 1, // 4: m2moauthserver.pb.M2MOAuthServer.GetBlacklistedTokens:input_type -> m2moauthserver.pb.GetBlacklistedTokensRequest + 4, // 5: m2moauthserver.pb.M2MOAuthServer.BlacklistTokens:input_type -> m2moauthserver.pb.BlacklistTokensRequest + 0, // 6: m2moauthserver.pb.M2MOAuthServer.GetTokens:output_type -> m2moauthserver.pb.Token + 2, // 7: m2moauthserver.pb.M2MOAuthServer.GetBlacklistedTokens:output_type -> m2moauthserver.pb.GetBlacklistedTokensResponse + 5, // 8: m2moauthserver.pb.M2MOAuthServer.BlacklistTokens:output_type -> m2moauthserver.pb.BlacklistTokensResponse + 6, // [6:9] is the sub-list for method output_type + 3, // [3:6] is the sub-list for method input_type + 3, // [3:3] is the sub-list for extension type_name + 3, // [3:3] is the sub-list for extension extendee + 0, // [0:3] is the sub-list for field type_name +} + +func init() { file_m2m_oauth_server_pb_service_proto_init() } +func file_m2m_oauth_server_pb_service_proto_init() { + if File_m2m_oauth_server_pb_service_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_m2m_oauth_server_pb_service_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Token); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_m2m_oauth_server_pb_service_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetBlacklistedTokensRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_m2m_oauth_server_pb_service_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetBlacklistedTokensResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_m2m_oauth_server_pb_service_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetTokensRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_m2m_oauth_server_pb_service_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*BlacklistTokensRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_m2m_oauth_server_pb_service_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*BlacklistTokensResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_m2m_oauth_server_pb_service_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Token_BlackListed); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_m2m_oauth_server_pb_service_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetBlacklistedTokensResponse_Item); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_m2m_oauth_server_pb_service_proto_rawDesc, + NumEnums: 0, + NumMessages: 8, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_m2m_oauth_server_pb_service_proto_goTypes, + DependencyIndexes: file_m2m_oauth_server_pb_service_proto_depIdxs, + MessageInfos: file_m2m_oauth_server_pb_service_proto_msgTypes, + }.Build() + File_m2m_oauth_server_pb_service_proto = out.File + file_m2m_oauth_server_pb_service_proto_rawDesc = nil + file_m2m_oauth_server_pb_service_proto_goTypes = nil + file_m2m_oauth_server_pb_service_proto_depIdxs = nil +} diff --git a/m2m-oauth-server/pb/service.pb.gw.go b/m2m-oauth-server/pb/service.pb.gw.go new file mode 100644 index 000000000..7ff770a53 --- /dev/null +++ b/m2m-oauth-server/pb/service.pb.gw.go @@ -0,0 +1,337 @@ +// Code generated by protoc-gen-grpc-gateway. DO NOT EDIT. +// source: m2m-oauth-server/pb/service.proto + +/* +Package pb is a reverse proxy. + +It translates gRPC into RESTful JSON APIs. +*/ +package pb + +import ( + "context" + "io" + "net/http" + + "github.com/grpc-ecosystem/grpc-gateway/v2/runtime" + "github.com/grpc-ecosystem/grpc-gateway/v2/utilities" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/grpclog" + "google.golang.org/grpc/metadata" + "google.golang.org/grpc/status" + "google.golang.org/protobuf/proto" +) + +// Suppress "imported and not used" errors +var _ codes.Code +var _ io.Reader +var _ status.Status +var _ = runtime.String +var _ = utilities.NewDoubleArray +var _ = metadata.Join + +var ( + filter_M2MOAuthServer_GetTokens_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)} +) + +func request_M2MOAuthServer_GetTokens_0(ctx context.Context, marshaler runtime.Marshaler, client M2MOAuthServerClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq GetTokensRequest + var metadata runtime.ServerMetadata + + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_M2MOAuthServer_GetTokens_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := client.GetTokens(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_M2MOAuthServer_GetTokens_0(ctx context.Context, marshaler runtime.Marshaler, server M2MOAuthServerServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq GetTokensRequest + var metadata runtime.ServerMetadata + + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_M2MOAuthServer_GetTokens_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.GetTokens(ctx, &protoReq) + return msg, metadata, err + +} + +var ( + filter_M2MOAuthServer_GetBlacklistedTokens_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)} +) + +func request_M2MOAuthServer_GetBlacklistedTokens_0(ctx context.Context, marshaler runtime.Marshaler, client M2MOAuthServerClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq GetBlacklistedTokensRequest + var metadata runtime.ServerMetadata + + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_M2MOAuthServer_GetBlacklistedTokens_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := client.GetBlacklistedTokens(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_M2MOAuthServer_GetBlacklistedTokens_0(ctx context.Context, marshaler runtime.Marshaler, server M2MOAuthServerServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq GetBlacklistedTokensRequest + var metadata runtime.ServerMetadata + + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_M2MOAuthServer_GetBlacklistedTokens_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.GetBlacklistedTokens(ctx, &protoReq) + return msg, metadata, err + +} + +func request_M2MOAuthServer_BlacklistTokens_0(ctx context.Context, marshaler runtime.Marshaler, client M2MOAuthServerClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq BlacklistTokensRequest + var metadata runtime.ServerMetadata + + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := client.BlacklistTokens(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_M2MOAuthServer_BlacklistTokens_0(ctx context.Context, marshaler runtime.Marshaler, server M2MOAuthServerServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq BlacklistTokensRequest + var metadata runtime.ServerMetadata + + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.BlacklistTokens(ctx, &protoReq) + return msg, metadata, err + +} + +// RegisterM2MOAuthServerHandlerServer registers the http handlers for service M2MOAuthServer to "mux". +// UnaryRPC :call M2MOAuthServerServer directly. +// StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906. +// Note that using this registration option will cause many gRPC library features to stop working. Consider using RegisterM2MOAuthServerHandlerFromEndpoint instead. +func RegisterM2MOAuthServerHandlerServer(ctx context.Context, mux *runtime.ServeMux, server M2MOAuthServerServer) error { + + mux.Handle("GET", pattern_M2MOAuthServer_GetTokens_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + var err error + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/m2moauthserver.pb.M2MOAuthServer/GetTokens", runtime.WithHTTPPathPattern("/m2m-oauth-server/api/v1/tokens")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_M2MOAuthServer_GetTokens_0(annotatedContext, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + + forward_M2MOAuthServer_GetTokens_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_M2MOAuthServer_GetBlacklistedTokens_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + var err error + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/m2moauthserver.pb.M2MOAuthServer/GetBlacklistedTokens", runtime.WithHTTPPathPattern("/m2m-oauth-server/api/v1/blacklist")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_M2MOAuthServer_GetBlacklistedTokens_0(annotatedContext, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + + forward_M2MOAuthServer_GetBlacklistedTokens_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_M2MOAuthServer_BlacklistTokens_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + var err error + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/m2moauthserver.pb.M2MOAuthServer/BlacklistTokens", runtime.WithHTTPPathPattern("/m2m-oauth-server/api/v1/blacklist")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_M2MOAuthServer_BlacklistTokens_0(annotatedContext, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + + forward_M2MOAuthServer_BlacklistTokens_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + return nil +} + +// RegisterM2MOAuthServerHandlerFromEndpoint is same as RegisterM2MOAuthServerHandler but +// automatically dials to "endpoint" and closes the connection when "ctx" gets done. +func RegisterM2MOAuthServerHandlerFromEndpoint(ctx context.Context, mux *runtime.ServeMux, endpoint string, opts []grpc.DialOption) (err error) { + conn, err := grpc.NewClient(endpoint, opts...) + if err != nil { + return err + } + defer func() { + if err != nil { + if cerr := conn.Close(); cerr != nil { + grpclog.Errorf("Failed to close conn to %s: %v", endpoint, cerr) + } + return + } + go func() { + <-ctx.Done() + if cerr := conn.Close(); cerr != nil { + grpclog.Errorf("Failed to close conn to %s: %v", endpoint, cerr) + } + }() + }() + + return RegisterM2MOAuthServerHandler(ctx, mux, conn) +} + +// RegisterM2MOAuthServerHandler registers the http handlers for service M2MOAuthServer to "mux". +// The handlers forward requests to the grpc endpoint over "conn". +func RegisterM2MOAuthServerHandler(ctx context.Context, mux *runtime.ServeMux, conn *grpc.ClientConn) error { + return RegisterM2MOAuthServerHandlerClient(ctx, mux, NewM2MOAuthServerClient(conn)) +} + +// RegisterM2MOAuthServerHandlerClient registers the http handlers for service M2MOAuthServer +// to "mux". The handlers forward requests to the grpc endpoint over the given implementation of "M2MOAuthServerClient". +// Note: the gRPC framework executes interceptors within the gRPC handler. If the passed in "M2MOAuthServerClient" +// doesn't go through the normal gRPC flow (creating a gRPC client etc.) then it will be up to the passed in +// "M2MOAuthServerClient" to call the correct interceptors. +func RegisterM2MOAuthServerHandlerClient(ctx context.Context, mux *runtime.ServeMux, client M2MOAuthServerClient) error { + + mux.Handle("GET", pattern_M2MOAuthServer_GetTokens_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + var err error + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/m2moauthserver.pb.M2MOAuthServer/GetTokens", runtime.WithHTTPPathPattern("/m2m-oauth-server/api/v1/tokens")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_M2MOAuthServer_GetTokens_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + + forward_M2MOAuthServer_GetTokens_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_M2MOAuthServer_GetBlacklistedTokens_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + var err error + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/m2moauthserver.pb.M2MOAuthServer/GetBlacklistedTokens", runtime.WithHTTPPathPattern("/m2m-oauth-server/api/v1/blacklist")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_M2MOAuthServer_GetBlacklistedTokens_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + + forward_M2MOAuthServer_GetBlacklistedTokens_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_M2MOAuthServer_BlacklistTokens_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + var err error + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/m2moauthserver.pb.M2MOAuthServer/BlacklistTokens", runtime.WithHTTPPathPattern("/m2m-oauth-server/api/v1/blacklist")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_M2MOAuthServer_BlacklistTokens_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + + forward_M2MOAuthServer_BlacklistTokens_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + return nil +} + +var ( + pattern_M2MOAuthServer_GetTokens_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"m2m-oauth-server", "api", "v1", "tokens"}, "")) + + pattern_M2MOAuthServer_GetBlacklistedTokens_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"m2m-oauth-server", "api", "v1", "blacklist"}, "")) + + pattern_M2MOAuthServer_BlacklistTokens_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"m2m-oauth-server", "api", "v1", "blacklist"}, "")) +) + +var ( + forward_M2MOAuthServer_GetTokens_0 = runtime.ForwardResponseMessage + + forward_M2MOAuthServer_GetBlacklistedTokens_0 = runtime.ForwardResponseMessage + + forward_M2MOAuthServer_BlacklistTokens_0 = runtime.ForwardResponseMessage +) diff --git a/m2m-oauth-server/pb/service.proto b/m2m-oauth-server/pb/service.proto new file mode 100644 index 000000000..eee0398c2 --- /dev/null +++ b/m2m-oauth-server/pb/service.proto @@ -0,0 +1,123 @@ +syntax = "proto3"; + +package m2moauthserver.pb; + +import "google/protobuf/struct.proto"; +import "google/api/annotations.proto"; +import "protoc-gen-openapiv2/options/annotations.proto"; + +option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_swagger) = { + info: { + title: "PLGD M2M API"; + version: "1.0"; + description: "API for to manage m2m tokens in PLGD"; + contact: { + name: "plgd.dev"; + url: "https://github.com/plgd-dev/hub"; + email: "info@plgd.dev"; + }; + license: { + name: "Apache License 2.0"; + url: "https://github.com/plgd-dev/hub/blob/v2/LICENSE"; + }; + }; + schemes: [HTTPS]; + consumes: ["application/json", "application/protojson"]; + produces: ["application/json", "application/protojson"]; +}; + +option go_package = "github.com/plgd-dev/hub/v2/m2m-oauth-server/pb;pb"; + +// Tokens are deleted from DB after they are expired and blacklisted/revoked +message Token { // driven by resource change event + // Token ID / jti + string id = 1; + // Incremetal version for update + uint64 version = 2; + // User-friendly token name + string name = 3; + // Owner of the token + string owner = 4; + // Unix timestamp in ns when the condition has been created/updated + int64 timestamp = 5; + // Token Audience + repeated string audience = 6; + // Token scopes + repeated string scopes = 7; + // Original token expiration + int64 expiration = 8; + // Original token claims + google.protobuf.Value original_token_claims = 9; + message BlackListed { + // Blacklisted/revoked enabled flag, if once token has been blacklisted/revoked then it can't be unblacklisted/unrevoked + bool flag = 1; + // Unix timestamp in ns when the token has been blacklisted/revoked + int64 timestamp = 2; + // Incremental version of the blacklisted/revoked token over all tokens of owner + uint64 version = 3; + } + // Token black list section + BlackListed blacklisted = 10; +} + +message GetBlacklistedTokensRequest { + // Returns all blacklisted/revoked not expired tokens of the owner from the given version + uint64 version = 1; +} + +message GetBlacklistedTokensResponse { + message Item { + string audience = 1; + string id = 2; + } + repeated Item black_list = 1; + // The biggest version of the blacklisted/revoked token over all tokens of owner + uint64 version = 2; +} + +message GetTokensRequest { + repeated string id_filter = 1; + repeated string audience_filter = 2; + bool include_blacklisted = 3; +} + +message BlacklistTokensRequest { + repeated string id_filter = 1; +} + +message BlacklistTokensResponse { + repeated string id = 1; +} + +service M2MOAuthServer { + // Returns all tokens of the owner + rpc GetTokens(GetTokensRequest) returns (Token) { + option (google.api.http) = { + get: "/m2m-oauth-server/api/v1/tokens"; + }; + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { + tags: [ "Tokens" ]; + }; + } + + // Returns all blacklisted/revoked not expired tokens + rpc GetBlacklistedTokens(GetBlacklistedTokensRequest) returns (GetBlacklistedTokensResponse) { + option (google.api.http) = { + get: "/m2m-oauth-server/api/v1/blacklist"; + }; + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { + tags: [ "Tokens" ]; + }; + } + + // Blacklists/revokes tokens + rpc BlacklistTokens(BlacklistTokensRequest) returns (BlacklistTokensResponse) { + option (google.api.http) = { + post: "/m2m-oauth-server/api/v1/blacklist"; + body: "*"; + }; + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { + tags: [ "Tokens" ]; + }; + } +} diff --git a/m2m-oauth-server/pb/service.swagger.json b/m2m-oauth-server/pb/service.swagger.json new file mode 100644 index 000000000..74574cf16 --- /dev/null +++ b/m2m-oauth-server/pb/service.swagger.json @@ -0,0 +1,312 @@ +{ + "swagger": "2.0", + "info": { + "title": "PLGD M2M API", + "description": "API for to manage m2m tokens in PLGD", + "version": "1.0", + "contact": { + "name": "plgd.dev", + "url": "https://github.com/plgd-dev/hub", + "email": "info@plgd.dev" + }, + "license": { + "name": "Apache License 2.0", + "url": "https://github.com/plgd-dev/hub/blob/v2/LICENSE" + } + }, + "tags": [ + { + "name": "M2MOAuthServer" + } + ], + "schemes": [ + "https" + ], + "consumes": [ + "application/json", + "application/protojson" + ], + "produces": [ + "application/json", + "application/protojson" + ], + "paths": { + "/m2m-oauth-server/api/v1/blacklist": { + "get": { + "summary": "Returns all blacklisted/revoked not expired tokens", + "operationId": "M2MOAuthServer_GetBlacklistedTokens", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/pbGetBlacklistedTokensResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "version", + "description": "Returns all blacklisted/revoked not expired tokens of the owner from the given version", + "in": "query", + "required": false, + "type": "string", + "format": "uint64" + } + ], + "tags": [ + "Tokens" + ] + }, + "post": { + "summary": "Blacklists/revokes tokens", + "operationId": "M2MOAuthServer_BlacklistTokens", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/pbBlacklistTokensResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/pbBlacklistTokensRequest" + } + } + ], + "tags": [ + "Tokens" + ] + } + }, + "/m2m-oauth-server/api/v1/tokens": { + "get": { + "summary": "Returns all tokens of the owner", + "operationId": "M2MOAuthServer_GetTokens", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/pbToken" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "idFilter", + "in": "query", + "required": false, + "type": "array", + "items": { + "type": "string" + }, + "collectionFormat": "multi" + }, + { + "name": "audienceFilter", + "in": "query", + "required": false, + "type": "array", + "items": { + "type": "string" + }, + "collectionFormat": "multi" + }, + { + "name": "includeBlacklisted", + "in": "query", + "required": false, + "type": "boolean" + } + ], + "tags": [ + "Tokens" + ] + } + } + }, + "definitions": { + "GetBlacklistedTokensResponseItem": { + "type": "object", + "properties": { + "audience": { + "type": "string" + }, + "id": { + "type": "string" + } + } + }, + "TokenBlackListed": { + "type": "object", + "properties": { + "flag": { + "type": "boolean", + "title": "Blacklisted/revoked enabled flag, if once token has been blacklisted/revoked then it can't be unblacklisted/unrevoked" + }, + "timestamp": { + "type": "string", + "format": "int64", + "title": "Unix timestamp in ns when the token has been blacklisted/revoked" + }, + "version": { + "type": "string", + "format": "uint64", + "title": "Incremental version of the blacklisted/revoked token over all tokens of owner" + } + } + }, + "pbBlacklistTokensRequest": { + "type": "object", + "properties": { + "idFilter": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "pbBlacklistTokensResponse": { + "type": "object", + "properties": { + "id": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "pbGetBlacklistedTokensResponse": { + "type": "object", + "properties": { + "blackList": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/GetBlacklistedTokensResponseItem" + } + }, + "version": { + "type": "string", + "format": "uint64", + "title": "The biggest version of the blacklisted/revoked token over all tokens of owner" + } + } + }, + "pbToken": { + "type": "object", + "properties": { + "id": { + "type": "string", + "title": "Token ID / jti" + }, + "version": { + "type": "string", + "format": "uint64", + "title": "Incremetal version for update" + }, + "name": { + "type": "string", + "title": "User-friendly token name" + }, + "owner": { + "type": "string", + "title": "Owner of the token" + }, + "timestamp": { + "type": "string", + "format": "int64", + "title": "Unix timestamp in ns when the condition has been created/updated" + }, + "audience": { + "type": "array", + "items": { + "type": "string" + }, + "title": "Token Audience" + }, + "scopes": { + "type": "array", + "items": { + "type": "string" + }, + "title": "Token scopes" + }, + "expiration": { + "type": "string", + "format": "int64", + "title": "Original token expiration" + }, + "originalTokenClaims": { + "title": "Original token claims" + }, + "blacklisted": { + "$ref": "#/definitions/TokenBlackListed", + "title": "Token black list section" + } + }, + "description": "driven by resource change event", + "title": "Tokens are deleted from DB after they are expired and blacklisted/revoked" + }, + "protobufAny": { + "type": "object", + "properties": { + "@type": { + "type": "string" + } + }, + "additionalProperties": {} + }, + "protobufNullValue": { + "type": "string", + "enum": [ + "NULL_VALUE" + ], + "default": "NULL_VALUE", + "description": "`NullValue` is a singleton enumeration to represent the null value for the\n`Value` type union.\n\nThe JSON representation for `NullValue` is JSON `null`.\n\n - NULL_VALUE: Null value." + }, + "rpcStatus": { + "type": "object", + "properties": { + "code": { + "type": "integer", + "format": "int32" + }, + "message": { + "type": "string" + }, + "details": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/protobufAny" + } + } + } + } + } +} diff --git a/m2m-oauth-server/pb/service_grpc.pb.go b/m2m-oauth-server/pb/service_grpc.pb.go new file mode 100644 index 000000000..8a85f1a87 --- /dev/null +++ b/m2m-oauth-server/pb/service_grpc.pb.go @@ -0,0 +1,189 @@ +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. +// versions: +// - protoc-gen-go-grpc v1.3.0 +// - protoc v5.26.1 +// source: m2m-oauth-server/pb/service.proto + +package pb + +import ( + context "context" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.32.0 or later. +const _ = grpc.SupportPackageIsVersion7 + +const ( + M2MOAuthServer_GetTokens_FullMethodName = "/m2moauthserver.pb.M2MOAuthServer/GetTokens" + M2MOAuthServer_GetBlacklistedTokens_FullMethodName = "/m2moauthserver.pb.M2MOAuthServer/GetBlacklistedTokens" + M2MOAuthServer_BlacklistTokens_FullMethodName = "/m2moauthserver.pb.M2MOAuthServer/BlacklistTokens" +) + +// M2MOAuthServerClient is the client API for M2MOAuthServer service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type M2MOAuthServerClient interface { + // Returns all tokens of the owner + GetTokens(ctx context.Context, in *GetTokensRequest, opts ...grpc.CallOption) (*Token, error) + // Returns all blacklisted/revoked not expired tokens + GetBlacklistedTokens(ctx context.Context, in *GetBlacklistedTokensRequest, opts ...grpc.CallOption) (*GetBlacklistedTokensResponse, error) + // Blacklists/revokes tokens + BlacklistTokens(ctx context.Context, in *BlacklistTokensRequest, opts ...grpc.CallOption) (*BlacklistTokensResponse, error) +} + +type m2MOAuthServerClient struct { + cc grpc.ClientConnInterface +} + +func NewM2MOAuthServerClient(cc grpc.ClientConnInterface) M2MOAuthServerClient { + return &m2MOAuthServerClient{cc} +} + +func (c *m2MOAuthServerClient) GetTokens(ctx context.Context, in *GetTokensRequest, opts ...grpc.CallOption) (*Token, error) { + out := new(Token) + err := c.cc.Invoke(ctx, M2MOAuthServer_GetTokens_FullMethodName, in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *m2MOAuthServerClient) GetBlacklistedTokens(ctx context.Context, in *GetBlacklistedTokensRequest, opts ...grpc.CallOption) (*GetBlacklistedTokensResponse, error) { + out := new(GetBlacklistedTokensResponse) + err := c.cc.Invoke(ctx, M2MOAuthServer_GetBlacklistedTokens_FullMethodName, in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *m2MOAuthServerClient) BlacklistTokens(ctx context.Context, in *BlacklistTokensRequest, opts ...grpc.CallOption) (*BlacklistTokensResponse, error) { + out := new(BlacklistTokensResponse) + err := c.cc.Invoke(ctx, M2MOAuthServer_BlacklistTokens_FullMethodName, in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// M2MOAuthServerServer is the server API for M2MOAuthServer service. +// All implementations must embed UnimplementedM2MOAuthServerServer +// for forward compatibility +type M2MOAuthServerServer interface { + // Returns all tokens of the owner + GetTokens(context.Context, *GetTokensRequest) (*Token, error) + // Returns all blacklisted/revoked not expired tokens + GetBlacklistedTokens(context.Context, *GetBlacklistedTokensRequest) (*GetBlacklistedTokensResponse, error) + // Blacklists/revokes tokens + BlacklistTokens(context.Context, *BlacklistTokensRequest) (*BlacklistTokensResponse, error) + mustEmbedUnimplementedM2MOAuthServerServer() +} + +// UnimplementedM2MOAuthServerServer must be embedded to have forward compatible implementations. +type UnimplementedM2MOAuthServerServer struct { +} + +func (UnimplementedM2MOAuthServerServer) GetTokens(context.Context, *GetTokensRequest) (*Token, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetTokens not implemented") +} +func (UnimplementedM2MOAuthServerServer) GetBlacklistedTokens(context.Context, *GetBlacklistedTokensRequest) (*GetBlacklistedTokensResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetBlacklistedTokens not implemented") +} +func (UnimplementedM2MOAuthServerServer) BlacklistTokens(context.Context, *BlacklistTokensRequest) (*BlacklistTokensResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method BlacklistTokens not implemented") +} +func (UnimplementedM2MOAuthServerServer) mustEmbedUnimplementedM2MOAuthServerServer() {} + +// UnsafeM2MOAuthServerServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to M2MOAuthServerServer will +// result in compilation errors. +type UnsafeM2MOAuthServerServer interface { + mustEmbedUnimplementedM2MOAuthServerServer() +} + +func RegisterM2MOAuthServerServer(s grpc.ServiceRegistrar, srv M2MOAuthServerServer) { + s.RegisterService(&M2MOAuthServer_ServiceDesc, srv) +} + +func _M2MOAuthServer_GetTokens_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetTokensRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(M2MOAuthServerServer).GetTokens(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: M2MOAuthServer_GetTokens_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(M2MOAuthServerServer).GetTokens(ctx, req.(*GetTokensRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _M2MOAuthServer_GetBlacklistedTokens_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetBlacklistedTokensRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(M2MOAuthServerServer).GetBlacklistedTokens(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: M2MOAuthServer_GetBlacklistedTokens_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(M2MOAuthServerServer).GetBlacklistedTokens(ctx, req.(*GetBlacklistedTokensRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _M2MOAuthServer_BlacklistTokens_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(BlacklistTokensRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(M2MOAuthServerServer).BlacklistTokens(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: M2MOAuthServer_BlacklistTokens_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(M2MOAuthServerServer).BlacklistTokens(ctx, req.(*BlacklistTokensRequest)) + } + return interceptor(ctx, in, info, handler) +} + +// M2MOAuthServer_ServiceDesc is the grpc.ServiceDesc for M2MOAuthServer service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var M2MOAuthServer_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "m2moauthserver.pb.M2MOAuthServer", + HandlerType: (*M2MOAuthServerServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "GetTokens", + Handler: _M2MOAuthServer_GetTokens_Handler, + }, + { + MethodName: "GetBlacklistedTokens", + Handler: _M2MOAuthServer_GetBlacklistedTokens_Handler, + }, + { + MethodName: "BlacklistTokens", + Handler: _M2MOAuthServer_BlacklistTokens_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "m2m-oauth-server/pb/service.proto", +} diff --git a/m2m-oauth-server/service/token.go b/m2m-oauth-server/service/token.go index dfdfb28cd..1fe097ef3 100644 --- a/m2m-oauth-server/service/token.go +++ b/m2m-oauth-server/service/token.go @@ -56,6 +56,9 @@ func makeAccessToken(clientCfg *Client, tokenReq tokenRequest, issuedAt, expires if err := setDeviceIDClaim(token, tokenReq); err != nil { return nil, err } + if err := setName(token, tokenReq); err != nil { + return nil, err + } if err := setOwnerClaim(token, tokenReq); err != nil { return nil, err } @@ -99,6 +102,13 @@ func setOwnerClaim(token jwt.Token, tokenReq tokenRequest) error { return nil } +func setName(token jwt.Token, tokenReq tokenRequest) error { + if tokenReq.TokenName != "" && tokenReq.ownerClaim != "name" { + return token.Set("name", tokenReq.TokenName) + } + return nil +} + func setOriginTokenClaims(token jwt.Token, tokenReq tokenRequest) error { if len(tokenReq.originalTokenClaims) > 0 { return token.Set(uri.OriginalTokenClaims, tokenReq.originalTokenClaims) @@ -152,6 +162,7 @@ type tokenRequest struct { GrantType GrantType `json:"grant_type"` ClientAssertionType string `json:"client_assertion_type"` ClientAssertion string `json:"client_assertion"` + TokenName string `json:"token_name"` deviceID string `json:"-"` owner string `json:"-"` @@ -186,6 +197,7 @@ func (requestHandler *RequestHandler) postToken(w http.ResponseWriter, r *http.R tokenReq.Secret = r.PostFormValue(uri.ClientSecretKey) tokenReq.ClientAssertionType = r.PostFormValue(uri.ClientAssertionTypeKey) tokenReq.ClientAssertion = r.PostFormValue(uri.ClientAssertionKey) + tokenReq.TokenName = r.PostFormValue(uri.TokenName) } else { err := json.ReadFrom(r.Body, &tokenReq) if err != nil { diff --git a/m2m-oauth-server/uri/uri.go b/m2m-oauth-server/uri/uri.go index 12705adce..58c553495 100644 --- a/m2m-oauth-server/uri/uri.go +++ b/m2m-oauth-server/uri/uri.go @@ -5,6 +5,7 @@ import "github.com/lestrrat-go/jwx/v2/jwt" const ( ClientIDKey = "client_id" ClientSecretKey = "client_secret" + TokenName = "token_name" ScopeKey = "scope" GrantTypeKey = "grant_type" AudienceKey = jwt.AudienceKey From 80a24e62aee711369e4bc4e245dc9cd49c979cbf Mon Sep 17 00:00:00 2001 From: Jozef Kralik Date: Tue, 16 Jul 2024 15:47:38 +0000 Subject: [PATCH 02/31] implement mongodb --- m2m-oauth-server/pb/README.md | 12 +- m2m-oauth-server/pb/doc.html | 34 +- m2m-oauth-server/pb/service.pb.go | 245 +++++----- m2m-oauth-server/pb/service.proto | 18 +- m2m-oauth-server/pb/service.swagger.json | 28 +- m2m-oauth-server/pb/tags.go | 11 + m2m-oauth-server/pb/token.go | 188 ++++++++ m2m-oauth-server/service/config.go | 5 + m2m-oauth-server/store/config/config.go | 44 ++ m2m-oauth-server/store/config/config_test.go | 50 +++ m2m-oauth-server/store/cqldb/config.go | 15 + m2m-oauth-server/store/cqldb/store.go | 9 + m2m-oauth-server/store/cqldb/tokens.go | 24 + m2m-oauth-server/store/mongodb/config.go | 13 + m2m-oauth-server/store/mongodb/store.go | 66 +++ m2m-oauth-server/store/mongodb/tokens.go | 195 ++++++++ m2m-oauth-server/store/mongodb/tokens_test.go | 419 ++++++++++++++++++ m2m-oauth-server/store/store.go | 83 ++++ m2m-oauth-server/test/service.go | 65 +++ m2m-oauth-server/test/test.go | 2 + 20 files changed, 1359 insertions(+), 167 deletions(-) create mode 100644 m2m-oauth-server/pb/tags.go create mode 100644 m2m-oauth-server/pb/token.go create mode 100644 m2m-oauth-server/store/config/config.go create mode 100644 m2m-oauth-server/store/config/config_test.go create mode 100644 m2m-oauth-server/store/cqldb/config.go create mode 100644 m2m-oauth-server/store/cqldb/store.go create mode 100644 m2m-oauth-server/store/cqldb/tokens.go create mode 100644 m2m-oauth-server/store/mongodb/config.go create mode 100644 m2m-oauth-server/store/mongodb/store.go create mode 100644 m2m-oauth-server/store/mongodb/tokens.go create mode 100644 m2m-oauth-server/store/mongodb/tokens_test.go create mode 100644 m2m-oauth-server/store/store.go create mode 100644 m2m-oauth-server/test/service.go diff --git a/m2m-oauth-server/pb/README.md b/m2m-oauth-server/pb/README.md index 2d0ea5342..31fc0c3c1 100644 --- a/m2m-oauth-server/pb/README.md +++ b/m2m-oauth-server/pb/README.md @@ -49,7 +49,7 @@ | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | -| id | [string](#string) | repeated | | +| count | [int64](#int64) | | | @@ -64,7 +64,7 @@ | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | -| version | [uint64](#uint64) | | Returns all blacklisted/revoked not expired tokens of the owner from the given version | +| timestamp | [int64](#int64) | | Returns all blacklisted/revoked not expired tokens of the owner from the given timestamp | @@ -80,7 +80,7 @@ | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | black_list | [GetBlacklistedTokensResponse.Item](#m2moauthserver-pb-GetBlacklistedTokensResponse-Item) | repeated | | -| version | [uint64](#uint64) | | The biggest version of the blacklisted/revoked token over all tokens of owner | +| timestamp | [int64](#int64) | | The biggest version of the blacklisted/revoked token over all tokens of owner | @@ -130,7 +130,9 @@ driven by resource change event | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | -| id | [string](#string) | | Token ID / jti | +| id | [string](#string) | | Token ID / jti + +@gotags: bson:"_id" | | version | [uint64](#uint64) | | Incremetal version for update | | name | [string](#string) | | User-friendly token name | | owner | [string](#string) | | Owner of the token | @@ -138,6 +140,7 @@ driven by resource change event | audience | [string](#string) | repeated | Token Audience | | scopes | [string](#string) | repeated | Token scopes | | expiration | [int64](#int64) | | Original token expiration | +| client_id | [string](#string) | | Client ID | | original_token_claims | [google.protobuf.Value](#google-protobuf-Value) | | Original token claims | | blacklisted | [Token.BlackListed](#m2moauthserver-pb-Token-BlackListed) | | Token black list section | @@ -156,7 +159,6 @@ driven by resource change event | ----- | ---- | ----- | ----------- | | flag | [bool](#bool) | | Blacklisted/revoked enabled flag, if once token has been blacklisted/revoked then it can't be unblacklisted/unrevoked | | timestamp | [int64](#int64) | | Unix timestamp in ns when the token has been blacklisted/revoked | -| version | [uint64](#uint64) | | Incremental version of the blacklisted/revoked token over all tokens of owner | diff --git a/m2m-oauth-server/pb/doc.html b/m2m-oauth-server/pb/doc.html index 9d227701c..7a6adf655 100644 --- a/m2m-oauth-server/pb/doc.html +++ b/m2m-oauth-server/pb/doc.html @@ -267,9 +267,9 @@

BlacklistTokensResponse

- id - string - repeated + count + int64 +

@@ -291,10 +291,10 @@

GetBlacklistedTokensReque - version - uint64 + timestamp + int64 -

Returns all blacklisted/revoked not expired tokens of the owner from the given version

+

Returns all blacklisted/revoked not expired tokens of the owner from the given timestamp

@@ -322,8 +322,8 @@

GetBlacklistedTokensResp - version - uint64 + timestamp + int64

The biggest version of the blacklisted/revoked token over all tokens of owner

@@ -418,7 +418,9 @@

Token

id string -

Token ID / jti

+

Token ID / jti + +@gotags: bson:"_id"

@@ -470,6 +472,13 @@

Token

Original token expiration

+ + client_id + string + +

Client ID

+ + original_token_claims google.protobuf.Value @@ -515,13 +524,6 @@

Token.BlackListed

Unix timestamp in ns when the token has been blacklisted/revoked

- - version - uint64 - -

Incremental version of the blacklisted/revoked token over all tokens of owner

- - diff --git a/m2m-oauth-server/pb/service.pb.go b/m2m-oauth-server/pb/service.pb.go index f9f38bc93..4b330e6da 100644 --- a/m2m-oauth-server/pb/service.pb.go +++ b/m2m-oauth-server/pb/service.pb.go @@ -30,7 +30,7 @@ type Token struct { unknownFields protoimpl.UnknownFields // Token ID / jti - Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty" bson:"_id"` // Incremetal version for update Version uint64 `protobuf:"varint,2,opt,name=version,proto3" json:"version,omitempty"` // User-friendly token name @@ -45,10 +45,12 @@ type Token struct { Scopes []string `protobuf:"bytes,7,rep,name=scopes,proto3" json:"scopes,omitempty"` // Original token expiration Expiration int64 `protobuf:"varint,8,opt,name=expiration,proto3" json:"expiration,omitempty"` + // Client ID + ClientId string `protobuf:"bytes,9,opt,name=client_id,json=clientId,proto3" json:"client_id,omitempty"` // Original token claims - OriginalTokenClaims *structpb.Value `protobuf:"bytes,9,opt,name=original_token_claims,json=originalTokenClaims,proto3" json:"original_token_claims,omitempty"` + OriginalTokenClaims *structpb.Value `protobuf:"bytes,10,opt,name=original_token_claims,json=originalTokenClaims,proto3" json:"original_token_claims,omitempty"` // Token black list section - Blacklisted *Token_BlackListed `protobuf:"bytes,10,opt,name=blacklisted,proto3" json:"blacklisted,omitempty"` + Blacklisted *Token_BlackListed `protobuf:"bytes,11,opt,name=blacklisted,proto3" json:"blacklisted,omitempty"` } func (x *Token) Reset() { @@ -139,6 +141,13 @@ func (x *Token) GetExpiration() int64 { return 0 } +func (x *Token) GetClientId() string { + if x != nil { + return x.ClientId + } + return "" +} + func (x *Token) GetOriginalTokenClaims() *structpb.Value { if x != nil { return x.OriginalTokenClaims @@ -158,8 +167,8 @@ type GetBlacklistedTokensRequest struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - // Returns all blacklisted/revoked not expired tokens of the owner from the given version - Version uint64 `protobuf:"varint,1,opt,name=version,proto3" json:"version,omitempty"` + // Returns all blacklisted/revoked not expired tokens of the owner from the given timestamp + Timestamp int64 `protobuf:"varint,1,opt,name=timestamp,proto3" json:"timestamp,omitempty"` } func (x *GetBlacklistedTokensRequest) Reset() { @@ -194,9 +203,9 @@ func (*GetBlacklistedTokensRequest) Descriptor() ([]byte, []int) { return file_m2m_oauth_server_pb_service_proto_rawDescGZIP(), []int{1} } -func (x *GetBlacklistedTokensRequest) GetVersion() uint64 { +func (x *GetBlacklistedTokensRequest) GetTimestamp() int64 { if x != nil { - return x.Version + return x.Timestamp } return 0 } @@ -208,7 +217,7 @@ type GetBlacklistedTokensResponse struct { BlackList []*GetBlacklistedTokensResponse_Item `protobuf:"bytes,1,rep,name=black_list,json=blackList,proto3" json:"black_list,omitempty"` // The biggest version of the blacklisted/revoked token over all tokens of owner - Version uint64 `protobuf:"varint,2,opt,name=version,proto3" json:"version,omitempty"` + Timestamp int64 `protobuf:"varint,2,opt,name=timestamp,proto3" json:"timestamp,omitempty"` } func (x *GetBlacklistedTokensResponse) Reset() { @@ -250,9 +259,9 @@ func (x *GetBlacklistedTokensResponse) GetBlackList() []*GetBlacklistedTokensRes return nil } -func (x *GetBlacklistedTokensResponse) GetVersion() uint64 { +func (x *GetBlacklistedTokensResponse) GetTimestamp() int64 { if x != nil { - return x.Version + return x.Timestamp } return 0 } @@ -372,7 +381,7 @@ type BlacklistTokensResponse struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - Id []string `protobuf:"bytes,1,rep,name=id,proto3" json:"id,omitempty"` + Count int64 `protobuf:"varint,1,opt,name=count,proto3" json:"count,omitempty"` } func (x *BlacklistTokensResponse) Reset() { @@ -407,11 +416,11 @@ func (*BlacklistTokensResponse) Descriptor() ([]byte, []int) { return file_m2m_oauth_server_pb_service_proto_rawDescGZIP(), []int{5} } -func (x *BlacklistTokensResponse) GetId() []string { +func (x *BlacklistTokensResponse) GetCount() int64 { if x != nil { - return x.Id + return x.Count } - return nil + return 0 } type Token_BlackListed struct { @@ -423,8 +432,6 @@ type Token_BlackListed struct { Flag bool `protobuf:"varint,1,opt,name=flag,proto3" json:"flag,omitempty"` // Unix timestamp in ns when the token has been blacklisted/revoked Timestamp int64 `protobuf:"varint,2,opt,name=timestamp,proto3" json:"timestamp,omitempty"` - // Incremental version of the blacklisted/revoked token over all tokens of owner - Version uint64 `protobuf:"varint,3,opt,name=version,proto3" json:"version,omitempty"` } func (x *Token_BlackListed) Reset() { @@ -473,13 +480,6 @@ func (x *Token_BlackListed) GetTimestamp() int64 { return 0 } -func (x *Token_BlackListed) GetVersion() uint64 { - if x != nil { - return x.Version - } - return 0 -} - type GetBlacklistedTokensResponse_Item struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -548,7 +548,7 @@ var file_m2m_oauth_server_pb_service_proto_rawDesc = []byte{ 0x74, 0x6f, 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x2d, 0x67, 0x65, 0x6e, 0x2d, 0x6f, 0x70, 0x65, 0x6e, 0x61, 0x70, 0x69, 0x76, 0x32, 0x2f, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2f, 0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x22, 0xbc, 0x03, 0x0a, 0x05, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x0e, 0x0a, 0x02, + 0x74, 0x6f, 0x22, 0xbf, 0x03, 0x0a, 0x05, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, @@ -561,104 +561,105 @@ var file_m2m_oauth_server_pb_service_proto_rawDesc = []byte{ 0x6f, 0x70, 0x65, 0x73, 0x18, 0x07, 0x20, 0x03, 0x28, 0x09, 0x52, 0x06, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x73, 0x12, 0x1e, 0x0a, 0x0a, 0x65, 0x78, 0x70, 0x69, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x08, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x65, 0x78, 0x70, 0x69, 0x72, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x12, 0x4a, 0x0a, 0x15, 0x6f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x61, 0x6c, 0x5f, 0x74, - 0x6f, 0x6b, 0x65, 0x6e, 0x5f, 0x63, 0x6c, 0x61, 0x69, 0x6d, 0x73, 0x18, 0x09, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x62, 0x75, 0x66, 0x2e, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x13, 0x6f, 0x72, 0x69, 0x67, 0x69, - 0x6e, 0x61, 0x6c, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x43, 0x6c, 0x61, 0x69, 0x6d, 0x73, 0x12, 0x46, - 0x0a, 0x0b, 0x62, 0x6c, 0x61, 0x63, 0x6b, 0x6c, 0x69, 0x73, 0x74, 0x65, 0x64, 0x18, 0x0a, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x6d, 0x32, 0x6d, 0x6f, 0x61, 0x75, 0x74, 0x68, 0x73, 0x65, - 0x72, 0x76, 0x65, 0x72, 0x2e, 0x70, 0x62, 0x2e, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x2e, 0x42, 0x6c, - 0x61, 0x63, 0x6b, 0x4c, 0x69, 0x73, 0x74, 0x65, 0x64, 0x52, 0x0b, 0x62, 0x6c, 0x61, 0x63, 0x6b, - 0x6c, 0x69, 0x73, 0x74, 0x65, 0x64, 0x1a, 0x59, 0x0a, 0x0b, 0x42, 0x6c, 0x61, 0x63, 0x6b, 0x4c, - 0x69, 0x73, 0x74, 0x65, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x66, 0x6c, 0x61, 0x67, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x08, 0x52, 0x04, 0x66, 0x6c, 0x61, 0x67, 0x12, 0x1c, 0x0a, 0x09, 0x74, 0x69, 0x6d, - 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x74, 0x69, - 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, - 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, - 0x6e, 0x22, 0x37, 0x0a, 0x1b, 0x47, 0x65, 0x74, 0x42, 0x6c, 0x61, 0x63, 0x6b, 0x6c, 0x69, 0x73, - 0x74, 0x65, 0x64, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x04, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0xc1, 0x01, 0x0a, 0x1c, 0x47, - 0x65, 0x74, 0x42, 0x6c, 0x61, 0x63, 0x6b, 0x6c, 0x69, 0x73, 0x74, 0x65, 0x64, 0x54, 0x6f, 0x6b, - 0x65, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x53, 0x0a, 0x0a, 0x62, - 0x6c, 0x61, 0x63, 0x6b, 0x5f, 0x6c, 0x69, 0x73, 0x74, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, - 0x34, 0x2e, 0x6d, 0x32, 0x6d, 0x6f, 0x61, 0x75, 0x74, 0x68, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, - 0x2e, 0x70, 0x62, 0x2e, 0x47, 0x65, 0x74, 0x42, 0x6c, 0x61, 0x63, 0x6b, 0x6c, 0x69, 0x73, 0x74, - 0x65, 0x64, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x2e, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x09, 0x62, 0x6c, 0x61, 0x63, 0x6b, 0x4c, 0x69, 0x73, 0x74, - 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x04, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x1a, 0x32, 0x0a, 0x04, 0x49, 0x74, - 0x65, 0x6d, 0x12, 0x1a, 0x0a, 0x08, 0x61, 0x75, 0x64, 0x69, 0x65, 0x6e, 0x63, 0x65, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x61, 0x75, 0x64, 0x69, 0x65, 0x6e, 0x63, 0x65, 0x12, 0x0e, - 0x0a, 0x02, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x22, 0x89, - 0x01, 0x0a, 0x10, 0x47, 0x65, 0x74, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x69, 0x64, 0x5f, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, - 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x08, 0x69, 0x64, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, - 0x12, 0x27, 0x0a, 0x0f, 0x61, 0x75, 0x64, 0x69, 0x65, 0x6e, 0x63, 0x65, 0x5f, 0x66, 0x69, 0x6c, - 0x74, 0x65, 0x72, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0e, 0x61, 0x75, 0x64, 0x69, 0x65, - 0x6e, 0x63, 0x65, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x12, 0x2f, 0x0a, 0x13, 0x69, 0x6e, 0x63, - 0x6c, 0x75, 0x64, 0x65, 0x5f, 0x62, 0x6c, 0x61, 0x63, 0x6b, 0x6c, 0x69, 0x73, 0x74, 0x65, 0x64, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x12, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x42, - 0x6c, 0x61, 0x63, 0x6b, 0x6c, 0x69, 0x73, 0x74, 0x65, 0x64, 0x22, 0x35, 0x0a, 0x16, 0x42, 0x6c, - 0x61, 0x63, 0x6b, 0x6c, 0x69, 0x73, 0x74, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x69, 0x64, 0x5f, 0x66, 0x69, 0x6c, 0x74, 0x65, - 0x72, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x08, 0x69, 0x64, 0x46, 0x69, 0x6c, 0x74, 0x65, - 0x72, 0x22, 0x29, 0x0a, 0x17, 0x42, 0x6c, 0x61, 0x63, 0x6b, 0x6c, 0x69, 0x73, 0x74, 0x54, 0x6f, - 0x6b, 0x65, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x0e, 0x0a, 0x02, - 0x69, 0x64, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x32, 0xe6, 0x03, 0x0a, - 0x0e, 0x4d, 0x32, 0x4d, 0x4f, 0x41, 0x75, 0x74, 0x68, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x12, - 0x7e, 0x0a, 0x09, 0x47, 0x65, 0x74, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x12, 0x23, 0x2e, 0x6d, - 0x32, 0x6d, 0x6f, 0x61, 0x75, 0x74, 0x68, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x70, 0x62, - 0x2e, 0x47, 0x65, 0x74, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x18, 0x2e, 0x6d, 0x32, 0x6d, 0x6f, 0x61, 0x75, 0x74, 0x68, 0x73, 0x65, 0x72, 0x76, - 0x65, 0x72, 0x2e, 0x70, 0x62, 0x2e, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0x32, 0x92, 0x41, 0x08, - 0x0a, 0x06, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x21, 0x12, 0x1f, - 0x2f, 0x6d, 0x32, 0x6d, 0x2d, 0x6f, 0x61, 0x75, 0x74, 0x68, 0x2d, 0x73, 0x65, 0x72, 0x76, 0x65, - 0x72, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x12, - 0xae, 0x01, 0x0a, 0x14, 0x47, 0x65, 0x74, 0x42, 0x6c, 0x61, 0x63, 0x6b, 0x6c, 0x69, 0x73, 0x74, - 0x65, 0x64, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x12, 0x2e, 0x2e, 0x6d, 0x32, 0x6d, 0x6f, 0x61, - 0x75, 0x74, 0x68, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x70, 0x62, 0x2e, 0x47, 0x65, 0x74, - 0x42, 0x6c, 0x61, 0x63, 0x6b, 0x6c, 0x69, 0x73, 0x74, 0x65, 0x64, 0x54, 0x6f, 0x6b, 0x65, 0x6e, - 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2f, 0x2e, 0x6d, 0x32, 0x6d, 0x6f, 0x61, - 0x75, 0x74, 0x68, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x70, 0x62, 0x2e, 0x47, 0x65, 0x74, - 0x42, 0x6c, 0x61, 0x63, 0x6b, 0x6c, 0x69, 0x73, 0x74, 0x65, 0x64, 0x54, 0x6f, 0x6b, 0x65, 0x6e, - 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x35, 0x92, 0x41, 0x08, 0x0a, 0x06, - 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x24, 0x12, 0x22, 0x2f, 0x6d, - 0x32, 0x6d, 0x2d, 0x6f, 0x61, 0x75, 0x74, 0x68, 0x2d, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2f, - 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x62, 0x6c, 0x61, 0x63, 0x6b, 0x6c, 0x69, 0x73, 0x74, - 0x12, 0xa2, 0x01, 0x0a, 0x0f, 0x42, 0x6c, 0x61, 0x63, 0x6b, 0x6c, 0x69, 0x73, 0x74, 0x54, 0x6f, - 0x6b, 0x65, 0x6e, 0x73, 0x12, 0x29, 0x2e, 0x6d, 0x32, 0x6d, 0x6f, 0x61, 0x75, 0x74, 0x68, 0x73, - 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x70, 0x62, 0x2e, 0x42, 0x6c, 0x61, 0x63, 0x6b, 0x6c, 0x69, - 0x73, 0x74, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, - 0x2a, 0x2e, 0x6d, 0x32, 0x6d, 0x6f, 0x61, 0x75, 0x74, 0x68, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, - 0x2e, 0x70, 0x62, 0x2e, 0x42, 0x6c, 0x61, 0x63, 0x6b, 0x6c, 0x69, 0x73, 0x74, 0x54, 0x6f, 0x6b, - 0x65, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x38, 0x92, 0x41, 0x08, - 0x0a, 0x06, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x27, 0x3a, 0x01, - 0x2a, 0x22, 0x22, 0x2f, 0x6d, 0x32, 0x6d, 0x2d, 0x6f, 0x61, 0x75, 0x74, 0x68, 0x2d, 0x73, 0x65, - 0x72, 0x76, 0x65, 0x72, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x62, 0x6c, 0x61, 0x63, - 0x6b, 0x6c, 0x69, 0x73, 0x74, 0x42, 0xcb, 0x02, 0x92, 0x41, 0x94, 0x02, 0x12, 0xbc, 0x01, 0x0a, - 0x0c, 0x50, 0x4c, 0x47, 0x44, 0x20, 0x4d, 0x32, 0x4d, 0x20, 0x41, 0x50, 0x49, 0x12, 0x24, 0x41, - 0x50, 0x49, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x74, 0x6f, 0x20, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, - 0x20, 0x6d, 0x32, 0x6d, 0x20, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x20, 0x69, 0x6e, 0x20, 0x50, - 0x4c, 0x47, 0x44, 0x22, 0x3a, 0x0a, 0x08, 0x70, 0x6c, 0x67, 0x64, 0x2e, 0x64, 0x65, 0x76, 0x12, - 0x1f, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, - 0x63, 0x6f, 0x6d, 0x2f, 0x70, 0x6c, 0x67, 0x64, 0x2d, 0x64, 0x65, 0x76, 0x2f, 0x68, 0x75, 0x62, - 0x1a, 0x0d, 0x69, 0x6e, 0x66, 0x6f, 0x40, 0x70, 0x6c, 0x67, 0x64, 0x2e, 0x64, 0x65, 0x76, 0x2a, - 0x45, 0x0a, 0x12, 0x41, 0x70, 0x61, 0x63, 0x68, 0x65, 0x20, 0x4c, 0x69, 0x63, 0x65, 0x6e, 0x73, - 0x65, 0x20, 0x32, 0x2e, 0x30, 0x12, 0x2f, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x67, - 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x70, 0x6c, 0x67, 0x64, 0x2d, 0x64, - 0x65, 0x76, 0x2f, 0x68, 0x75, 0x62, 0x2f, 0x62, 0x6c, 0x6f, 0x62, 0x2f, 0x76, 0x32, 0x2f, 0x4c, - 0x49, 0x43, 0x45, 0x4e, 0x53, 0x45, 0x32, 0x03, 0x31, 0x2e, 0x30, 0x2a, 0x01, 0x02, 0x32, 0x10, - 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x6a, 0x73, 0x6f, 0x6e, - 0x32, 0x15, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x6a, 0x73, 0x6f, 0x6e, 0x3a, 0x10, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x6a, 0x73, 0x6f, 0x6e, 0x3a, 0x15, 0x61, 0x70, 0x70, 0x6c, 0x69, - 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x6a, 0x73, 0x6f, 0x6e, - 0x5a, 0x31, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x70, 0x6c, 0x67, - 0x64, 0x2d, 0x64, 0x65, 0x76, 0x2f, 0x68, 0x75, 0x62, 0x2f, 0x76, 0x32, 0x2f, 0x6d, 0x32, 0x6d, - 0x2d, 0x6f, 0x61, 0x75, 0x74, 0x68, 0x2d, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2f, 0x70, 0x62, - 0x3b, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x6f, 0x6e, 0x12, 0x1b, 0x0a, 0x09, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, + 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x12, + 0x4a, 0x0a, 0x15, 0x6f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x61, 0x6c, 0x5f, 0x74, 0x6f, 0x6b, 0x65, + 0x6e, 0x5f, 0x63, 0x6c, 0x61, 0x69, 0x6d, 0x73, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, + 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, + 0x2e, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x13, 0x6f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x61, 0x6c, + 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x43, 0x6c, 0x61, 0x69, 0x6d, 0x73, 0x12, 0x46, 0x0a, 0x0b, 0x62, + 0x6c, 0x61, 0x63, 0x6b, 0x6c, 0x69, 0x73, 0x74, 0x65, 0x64, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x24, 0x2e, 0x6d, 0x32, 0x6d, 0x6f, 0x61, 0x75, 0x74, 0x68, 0x73, 0x65, 0x72, 0x76, 0x65, + 0x72, 0x2e, 0x70, 0x62, 0x2e, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x2e, 0x42, 0x6c, 0x61, 0x63, 0x6b, + 0x4c, 0x69, 0x73, 0x74, 0x65, 0x64, 0x52, 0x0b, 0x62, 0x6c, 0x61, 0x63, 0x6b, 0x6c, 0x69, 0x73, + 0x74, 0x65, 0x64, 0x1a, 0x3f, 0x0a, 0x0b, 0x42, 0x6c, 0x61, 0x63, 0x6b, 0x4c, 0x69, 0x73, 0x74, + 0x65, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x66, 0x6c, 0x61, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, + 0x52, 0x04, 0x66, 0x6c, 0x61, 0x67, 0x12, 0x1c, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, + 0x61, 0x6d, 0x70, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, + 0x74, 0x61, 0x6d, 0x70, 0x22, 0x3b, 0x0a, 0x1b, 0x47, 0x65, 0x74, 0x42, 0x6c, 0x61, 0x63, 0x6b, + 0x6c, 0x69, 0x73, 0x74, 0x65, 0x64, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x12, 0x1c, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, + 0x70, 0x22, 0xc5, 0x01, 0x0a, 0x1c, 0x47, 0x65, 0x74, 0x42, 0x6c, 0x61, 0x63, 0x6b, 0x6c, 0x69, + 0x73, 0x74, 0x65, 0x64, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x53, 0x0a, 0x0a, 0x62, 0x6c, 0x61, 0x63, 0x6b, 0x5f, 0x6c, 0x69, 0x73, 0x74, + 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x34, 0x2e, 0x6d, 0x32, 0x6d, 0x6f, 0x61, 0x75, 0x74, + 0x68, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x70, 0x62, 0x2e, 0x47, 0x65, 0x74, 0x42, 0x6c, + 0x61, 0x63, 0x6b, 0x6c, 0x69, 0x73, 0x74, 0x65, 0x64, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x09, 0x62, 0x6c, + 0x61, 0x63, 0x6b, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x1c, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, + 0x74, 0x61, 0x6d, 0x70, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, + 0x73, 0x74, 0x61, 0x6d, 0x70, 0x1a, 0x32, 0x0a, 0x04, 0x49, 0x74, 0x65, 0x6d, 0x12, 0x1a, 0x0a, + 0x08, 0x61, 0x75, 0x64, 0x69, 0x65, 0x6e, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x08, 0x61, 0x75, 0x64, 0x69, 0x65, 0x6e, 0x63, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x22, 0x89, 0x01, 0x0a, 0x10, 0x47, 0x65, + 0x74, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1b, + 0x0a, 0x09, 0x69, 0x64, 0x5f, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x18, 0x01, 0x20, 0x03, 0x28, + 0x09, 0x52, 0x08, 0x69, 0x64, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x12, 0x27, 0x0a, 0x0f, 0x61, + 0x75, 0x64, 0x69, 0x65, 0x6e, 0x63, 0x65, 0x5f, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x18, 0x02, + 0x20, 0x03, 0x28, 0x09, 0x52, 0x0e, 0x61, 0x75, 0x64, 0x69, 0x65, 0x6e, 0x63, 0x65, 0x46, 0x69, + 0x6c, 0x74, 0x65, 0x72, 0x12, 0x2f, 0x0a, 0x13, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x5f, + 0x62, 0x6c, 0x61, 0x63, 0x6b, 0x6c, 0x69, 0x73, 0x74, 0x65, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x08, 0x52, 0x12, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x42, 0x6c, 0x61, 0x63, 0x6b, 0x6c, + 0x69, 0x73, 0x74, 0x65, 0x64, 0x22, 0x35, 0x0a, 0x16, 0x42, 0x6c, 0x61, 0x63, 0x6b, 0x6c, 0x69, + 0x73, 0x74, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, + 0x1b, 0x0a, 0x09, 0x69, 0x64, 0x5f, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x18, 0x01, 0x20, 0x03, + 0x28, 0x09, 0x52, 0x08, 0x69, 0x64, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x22, 0x2f, 0x0a, 0x17, + 0x42, 0x6c, 0x61, 0x63, 0x6b, 0x6c, 0x69, 0x73, 0x74, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x32, 0xe6, 0x03, + 0x0a, 0x0e, 0x4d, 0x32, 0x4d, 0x4f, 0x41, 0x75, 0x74, 0x68, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, + 0x12, 0x7e, 0x0a, 0x09, 0x47, 0x65, 0x74, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x12, 0x23, 0x2e, + 0x6d, 0x32, 0x6d, 0x6f, 0x61, 0x75, 0x74, 0x68, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x70, + 0x62, 0x2e, 0x47, 0x65, 0x74, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x6d, 0x32, 0x6d, 0x6f, 0x61, 0x75, 0x74, 0x68, 0x73, 0x65, 0x72, + 0x76, 0x65, 0x72, 0x2e, 0x70, 0x62, 0x2e, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0x32, 0x92, 0x41, + 0x08, 0x0a, 0x06, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x21, 0x12, + 0x1f, 0x2f, 0x6d, 0x32, 0x6d, 0x2d, 0x6f, 0x61, 0x75, 0x74, 0x68, 0x2d, 0x73, 0x65, 0x72, 0x76, + 0x65, 0x72, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x73, + 0x12, 0xae, 0x01, 0x0a, 0x14, 0x47, 0x65, 0x74, 0x42, 0x6c, 0x61, 0x63, 0x6b, 0x6c, 0x69, 0x73, + 0x74, 0x65, 0x64, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x12, 0x2e, 0x2e, 0x6d, 0x32, 0x6d, 0x6f, + 0x61, 0x75, 0x74, 0x68, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x70, 0x62, 0x2e, 0x47, 0x65, + 0x74, 0x42, 0x6c, 0x61, 0x63, 0x6b, 0x6c, 0x69, 0x73, 0x74, 0x65, 0x64, 0x54, 0x6f, 0x6b, 0x65, + 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2f, 0x2e, 0x6d, 0x32, 0x6d, 0x6f, + 0x61, 0x75, 0x74, 0x68, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x70, 0x62, 0x2e, 0x47, 0x65, + 0x74, 0x42, 0x6c, 0x61, 0x63, 0x6b, 0x6c, 0x69, 0x73, 0x74, 0x65, 0x64, 0x54, 0x6f, 0x6b, 0x65, + 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x35, 0x92, 0x41, 0x08, 0x0a, + 0x06, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x24, 0x12, 0x22, 0x2f, + 0x6d, 0x32, 0x6d, 0x2d, 0x6f, 0x61, 0x75, 0x74, 0x68, 0x2d, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, + 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x62, 0x6c, 0x61, 0x63, 0x6b, 0x6c, 0x69, 0x73, + 0x74, 0x12, 0xa2, 0x01, 0x0a, 0x0f, 0x42, 0x6c, 0x61, 0x63, 0x6b, 0x6c, 0x69, 0x73, 0x74, 0x54, + 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x12, 0x29, 0x2e, 0x6d, 0x32, 0x6d, 0x6f, 0x61, 0x75, 0x74, 0x68, + 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x70, 0x62, 0x2e, 0x42, 0x6c, 0x61, 0x63, 0x6b, 0x6c, + 0x69, 0x73, 0x74, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x2a, 0x2e, 0x6d, 0x32, 0x6d, 0x6f, 0x61, 0x75, 0x74, 0x68, 0x73, 0x65, 0x72, 0x76, 0x65, + 0x72, 0x2e, 0x70, 0x62, 0x2e, 0x42, 0x6c, 0x61, 0x63, 0x6b, 0x6c, 0x69, 0x73, 0x74, 0x54, 0x6f, + 0x6b, 0x65, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x38, 0x92, 0x41, + 0x08, 0x0a, 0x06, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x27, 0x3a, + 0x01, 0x2a, 0x22, 0x22, 0x2f, 0x6d, 0x32, 0x6d, 0x2d, 0x6f, 0x61, 0x75, 0x74, 0x68, 0x2d, 0x73, + 0x65, 0x72, 0x76, 0x65, 0x72, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x62, 0x6c, 0x61, + 0x63, 0x6b, 0x6c, 0x69, 0x73, 0x74, 0x42, 0xcb, 0x02, 0x92, 0x41, 0x94, 0x02, 0x12, 0xbc, 0x01, + 0x0a, 0x0c, 0x50, 0x4c, 0x47, 0x44, 0x20, 0x4d, 0x32, 0x4d, 0x20, 0x41, 0x50, 0x49, 0x12, 0x24, + 0x41, 0x50, 0x49, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x74, 0x6f, 0x20, 0x6d, 0x61, 0x6e, 0x61, 0x67, + 0x65, 0x20, 0x6d, 0x32, 0x6d, 0x20, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x20, 0x69, 0x6e, 0x20, + 0x50, 0x4c, 0x47, 0x44, 0x22, 0x3a, 0x0a, 0x08, 0x70, 0x6c, 0x67, 0x64, 0x2e, 0x64, 0x65, 0x76, + 0x12, 0x1f, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, + 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x70, 0x6c, 0x67, 0x64, 0x2d, 0x64, 0x65, 0x76, 0x2f, 0x68, 0x75, + 0x62, 0x1a, 0x0d, 0x69, 0x6e, 0x66, 0x6f, 0x40, 0x70, 0x6c, 0x67, 0x64, 0x2e, 0x64, 0x65, 0x76, + 0x2a, 0x45, 0x0a, 0x12, 0x41, 0x70, 0x61, 0x63, 0x68, 0x65, 0x20, 0x4c, 0x69, 0x63, 0x65, 0x6e, + 0x73, 0x65, 0x20, 0x32, 0x2e, 0x30, 0x12, 0x2f, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, + 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x70, 0x6c, 0x67, 0x64, 0x2d, + 0x64, 0x65, 0x76, 0x2f, 0x68, 0x75, 0x62, 0x2f, 0x62, 0x6c, 0x6f, 0x62, 0x2f, 0x76, 0x32, 0x2f, + 0x4c, 0x49, 0x43, 0x45, 0x4e, 0x53, 0x45, 0x32, 0x03, 0x31, 0x2e, 0x30, 0x2a, 0x01, 0x02, 0x32, + 0x10, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x6a, 0x73, 0x6f, + 0x6e, 0x32, 0x15, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x6a, 0x73, 0x6f, 0x6e, 0x3a, 0x10, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x6a, 0x73, 0x6f, 0x6e, 0x3a, 0x15, 0x61, 0x70, 0x70, 0x6c, + 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x6a, 0x73, 0x6f, + 0x6e, 0x5a, 0x31, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x70, 0x6c, + 0x67, 0x64, 0x2d, 0x64, 0x65, 0x76, 0x2f, 0x68, 0x75, 0x62, 0x2f, 0x76, 0x32, 0x2f, 0x6d, 0x32, + 0x6d, 0x2d, 0x6f, 0x61, 0x75, 0x74, 0x68, 0x2d, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2f, 0x70, + 0x62, 0x3b, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/m2m-oauth-server/pb/service.proto b/m2m-oauth-server/pb/service.proto index eee0398c2..3d1485bf6 100644 --- a/m2m-oauth-server/pb/service.proto +++ b/m2m-oauth-server/pb/service.proto @@ -31,7 +31,7 @@ option go_package = "github.com/plgd-dev/hub/v2/m2m-oauth-server/pb;pb"; // Tokens are deleted from DB after they are expired and blacklisted/revoked message Token { // driven by resource change event // Token ID / jti - string id = 1; + string id = 1; // @gotags: bson:"_id" // Incremetal version for update uint64 version = 2; // User-friendly token name @@ -46,23 +46,23 @@ message Token { // driven by resource change event repeated string scopes = 7; // Original token expiration int64 expiration = 8; + // Client ID + string client_id = 9; // Original token claims - google.protobuf.Value original_token_claims = 9; + google.protobuf.Value original_token_claims = 10; message BlackListed { // Blacklisted/revoked enabled flag, if once token has been blacklisted/revoked then it can't be unblacklisted/unrevoked bool flag = 1; // Unix timestamp in ns when the token has been blacklisted/revoked int64 timestamp = 2; - // Incremental version of the blacklisted/revoked token over all tokens of owner - uint64 version = 3; } // Token black list section - BlackListed blacklisted = 10; + BlackListed blacklisted = 11; } message GetBlacklistedTokensRequest { - // Returns all blacklisted/revoked not expired tokens of the owner from the given version - uint64 version = 1; + // Returns all blacklisted/revoked not expired tokens of the owner from the given timestamp + int64 timestamp = 1; } message GetBlacklistedTokensResponse { @@ -72,7 +72,7 @@ message GetBlacklistedTokensResponse { } repeated Item black_list = 1; // The biggest version of the blacklisted/revoked token over all tokens of owner - uint64 version = 2; + int64 timestamp = 2; } message GetTokensRequest { @@ -86,7 +86,7 @@ message BlacklistTokensRequest { } message BlacklistTokensResponse { - repeated string id = 1; + int64 count = 1; } service M2MOAuthServer { diff --git a/m2m-oauth-server/pb/service.swagger.json b/m2m-oauth-server/pb/service.swagger.json index 74574cf16..fb34c96c4 100644 --- a/m2m-oauth-server/pb/service.swagger.json +++ b/m2m-oauth-server/pb/service.swagger.json @@ -51,12 +51,12 @@ }, "parameters": [ { - "name": "version", - "description": "Returns all blacklisted/revoked not expired tokens of the owner from the given version", + "name": "timestamp", + "description": "Returns all blacklisted/revoked not expired tokens of the owner from the given timestamp", "in": "query", "required": false, "type": "string", - "format": "uint64" + "format": "int64" } ], "tags": [ @@ -170,11 +170,6 @@ "type": "string", "format": "int64", "title": "Unix timestamp in ns when the token has been blacklisted/revoked" - }, - "version": { - "type": "string", - "format": "uint64", - "title": "Incremental version of the blacklisted/revoked token over all tokens of owner" } } }, @@ -192,11 +187,9 @@ "pbBlacklistTokensResponse": { "type": "object", "properties": { - "id": { - "type": "array", - "items": { - "type": "string" - } + "count": { + "type": "string", + "format": "int64" } } }, @@ -210,9 +203,9 @@ "$ref": "#/definitions/GetBlacklistedTokensResponseItem" } }, - "version": { + "timestamp": { "type": "string", - "format": "uint64", + "format": "int64", "title": "The biggest version of the blacklisted/revoked token over all tokens of owner" } } @@ -222,6 +215,7 @@ "properties": { "id": { "type": "string", + "description": "@gotags: bson:\"_id\"", "title": "Token ID / jti" }, "version": { @@ -261,6 +255,10 @@ "format": "int64", "title": "Original token expiration" }, + "clientId": { + "type": "string", + "title": "Client ID" + }, "originalTokenClaims": { "title": "Original token claims" }, diff --git a/m2m-oauth-server/pb/tags.go b/m2m-oauth-server/pb/tags.go new file mode 100644 index 000000000..7933036c6 --- /dev/null +++ b/m2m-oauth-server/pb/tags.go @@ -0,0 +1,11 @@ +package pb + +const ( + ExpirationKey = "expiration" + OwnerKey = "owner" + BlackListedFlagKey = "blacklisted.flag" + BlackListedTimestampKey = "blacklisted.timestamp" + BlackListedKey = "blacklisted" + TimestampKey = "timestamp" + AudienceKey = "audience" +) diff --git a/m2m-oauth-server/pb/token.go b/m2m-oauth-server/pb/token.go new file mode 100644 index 000000000..f4ba31187 --- /dev/null +++ b/m2m-oauth-server/pb/token.go @@ -0,0 +1,188 @@ +package pb + +import ( + "encoding/json" + "errors" + "fmt" + "strconv" + + "github.com/hashicorp/go-multierror" + "google.golang.org/protobuf/encoding/protojson" +) + +func (x *Token) Validate() error { + if x == nil { + return errors.New("Token is nil") + } + if x.GetId() == "" { + return errors.New("Token.Id is empty") + } + if x.GetOwner() == "" { + return errors.New("Token.Owner is empty") + } + if x.GetClientId() == "" { + return errors.New("Token.ClientId is empty") + } + if x.GetTimestamp() == 0 { + return errors.New("Token.Timestamp is empty") + } + return nil +} + +func (x *Token) ToMap() (map[string]interface{}, error) { + v := protojson.MarshalOptions{ + AllowPartial: true, + EmitUnpopulated: true, + } + data, err := v.Marshal(x) + if err != nil { + return nil, err + } + var m map[string]interface{} + err = json.Unmarshal(data, &m) + if err != nil { + return nil, err + } + return m, nil +} + +func replaceStrToInt64(m map[string]interface{}, keys ...string) error { + var errs *multierror.Error + for _, k := range keys { + exp, ok := m[k] + if ok { + str, ok := exp.(string) + if ok { + i, err := strconv.ParseInt(str, 10, 64) + if err != nil { + errs = multierror.Append(errs, fmt.Errorf("cannot convert key %v to int64, %w", k, err)) + return err + } + m[k] = i + } + } + } + return errs.ErrorOrNil() +} + +func replaceInt64ToStr(m map[string]interface{}, keys ...string) { + for _, k := range keys { + exp, ok := m[k] + if ok { + i, ok := exp.(int64) + if ok { + m[k] = strconv.FormatInt(i, 10) + } + } + } +} + +func (x *Token) ToBsonMap() (map[string]interface{}, error) { + m, err := x.ToMap() + if err != nil { + return nil, err + } + m["_id"] = x.GetId() + delete(m, "id") + err = replaceStrToInt64(m, ExpirationKey, TimestampKey) + if err != nil { + return nil, err + } + blackListed, ok := m[BlackListedKey] + if ok { + mapBlacklisted, ok := blackListed.(map[string]interface{}) + if ok { + err = replaceStrToInt64(mapBlacklisted, TimestampKey) + if err != nil { + return nil, err + } + } + } + return m, nil +} + +func (x *Token) FromMap(m map[string]interface{}) error { + if x == nil { + return errors.New("Token is nil") + } + data, err := json.Marshal(m) + if err != nil { + return err + } + v := protojson.UnmarshalOptions{ + AllowPartial: true, + DiscardUnknown: true, + } + return v.Unmarshal(data, x) +} + +func (x *Token) FromBsonMap(m map[string]interface{}) error { + if x == nil { + return errors.New("Token is nil") + } + m["id"] = m["_id"] + delete(m, "_id") + + replaceInt64ToStr(m, ExpirationKey, TimestampKey) + blackListed, ok := m[BlackListedKey] + if ok { + mapBlacklisted, ok := blackListed.(map[string]interface{}) + if ok { + replaceInt64ToStr(mapBlacklisted, TimestampKey) + } + } + + return x.FromMap(m) +} + +func (x *Token_BlackListed) ToMap() (map[string]interface{}, error) { + v := protojson.MarshalOptions{ + AllowPartial: true, + EmitUnpopulated: true, + } + data, err := v.Marshal(x) + if err != nil { + return nil, err + } + var m map[string]interface{} + err = json.Unmarshal(data, &m) + if err != nil { + return nil, err + } + return m, nil +} + +func (x *Token_BlackListed) FromMap(m map[string]interface{}) error { + if x == nil { + return errors.New("Token_BlackListed is nil") + } + data, err := json.Marshal(m) + if err != nil { + return err + } + v := protojson.UnmarshalOptions{ + AllowPartial: true, + DiscardUnknown: true, + } + return v.Unmarshal(data, x) +} + +func (x *Token_BlackListed) ToBsonMap() (map[string]interface{}, error) { + m, err := x.ToMap() + if err != nil { + return nil, err + } + err = replaceStrToInt64(m, TimestampKey) + if err != nil { + return nil, err + } + return m, nil +} + +func (x *Token_BlackListed) FromBsonMap(m map[string]interface{}) error { + if x == nil { + return errors.New("Token_BlackListed is nil") + } + replaceInt64ToStr(m, TimestampKey) + return x.FromMap(m) +} diff --git a/m2m-oauth-server/service/config.go b/m2m-oauth-server/service/config.go index b2ec547cb..24ec28cf6 100644 --- a/m2m-oauth-server/service/config.go +++ b/m2m-oauth-server/service/config.go @@ -4,6 +4,7 @@ import ( "fmt" "time" + storeConfig "github.com/plgd-dev/hub/v2/m2m-oauth-server/store/config" "github.com/plgd-dev/hub/v2/pkg/config" "github.com/plgd-dev/hub/v2/pkg/config/property/urischeme" "github.com/plgd-dev/hub/v2/pkg/log" @@ -99,6 +100,7 @@ func (c OAuthClientsConfig) Find(id string) *Client { } type ClientsConfig struct { + Storage storeConfig.Config `yaml:"storage" json:"storage"` OpenTelemetryCollector http.OpenTelemetryCollectorConfig `yaml:"openTelemetryCollector" json:"openTelemetryCollector"` } @@ -106,6 +108,9 @@ func (c *ClientsConfig) Validate() error { if err := c.OpenTelemetryCollector.Validate(); err != nil { return fmt.Errorf("openTelemetryCollector.%w", err) } + if err := c.Storage.Validate(); err != nil { + return fmt.Errorf("storage.%w", err) + } return nil } diff --git a/m2m-oauth-server/store/config/config.go b/m2m-oauth-server/store/config/config.go new file mode 100644 index 000000000..f315cbf6c --- /dev/null +++ b/m2m-oauth-server/store/config/config.go @@ -0,0 +1,44 @@ +package config + +import ( + "fmt" + "time" + + "github.com/go-co-op/gocron/v2" + "github.com/plgd-dev/hub/v2/m2m-oauth-server/store/cqldb" + "github.com/plgd-dev/hub/v2/m2m-oauth-server/store/mongodb" + "github.com/plgd-dev/hub/v2/pkg/config/database" + "github.com/plgd-dev/hub/v2/pkg/log" +) + +type Config struct { + CleanUpDeletedTokens string `yaml:"cleanUpDeletedTokens" json:"cleanUpDeletedTokens"` + ExtendCronParserBySeconds bool `yaml:"-" json:"-"` + database.Config[*mongodb.Config, *cqldb.Config] `yaml:",inline" json:",inline"` +} + +func (c *Config) Validate() error { + if err := c.Config.Validate(); err != nil { + return err + } + if c.CleanUpDeletedTokens == "" { + return nil + } + s, err := gocron.NewScheduler(gocron.WithLocation(time.Local)) //nolint:gosmopolitan + if err != nil { + return fmt.Errorf("cannot create cron job: %w", err) + } + defer func() { + if errS := s.Shutdown(); errS != nil { + log.Errorf("failed to shutdown cron job: %w", errS) + } + }() + _, err = s.NewJob(gocron.CronJob(c.CleanUpDeletedTokens, c.ExtendCronParserBySeconds), + gocron.NewTask(func() { + // do nothing + })) + if err != nil { + return fmt.Errorf("cleanUpExpiredUpdates('%v') - %w", c.CleanUpDeletedTokens, err) + } + return nil +} diff --git a/m2m-oauth-server/store/config/config_test.go b/m2m-oauth-server/store/config/config_test.go new file mode 100644 index 000000000..55245459c --- /dev/null +++ b/m2m-oauth-server/store/config/config_test.go @@ -0,0 +1,50 @@ +package config_test + +import ( + "testing" + + "github.com/plgd-dev/hub/v2/m2m-oauth-server/store/config" + "github.com/plgd-dev/hub/v2/m2m-oauth-server/test" + "github.com/stretchr/testify/require" +) + +func TestConfig(t *testing.T) { + tests := []struct { + name string + cfg config.Config + wantErr bool + }{ + { + name: "valid", + cfg: test.MakeStoreConfig(), + }, + { + name: "valid - no cron", + cfg: func() config.Config { + cfg := test.MakeStoreConfig() + cfg.CleanUpDeletedTokens = "" + return cfg + }(), + }, + { + name: "invalid - bad cron expression", + cfg: func() config.Config { + cfg := test.MakeStoreConfig() + cfg.CleanUpDeletedTokens = "bad" + return cfg + }(), + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := tt.cfg.Validate() + if tt.wantErr { + require.Error(t, err) + return + } + require.NoError(t, err) + }) + } +} diff --git a/m2m-oauth-server/store/cqldb/config.go b/m2m-oauth-server/store/cqldb/config.go new file mode 100644 index 000000000..9ef1fcdd7 --- /dev/null +++ b/m2m-oauth-server/store/cqldb/config.go @@ -0,0 +1,15 @@ +package cqldb + +import ( + "github.com/plgd-dev/hub/v2/pkg/cqldb" +) + +// Config provides Mongo DB configuration options +type Config struct { + Embedded cqldb.Config `yaml:",inline" json:",inline"` + Table string `yaml:"table" json:"table"` +} + +func (c *Config) Validate() error { + return c.Embedded.Validate() +} diff --git a/m2m-oauth-server/store/cqldb/store.go b/m2m-oauth-server/store/cqldb/store.go new file mode 100644 index 000000000..2b608bf94 --- /dev/null +++ b/m2m-oauth-server/store/cqldb/store.go @@ -0,0 +1,9 @@ +package cqldb + +import ( + "github.com/plgd-dev/hub/v2/pkg/cqldb" +) + +type Store struct { + *cqldb.Store +} diff --git a/m2m-oauth-server/store/cqldb/tokens.go b/m2m-oauth-server/store/cqldb/tokens.go new file mode 100644 index 000000000..b3bd34e74 --- /dev/null +++ b/m2m-oauth-server/store/cqldb/tokens.go @@ -0,0 +1,24 @@ +package cqldb + +import ( + "context" + + "github.com/plgd-dev/hub/v2/m2m-oauth-server/pb" + "github.com/plgd-dev/hub/v2/m2m-oauth-server/store" +) + +func (s *Store) CreateToken(context.Context, *pb.Token) (*pb.Token, error) { + return nil, store.ErrNotSupported +} +func (s *Store) GetTokens(context.Context, string, *pb.GetTokensRequest, store.ProcessTokens) error { + return store.ErrNotSupported +} +func (s *Store) DeleteTokens(context.Context) error { + return store.ErrNotSupported +} +func (s *Store) GetBlacklistedTokens(context.Context, string, *pb.GetBlacklistedTokensRequest, store.ProcessTokens) error { + return store.ErrNotSupported +} +func (s *Store) BlacklistTokens(context.Context, string, *pb.BlacklistTokensRequest) (*pb.BlacklistTokensResponse, error) { + return nil, store.ErrNotSupported +} diff --git a/m2m-oauth-server/store/mongodb/config.go b/m2m-oauth-server/store/mongodb/config.go new file mode 100644 index 000000000..8034a68c7 --- /dev/null +++ b/m2m-oauth-server/store/mongodb/config.go @@ -0,0 +1,13 @@ +package mongodb + +import ( + pkgMongo "github.com/plgd-dev/hub/v2/pkg/mongodb" +) + +type Config struct { + Mongo pkgMongo.Config `yaml:",inline"` +} + +func (c *Config) Validate() error { + return c.Mongo.Validate() +} diff --git a/m2m-oauth-server/store/mongodb/store.go b/m2m-oauth-server/store/mongodb/store.go new file mode 100644 index 000000000..10ebf1915 --- /dev/null +++ b/m2m-oauth-server/store/mongodb/store.go @@ -0,0 +1,66 @@ +package mongodb + +import ( + "context" + "fmt" + + "github.com/hashicorp/go-multierror" + "github.com/plgd-dev/hub/v2/m2m-oauth-server/pb" + "github.com/plgd-dev/hub/v2/pkg/fsnotify" + "github.com/plgd-dev/hub/v2/pkg/log" + pkgMongo "github.com/plgd-dev/hub/v2/pkg/mongodb" + "github.com/plgd-dev/hub/v2/pkg/security/certManager/client" + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/mongo" + "go.opentelemetry.io/otel/trace" +) + +type Store struct { + *pkgMongo.Store +} + +const ( + tokensCol = "tokens" +) + +var expirationOwnerBlacklistedIndex = mongo.IndexModel{ + Keys: bson.D{ + {Key: pb.OwnerKey, Value: 1}, + {Key: pb.ExpirationKey, Value: 1}, + {Key: pb.BlackListedTimestampKey, Value: 1}, + {Key: pb.BlackListedFlagKey, Value: 1}, + }, +} + +func New(ctx context.Context, cfg *Config, fileWatcher *fsnotify.Watcher, logger log.Logger, tracerProvider trace.TracerProvider) (*Store, error) { + certManager, err := client.New(cfg.Mongo.TLS, fileWatcher, logger) + if err != nil { + return nil, fmt.Errorf("could not create cert manager: %w", err) + } + + m, err := pkgMongo.NewStoreWithCollections(ctx, &cfg.Mongo, certManager.GetTLSConfig(), tracerProvider, map[string][]mongo.IndexModel{ + tokensCol: {expirationOwnerBlacklistedIndex}, + }) + if err != nil { + certManager.Close() + return nil, err + } + s := Store{Store: m} + s.SetOnClear(s.clearDatabases) + s.AddCloseFunc(certManager.Close) + return &s, nil +} + +func (s *Store) clearDatabases(ctx context.Context) error { + var errors *multierror.Error + collections := []string{tokensCol} + for _, collection := range collections { + err := s.Collection(collection).Drop(ctx) + errors = multierror.Append(errors, err) + } + return errors.ErrorOrNil() +} + +func (s *Store) Close(ctx context.Context) error { + return s.Store.Close(ctx) +} diff --git a/m2m-oauth-server/store/mongodb/tokens.go b/m2m-oauth-server/store/mongodb/tokens.go new file mode 100644 index 000000000..93d3e28e1 --- /dev/null +++ b/m2m-oauth-server/store/mongodb/tokens.go @@ -0,0 +1,195 @@ +package mongodb + +import ( + "context" + "time" + + "github.com/google/uuid" + "github.com/hashicorp/go-multierror" + "github.com/plgd-dev/hub/v2/m2m-oauth-server/pb" + "github.com/plgd-dev/hub/v2/m2m-oauth-server/store" + "github.com/plgd-dev/hub/v2/pkg/mongodb" + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/mongo" + "go.mongodb.org/mongo-driver/mongo/options" +) + +func (s *Store) CreateToken(ctx context.Context, owner string, token *pb.Token) (*pb.Token, error) { + if owner == "" { + token.Owner = owner + } + if token.GetId() == "" { + token.Id = uuid.NewString() + } + if owner != token.GetOwner() { + return nil, store.ErrInvalidArgument + } + err := token.Validate() + if err != nil { + return nil, err + } + m, err := token.ToBsonMap() + if err != nil { + return nil, err + } + _, err = s.Store.Collection(tokensCol).InsertOne(ctx, m) + if err != nil { + return nil, err + } + return token, nil +} + +func toFilter(owner string, req *pb.GetTokensRequest) (filter bson.D) { + if owner != "" { + filter = append(filter, bson.E{Key: pb.OwnerKey, Value: owner}) + } + if len(req.GetIdFilter()) > 0 { + filter = append(filter, bson.E{Key: "_id", Value: bson.M{mongodb.In: req.GetIdFilter()}}) + } + if len(req.GetAudienceFilter()) > 0 { + filter = append(filter, bson.E{Key: pb.AudienceKey, Value: bson.M{mongodb.In: req.GetAudienceFilter()}}) + } + if !req.GetIncludeBlacklisted() { + filter = append(filter, + bson.E{ + Key: mongodb.Or, Value: bson.A{ + bson.M{ + pb.BlackListedFlagKey: bson.M{ + mongodb.Exists: false, + }, + }, + bson.M{ + pb.BlackListedFlagKey: false, + }, + }, + }) + } + return filter +} + +func processCursor[T any](ctx context.Context, cr *mongo.Cursor, process store.Process[T]) error { + var errors *multierror.Error + iter := store.MongoIterator[T]{ + Cursor: cr, + } + for { + var stored T + if !iter.Next(ctx, &stored) { + break + } + err := process(&stored) + if err != nil { + errors = multierror.Append(errors, err) + break + } + } + errors = multierror.Append(errors, iter.Err()) + errClose := cr.Close(ctx) + errors = multierror.Append(errors, errClose) + return errors.ErrorOrNil() +} + +func (s *Store) GetTokens(ctx context.Context, owner string, req *pb.GetTokensRequest, process store.ProcessTokens) error { + if owner == "" { + return store.ErrInvalidArgument + } + filter := toFilter(owner, req) + cur, err := s.Store.Collection(tokensCol).Find(ctx, filter) + if err != nil { + return err + } + return processCursor(ctx, cur, process) +} + +func (s *Store) DeleteTokens(ctx context.Context, now time.Time) error { + deleteFilter := bson.D{ + {Key: pb.ExpirationKey, Value: bson.M{"$lt": now.UnixNano()}}, + {Key: pb.ExpirationKey, Value: bson.M{"$gt": int64(0)}}, + {Key: pb.BlackListedFlagKey, Value: true}, + } + _, err := s.Store.Collection(tokensCol).DeleteMany(ctx, deleteFilter) + return err +} + +func (s *Store) GetBlacklistedTokens(ctx context.Context, owner string, req *pb.GetBlacklistedTokensRequest, process store.ProcessTokens) error { + if owner == "" { + return store.ErrInvalidArgument + } + filter := bson.D{ + {Key: pb.OwnerKey, Value: owner}, + { + Key: mongodb.Or, Value: bson.A{ + bson.M{ + pb.ExpirationKey: bson.M{"$gte": time.Now().UnixNano()}, + }, + bson.M{ + pb.ExpirationKey: bson.M{mongodb.Exists: false}, + }, + bson.M{ + pb.ExpirationKey: int64(0), + }, + }, + }, + {Key: pb.BlackListedTimestampKey, Value: bson.M{"$gt": req.GetTimestamp()}}, + {Key: pb.BlackListedFlagKey, Value: true}, + } + cur, err := s.Store.Collection(tokensCol).Find(ctx, filter, options.Find().SetHint(expirationOwnerBlacklistedIndex.Keys)) + if err != nil { + return err + } + return processCursor(ctx, cur, process) +} + +func (s *Store) BlacklistTokens(ctx context.Context, owner string, req *pb.BlacklistTokensRequest) (*pb.BlacklistTokensResponse, error) { + if owner == "" { + return nil, store.ErrInvalidArgument + } + filter := bson.D{ + {Key: pb.OwnerKey, Value: owner}, + { + Key: mongodb.Or, Value: bson.A{ + bson.M{ + pb.ExpirationKey: bson.M{"$gte": time.Now().UnixNano()}, + }, + bson.M{ + pb.ExpirationKey: bson.M{mongodb.Exists: false}, + }, + bson.M{ + pb.ExpirationKey: int64(0), + }, + }, + }, + { + Key: mongodb.Or, Value: bson.A{ + bson.M{pb.BlackListedFlagKey: false}, + bson.M{pb.BlackListedFlagKey: bson.M{mongodb.Exists: false}}, + }, + }, + } + if len(req.GetIdFilter()) > 0 { + filter = append(filter, bson.E{Key: "_id", Value: bson.M{mongodb.In: req.GetIdFilter()}}) + } + blacklisted := pb.Token_BlackListed{ + Flag: true, + Timestamp: time.Now().UnixNano(), + } + value, err := blacklisted.ToBsonMap() + if err != nil { + return nil, err + } + + update := bson.D{ + { + Key: mongodb.Set, Value: bson.M{ + pb.BlackListedKey: value, + }, + }, + } + ret, err := s.Store.Collection(tokensCol).UpdateMany(ctx, filter, update) + if err != nil { + return nil, err + } + return &pb.BlacklistTokensResponse{ + Count: ret.MatchedCount, + }, nil +} diff --git a/m2m-oauth-server/store/mongodb/tokens_test.go b/m2m-oauth-server/store/mongodb/tokens_test.go new file mode 100644 index 000000000..face3fe41 --- /dev/null +++ b/m2m-oauth-server/store/mongodb/tokens_test.go @@ -0,0 +1,419 @@ +package mongodb_test + +import ( + "context" + "testing" + "time" + + "github.com/plgd-dev/hub/v2/m2m-oauth-server/pb" + "github.com/plgd-dev/hub/v2/m2m-oauth-server/test" + "github.com/plgd-dev/hub/v2/test/config" + "github.com/stretchr/testify/require" +) + +func TestGetTokens(t *testing.T) { + s, cleanUpStore := test.NewMongoStore(t) + defer cleanUpStore() + + ctx, cancel := context.WithTimeout(context.Background(), config.TEST_TIMEOUT*100) + defer cancel() + + // Set the owner and request parameters + owner := "testOwner" + tokens := []*pb.Token{ + { + Id: "token1", + Owner: owner, + Version: 0, + Name: "name1", + Timestamp: time.Now().UnixNano(), + ClientId: "client1", + }, + { + Id: "token2", + Owner: owner, + Version: 0, + Name: "name2", + Timestamp: time.Now().UnixNano(), + ClientId: "client1", + Blacklisted: &pb.Token_BlackListed{ + Flag: true, + Timestamp: time.Now().UnixNano(), + }, + }, + } + + type args struct { + ctx context.Context + owner string + req *pb.GetTokensRequest + } + + tests := []struct { + name string + args args + wantLen int + }{ + { + name: "all tokens", + args: args{ + ctx: ctx, + owner: owner, + req: &pb.GetTokensRequest{}, + }, + wantLen: 1, + }, + { + name: "all tokens including blacklisted", + args: args{ + ctx: ctx, + owner: owner, + req: &pb.GetTokensRequest{ + IncludeBlacklisted: true, + }, + }, + wantLen: 2, + }, + { + name: "certain token", + args: args{ + ctx: ctx, + owner: owner, + req: &pb.GetTokensRequest{ + IdFilter: []string{"token2"}, + IncludeBlacklisted: true, + }, + }, + wantLen: 1, + }, + { + name: "all tokens another owner", + args: args{ + ctx: ctx, + owner: "anotherOwner", + req: &pb.GetTokensRequest{}, + }, + wantLen: 0, + }, + } + + for _, token := range tokens { + _, err := s.CreateToken(ctx, owner, token) + require.NoError(t, err) + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := make(map[string]*pb.Token) + // Define a mock process function + process := func(token *pb.Token) error { + result[token.GetId()] = token + return nil + } + + // Call the GetTokens method + err := s.GetTokens(tt.args.ctx, tt.args.owner, tt.args.req, process) + require.NoError(t, err) + require.Len(t, result, tt.wantLen) + }) + } +} + +func TestGetBlacklistedTokens(t *testing.T) { + s, cleanUpStore := test.NewMongoStore(t) + defer cleanUpStore() + + ctx, cancel := context.WithTimeout(context.Background(), config.TEST_TIMEOUT*100) + defer cancel() + + owner := "testOwner" + tokens := []*pb.Token{ + { + Id: "token1", + Owner: owner, + Version: 0, + Name: "name1", + Timestamp: time.Now().UnixNano(), + ClientId: "client1", + Blacklisted: &pb.Token_BlackListed{ + Flag: true, + Timestamp: time.Now().UnixNano(), + }, + }, + { + Id: "token2", + Owner: owner, + Version: 0, + Name: "name2", + Timestamp: time.Now().UnixNano(), + ClientId: "client1", + Blacklisted: &pb.Token_BlackListed{ + Flag: true, + Timestamp: time.Now().Add(time.Hour).UnixNano(), + }, + }, + { + Id: "token3", + Owner: owner, + Version: 0, + Name: "name3", + Timestamp: time.Now().UnixNano(), + ClientId: "client1", + }, + } + + type args struct { + ctx context.Context + owner string + req *pb.GetBlacklistedTokensRequest + } + tests := []struct { + name string + args args + wantLen int + }{ + { + name: "all blacklisted tokens", + args: args{ + ctx: ctx, + owner: owner, + req: &pb.GetBlacklistedTokensRequest{}, + }, + wantLen: 2, + }, + { + name: "all blacklisted tokens with timestamp", + args: args{ + ctx: ctx, + owner: owner, + req: &pb.GetBlacklistedTokensRequest{ + Timestamp: time.Now().UnixNano(), + }, + }, + wantLen: 1, + }, + { + name: "all blacklisted tokens with timestamp in the future", + args: args{ + ctx: ctx, + owner: owner, + req: &pb.GetBlacklistedTokensRequest{ + Timestamp: time.Now().Add(2 * time.Hour).UnixNano(), + }, + }, + wantLen: 0, + }, + { + name: "all blacklisted tokens with timestamp in the past", + args: args{ + ctx: ctx, + owner: owner, + req: &pb.GetBlacklistedTokensRequest{ + Timestamp: time.Now().Add(-2 * time.Hour).UnixNano(), + }, + }, + wantLen: 2, + }, + { + name: "another owner", + args: args{ + ctx: ctx, + owner: "anotherOwner", + req: &pb.GetBlacklistedTokensRequest{}, + }, + wantLen: 0, + }, + } + + for _, token := range tokens { + _, err := s.CreateToken(ctx, owner, token) + require.NoError(t, err) + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := make(map[string]*pb.Token) + process := func(token *pb.Token) error { + result[token.GetId()] = token + return nil + } + + err := s.GetBlacklistedTokens(tt.args.ctx, tt.args.owner, tt.args.req, process) + require.NoError(t, err) + require.Len(t, result, tt.wantLen) + }) + } +} + +func TestBlacklistTokens(t *testing.T) { + s, cleanUpStore := test.NewMongoStore(t) + defer cleanUpStore() + + ctx, cancel := context.WithTimeout(context.Background(), config.TEST_TIMEOUT*100) + defer cancel() + + owner := "testOwner" + tokens := []*pb.Token{ + { + Id: "token1", + Owner: owner, + Version: 0, + Name: "name1", + Timestamp: time.Now().UnixNano(), + ClientId: "client1", + }, + { + Id: "token2", + Owner: owner, + Version: 0, + Name: "name2", + Timestamp: time.Now().UnixNano(), + ClientId: "client1", + }, + { + Id: "token3", + Owner: owner, + Version: 0, + Name: "name3", + Timestamp: time.Now().UnixNano(), + ClientId: "client1", + }, + } + + for _, token := range tokens { + _, err := s.CreateToken(ctx, owner, token) + require.NoError(t, err) + } + + req := &pb.BlacklistTokensRequest{ + IdFilter: []string{"token1", "token2"}, + } + + resp, err := s.BlacklistTokens(ctx, owner, req) + require.NoError(t, err) + require.Equal(t, int64(2), resp.GetCount()) + + blacklistedTokens := []*pb.Token{ + { + Id: "token1", + Owner: owner, + Version: 0, + Name: "name1", + Timestamp: time.Now().UnixNano(), + ClientId: "client1", + Blacklisted: &pb.Token_BlackListed{ + Flag: true, + Timestamp: time.Now().UnixNano(), + }, + }, + { + Id: "token2", + Owner: owner, + Version: 0, + Name: "name2", + Timestamp: time.Now().UnixNano(), + ClientId: "client1", + Blacklisted: &pb.Token_BlackListed{ + Flag: true, + Timestamp: time.Now().UnixNano(), + }, + }, + } + + for _, token := range blacklistedTokens { + storedToken := make(map[string]*pb.Token) + process := func(token *pb.Token) error { + storedToken[token.GetId()] = token + return nil + } + + err := s.GetTokens(ctx, owner, &pb.GetTokensRequest{ + IdFilter: []string{token.GetId()}, + IncludeBlacklisted: true, + }, process) + require.NoError(t, err) + require.NotNil(t, storedToken) + require.True(t, storedToken[token.GetId()].GetBlacklisted().GetFlag()) + require.Greater(t, storedToken[token.GetId()].GetBlacklisted().GetTimestamp(), int64(0)) + } +} + +func TestDeleteTokens(t *testing.T) { + s, cleanUpStore := test.NewMongoStore(t) + defer cleanUpStore() + + ctx, cancel := context.WithTimeout(context.Background(), config.TEST_TIMEOUT*100) + defer cancel() + + owner := "testOwner" + tokens := []*pb.Token{ + { + Id: "token1", + Owner: owner, + Version: 0, + Name: "name1", + Timestamp: time.Now().UnixNano(), + ClientId: "client1", + Expiration: time.Now().Add(time.Minute * 10).UnixNano(), + Blacklisted: &pb.Token_BlackListed{ + Flag: true, + Timestamp: time.Now().UnixNano(), + }, + }, + { + Id: "token2", + Owner: owner, + Version: 0, + Name: "name2", + Timestamp: time.Now().UnixNano(), + ClientId: "client1", + Expiration: time.Now().Add(time.Minute * 10).UnixNano(), + Blacklisted: &pb.Token_BlackListed{ + Flag: true, + Timestamp: time.Now().Add(time.Minute).UnixNano(), + }, + }, + { + Id: "token3", + Owner: owner, + Version: 0, + Name: "name3", + Timestamp: time.Now().UnixNano(), + ClientId: "client1", + }, + } + + for _, token := range tokens { + _, err := s.CreateToken(ctx, owner, token) + require.NoError(t, err) + } + + err := s.DeleteTokens(ctx, time.Now().Add(time.Hour)) + require.NoError(t, err) + + remainingTokens := []*pb.Token{ + { + Id: "token3", + Owner: owner, + Version: 0, + Name: "name3", + Timestamp: time.Now().UnixNano(), + ClientId: "client1", + }, + } + + result := make(map[string]*pb.Token) + process := func(token *pb.Token) error { + result[token.GetId()] = token + return nil + } + + err = s.GetTokens(ctx, owner, &pb.GetTokensRequest{ + IncludeBlacklisted: true, + }, process) + require.NoError(t, err) + require.Len(t, result, len(remainingTokens)) + for _, token := range remainingTokens { + require.Contains(t, result, token.GetId()) + } +} diff --git a/m2m-oauth-server/store/store.go b/m2m-oauth-server/store/store.go new file mode 100644 index 000000000..6aeac71ba --- /dev/null +++ b/m2m-oauth-server/store/store.go @@ -0,0 +1,83 @@ +package store + +import ( + "context" + "errors" + "fmt" + + "github.com/plgd-dev/hub/v2/m2m-oauth-server/pb" + "go.mongodb.org/mongo-driver/mongo" +) + +type Iterator[T any] interface { + Next(ctx context.Context, v *T) bool + Err() error +} + +type ( + Process[T any] func(v *T) error + ProcessTokens = Process[pb.Token] +) + +var ( + ErrNotSupported = errors.New("not supported") + ErrNotFound = errors.New("not found") + ErrNotModified = errors.New("not modified") + ErrInvalidArgument = errors.New("invalid argument") + ErrPartialDelete = errors.New("some errors occurred while deleting") +) + +func errInvalidArgument(err error) error { + return fmt.Errorf("%w: %w", ErrInvalidArgument, err) +} + +func IsDuplicateKeyError(err error) bool { + return mongo.IsDuplicateKeyError(err) +} + +type BsonMapper interface { + FromBsonMap(m map[string]interface{}) error +} + +type MongoIterator[T any] struct { + Cursor *mongo.Cursor +} + +func (i *MongoIterator[T]) Next(ctx context.Context, s *T) bool { + if !i.Cursor.Next(ctx) { + return false + } + var tmp interface{} = s + if tmp, ok := tmp.(BsonMapper); ok { + var mapValue map[string]interface{} + err := i.Cursor.Decode(&mapValue) + if err == nil { + err = tmp.FromBsonMap(mapValue) + } + return err == nil + } + err := i.Cursor.Decode(s) + return err == nil +} + +func (i *MongoIterator[T]) Err() error { + return i.Cursor.Err() +} + +type Store interface { + // CreateToken creates a new token. If the token already exists, it will throw an error. + CreateToken(ctx context.Context, token *pb.Token) (*pb.Token, error) + // GetTokens loads tokens from the database. + GetTokens(ctx context.Context, owner string, query *pb.GetTokensRequest, p ProcessTokens) error + + // DeleteTokens deletes blacklisted expired tokens from the database. + DeleteTokens(ctx context.Context) error + + // GetBlacklistedTokens get blacklisted tokens which are not expired. + GetBlacklistedTokens(ctx context.Context, owner string, query *pb.GetBlacklistedTokensRequest, p ProcessTokens) error + + // Set tokens as blacklisted + BlacklistTokens(ctx context.Context, owner string, req *pb.BlacklistTokensRequest) (*pb.BlacklistTokensResponse, error) + + Close(ctx context.Context) error +} diff --git a/m2m-oauth-server/test/service.go b/m2m-oauth-server/test/service.go new file mode 100644 index 000000000..02db193dc --- /dev/null +++ b/m2m-oauth-server/test/service.go @@ -0,0 +1,65 @@ +package test + +import ( + "context" + "time" + + storeConfig "github.com/plgd-dev/hub/v2/m2m-oauth-server/store/config" + storeCqlDB "github.com/plgd-dev/hub/v2/m2m-oauth-server/store/cqldb" + storeMongo "github.com/plgd-dev/hub/v2/m2m-oauth-server/store/mongodb" + "github.com/plgd-dev/hub/v2/pkg/config/database" + "github.com/plgd-dev/hub/v2/pkg/fsnotify" + "github.com/plgd-dev/hub/v2/pkg/log" + "github.com/plgd-dev/hub/v2/pkg/mongodb" + "github.com/plgd-dev/hub/v2/test/config" + "github.com/stretchr/testify/require" + "go.opentelemetry.io/otel/trace/noop" +) + +func MakeStoreConfig() storeConfig.Config { + return storeConfig.Config{ + // TODO: add cqldb support + // Use: config.ACTIVE_DATABASE(), + CleanUpDeletedTokens: "0 * * * *", + ExtendCronParserBySeconds: false, + Config: database.Config[*storeMongo.Config, *storeCqlDB.Config]{ + Use: database.MongoDB, + MongoDB: &storeMongo.Config{ + Mongo: mongodb.Config{ + MaxPoolSize: 16, + MaxConnIdleTime: time.Minute * 4, + URI: config.MONGODB_URI, + Database: "m2mOAuthServer", + TLS: config.MakeTLSClientConfig(), + }, + }, + CqlDB: &storeCqlDB.Config{ + Embedded: config.MakeCqlDBConfig(), + Table: "m2mOAuthServer", + }, + }, + } +} + +func NewMongoStore(t require.TestingT) (*storeMongo.Store, func()) { + cfg := MakeConfig(t) + logger := log.NewLogger(cfg.Log) + + fileWatcher, err := fsnotify.NewWatcher(logger) + require.NoError(t, err) + + ctx := context.Background() + store, err := storeMongo.New(ctx, cfg.Clients.Storage.MongoDB, fileWatcher, logger, noop.NewTracerProvider()) + require.NoError(t, err) + + cleanUp := func() { + err := store.Clear(ctx) + require.NoError(t, err) + _ = store.Close(ctx) + + err = fileWatcher.Close() + require.NoError(t, err) + } + + return store, cleanUp +} diff --git a/m2m-oauth-server/test/test.go b/m2m-oauth-server/test/test.go index 91c5da67d..1fe048bc8 100644 --- a/m2m-oauth-server/test/test.go +++ b/m2m-oauth-server/test/test.go @@ -72,6 +72,8 @@ func MakeConfig(t require.TestingT) service.Config { Config: config.MakeOpenTelemetryCollectorClient(), } + cfg.Clients.Storage = MakeStoreConfig() + cfg.OAuthSigner.PrivateKeyFile = urischeme.URIScheme(os.Getenv("M2M_OAUTH_SERVER_PRIVATE_KEY")) cfg.OAuthSigner.Domain = config.M2M_OAUTH_SERVER_HTTP_HOST cfg.OAuthSigner.Clients = OAuthClients From 683915b9f3b7b26c177678f21f2cc0c6eea21bc7 Mon Sep 17 00:00:00 2001 From: Jozef Kralik Date: Tue, 16 Jul 2024 15:51:47 +0000 Subject: [PATCH 03/31] update swagger --- m2m-oauth-server/swagger.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/m2m-oauth-server/swagger.yaml b/m2m-oauth-server/swagger.yaml index fb0588453..3f0099495 100644 --- a/m2m-oauth-server/swagger.yaml +++ b/m2m-oauth-server/swagger.yaml @@ -18,6 +18,9 @@ paths: client_id: type: string description: "The client ID." + token_name: + type: string + description: "The name of the token which will be used in the name claim." scope: type: string description: "The scopes that are requested, separated by space. Must be a subset of the allowed scopes for the client." From a56992b5906b95e1957ac8f228302b1f87881745 Mon Sep 17 00:00:00 2001 From: Jozef Kralik Date: Tue, 16 Jul 2024 16:04:07 +0000 Subject: [PATCH 04/31] add time_to_live in ns --- m2m-oauth-server/swagger.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/m2m-oauth-server/swagger.yaml b/m2m-oauth-server/swagger.yaml index 3f0099495..8ea84b2a3 100644 --- a/m2m-oauth-server/swagger.yaml +++ b/m2m-oauth-server/swagger.yaml @@ -21,6 +21,9 @@ paths: token_name: type: string description: "The name of the token which will be used in the name claim." + time_to_live: + type: integer + description: "The time to live of the token in nano seconds. If not provided, the token will be max allowed by client." scope: type: string description: "The scopes that are requested, separated by space. Must be a subset of the allowed scopes for the client." From 54c66f5a3c703dd25e391993f20192c7a9ae5af0 Mon Sep 17 00:00:00 2001 From: Jozef Kralik Date: Wed, 17 Jul 2024 18:13:27 +0000 Subject: [PATCH 05/31] m2m-oauth-server supports grpc and http APIs --- Makefile | 2 +- m2m-oauth-server/config.yaml | 75 ++- m2m-oauth-server/oauthSigner/config.go | 128 ++++ .../{service => oauthSigner}/loadKeys.go | 2 +- m2m-oauth-server/oauthSigner/oauthSigner.go | 99 +++ m2m-oauth-server/pb/README.md | 61 +- m2m-oauth-server/pb/doc.html | 137 ++-- m2m-oauth-server/pb/service.pb.go | 603 +++++++++--------- m2m-oauth-server/pb/service.pb.gw.go | 172 ++--- m2m-oauth-server/pb/service.proto | 53 +- m2m-oauth-server/pb/service.swagger.json | 155 +++-- m2m-oauth-server/pb/service_grpc.pb.go | 180 +++--- m2m-oauth-server/pb/token.go | 2 +- m2m-oauth-server/service/config.go | 145 +---- .../service/expiredUpdatesChecker.go | 21 + m2m-oauth-server/service/grpc/config.go | 7 + m2m-oauth-server/service/grpc/server.go | 164 +++++ m2m-oauth-server/service/grpc/service.go | 35 + m2m-oauth-server/service/grpc/token.go | 289 +++++++++ m2m-oauth-server/service/http/config.go | 13 + .../service/http/createToken_test.go | 82 +++ .../service/{ => http}/getJWKs.go | 4 +- .../service/{ => http}/getJWKs_test.go | 18 +- .../{ => http}/getOpenIDConfiguration.go | 17 +- .../http/getOpenIDConfiguration_test.go | 39 ++ .../service/http/getTokens_test.go | 257 ++++++++ .../service/{ => http}/jsonWriter.go | 2 +- m2m-oauth-server/service/http/postToken.go | 113 ++++ .../{token_test.go => http/postToken_test.go} | 25 +- .../service/http/requestHandler.go | 46 ++ m2m-oauth-server/service/http/service.go | 69 ++ m2m-oauth-server/service/httpApi.go | 86 --- m2m-oauth-server/service/service.go | 164 +++-- m2m-oauth-server/service/service_test.go | 22 + m2m-oauth-server/service/token.go | 361 ----------- m2m-oauth-server/service/writeError.go | 22 - m2m-oauth-server/store/cqldb/tokens.go | 3 - m2m-oauth-server/store/mongodb/tokens.go | 32 +- m2m-oauth-server/store/mongodb/tokens_test.go | 238 ++----- m2m-oauth-server/store/store.go | 8 +- m2m-oauth-server/swagger.yaml | 296 ++++++++- m2m-oauth-server/test/service.go | 5 + m2m-oauth-server/test/test.go | 22 +- m2m-oauth-server/uri/uri.go | 8 +- pkg/security/jwt/claims.go | 2 +- pkg/security/jwt/validator/validator.go | 27 +- pkg/security/openid/config.go | 1 + snippet-service/service/http/service.go | 3 +- test/config/config.go | 1 + 49 files changed, 2747 insertions(+), 1569 deletions(-) create mode 100644 m2m-oauth-server/oauthSigner/config.go rename m2m-oauth-server/{service => oauthSigner}/loadKeys.go (97%) create mode 100644 m2m-oauth-server/oauthSigner/oauthSigner.go create mode 100644 m2m-oauth-server/service/expiredUpdatesChecker.go create mode 100644 m2m-oauth-server/service/grpc/config.go create mode 100644 m2m-oauth-server/service/grpc/server.go create mode 100644 m2m-oauth-server/service/grpc/service.go create mode 100644 m2m-oauth-server/service/grpc/token.go create mode 100644 m2m-oauth-server/service/http/config.go create mode 100644 m2m-oauth-server/service/http/createToken_test.go rename m2m-oauth-server/service/{ => http}/getJWKs.go (85%) rename m2m-oauth-server/service/{ => http}/getJWKs_test.go (62%) rename m2m-oauth-server/service/{ => http}/getOpenIDConfiguration.go (53%) create mode 100644 m2m-oauth-server/service/http/getOpenIDConfiguration_test.go create mode 100644 m2m-oauth-server/service/http/getTokens_test.go rename m2m-oauth-server/service/{ => http}/jsonWriter.go (96%) create mode 100644 m2m-oauth-server/service/http/postToken.go rename m2m-oauth-server/service/{token_test.go => http/postToken_test.go} (86%) create mode 100644 m2m-oauth-server/service/http/requestHandler.go create mode 100644 m2m-oauth-server/service/http/service.go delete mode 100644 m2m-oauth-server/service/httpApi.go create mode 100644 m2m-oauth-server/service/service_test.go delete mode 100644 m2m-oauth-server/service/token.go delete mode 100644 m2m-oauth-server/service/writeError.go diff --git a/Makefile b/Makefile index 874e20c1f..68deda976 100644 --- a/Makefile +++ b/Makefile @@ -47,7 +47,7 @@ CERT_TOOL_SIGN_ALG ?= ECDSA-SHA256 CERT_TOOL_ELLIPTIC_CURVE ?= P256 CERT_TOOL_IMAGE = ghcr.io/plgd-dev/hub/cert-tool:vnext -SUBDIRS := bundle certificate-authority cloud2cloud-connector cloud2cloud-gateway coap-gateway grpc-gateway resource-aggregate resource-directory http-gateway identity-store snippet-service test/oauth-server tools/cert-tool +SUBDIRS := bundle certificate-authority cloud2cloud-connector cloud2cloud-gateway coap-gateway grpc-gateway resource-aggregate resource-directory http-gateway identity-store snippet-service m2m-oauth-server test/oauth-server tools/cert-tool .PHONY: $(SUBDIRS) push proto/generate clean build test env mongo nats certificates hub-build http-gateway-www simulators default: build diff --git a/m2m-oauth-server/config.yaml b/m2m-oauth-server/config.yaml index fec4b0d13..9264ee7b1 100644 --- a/m2m-oauth-server/config.yaml +++ b/m2m-oauth-server/config.yaml @@ -1,25 +1,62 @@ log: - level: info - encoding: json - stacktrace: - enabled: false - level: warn - encoderConfig: - timeEncoder: rfc3339nano - dumpBody: false + level: info + encoding: json + stacktrace: + enabled: false + level: warn + encoderConfig: + timeEncoder: rfc3339nano apis: - http: + grpc: address: "0.0.0.0:9100" - readTimeout: 8s - readHeaderTimeout: 4s - writeTimeout: 16s - idleTimeout: 30s + sendMsgSize: 4194304 + recvMsgSize: 4194304 + enforcementPolicy: + minTime: 5s + permitWithoutStream: true + keepAlive: + # 0s - means infinity + maxConnectionIdle: 0s + # 0s - means infinity + maxConnectionAge: 0s + # 0s - means infinity + maxConnectionAgeGrace: 0s + time: 2h + timeout: 20s tls: caPool: "/secrets/public/rootca.crt" keyFile: "/secrets/private/cert.key" - certFile: "/secrets/public/cert.crt" + certFile: "/secrets/private/cert.crt" clientCertificateRequired: true + authorization: + ownerClaim: "sub" + authority: "" + audience: "" + http: + maxIdleConns: 16 + maxConnsPerHost: 32 + maxIdleConnsPerHost: 16 + idleConnTimeout: "30s" + timeout: "10s" + tls: + caPool: "/secrets/public/rootca.crt" + keyFile: "/secrets/private/cert.key" + certFile: "/secrets/public/cert.crt" + useSystemCAPool: false clients: + storage: + cleanUpDeletedTokens: 0 * * * * + use: mongoDB + mongoDB: + uri: + database: m2mOAuthServer + maxPoolSize: 16 + maxConnIdleTime: 4m0s + tls: + caPool: "/secrets/public/rootca.crt" + keyFile: "/secrets/private/cert.key" + certFile: "/secrets/public/cert.crt" + useSystemCAPool: false openTelemetryCollector: grpc: enabled: false @@ -36,8 +73,8 @@ clients: certFile: "/secrets/public/cert.crt" useSystemCAPool: false oauthSigner: - privateKeyFile: "/secrets/private/private.key" - domain: - ownerClaim: sub - deviceIDClaim: - clients: \ No newline at end of file + privateKeyFile: "/secrets/private/private.key" + domain: + ownerClaim: sub + deviceIDClaim: + clients: diff --git a/m2m-oauth-server/oauthSigner/config.go b/m2m-oauth-server/oauthSigner/config.go new file mode 100644 index 000000000..000234bb9 --- /dev/null +++ b/m2m-oauth-server/oauthSigner/config.go @@ -0,0 +1,128 @@ +package oauthsigner + +import ( + "fmt" + "time" + + "github.com/plgd-dev/hub/v2/m2m-oauth-server/uri" + "github.com/plgd-dev/hub/v2/pkg/config/property/urischeme" + "github.com/plgd-dev/hub/v2/pkg/security/jwt/validator" +) + +type AccessTokenType string + +const AccessTokenType_JWT AccessTokenType = "jwt" + +type GrantType string + +const ( + GrantTypeClientCredentials GrantType = "client_credentials" +) + +type PrivateKeyJWTConfig struct { + Enabled bool `yaml:"enabled"` + Authorization validator.Config `yaml:"authorization,omitempty"` +} + +func (c *PrivateKeyJWTConfig) Validate() error { + if !c.Enabled { + return nil + } + if err := c.Authorization.Validate(); err != nil { + return fmt.Errorf("authorization.%w", err) + } + return nil +} + +type Client struct { + ID string `yaml:"id"` + SecretFile urischeme.URIScheme `yaml:"secretFile"` + Owner string `yaml:"owner"` + AccessTokenLifetime time.Duration `yaml:"accessTokenLifetime"` + AllowedGrantTypes []GrantType `yaml:"allowedGrantTypes"` + AllowedAudiences []string `yaml:"allowedAudiences"` + AllowedScopes []string `yaml:"allowedScopes"` + JWTPrivateKey PrivateKeyJWTConfig `yaml:"jwtPrivateKey"` + InsertTokenClaims map[string]interface{} `yaml:"insertTokenClaims"` + + // runtime + Secret string `yaml:"-"` +} + +func (c *Client) Validate() error { + if c.ID == "" { + return fmt.Errorf("id('%v')", c.ID) + } + if !c.JWTPrivateKey.Enabled { + if c.SecretFile == "" { + return fmt.Errorf("secretFile('%v') - one of [secretFile, privateKeyJWT] need to be set", c.SecretFile) + } + if c.Owner == "" { + return fmt.Errorf("owner('%v')", c.Owner) + } + data, err := c.SecretFile.Read() + if err != nil { + return fmt.Errorf("secretFile('%v') - %w", c.SecretFile, err) + } + c.Secret = string(data) + } + if len(c.AllowedGrantTypes) == 0 { + return fmt.Errorf("allowedGrantTypes('%v') - is empty", c.AllowedGrantTypes) + } + for _, gt := range c.AllowedGrantTypes { + switch gt { + case GrantTypeClientCredentials: + default: + return fmt.Errorf("allowedGrantTypes('%v') - only %v is supported", c.AllowedGrantTypes, GrantTypeClientCredentials) + } + } + if err := c.JWTPrivateKey.Validate(); err != nil { + return fmt.Errorf("privateKeyJWT.%w", err) + } + return nil +} + +type OAuthClientsConfig []*Client + +func (c OAuthClientsConfig) Find(id string) *Client { + for _, client := range c { + if client.ID == id { + return client + } + } + return nil +} + +type Config struct { + PrivateKeyFile urischeme.URIScheme `yaml:"privateKeyFile" json:"privateKeyFile"` + Domain string `yaml:"domain" json:"domain"` + OwnerClaim string `yaml:"ownerClaim" json:"ownerClaim"` + DeviceIDClaim string `yaml:"deviceIDClaim" json:"deviceIDClaim"` + Clients OAuthClientsConfig `yaml:"clients" json:"clients"` +} + +func (c *Config) GetDomain() string { + return "https://" + c.Domain +} + +func (c *Config) GetAuthority() string { + return c.GetDomain() + uri.Base +} + +func (c *Config) Validate() error { + if c.PrivateKeyFile == "" { + return fmt.Errorf("privateKeyFile('%v')", c.PrivateKeyFile) + } + if c.Domain == "" { + return fmt.Errorf("domain('%v')", c.Domain) + } + if len(c.Clients) == 0 { + return fmt.Errorf("clients('%v')", c.Clients) + } + for idx, client := range c.Clients { + if err := client.Validate(); err != nil { + return fmt.Errorf("clients[%v].%w", idx, err) + } + } + return nil +} diff --git a/m2m-oauth-server/service/loadKeys.go b/m2m-oauth-server/oauthSigner/loadKeys.go similarity index 97% rename from m2m-oauth-server/service/loadKeys.go rename to m2m-oauth-server/oauthSigner/loadKeys.go index 683dadf40..876048461 100644 --- a/m2m-oauth-server/service/loadKeys.go +++ b/m2m-oauth-server/oauthSigner/loadKeys.go @@ -1,4 +1,4 @@ -package service +package oauthsigner import ( "crypto/x509" diff --git a/m2m-oauth-server/oauthSigner/oauthSigner.go b/m2m-oauth-server/oauthSigner/oauthSigner.go new file mode 100644 index 000000000..050fe45c4 --- /dev/null +++ b/m2m-oauth-server/oauthSigner/oauthSigner.go @@ -0,0 +1,99 @@ +package oauthsigner + +import ( + "context" + "fmt" + + "github.com/lestrrat-go/jwx/v2/jwk" + "github.com/lestrrat-go/jwx/v2/jws" + "github.com/lestrrat-go/jwx/v2/jwt" + "github.com/plgd-dev/hub/v2/pkg/fn" + "github.com/plgd-dev/hub/v2/pkg/fsnotify" + "github.com/plgd-dev/hub/v2/pkg/log" + pkgJwt "github.com/plgd-dev/hub/v2/pkg/security/jwt" + "github.com/plgd-dev/hub/v2/pkg/security/jwt/validator" + "github.com/plgd-dev/kit/v2/codec/json" + "go.opentelemetry.io/otel/trace" +) + +func setKeyError(key string, err error) error { + return fmt.Errorf("failed to set %v: %w", key, err) +} + +type OAuthSigner struct { + privateKeyJWTValidators map[string]*validator.Validator + closer fn.FuncList + Config Config + accessTokenKey interface{} + accessTokenJwkKey jwk.Key +} + +func New(ctx context.Context, config Config, getOpenIDConfiguration validator.GetOpenIDConfigurationFunc, fileWatcher *fsnotify.Watcher, logger log.Logger, tracerProvider trace.TracerProvider) (*OAuthSigner, error) { + accessTokenKey, err := LoadPrivateKey(config.PrivateKeyFile) + if err != nil { + return nil, fmt.Errorf("cannot load private privateKeyFile(%v): %w", config.PrivateKeyFile, err) + } + accessTokenJwkKey, err := pkgJwt.CreateJwkKey(accessTokenKey) + if err != nil { + return nil, fmt.Errorf("cannot create jwk for idToken: %w", err) + } + + privateKeyJWTValidators := make(map[string]*validator.Validator, len(config.Clients)) + var closer fn.FuncList + for _, c := range config.Clients { + if !c.JWTPrivateKey.Enabled { + continue + } + validator, err := validator.New(ctx, c.JWTPrivateKey.Authorization, fileWatcher, logger, tracerProvider, validator.WithGetOpenIDConfiguration(getOpenIDConfiguration)) + if err != nil { + closer.Execute() + return nil, fmt.Errorf("cannot create validator: %w", err) + } + privateKeyJWTValidators[c.ID] = validator + closer.AddFunc(validator.Close) + } + return &OAuthSigner{ + privateKeyJWTValidators: privateKeyJWTValidators, + closer: closer, + Config: config, + accessTokenKey: accessTokenKey, + accessTokenJwkKey: accessTokenJwkKey, + }, nil +} + +func (s *OAuthSigner) GetValidator(clientID string) (*validator.Validator, bool) { + v, ok := s.privateKeyJWTValidators[clientID] + return v, ok +} + +func (s *OAuthSigner) SignRaw(data []byte) ([]byte, error) { + hdr := jws.NewHeaders() + if err := hdr.Set(jws.TypeKey, `JWT`); err != nil { + return nil, setKeyError(jws.TypeKey, err) + } + if err := hdr.Set(jws.KeyIDKey, s.accessTokenJwkKey.KeyID()); err != nil { + return nil, setKeyError(jws.KeyIDKey, err) + } + + payload, err := jws.Sign(data, jws.WithKey(s.accessTokenJwkKey.Algorithm(), s.accessTokenKey, jws.WithProtectedHeaders(hdr))) + if err != nil { + return nil, fmt.Errorf("failed to create UserToken: %w", err) + } + return payload, nil +} + +func (s *OAuthSigner) GetJWK() jwk.Key { + return s.accessTokenJwkKey +} + +func (s *OAuthSigner) Sign(token jwt.Token) ([]byte, error) { + buf, err := json.Encode(token) + if err != nil { + return nil, fmt.Errorf("failed to encode token: %w", err) + } + return s.SignRaw(buf) +} + +func (s *OAuthSigner) Close() { + s.closer.Execute() +} diff --git a/m2m-oauth-server/pb/README.md b/m2m-oauth-server/pb/README.md index 31fc0c3c1..a8c73a840 100644 --- a/m2m-oauth-server/pb/README.md +++ b/m2m-oauth-server/pb/README.md @@ -6,14 +6,13 @@ - [m2m-oauth-server/pb/service.proto](#m2m-oauth-server_pb_service-proto) - [BlacklistTokensRequest](#m2moauthserver-pb-BlacklistTokensRequest) - [BlacklistTokensResponse](#m2moauthserver-pb-BlacklistTokensResponse) - - [GetBlacklistedTokensRequest](#m2moauthserver-pb-GetBlacklistedTokensRequest) - - [GetBlacklistedTokensResponse](#m2moauthserver-pb-GetBlacklistedTokensResponse) - - [GetBlacklistedTokensResponse.Item](#m2moauthserver-pb-GetBlacklistedTokensResponse-Item) + - [CreateTokenRequest](#m2moauthserver-pb-CreateTokenRequest) + - [CreateTokenResponse](#m2moauthserver-pb-CreateTokenResponse) - [GetTokensRequest](#m2moauthserver-pb-GetTokensRequest) - [Token](#m2moauthserver-pb-Token) - [Token.BlackListed](#m2moauthserver-pb-Token-BlackListed) - - [M2MOAuthServer](#m2moauthserver-pb-M2MOAuthServer) + - [M2MOAuthService](#m2moauthserver-pb-M2MOAuthService) - [Scalar Value Types](#scalar-value-types) @@ -56,47 +55,41 @@ - + -### GetBlacklistedTokensRequest +### CreateTokenRequest | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | -| timestamp | [int64](#int64) | | Returns all blacklisted/revoked not expired tokens of the owner from the given timestamp | +| client_id | [string](#string) | | | +| client_secret | [string](#string) | | | +| audience | [string](#string) | repeated | | +| scope | [string](#string) | repeated | | +| time_to_live | [int64](#int64) | | | +| client_assertion_type | [string](#string) | | | +| client_assertion | [string](#string) | | | +| token_name | [string](#string) | | | +| grant_type | [string](#string) | | | - + -### GetBlacklistedTokensResponse +### CreateTokenResponse | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | -| black_list | [GetBlacklistedTokensResponse.Item](#m2moauthserver-pb-GetBlacklistedTokensResponse-Item) | repeated | | -| timestamp | [int64](#int64) | | The biggest version of the blacklisted/revoked token over all tokens of owner | - - - - - - - - -### GetBlacklistedTokensResponse.Item - - - -| Field | Type | Label | Description | -| ----- | ---- | ----- | ----------- | -| audience | [string](#string) | | | -| id | [string](#string) | | | +| access_token | [string](#string) | | | +| token_type | [string](#string) | | | +| expires_in | [int64](#int64) | | | +| scope | [string](#string) | repeated | | @@ -133,12 +126,12 @@ driven by resource change event | id | [string](#string) | | Token ID / jti @gotags: bson:"_id" | -| version | [uint64](#uint64) | | Incremetal version for update | +| version | [uint64](#uint64) | | Incremental version for update | | name | [string](#string) | | User-friendly token name | | owner | [string](#string) | | Owner of the token | -| timestamp | [int64](#int64) | | Unix timestamp in ns when the condition has been created/updated | +| issued_at | [int64](#int64) | | Unix timestamp in ns when the condition has been created/updated | | audience | [string](#string) | repeated | Token Audience | -| scopes | [string](#string) | repeated | Token scopes | +| scope | [string](#string) | repeated | Token scopes | | expiration | [int64](#int64) | | Original token expiration | | client_id | [string](#string) | | Client ID | | original_token_claims | [google.protobuf.Value](#google-protobuf-Value) | | Original token claims | @@ -171,15 +164,15 @@ driven by resource change event - + -### M2MOAuthServer +### M2MOAuthService | Method Name | Request Type | Response Type | Description | | ----------- | ------------ | ------------- | ------------| -| GetTokens | [GetTokensRequest](#m2moauthserver-pb-GetTokensRequest) | [Token](#m2moauthserver-pb-Token) | Returns all tokens of the owner | -| GetBlacklistedTokens | [GetBlacklistedTokensRequest](#m2moauthserver-pb-GetBlacklistedTokensRequest) | [GetBlacklistedTokensResponse](#m2moauthserver-pb-GetBlacklistedTokensResponse) | Returns all blacklisted/revoked not expired tokens | +| CreateToken | [CreateTokenRequest](#m2moauthserver-pb-CreateTokenRequest) | [CreateTokenResponse](#m2moauthserver-pb-CreateTokenResponse) | Creates a new token | +| GetTokens | [GetTokensRequest](#m2moauthserver-pb-GetTokensRequest) | [Token](#m2moauthserver-pb-Token) stream | Returns all tokens of the owner | | BlacklistTokens | [BlacklistTokensRequest](#m2moauthserver-pb-BlacklistTokensRequest) | [BlacklistTokensResponse](#m2moauthserver-pb-BlacklistTokensResponse) | Blacklists/revokes tokens | diff --git a/m2m-oauth-server/pb/doc.html b/m2m-oauth-server/pb/doc.html index 7a6adf655..e2be77a3c 100644 --- a/m2m-oauth-server/pb/doc.html +++ b/m2m-oauth-server/pb/doc.html @@ -187,15 +187,11 @@

Table of Contents

  • - MGetBlacklistedTokensRequest + MCreateTokenRequest
  • - MGetBlacklistedTokensResponse -
  • - -
  • - MGetBlacklistedTokensResponse.Item + MCreateTokenResponse
  • @@ -214,7 +210,7 @@

    Table of Contents

  • - SM2MOAuthServer + SM2MOAuthService
  • @@ -280,7 +276,7 @@

    BlacklistTokensResponse

    -

    GetBlacklistedTokensRequest

    +

    CreateTokenRequest

    @@ -291,41 +287,66 @@

    GetBlacklistedTokensReque - timestamp - int64 + client_id + string -

    Returns all blacklisted/revoked not expired tokens of the owner from the given timestamp

    +

    - - - - - - - -

    GetBlacklistedTokensResponse

    -

    - - - - - - - + + + + + + - - + + - + + + + + + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -335,7 +356,7 @@

    GetBlacklistedTokensResp -

    GetBlacklistedTokensResponse.Item

    +

    CreateTokenResponse

    @@ -346,19 +367,33 @@

    GetBlacklistedToken

    - + - + + + + + + + + + + + + + + +
    FieldTypeLabelDescription
    client_secretstring

    black_listGetBlacklistedTokensResponse.Itemaudiencestring repeated

    timestampscopestringrepeated

    time_to_live int64

    The biggest version of the blacklisted/revoked token over all tokens of owner

    client_assertion_typestring

    client_assertionstring

    token_namestring

    grant_typestring

    audienceaccess_token string

    idtoken_type string

    expires_inint64

    scopestringrepeated

    @@ -427,7 +462,7 @@

    Token

    version uint64 -

    Incremetal version for update

    +

    Incremental version for update

    @@ -445,7 +480,7 @@

    Token

    - timestamp + issued_at int64

    Unix timestamp in ns when the condition has been created/updated

    @@ -459,7 +494,7 @@

    Token

    - scopes + scope string repeated

    Token scopes

    @@ -537,7 +572,7 @@

    Token.BlackListed

    -

    M2MOAuthServer

    +

    M2MOAuthService

    @@ -546,17 +581,17 @@

    M2MOAuthServer

    - - - - + + + + - - - - + + + + @@ -587,19 +622,19 @@

    Methods with HTTP bindings

    - - + + - + - + - + diff --git a/m2m-oauth-server/pb/service.pb.go b/m2m-oauth-server/pb/service.pb.go index 4b330e6da..ecf7b3bf1 100644 --- a/m2m-oauth-server/pb/service.pb.go +++ b/m2m-oauth-server/pb/service.pb.go @@ -31,18 +31,18 @@ type Token struct { // Token ID / jti Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty" bson:"_id"` - // Incremetal version for update + // Incremental version for update Version uint64 `protobuf:"varint,2,opt,name=version,proto3" json:"version,omitempty"` // User-friendly token name Name string `protobuf:"bytes,3,opt,name=name,proto3" json:"name,omitempty"` // Owner of the token Owner string `protobuf:"bytes,4,opt,name=owner,proto3" json:"owner,omitempty"` // Unix timestamp in ns when the condition has been created/updated - Timestamp int64 `protobuf:"varint,5,opt,name=timestamp,proto3" json:"timestamp,omitempty"` + IssuedAt int64 `protobuf:"varint,5,opt,name=issued_at,json=issuedAt,proto3" json:"issued_at,omitempty"` // Token Audience Audience []string `protobuf:"bytes,6,rep,name=audience,proto3" json:"audience,omitempty"` // Token scopes - Scopes []string `protobuf:"bytes,7,rep,name=scopes,proto3" json:"scopes,omitempty"` + Scope []string `protobuf:"bytes,7,rep,name=scope,proto3" json:"scope,omitempty"` // Original token expiration Expiration int64 `protobuf:"varint,8,opt,name=expiration,proto3" json:"expiration,omitempty"` // Client ID @@ -113,9 +113,9 @@ func (x *Token) GetOwner() string { return "" } -func (x *Token) GetTimestamp() int64 { +func (x *Token) GetIssuedAt() int64 { if x != nil { - return x.Timestamp + return x.IssuedAt } return 0 } @@ -127,9 +127,9 @@ func (x *Token) GetAudience() []string { return nil } -func (x *Token) GetScopes() []string { +func (x *Token) GetScope() []string { if x != nil { - return x.Scopes + return x.Scope } return nil } @@ -162,17 +162,18 @@ func (x *Token) GetBlacklisted() *Token_BlackListed { return nil } -type GetBlacklistedTokensRequest struct { +type GetTokensRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - // Returns all blacklisted/revoked not expired tokens of the owner from the given timestamp - Timestamp int64 `protobuf:"varint,1,opt,name=timestamp,proto3" json:"timestamp,omitempty"` + IdFilter []string `protobuf:"bytes,1,rep,name=id_filter,json=idFilter,proto3" json:"id_filter,omitempty"` + AudienceFilter []string `protobuf:"bytes,2,rep,name=audience_filter,json=audienceFilter,proto3" json:"audience_filter,omitempty"` + IncludeBlacklisted bool `protobuf:"varint,3,opt,name=include_blacklisted,json=includeBlacklisted,proto3" json:"include_blacklisted,omitempty"` } -func (x *GetBlacklistedTokensRequest) Reset() { - *x = GetBlacklistedTokensRequest{} +func (x *GetTokensRequest) Reset() { + *x = GetTokensRequest{} if protoimpl.UnsafeEnabled { mi := &file_m2m_oauth_server_pb_service_proto_msgTypes[1] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -180,13 +181,13 @@ func (x *GetBlacklistedTokensRequest) Reset() { } } -func (x *GetBlacklistedTokensRequest) String() string { +func (x *GetTokensRequest) String() string { return protoimpl.X.MessageStringOf(x) } -func (*GetBlacklistedTokensRequest) ProtoMessage() {} +func (*GetTokensRequest) ProtoMessage() {} -func (x *GetBlacklistedTokensRequest) ProtoReflect() protoreflect.Message { +func (x *GetTokensRequest) ProtoReflect() protoreflect.Message { mi := &file_m2m_oauth_server_pb_service_proto_msgTypes[1] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -198,30 +199,42 @@ func (x *GetBlacklistedTokensRequest) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use GetBlacklistedTokensRequest.ProtoReflect.Descriptor instead. -func (*GetBlacklistedTokensRequest) Descriptor() ([]byte, []int) { +// Deprecated: Use GetTokensRequest.ProtoReflect.Descriptor instead. +func (*GetTokensRequest) Descriptor() ([]byte, []int) { return file_m2m_oauth_server_pb_service_proto_rawDescGZIP(), []int{1} } -func (x *GetBlacklistedTokensRequest) GetTimestamp() int64 { +func (x *GetTokensRequest) GetIdFilter() []string { if x != nil { - return x.Timestamp + return x.IdFilter } - return 0 + return nil +} + +func (x *GetTokensRequest) GetAudienceFilter() []string { + if x != nil { + return x.AudienceFilter + } + return nil +} + +func (x *GetTokensRequest) GetIncludeBlacklisted() bool { + if x != nil { + return x.IncludeBlacklisted + } + return false } -type GetBlacklistedTokensResponse struct { +type BlacklistTokensRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - BlackList []*GetBlacklistedTokensResponse_Item `protobuf:"bytes,1,rep,name=black_list,json=blackList,proto3" json:"black_list,omitempty"` - // The biggest version of the blacklisted/revoked token over all tokens of owner - Timestamp int64 `protobuf:"varint,2,opt,name=timestamp,proto3" json:"timestamp,omitempty"` + IdFilter []string `protobuf:"bytes,1,rep,name=id_filter,json=idFilter,proto3" json:"id_filter,omitempty"` } -func (x *GetBlacklistedTokensResponse) Reset() { - *x = GetBlacklistedTokensResponse{} +func (x *BlacklistTokensRequest) Reset() { + *x = BlacklistTokensRequest{} if protoimpl.UnsafeEnabled { mi := &file_m2m_oauth_server_pb_service_proto_msgTypes[2] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -229,13 +242,13 @@ func (x *GetBlacklistedTokensResponse) Reset() { } } -func (x *GetBlacklistedTokensResponse) String() string { +func (x *BlacklistTokensRequest) String() string { return protoimpl.X.MessageStringOf(x) } -func (*GetBlacklistedTokensResponse) ProtoMessage() {} +func (*BlacklistTokensRequest) ProtoMessage() {} -func (x *GetBlacklistedTokensResponse) ProtoReflect() protoreflect.Message { +func (x *BlacklistTokensRequest) ProtoReflect() protoreflect.Message { mi := &file_m2m_oauth_server_pb_service_proto_msgTypes[2] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -247,37 +260,28 @@ func (x *GetBlacklistedTokensResponse) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use GetBlacklistedTokensResponse.ProtoReflect.Descriptor instead. -func (*GetBlacklistedTokensResponse) Descriptor() ([]byte, []int) { +// Deprecated: Use BlacklistTokensRequest.ProtoReflect.Descriptor instead. +func (*BlacklistTokensRequest) Descriptor() ([]byte, []int) { return file_m2m_oauth_server_pb_service_proto_rawDescGZIP(), []int{2} } -func (x *GetBlacklistedTokensResponse) GetBlackList() []*GetBlacklistedTokensResponse_Item { +func (x *BlacklistTokensRequest) GetIdFilter() []string { if x != nil { - return x.BlackList + return x.IdFilter } return nil } -func (x *GetBlacklistedTokensResponse) GetTimestamp() int64 { - if x != nil { - return x.Timestamp - } - return 0 -} - -type GetTokensRequest struct { +type BlacklistTokensResponse struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - IdFilter []string `protobuf:"bytes,1,rep,name=id_filter,json=idFilter,proto3" json:"id_filter,omitempty"` - AudienceFilter []string `protobuf:"bytes,2,rep,name=audience_filter,json=audienceFilter,proto3" json:"audience_filter,omitempty"` - IncludeBlacklisted bool `protobuf:"varint,3,opt,name=include_blacklisted,json=includeBlacklisted,proto3" json:"include_blacklisted,omitempty"` + Count int64 `protobuf:"varint,1,opt,name=count,proto3" json:"count,omitempty"` } -func (x *GetTokensRequest) Reset() { - *x = GetTokensRequest{} +func (x *BlacklistTokensResponse) Reset() { + *x = BlacklistTokensResponse{} if protoimpl.UnsafeEnabled { mi := &file_m2m_oauth_server_pb_service_proto_msgTypes[3] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -285,13 +289,13 @@ func (x *GetTokensRequest) Reset() { } } -func (x *GetTokensRequest) String() string { +func (x *BlacklistTokensResponse) String() string { return protoimpl.X.MessageStringOf(x) } -func (*GetTokensRequest) ProtoMessage() {} +func (*BlacklistTokensResponse) ProtoMessage() {} -func (x *GetTokensRequest) ProtoReflect() protoreflect.Message { +func (x *BlacklistTokensResponse) ProtoReflect() protoreflect.Message { mi := &file_m2m_oauth_server_pb_service_proto_msgTypes[3] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -303,42 +307,36 @@ func (x *GetTokensRequest) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use GetTokensRequest.ProtoReflect.Descriptor instead. -func (*GetTokensRequest) Descriptor() ([]byte, []int) { +// Deprecated: Use BlacklistTokensResponse.ProtoReflect.Descriptor instead. +func (*BlacklistTokensResponse) Descriptor() ([]byte, []int) { return file_m2m_oauth_server_pb_service_proto_rawDescGZIP(), []int{3} } -func (x *GetTokensRequest) GetIdFilter() []string { - if x != nil { - return x.IdFilter - } - return nil -} - -func (x *GetTokensRequest) GetAudienceFilter() []string { - if x != nil { - return x.AudienceFilter - } - return nil -} - -func (x *GetTokensRequest) GetIncludeBlacklisted() bool { +func (x *BlacklistTokensResponse) GetCount() int64 { if x != nil { - return x.IncludeBlacklisted + return x.Count } - return false + return 0 } -type BlacklistTokensRequest struct { +type CreateTokenRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - IdFilter []string `protobuf:"bytes,1,rep,name=id_filter,json=idFilter,proto3" json:"id_filter,omitempty"` + ClientId string `protobuf:"bytes,1,opt,name=client_id,json=clientId,proto3" json:"client_id,omitempty"` + ClientSecret string `protobuf:"bytes,2,opt,name=client_secret,json=clientSecret,proto3" json:"client_secret,omitempty"` + Audience []string `protobuf:"bytes,3,rep,name=audience,proto3" json:"audience,omitempty"` + Scope []string `protobuf:"bytes,4,rep,name=scope,proto3" json:"scope,omitempty"` + TimeToLive int64 `protobuf:"varint,5,opt,name=time_to_live,json=timeToLive,proto3" json:"time_to_live,omitempty"` + ClientAssertionType string `protobuf:"bytes,6,opt,name=client_assertion_type,json=clientAssertionType,proto3" json:"client_assertion_type,omitempty"` + ClientAssertion string `protobuf:"bytes,7,opt,name=client_assertion,json=clientAssertion,proto3" json:"client_assertion,omitempty"` + TokenName string `protobuf:"bytes,8,opt,name=token_name,json=tokenName,proto3" json:"token_name,omitempty"` + GrantType string `protobuf:"bytes,9,opt,name=grant_type,json=grantType,proto3" json:"grant_type,omitempty"` } -func (x *BlacklistTokensRequest) Reset() { - *x = BlacklistTokensRequest{} +func (x *CreateTokenRequest) Reset() { + *x = CreateTokenRequest{} if protoimpl.UnsafeEnabled { mi := &file_m2m_oauth_server_pb_service_proto_msgTypes[4] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -346,13 +344,13 @@ func (x *BlacklistTokensRequest) Reset() { } } -func (x *BlacklistTokensRequest) String() string { +func (x *CreateTokenRequest) String() string { return protoimpl.X.MessageStringOf(x) } -func (*BlacklistTokensRequest) ProtoMessage() {} +func (*CreateTokenRequest) ProtoMessage() {} -func (x *BlacklistTokensRequest) ProtoReflect() protoreflect.Message { +func (x *CreateTokenRequest) ProtoReflect() protoreflect.Message { mi := &file_m2m_oauth_server_pb_service_proto_msgTypes[4] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -364,28 +362,87 @@ func (x *BlacklistTokensRequest) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use BlacklistTokensRequest.ProtoReflect.Descriptor instead. -func (*BlacklistTokensRequest) Descriptor() ([]byte, []int) { +// Deprecated: Use CreateTokenRequest.ProtoReflect.Descriptor instead. +func (*CreateTokenRequest) Descriptor() ([]byte, []int) { return file_m2m_oauth_server_pb_service_proto_rawDescGZIP(), []int{4} } -func (x *BlacklistTokensRequest) GetIdFilter() []string { +func (x *CreateTokenRequest) GetClientId() string { if x != nil { - return x.IdFilter + return x.ClientId + } + return "" +} + +func (x *CreateTokenRequest) GetClientSecret() string { + if x != nil { + return x.ClientSecret + } + return "" +} + +func (x *CreateTokenRequest) GetAudience() []string { + if x != nil { + return x.Audience } return nil } -type BlacklistTokensResponse struct { +func (x *CreateTokenRequest) GetScope() []string { + if x != nil { + return x.Scope + } + return nil +} + +func (x *CreateTokenRequest) GetTimeToLive() int64 { + if x != nil { + return x.TimeToLive + } + return 0 +} + +func (x *CreateTokenRequest) GetClientAssertionType() string { + if x != nil { + return x.ClientAssertionType + } + return "" +} + +func (x *CreateTokenRequest) GetClientAssertion() string { + if x != nil { + return x.ClientAssertion + } + return "" +} + +func (x *CreateTokenRequest) GetTokenName() string { + if x != nil { + return x.TokenName + } + return "" +} + +func (x *CreateTokenRequest) GetGrantType() string { + if x != nil { + return x.GrantType + } + return "" +} + +type CreateTokenResponse struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - Count int64 `protobuf:"varint,1,opt,name=count,proto3" json:"count,omitempty"` + AccessToken string `protobuf:"bytes,1,opt,name=access_token,json=accessToken,proto3" json:"access_token,omitempty"` + TokenType string `protobuf:"bytes,2,opt,name=token_type,json=tokenType,proto3" json:"token_type,omitempty"` + ExpiresIn int64 `protobuf:"varint,3,opt,name=expires_in,json=expiresIn,proto3" json:"expires_in,omitempty"` + Scope []string `protobuf:"bytes,4,rep,name=scope,proto3" json:"scope,omitempty"` } -func (x *BlacklistTokensResponse) Reset() { - *x = BlacklistTokensResponse{} +func (x *CreateTokenResponse) Reset() { + *x = CreateTokenResponse{} if protoimpl.UnsafeEnabled { mi := &file_m2m_oauth_server_pb_service_proto_msgTypes[5] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -393,13 +450,13 @@ func (x *BlacklistTokensResponse) Reset() { } } -func (x *BlacklistTokensResponse) String() string { +func (x *CreateTokenResponse) String() string { return protoimpl.X.MessageStringOf(x) } -func (*BlacklistTokensResponse) ProtoMessage() {} +func (*CreateTokenResponse) ProtoMessage() {} -func (x *BlacklistTokensResponse) ProtoReflect() protoreflect.Message { +func (x *CreateTokenResponse) ProtoReflect() protoreflect.Message { mi := &file_m2m_oauth_server_pb_service_proto_msgTypes[5] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -411,18 +468,39 @@ func (x *BlacklistTokensResponse) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use BlacklistTokensResponse.ProtoReflect.Descriptor instead. -func (*BlacklistTokensResponse) Descriptor() ([]byte, []int) { +// Deprecated: Use CreateTokenResponse.ProtoReflect.Descriptor instead. +func (*CreateTokenResponse) Descriptor() ([]byte, []int) { return file_m2m_oauth_server_pb_service_proto_rawDescGZIP(), []int{5} } -func (x *BlacklistTokensResponse) GetCount() int64 { +func (x *CreateTokenResponse) GetAccessToken() string { if x != nil { - return x.Count + return x.AccessToken + } + return "" +} + +func (x *CreateTokenResponse) GetTokenType() string { + if x != nil { + return x.TokenType + } + return "" +} + +func (x *CreateTokenResponse) GetExpiresIn() int64 { + if x != nil { + return x.ExpiresIn } return 0 } +func (x *CreateTokenResponse) GetScope() []string { + if x != nil { + return x.Scope + } + return nil +} + type Token_BlackListed struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -480,61 +558,6 @@ func (x *Token_BlackListed) GetTimestamp() int64 { return 0 } -type GetBlacklistedTokensResponse_Item struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Audience string `protobuf:"bytes,1,opt,name=audience,proto3" json:"audience,omitempty"` - Id string `protobuf:"bytes,2,opt,name=id,proto3" json:"id,omitempty"` -} - -func (x *GetBlacklistedTokensResponse_Item) Reset() { - *x = GetBlacklistedTokensResponse_Item{} - if protoimpl.UnsafeEnabled { - mi := &file_m2m_oauth_server_pb_service_proto_msgTypes[7] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *GetBlacklistedTokensResponse_Item) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*GetBlacklistedTokensResponse_Item) ProtoMessage() {} - -func (x *GetBlacklistedTokensResponse_Item) ProtoReflect() protoreflect.Message { - mi := &file_m2m_oauth_server_pb_service_proto_msgTypes[7] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use GetBlacklistedTokensResponse_Item.ProtoReflect.Descriptor instead. -func (*GetBlacklistedTokensResponse_Item) Descriptor() ([]byte, []int) { - return file_m2m_oauth_server_pb_service_proto_rawDescGZIP(), []int{2, 0} -} - -func (x *GetBlacklistedTokensResponse_Item) GetAudience() string { - if x != nil { - return x.Audience - } - return "" -} - -func (x *GetBlacklistedTokensResponse_Item) GetId() string { - if x != nil { - return x.Id - } - return "" -} - var File_m2m_oauth_server_pb_service_proto protoreflect.FileDescriptor var file_m2m_oauth_server_pb_service_proto_rawDesc = []byte{ @@ -548,118 +571,130 @@ var file_m2m_oauth_server_pb_service_proto_rawDesc = []byte{ 0x74, 0x6f, 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x2d, 0x67, 0x65, 0x6e, 0x2d, 0x6f, 0x70, 0x65, 0x6e, 0x61, 0x70, 0x69, 0x76, 0x32, 0x2f, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2f, 0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x22, 0xbf, 0x03, 0x0a, 0x05, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x0e, 0x0a, 0x02, + 0x74, 0x6f, 0x22, 0xbc, 0x03, 0x0a, 0x05, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6f, 0x77, 0x6e, 0x65, 0x72, - 0x12, 0x1c, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x05, 0x20, - 0x01, 0x28, 0x03, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x12, 0x1a, - 0x0a, 0x08, 0x61, 0x75, 0x64, 0x69, 0x65, 0x6e, 0x63, 0x65, 0x18, 0x06, 0x20, 0x03, 0x28, 0x09, - 0x52, 0x08, 0x61, 0x75, 0x64, 0x69, 0x65, 0x6e, 0x63, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x63, - 0x6f, 0x70, 0x65, 0x73, 0x18, 0x07, 0x20, 0x03, 0x28, 0x09, 0x52, 0x06, 0x73, 0x63, 0x6f, 0x70, - 0x65, 0x73, 0x12, 0x1e, 0x0a, 0x0a, 0x65, 0x78, 0x70, 0x69, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x18, 0x08, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x65, 0x78, 0x70, 0x69, 0x72, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x12, 0x1b, 0x0a, 0x09, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, - 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x12, - 0x4a, 0x0a, 0x15, 0x6f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x61, 0x6c, 0x5f, 0x74, 0x6f, 0x6b, 0x65, - 0x6e, 0x5f, 0x63, 0x6c, 0x61, 0x69, 0x6d, 0x73, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, - 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, - 0x2e, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x13, 0x6f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x61, 0x6c, - 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x43, 0x6c, 0x61, 0x69, 0x6d, 0x73, 0x12, 0x46, 0x0a, 0x0b, 0x62, - 0x6c, 0x61, 0x63, 0x6b, 0x6c, 0x69, 0x73, 0x74, 0x65, 0x64, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x24, 0x2e, 0x6d, 0x32, 0x6d, 0x6f, 0x61, 0x75, 0x74, 0x68, 0x73, 0x65, 0x72, 0x76, 0x65, - 0x72, 0x2e, 0x70, 0x62, 0x2e, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x2e, 0x42, 0x6c, 0x61, 0x63, 0x6b, - 0x4c, 0x69, 0x73, 0x74, 0x65, 0x64, 0x52, 0x0b, 0x62, 0x6c, 0x61, 0x63, 0x6b, 0x6c, 0x69, 0x73, - 0x74, 0x65, 0x64, 0x1a, 0x3f, 0x0a, 0x0b, 0x42, 0x6c, 0x61, 0x63, 0x6b, 0x4c, 0x69, 0x73, 0x74, - 0x65, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x66, 0x6c, 0x61, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, - 0x52, 0x04, 0x66, 0x6c, 0x61, 0x67, 0x12, 0x1c, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, - 0x61, 0x6d, 0x70, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, - 0x74, 0x61, 0x6d, 0x70, 0x22, 0x3b, 0x0a, 0x1b, 0x47, 0x65, 0x74, 0x42, 0x6c, 0x61, 0x63, 0x6b, - 0x6c, 0x69, 0x73, 0x74, 0x65, 0x64, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x12, 0x1c, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, - 0x70, 0x22, 0xc5, 0x01, 0x0a, 0x1c, 0x47, 0x65, 0x74, 0x42, 0x6c, 0x61, 0x63, 0x6b, 0x6c, 0x69, - 0x73, 0x74, 0x65, 0x64, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x53, 0x0a, 0x0a, 0x62, 0x6c, 0x61, 0x63, 0x6b, 0x5f, 0x6c, 0x69, 0x73, 0x74, - 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x34, 0x2e, 0x6d, 0x32, 0x6d, 0x6f, 0x61, 0x75, 0x74, - 0x68, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x70, 0x62, 0x2e, 0x47, 0x65, 0x74, 0x42, 0x6c, - 0x61, 0x63, 0x6b, 0x6c, 0x69, 0x73, 0x74, 0x65, 0x64, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x09, 0x62, 0x6c, - 0x61, 0x63, 0x6b, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x1c, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, - 0x74, 0x61, 0x6d, 0x70, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, - 0x73, 0x74, 0x61, 0x6d, 0x70, 0x1a, 0x32, 0x0a, 0x04, 0x49, 0x74, 0x65, 0x6d, 0x12, 0x1a, 0x0a, - 0x08, 0x61, 0x75, 0x64, 0x69, 0x65, 0x6e, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x08, 0x61, 0x75, 0x64, 0x69, 0x65, 0x6e, 0x63, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x22, 0x89, 0x01, 0x0a, 0x10, 0x47, 0x65, - 0x74, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1b, - 0x0a, 0x09, 0x69, 0x64, 0x5f, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x18, 0x01, 0x20, 0x03, 0x28, - 0x09, 0x52, 0x08, 0x69, 0x64, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x12, 0x27, 0x0a, 0x0f, 0x61, - 0x75, 0x64, 0x69, 0x65, 0x6e, 0x63, 0x65, 0x5f, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x18, 0x02, - 0x20, 0x03, 0x28, 0x09, 0x52, 0x0e, 0x61, 0x75, 0x64, 0x69, 0x65, 0x6e, 0x63, 0x65, 0x46, 0x69, - 0x6c, 0x74, 0x65, 0x72, 0x12, 0x2f, 0x0a, 0x13, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x5f, - 0x62, 0x6c, 0x61, 0x63, 0x6b, 0x6c, 0x69, 0x73, 0x74, 0x65, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, - 0x08, 0x52, 0x12, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x42, 0x6c, 0x61, 0x63, 0x6b, 0x6c, - 0x69, 0x73, 0x74, 0x65, 0x64, 0x22, 0x35, 0x0a, 0x16, 0x42, 0x6c, 0x61, 0x63, 0x6b, 0x6c, 0x69, - 0x73, 0x74, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, - 0x1b, 0x0a, 0x09, 0x69, 0x64, 0x5f, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x18, 0x01, 0x20, 0x03, - 0x28, 0x09, 0x52, 0x08, 0x69, 0x64, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x22, 0x2f, 0x0a, 0x17, - 0x42, 0x6c, 0x61, 0x63, 0x6b, 0x6c, 0x69, 0x73, 0x74, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x32, 0xe6, 0x03, - 0x0a, 0x0e, 0x4d, 0x32, 0x4d, 0x4f, 0x41, 0x75, 0x74, 0x68, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, - 0x12, 0x7e, 0x0a, 0x09, 0x47, 0x65, 0x74, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x12, 0x23, 0x2e, + 0x12, 0x1b, 0x0a, 0x09, 0x69, 0x73, 0x73, 0x75, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x05, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x08, 0x69, 0x73, 0x73, 0x75, 0x65, 0x64, 0x41, 0x74, 0x12, 0x1a, 0x0a, + 0x08, 0x61, 0x75, 0x64, 0x69, 0x65, 0x6e, 0x63, 0x65, 0x18, 0x06, 0x20, 0x03, 0x28, 0x09, 0x52, + 0x08, 0x61, 0x75, 0x64, 0x69, 0x65, 0x6e, 0x63, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x63, 0x6f, + 0x70, 0x65, 0x18, 0x07, 0x20, 0x03, 0x28, 0x09, 0x52, 0x05, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x12, + 0x1e, 0x0a, 0x0a, 0x65, 0x78, 0x70, 0x69, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x08, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x0a, 0x65, 0x78, 0x70, 0x69, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, + 0x1b, 0x0a, 0x09, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x09, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x08, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x4a, 0x0a, 0x15, + 0x6f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x61, 0x6c, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x5f, 0x63, + 0x6c, 0x61, 0x69, 0x6d, 0x73, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x67, 0x6f, + 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x56, 0x61, + 0x6c, 0x75, 0x65, 0x52, 0x13, 0x6f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x61, 0x6c, 0x54, 0x6f, 0x6b, + 0x65, 0x6e, 0x43, 0x6c, 0x61, 0x69, 0x6d, 0x73, 0x12, 0x46, 0x0a, 0x0b, 0x62, 0x6c, 0x61, 0x63, + 0x6b, 0x6c, 0x69, 0x73, 0x74, 0x65, 0x64, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x6d, 0x32, 0x6d, 0x6f, 0x61, 0x75, 0x74, 0x68, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x70, - 0x62, 0x2e, 0x47, 0x65, 0x74, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x6d, 0x32, 0x6d, 0x6f, 0x61, 0x75, 0x74, 0x68, 0x73, 0x65, 0x72, - 0x76, 0x65, 0x72, 0x2e, 0x70, 0x62, 0x2e, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0x32, 0x92, 0x41, - 0x08, 0x0a, 0x06, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x21, 0x12, - 0x1f, 0x2f, 0x6d, 0x32, 0x6d, 0x2d, 0x6f, 0x61, 0x75, 0x74, 0x68, 0x2d, 0x73, 0x65, 0x72, 0x76, - 0x65, 0x72, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x73, - 0x12, 0xae, 0x01, 0x0a, 0x14, 0x47, 0x65, 0x74, 0x42, 0x6c, 0x61, 0x63, 0x6b, 0x6c, 0x69, 0x73, - 0x74, 0x65, 0x64, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x12, 0x2e, 0x2e, 0x6d, 0x32, 0x6d, 0x6f, - 0x61, 0x75, 0x74, 0x68, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x70, 0x62, 0x2e, 0x47, 0x65, - 0x74, 0x42, 0x6c, 0x61, 0x63, 0x6b, 0x6c, 0x69, 0x73, 0x74, 0x65, 0x64, 0x54, 0x6f, 0x6b, 0x65, - 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2f, 0x2e, 0x6d, 0x32, 0x6d, 0x6f, - 0x61, 0x75, 0x74, 0x68, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x70, 0x62, 0x2e, 0x47, 0x65, - 0x74, 0x42, 0x6c, 0x61, 0x63, 0x6b, 0x6c, 0x69, 0x73, 0x74, 0x65, 0x64, 0x54, 0x6f, 0x6b, 0x65, - 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x35, 0x92, 0x41, 0x08, 0x0a, - 0x06, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x24, 0x12, 0x22, 0x2f, - 0x6d, 0x32, 0x6d, 0x2d, 0x6f, 0x61, 0x75, 0x74, 0x68, 0x2d, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, - 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x62, 0x6c, 0x61, 0x63, 0x6b, 0x6c, 0x69, 0x73, - 0x74, 0x12, 0xa2, 0x01, 0x0a, 0x0f, 0x42, 0x6c, 0x61, 0x63, 0x6b, 0x6c, 0x69, 0x73, 0x74, 0x54, - 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x12, 0x29, 0x2e, 0x6d, 0x32, 0x6d, 0x6f, 0x61, 0x75, 0x74, 0x68, - 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x70, 0x62, 0x2e, 0x42, 0x6c, 0x61, 0x63, 0x6b, 0x6c, - 0x69, 0x73, 0x74, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x2a, 0x2e, 0x6d, 0x32, 0x6d, 0x6f, 0x61, 0x75, 0x74, 0x68, 0x73, 0x65, 0x72, 0x76, 0x65, - 0x72, 0x2e, 0x70, 0x62, 0x2e, 0x42, 0x6c, 0x61, 0x63, 0x6b, 0x6c, 0x69, 0x73, 0x74, 0x54, 0x6f, - 0x6b, 0x65, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x38, 0x92, 0x41, - 0x08, 0x0a, 0x06, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x27, 0x3a, - 0x01, 0x2a, 0x22, 0x22, 0x2f, 0x6d, 0x32, 0x6d, 0x2d, 0x6f, 0x61, 0x75, 0x74, 0x68, 0x2d, 0x73, - 0x65, 0x72, 0x76, 0x65, 0x72, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x62, 0x6c, 0x61, - 0x63, 0x6b, 0x6c, 0x69, 0x73, 0x74, 0x42, 0xcb, 0x02, 0x92, 0x41, 0x94, 0x02, 0x12, 0xbc, 0x01, - 0x0a, 0x0c, 0x50, 0x4c, 0x47, 0x44, 0x20, 0x4d, 0x32, 0x4d, 0x20, 0x41, 0x50, 0x49, 0x12, 0x24, - 0x41, 0x50, 0x49, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x74, 0x6f, 0x20, 0x6d, 0x61, 0x6e, 0x61, 0x67, - 0x65, 0x20, 0x6d, 0x32, 0x6d, 0x20, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x20, 0x69, 0x6e, 0x20, - 0x50, 0x4c, 0x47, 0x44, 0x22, 0x3a, 0x0a, 0x08, 0x70, 0x6c, 0x67, 0x64, 0x2e, 0x64, 0x65, 0x76, - 0x12, 0x1f, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, + 0x62, 0x2e, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x2e, 0x42, 0x6c, 0x61, 0x63, 0x6b, 0x4c, 0x69, 0x73, + 0x74, 0x65, 0x64, 0x52, 0x0b, 0x62, 0x6c, 0x61, 0x63, 0x6b, 0x6c, 0x69, 0x73, 0x74, 0x65, 0x64, + 0x1a, 0x3f, 0x0a, 0x0b, 0x42, 0x6c, 0x61, 0x63, 0x6b, 0x4c, 0x69, 0x73, 0x74, 0x65, 0x64, 0x12, + 0x12, 0x0a, 0x04, 0x66, 0x6c, 0x61, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x04, 0x66, + 0x6c, 0x61, 0x67, 0x12, 0x1c, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, + 0x70, 0x22, 0x89, 0x01, 0x0a, 0x10, 0x47, 0x65, 0x74, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x69, 0x64, 0x5f, 0x66, 0x69, 0x6c, + 0x74, 0x65, 0x72, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x08, 0x69, 0x64, 0x46, 0x69, 0x6c, + 0x74, 0x65, 0x72, 0x12, 0x27, 0x0a, 0x0f, 0x61, 0x75, 0x64, 0x69, 0x65, 0x6e, 0x63, 0x65, 0x5f, + 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0e, 0x61, 0x75, + 0x64, 0x69, 0x65, 0x6e, 0x63, 0x65, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x12, 0x2f, 0x0a, 0x13, + 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x5f, 0x62, 0x6c, 0x61, 0x63, 0x6b, 0x6c, 0x69, 0x73, + 0x74, 0x65, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x12, 0x69, 0x6e, 0x63, 0x6c, 0x75, + 0x64, 0x65, 0x42, 0x6c, 0x61, 0x63, 0x6b, 0x6c, 0x69, 0x73, 0x74, 0x65, 0x64, 0x22, 0x35, 0x0a, + 0x16, 0x42, 0x6c, 0x61, 0x63, 0x6b, 0x6c, 0x69, 0x73, 0x74, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x69, 0x64, 0x5f, 0x66, 0x69, + 0x6c, 0x74, 0x65, 0x72, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x08, 0x69, 0x64, 0x46, 0x69, + 0x6c, 0x74, 0x65, 0x72, 0x22, 0x2f, 0x0a, 0x17, 0x42, 0x6c, 0x61, 0x63, 0x6b, 0x6c, 0x69, 0x73, + 0x74, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x14, 0x0a, 0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, + 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0xc7, 0x02, 0x0a, 0x12, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, + 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1b, 0x0a, 0x09, + 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x08, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x6c, 0x69, + 0x65, 0x6e, 0x74, 0x5f, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x0c, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x12, 0x1a, + 0x0a, 0x08, 0x61, 0x75, 0x64, 0x69, 0x65, 0x6e, 0x63, 0x65, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, + 0x52, 0x08, 0x61, 0x75, 0x64, 0x69, 0x65, 0x6e, 0x63, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x63, + 0x6f, 0x70, 0x65, 0x18, 0x04, 0x20, 0x03, 0x28, 0x09, 0x52, 0x05, 0x73, 0x63, 0x6f, 0x70, 0x65, + 0x12, 0x20, 0x0a, 0x0c, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x74, 0x6f, 0x5f, 0x6c, 0x69, 0x76, 0x65, + 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x74, 0x69, 0x6d, 0x65, 0x54, 0x6f, 0x4c, 0x69, + 0x76, 0x65, 0x12, 0x32, 0x0a, 0x15, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x61, 0x73, 0x73, + 0x65, 0x72, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x13, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x41, 0x73, 0x73, 0x65, 0x72, 0x74, 0x69, + 0x6f, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x12, 0x29, 0x0a, 0x10, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, + 0x5f, 0x61, 0x73, 0x73, 0x65, 0x72, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x0f, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x41, 0x73, 0x73, 0x65, 0x72, 0x74, 0x69, 0x6f, + 0x6e, 0x12, 0x1d, 0x0a, 0x0a, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, + 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x4e, 0x61, 0x6d, 0x65, + 0x12, 0x1d, 0x0a, 0x0a, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x09, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x22, + 0x8c, 0x01, 0x0a, 0x13, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x61, 0x63, 0x63, 0x65, 0x73, + 0x73, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x61, + 0x63, 0x63, 0x65, 0x73, 0x73, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x1d, 0x0a, 0x0a, 0x74, 0x6f, + 0x6b, 0x65, 0x6e, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, + 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x65, 0x78, 0x70, + 0x69, 0x72, 0x65, 0x73, 0x5f, 0x69, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x65, + 0x78, 0x70, 0x69, 0x72, 0x65, 0x73, 0x49, 0x6e, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x63, 0x6f, 0x70, + 0x65, 0x18, 0x04, 0x20, 0x03, 0x28, 0x09, 0x52, 0x05, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x32, 0xcf, + 0x03, 0x0a, 0x0f, 0x4d, 0x32, 0x4d, 0x4f, 0x41, 0x75, 0x74, 0x68, 0x53, 0x65, 0x72, 0x76, 0x69, + 0x63, 0x65, 0x12, 0x93, 0x01, 0x0a, 0x0b, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x6f, 0x6b, + 0x65, 0x6e, 0x12, 0x25, 0x2e, 0x6d, 0x32, 0x6d, 0x6f, 0x61, 0x75, 0x74, 0x68, 0x73, 0x65, 0x72, + 0x76, 0x65, 0x72, 0x2e, 0x70, 0x62, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x6f, 0x6b, + 0x65, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x26, 0x2e, 0x6d, 0x32, 0x6d, 0x6f, + 0x61, 0x75, 0x74, 0x68, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x70, 0x62, 0x2e, 0x43, 0x72, + 0x65, 0x61, 0x74, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x22, 0x35, 0x92, 0x41, 0x08, 0x0a, 0x06, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x82, 0xd3, + 0xe4, 0x93, 0x02, 0x24, 0x3a, 0x01, 0x2a, 0x22, 0x1f, 0x2f, 0x6d, 0x32, 0x6d, 0x2d, 0x6f, 0x61, + 0x75, 0x74, 0x68, 0x2d, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, + 0x31, 0x2f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x12, 0x80, 0x01, 0x0a, 0x09, 0x47, 0x65, 0x74, + 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x12, 0x23, 0x2e, 0x6d, 0x32, 0x6d, 0x6f, 0x61, 0x75, 0x74, + 0x68, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x70, 0x62, 0x2e, 0x47, 0x65, 0x74, 0x54, 0x6f, + 0x6b, 0x65, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x6d, 0x32, + 0x6d, 0x6f, 0x61, 0x75, 0x74, 0x68, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x70, 0x62, 0x2e, + 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0x32, 0x92, 0x41, 0x08, 0x0a, 0x06, 0x54, 0x6f, 0x6b, 0x65, + 0x6e, 0x73, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x21, 0x12, 0x1f, 0x2f, 0x6d, 0x32, 0x6d, 0x2d, 0x6f, + 0x61, 0x75, 0x74, 0x68, 0x2d, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2f, 0x61, 0x70, 0x69, 0x2f, + 0x76, 0x31, 0x2f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x30, 0x01, 0x12, 0xa2, 0x01, 0x0a, 0x0f, + 0x42, 0x6c, 0x61, 0x63, 0x6b, 0x6c, 0x69, 0x73, 0x74, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x12, + 0x29, 0x2e, 0x6d, 0x32, 0x6d, 0x6f, 0x61, 0x75, 0x74, 0x68, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, + 0x2e, 0x70, 0x62, 0x2e, 0x42, 0x6c, 0x61, 0x63, 0x6b, 0x6c, 0x69, 0x73, 0x74, 0x54, 0x6f, 0x6b, + 0x65, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x6d, 0x32, 0x6d, + 0x6f, 0x61, 0x75, 0x74, 0x68, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x70, 0x62, 0x2e, 0x42, + 0x6c, 0x61, 0x63, 0x6b, 0x6c, 0x69, 0x73, 0x74, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x38, 0x92, 0x41, 0x08, 0x0a, 0x06, 0x54, 0x6f, 0x6b, + 0x65, 0x6e, 0x73, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x27, 0x3a, 0x01, 0x2a, 0x22, 0x22, 0x2f, 0x6d, + 0x32, 0x6d, 0x2d, 0x6f, 0x61, 0x75, 0x74, 0x68, 0x2d, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2f, + 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x62, 0x6c, 0x61, 0x63, 0x6b, 0x6c, 0x69, 0x73, 0x74, + 0x42, 0xcb, 0x02, 0x92, 0x41, 0x94, 0x02, 0x12, 0xbc, 0x01, 0x0a, 0x0c, 0x50, 0x4c, 0x47, 0x44, + 0x20, 0x4d, 0x32, 0x4d, 0x20, 0x41, 0x50, 0x49, 0x12, 0x24, 0x41, 0x50, 0x49, 0x20, 0x66, 0x6f, + 0x72, 0x20, 0x74, 0x6f, 0x20, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x20, 0x6d, 0x32, 0x6d, 0x20, + 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x20, 0x69, 0x6e, 0x20, 0x50, 0x4c, 0x47, 0x44, 0x22, 0x3a, + 0x0a, 0x08, 0x70, 0x6c, 0x67, 0x64, 0x2e, 0x64, 0x65, 0x76, 0x12, 0x1f, 0x68, 0x74, 0x74, 0x70, + 0x73, 0x3a, 0x2f, 0x2f, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x70, + 0x6c, 0x67, 0x64, 0x2d, 0x64, 0x65, 0x76, 0x2f, 0x68, 0x75, 0x62, 0x1a, 0x0d, 0x69, 0x6e, 0x66, + 0x6f, 0x40, 0x70, 0x6c, 0x67, 0x64, 0x2e, 0x64, 0x65, 0x76, 0x2a, 0x45, 0x0a, 0x12, 0x41, 0x70, + 0x61, 0x63, 0x68, 0x65, 0x20, 0x4c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x20, 0x32, 0x2e, 0x30, + 0x12, 0x2f, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x70, 0x6c, 0x67, 0x64, 0x2d, 0x64, 0x65, 0x76, 0x2f, 0x68, 0x75, - 0x62, 0x1a, 0x0d, 0x69, 0x6e, 0x66, 0x6f, 0x40, 0x70, 0x6c, 0x67, 0x64, 0x2e, 0x64, 0x65, 0x76, - 0x2a, 0x45, 0x0a, 0x12, 0x41, 0x70, 0x61, 0x63, 0x68, 0x65, 0x20, 0x4c, 0x69, 0x63, 0x65, 0x6e, - 0x73, 0x65, 0x20, 0x32, 0x2e, 0x30, 0x12, 0x2f, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, - 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x70, 0x6c, 0x67, 0x64, 0x2d, - 0x64, 0x65, 0x76, 0x2f, 0x68, 0x75, 0x62, 0x2f, 0x62, 0x6c, 0x6f, 0x62, 0x2f, 0x76, 0x32, 0x2f, - 0x4c, 0x49, 0x43, 0x45, 0x4e, 0x53, 0x45, 0x32, 0x03, 0x31, 0x2e, 0x30, 0x2a, 0x01, 0x02, 0x32, - 0x10, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x6a, 0x73, 0x6f, - 0x6e, 0x32, 0x15, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x6a, 0x73, 0x6f, 0x6e, 0x3a, 0x10, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x6a, 0x73, 0x6f, 0x6e, 0x3a, 0x15, 0x61, 0x70, 0x70, 0x6c, - 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x6a, 0x73, 0x6f, - 0x6e, 0x5a, 0x31, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x70, 0x6c, - 0x67, 0x64, 0x2d, 0x64, 0x65, 0x76, 0x2f, 0x68, 0x75, 0x62, 0x2f, 0x76, 0x32, 0x2f, 0x6d, 0x32, - 0x6d, 0x2d, 0x6f, 0x61, 0x75, 0x74, 0x68, 0x2d, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2f, 0x70, - 0x62, 0x3b, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x62, 0x2f, 0x62, 0x6c, 0x6f, 0x62, 0x2f, 0x76, 0x32, 0x2f, 0x4c, 0x49, 0x43, 0x45, 0x4e, 0x53, + 0x45, 0x32, 0x03, 0x31, 0x2e, 0x30, 0x2a, 0x01, 0x02, 0x32, 0x10, 0x61, 0x70, 0x70, 0x6c, 0x69, + 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x6a, 0x73, 0x6f, 0x6e, 0x32, 0x15, 0x61, 0x70, 0x70, + 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x6a, 0x73, + 0x6f, 0x6e, 0x3a, 0x10, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, + 0x6a, 0x73, 0x6f, 0x6e, 0x3a, 0x15, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x6a, 0x73, 0x6f, 0x6e, 0x5a, 0x31, 0x67, 0x69, 0x74, + 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x70, 0x6c, 0x67, 0x64, 0x2d, 0x64, 0x65, 0x76, + 0x2f, 0x68, 0x75, 0x62, 0x2f, 0x76, 0x32, 0x2f, 0x6d, 0x32, 0x6d, 0x2d, 0x6f, 0x61, 0x75, 0x74, + 0x68, 0x2d, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2f, 0x70, 0x62, 0x3b, 0x70, 0x62, 0x62, 0x06, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -674,33 +709,31 @@ func file_m2m_oauth_server_pb_service_proto_rawDescGZIP() []byte { return file_m2m_oauth_server_pb_service_proto_rawDescData } -var file_m2m_oauth_server_pb_service_proto_msgTypes = make([]protoimpl.MessageInfo, 8) +var file_m2m_oauth_server_pb_service_proto_msgTypes = make([]protoimpl.MessageInfo, 7) var file_m2m_oauth_server_pb_service_proto_goTypes = []interface{}{ - (*Token)(nil), // 0: m2moauthserver.pb.Token - (*GetBlacklistedTokensRequest)(nil), // 1: m2moauthserver.pb.GetBlacklistedTokensRequest - (*GetBlacklistedTokensResponse)(nil), // 2: m2moauthserver.pb.GetBlacklistedTokensResponse - (*GetTokensRequest)(nil), // 3: m2moauthserver.pb.GetTokensRequest - (*BlacklistTokensRequest)(nil), // 4: m2moauthserver.pb.BlacklistTokensRequest - (*BlacklistTokensResponse)(nil), // 5: m2moauthserver.pb.BlacklistTokensResponse - (*Token_BlackListed)(nil), // 6: m2moauthserver.pb.Token.BlackListed - (*GetBlacklistedTokensResponse_Item)(nil), // 7: m2moauthserver.pb.GetBlacklistedTokensResponse.Item - (*structpb.Value)(nil), // 8: google.protobuf.Value + (*Token)(nil), // 0: m2moauthserver.pb.Token + (*GetTokensRequest)(nil), // 1: m2moauthserver.pb.GetTokensRequest + (*BlacklistTokensRequest)(nil), // 2: m2moauthserver.pb.BlacklistTokensRequest + (*BlacklistTokensResponse)(nil), // 3: m2moauthserver.pb.BlacklistTokensResponse + (*CreateTokenRequest)(nil), // 4: m2moauthserver.pb.CreateTokenRequest + (*CreateTokenResponse)(nil), // 5: m2moauthserver.pb.CreateTokenResponse + (*Token_BlackListed)(nil), // 6: m2moauthserver.pb.Token.BlackListed + (*structpb.Value)(nil), // 7: google.protobuf.Value } var file_m2m_oauth_server_pb_service_proto_depIdxs = []int32{ - 8, // 0: m2moauthserver.pb.Token.original_token_claims:type_name -> google.protobuf.Value + 7, // 0: m2moauthserver.pb.Token.original_token_claims:type_name -> google.protobuf.Value 6, // 1: m2moauthserver.pb.Token.blacklisted:type_name -> m2moauthserver.pb.Token.BlackListed - 7, // 2: m2moauthserver.pb.GetBlacklistedTokensResponse.black_list:type_name -> m2moauthserver.pb.GetBlacklistedTokensResponse.Item - 3, // 3: m2moauthserver.pb.M2MOAuthServer.GetTokens:input_type -> m2moauthserver.pb.GetTokensRequest - 1, // 4: m2moauthserver.pb.M2MOAuthServer.GetBlacklistedTokens:input_type -> m2moauthserver.pb.GetBlacklistedTokensRequest - 4, // 5: m2moauthserver.pb.M2MOAuthServer.BlacklistTokens:input_type -> m2moauthserver.pb.BlacklistTokensRequest - 0, // 6: m2moauthserver.pb.M2MOAuthServer.GetTokens:output_type -> m2moauthserver.pb.Token - 2, // 7: m2moauthserver.pb.M2MOAuthServer.GetBlacklistedTokens:output_type -> m2moauthserver.pb.GetBlacklistedTokensResponse - 5, // 8: m2moauthserver.pb.M2MOAuthServer.BlacklistTokens:output_type -> m2moauthserver.pb.BlacklistTokensResponse - 6, // [6:9] is the sub-list for method output_type - 3, // [3:6] is the sub-list for method input_type - 3, // [3:3] is the sub-list for extension type_name - 3, // [3:3] is the sub-list for extension extendee - 0, // [0:3] is the sub-list for field type_name + 4, // 2: m2moauthserver.pb.M2MOAuthService.CreateToken:input_type -> m2moauthserver.pb.CreateTokenRequest + 1, // 3: m2moauthserver.pb.M2MOAuthService.GetTokens:input_type -> m2moauthserver.pb.GetTokensRequest + 2, // 4: m2moauthserver.pb.M2MOAuthService.BlacklistTokens:input_type -> m2moauthserver.pb.BlacklistTokensRequest + 5, // 5: m2moauthserver.pb.M2MOAuthService.CreateToken:output_type -> m2moauthserver.pb.CreateTokenResponse + 0, // 6: m2moauthserver.pb.M2MOAuthService.GetTokens:output_type -> m2moauthserver.pb.Token + 3, // 7: m2moauthserver.pb.M2MOAuthService.BlacklistTokens:output_type -> m2moauthserver.pb.BlacklistTokensResponse + 5, // [5:8] is the sub-list for method output_type + 2, // [2:5] is the sub-list for method input_type + 2, // [2:2] is the sub-list for extension type_name + 2, // [2:2] is the sub-list for extension extendee + 0, // [0:2] is the sub-list for field type_name } func init() { file_m2m_oauth_server_pb_service_proto_init() } @@ -722,7 +755,7 @@ func file_m2m_oauth_server_pb_service_proto_init() { } } file_m2m_oauth_server_pb_service_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetBlacklistedTokensRequest); i { + switch v := v.(*GetTokensRequest); i { case 0: return &v.state case 1: @@ -734,7 +767,7 @@ func file_m2m_oauth_server_pb_service_proto_init() { } } file_m2m_oauth_server_pb_service_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetBlacklistedTokensResponse); i { + switch v := v.(*BlacklistTokensRequest); i { case 0: return &v.state case 1: @@ -746,7 +779,7 @@ func file_m2m_oauth_server_pb_service_proto_init() { } } file_m2m_oauth_server_pb_service_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetTokensRequest); i { + switch v := v.(*BlacklistTokensResponse); i { case 0: return &v.state case 1: @@ -758,7 +791,7 @@ func file_m2m_oauth_server_pb_service_proto_init() { } } file_m2m_oauth_server_pb_service_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*BlacklistTokensRequest); i { + switch v := v.(*CreateTokenRequest); i { case 0: return &v.state case 1: @@ -770,7 +803,7 @@ func file_m2m_oauth_server_pb_service_proto_init() { } } file_m2m_oauth_server_pb_service_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*BlacklistTokensResponse); i { + switch v := v.(*CreateTokenResponse); i { case 0: return &v.state case 1: @@ -793,18 +826,6 @@ func file_m2m_oauth_server_pb_service_proto_init() { return nil } } - file_m2m_oauth_server_pb_service_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetBlacklistedTokensResponse_Item); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } } type x struct{} out := protoimpl.TypeBuilder{ @@ -812,7 +833,7 @@ func file_m2m_oauth_server_pb_service_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_m2m_oauth_server_pb_service_proto_rawDesc, NumEnums: 0, - NumMessages: 8, + NumMessages: 7, NumExtensions: 0, NumServices: 1, }, diff --git a/m2m-oauth-server/pb/service.pb.gw.go b/m2m-oauth-server/pb/service.pb.gw.go index 7ff770a53..aa6e8b730 100644 --- a/m2m-oauth-server/pb/service.pb.gw.go +++ b/m2m-oauth-server/pb/service.pb.gw.go @@ -31,79 +31,61 @@ var _ = runtime.String var _ = utilities.NewDoubleArray var _ = metadata.Join -var ( - filter_M2MOAuthServer_GetTokens_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)} -) - -func request_M2MOAuthServer_GetTokens_0(ctx context.Context, marshaler runtime.Marshaler, client M2MOAuthServerClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { - var protoReq GetTokensRequest +func request_M2MOAuthService_CreateToken_0(ctx context.Context, marshaler runtime.Marshaler, client M2MOAuthServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq CreateTokenRequest var metadata runtime.ServerMetadata - if err := req.ParseForm(); err != nil { - return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) - } - if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_M2MOAuthServer_GetTokens_0); err != nil { + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && err != io.EOF { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } - msg, err := client.GetTokens(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + msg, err := client.CreateToken(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } -func local_request_M2MOAuthServer_GetTokens_0(ctx context.Context, marshaler runtime.Marshaler, server M2MOAuthServerServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { - var protoReq GetTokensRequest +func local_request_M2MOAuthService_CreateToken_0(ctx context.Context, marshaler runtime.Marshaler, server M2MOAuthServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq CreateTokenRequest var metadata runtime.ServerMetadata - if err := req.ParseForm(); err != nil { - return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) - } - if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_M2MOAuthServer_GetTokens_0); err != nil { + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && err != io.EOF { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } - msg, err := server.GetTokens(ctx, &protoReq) + msg, err := server.CreateToken(ctx, &protoReq) return msg, metadata, err } var ( - filter_M2MOAuthServer_GetBlacklistedTokens_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)} + filter_M2MOAuthService_GetTokens_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)} ) -func request_M2MOAuthServer_GetBlacklistedTokens_0(ctx context.Context, marshaler runtime.Marshaler, client M2MOAuthServerClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { - var protoReq GetBlacklistedTokensRequest +func request_M2MOAuthService_GetTokens_0(ctx context.Context, marshaler runtime.Marshaler, client M2MOAuthServiceClient, req *http.Request, pathParams map[string]string) (M2MOAuthService_GetTokensClient, runtime.ServerMetadata, error) { + var protoReq GetTokensRequest var metadata runtime.ServerMetadata if err := req.ParseForm(); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } - if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_M2MOAuthServer_GetBlacklistedTokens_0); err != nil { + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_M2MOAuthService_GetTokens_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } - msg, err := client.GetBlacklistedTokens(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) - return msg, metadata, err - -} - -func local_request_M2MOAuthServer_GetBlacklistedTokens_0(ctx context.Context, marshaler runtime.Marshaler, server M2MOAuthServerServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { - var protoReq GetBlacklistedTokensRequest - var metadata runtime.ServerMetadata - - if err := req.ParseForm(); err != nil { - return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + stream, err := client.GetTokens(ctx, &protoReq) + if err != nil { + return nil, metadata, err } - if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_M2MOAuthServer_GetBlacklistedTokens_0); err != nil { - return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + header, err := stream.Header() + if err != nil { + return nil, metadata, err } - - msg, err := server.GetBlacklistedTokens(ctx, &protoReq) - return msg, metadata, err + metadata.HeaderMD = header + return stream, metadata, nil } -func request_M2MOAuthServer_BlacklistTokens_0(ctx context.Context, marshaler runtime.Marshaler, client M2MOAuthServerClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { +func request_M2MOAuthService_BlacklistTokens_0(ctx context.Context, marshaler runtime.Marshaler, client M2MOAuthServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { var protoReq BlacklistTokensRequest var metadata runtime.ServerMetadata @@ -116,7 +98,7 @@ func request_M2MOAuthServer_BlacklistTokens_0(ctx context.Context, marshaler run } -func local_request_M2MOAuthServer_BlacklistTokens_0(ctx context.Context, marshaler runtime.Marshaler, server M2MOAuthServerServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { +func local_request_M2MOAuthService_BlacklistTokens_0(ctx context.Context, marshaler runtime.Marshaler, server M2MOAuthServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { var protoReq BlacklistTokensRequest var metadata runtime.ServerMetadata @@ -129,13 +111,13 @@ func local_request_M2MOAuthServer_BlacklistTokens_0(ctx context.Context, marshal } -// RegisterM2MOAuthServerHandlerServer registers the http handlers for service M2MOAuthServer to "mux". -// UnaryRPC :call M2MOAuthServerServer directly. +// RegisterM2MOAuthServiceHandlerServer registers the http handlers for service M2MOAuthService to "mux". +// UnaryRPC :call M2MOAuthServiceServer directly. // StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906. -// Note that using this registration option will cause many gRPC library features to stop working. Consider using RegisterM2MOAuthServerHandlerFromEndpoint instead. -func RegisterM2MOAuthServerHandlerServer(ctx context.Context, mux *runtime.ServeMux, server M2MOAuthServerServer) error { +// Note that using this registration option will cause many gRPC library features to stop working. Consider using RegisterM2MOAuthServiceHandlerFromEndpoint instead. +func RegisterM2MOAuthServiceHandlerServer(ctx context.Context, mux *runtime.ServeMux, server M2MOAuthServiceServer) error { - mux.Handle("GET", pattern_M2MOAuthServer_GetTokens_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + mux.Handle("POST", pattern_M2MOAuthService_CreateToken_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { ctx, cancel := context.WithCancel(req.Context()) defer cancel() var stream runtime.ServerTransportStream @@ -143,12 +125,12 @@ func RegisterM2MOAuthServerHandlerServer(ctx context.Context, mux *runtime.Serve inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error var annotatedContext context.Context - annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/m2moauthserver.pb.M2MOAuthServer/GetTokens", runtime.WithHTTPPathPattern("/m2m-oauth-server/api/v1/tokens")) + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/m2moauthserver.pb.M2MOAuthService/CreateToken", runtime.WithHTTPPathPattern("/m2m-oauth-server/api/v1/tokens")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := local_request_M2MOAuthServer_GetTokens_0(annotatedContext, inboundMarshaler, server, req, pathParams) + resp, md, err := local_request_M2MOAuthService_CreateToken_0(annotatedContext, inboundMarshaler, server, req, pathParams) md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { @@ -156,36 +138,18 @@ func RegisterM2MOAuthServerHandlerServer(ctx context.Context, mux *runtime.Serve return } - forward_M2MOAuthServer_GetTokens_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_M2MOAuthService_CreateToken_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) - mux.Handle("GET", pattern_M2MOAuthServer_GetBlacklistedTokens_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { - ctx, cancel := context.WithCancel(req.Context()) - defer cancel() - var stream runtime.ServerTransportStream - ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) - inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) - var err error - var annotatedContext context.Context - annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/m2moauthserver.pb.M2MOAuthServer/GetBlacklistedTokens", runtime.WithHTTPPathPattern("/m2m-oauth-server/api/v1/blacklist")) - if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) - return - } - resp, md, err := local_request_M2MOAuthServer_GetBlacklistedTokens_0(annotatedContext, inboundMarshaler, server, req, pathParams) - md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) - annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) - if err != nil { - runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) - return - } - - forward_M2MOAuthServer_GetBlacklistedTokens_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) - + mux.Handle("GET", pattern_M2MOAuthService_GetTokens_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + err := status.Error(codes.Unimplemented, "streaming calls are not yet supported in the in-process transport") + _, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return }) - mux.Handle("POST", pattern_M2MOAuthServer_BlacklistTokens_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + mux.Handle("POST", pattern_M2MOAuthService_BlacklistTokens_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { ctx, cancel := context.WithCancel(req.Context()) defer cancel() var stream runtime.ServerTransportStream @@ -193,12 +157,12 @@ func RegisterM2MOAuthServerHandlerServer(ctx context.Context, mux *runtime.Serve inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error var annotatedContext context.Context - annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/m2moauthserver.pb.M2MOAuthServer/BlacklistTokens", runtime.WithHTTPPathPattern("/m2m-oauth-server/api/v1/blacklist")) + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/m2moauthserver.pb.M2MOAuthService/BlacklistTokens", runtime.WithHTTPPathPattern("/m2m-oauth-server/api/v1/blacklist")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := local_request_M2MOAuthServer_BlacklistTokens_0(annotatedContext, inboundMarshaler, server, req, pathParams) + resp, md, err := local_request_M2MOAuthService_BlacklistTokens_0(annotatedContext, inboundMarshaler, server, req, pathParams) md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { @@ -206,16 +170,16 @@ func RegisterM2MOAuthServerHandlerServer(ctx context.Context, mux *runtime.Serve return } - forward_M2MOAuthServer_BlacklistTokens_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_M2MOAuthService_BlacklistTokens_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) return nil } -// RegisterM2MOAuthServerHandlerFromEndpoint is same as RegisterM2MOAuthServerHandler but +// RegisterM2MOAuthServiceHandlerFromEndpoint is same as RegisterM2MOAuthServiceHandler but // automatically dials to "endpoint" and closes the connection when "ctx" gets done. -func RegisterM2MOAuthServerHandlerFromEndpoint(ctx context.Context, mux *runtime.ServeMux, endpoint string, opts []grpc.DialOption) (err error) { +func RegisterM2MOAuthServiceHandlerFromEndpoint(ctx context.Context, mux *runtime.ServeMux, endpoint string, opts []grpc.DialOption) (err error) { conn, err := grpc.NewClient(endpoint, opts...) if err != nil { return err @@ -235,85 +199,85 @@ func RegisterM2MOAuthServerHandlerFromEndpoint(ctx context.Context, mux *runtime }() }() - return RegisterM2MOAuthServerHandler(ctx, mux, conn) + return RegisterM2MOAuthServiceHandler(ctx, mux, conn) } -// RegisterM2MOAuthServerHandler registers the http handlers for service M2MOAuthServer to "mux". +// RegisterM2MOAuthServiceHandler registers the http handlers for service M2MOAuthService to "mux". // The handlers forward requests to the grpc endpoint over "conn". -func RegisterM2MOAuthServerHandler(ctx context.Context, mux *runtime.ServeMux, conn *grpc.ClientConn) error { - return RegisterM2MOAuthServerHandlerClient(ctx, mux, NewM2MOAuthServerClient(conn)) +func RegisterM2MOAuthServiceHandler(ctx context.Context, mux *runtime.ServeMux, conn *grpc.ClientConn) error { + return RegisterM2MOAuthServiceHandlerClient(ctx, mux, NewM2MOAuthServiceClient(conn)) } -// RegisterM2MOAuthServerHandlerClient registers the http handlers for service M2MOAuthServer -// to "mux". The handlers forward requests to the grpc endpoint over the given implementation of "M2MOAuthServerClient". -// Note: the gRPC framework executes interceptors within the gRPC handler. If the passed in "M2MOAuthServerClient" +// RegisterM2MOAuthServiceHandlerClient registers the http handlers for service M2MOAuthService +// to "mux". The handlers forward requests to the grpc endpoint over the given implementation of "M2MOAuthServiceClient". +// Note: the gRPC framework executes interceptors within the gRPC handler. If the passed in "M2MOAuthServiceClient" // doesn't go through the normal gRPC flow (creating a gRPC client etc.) then it will be up to the passed in -// "M2MOAuthServerClient" to call the correct interceptors. -func RegisterM2MOAuthServerHandlerClient(ctx context.Context, mux *runtime.ServeMux, client M2MOAuthServerClient) error { +// "M2MOAuthServiceClient" to call the correct interceptors. +func RegisterM2MOAuthServiceHandlerClient(ctx context.Context, mux *runtime.ServeMux, client M2MOAuthServiceClient) error { - mux.Handle("GET", pattern_M2MOAuthServer_GetTokens_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + mux.Handle("POST", pattern_M2MOAuthService_CreateToken_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { ctx, cancel := context.WithCancel(req.Context()) defer cancel() inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error var annotatedContext context.Context - annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/m2moauthserver.pb.M2MOAuthServer/GetTokens", runtime.WithHTTPPathPattern("/m2m-oauth-server/api/v1/tokens")) + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/m2moauthserver.pb.M2MOAuthService/CreateToken", runtime.WithHTTPPathPattern("/m2m-oauth-server/api/v1/tokens")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := request_M2MOAuthServer_GetTokens_0(annotatedContext, inboundMarshaler, client, req, pathParams) + resp, md, err := request_M2MOAuthService_CreateToken_0(annotatedContext, inboundMarshaler, client, req, pathParams) annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_M2MOAuthServer_GetTokens_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_M2MOAuthService_CreateToken_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) - mux.Handle("GET", pattern_M2MOAuthServer_GetBlacklistedTokens_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + mux.Handle("GET", pattern_M2MOAuthService_GetTokens_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { ctx, cancel := context.WithCancel(req.Context()) defer cancel() inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error var annotatedContext context.Context - annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/m2moauthserver.pb.M2MOAuthServer/GetBlacklistedTokens", runtime.WithHTTPPathPattern("/m2m-oauth-server/api/v1/blacklist")) + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/m2moauthserver.pb.M2MOAuthService/GetTokens", runtime.WithHTTPPathPattern("/m2m-oauth-server/api/v1/tokens")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := request_M2MOAuthServer_GetBlacklistedTokens_0(annotatedContext, inboundMarshaler, client, req, pathParams) + resp, md, err := request_M2MOAuthService_GetTokens_0(annotatedContext, inboundMarshaler, client, req, pathParams) annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_M2MOAuthServer_GetBlacklistedTokens_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_M2MOAuthService_GetTokens_0(annotatedContext, mux, outboundMarshaler, w, req, func() (proto.Message, error) { return resp.Recv() }, mux.GetForwardResponseOptions()...) }) - mux.Handle("POST", pattern_M2MOAuthServer_BlacklistTokens_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + mux.Handle("POST", pattern_M2MOAuthService_BlacklistTokens_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { ctx, cancel := context.WithCancel(req.Context()) defer cancel() inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error var annotatedContext context.Context - annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/m2moauthserver.pb.M2MOAuthServer/BlacklistTokens", runtime.WithHTTPPathPattern("/m2m-oauth-server/api/v1/blacklist")) + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/m2moauthserver.pb.M2MOAuthService/BlacklistTokens", runtime.WithHTTPPathPattern("/m2m-oauth-server/api/v1/blacklist")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := request_M2MOAuthServer_BlacklistTokens_0(annotatedContext, inboundMarshaler, client, req, pathParams) + resp, md, err := request_M2MOAuthService_BlacklistTokens_0(annotatedContext, inboundMarshaler, client, req, pathParams) annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_M2MOAuthServer_BlacklistTokens_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_M2MOAuthService_BlacklistTokens_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -321,17 +285,17 @@ func RegisterM2MOAuthServerHandlerClient(ctx context.Context, mux *runtime.Serve } var ( - pattern_M2MOAuthServer_GetTokens_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"m2m-oauth-server", "api", "v1", "tokens"}, "")) + pattern_M2MOAuthService_CreateToken_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"m2m-oauth-server", "api", "v1", "tokens"}, "")) - pattern_M2MOAuthServer_GetBlacklistedTokens_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"m2m-oauth-server", "api", "v1", "blacklist"}, "")) + pattern_M2MOAuthService_GetTokens_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"m2m-oauth-server", "api", "v1", "tokens"}, "")) - pattern_M2MOAuthServer_BlacklistTokens_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"m2m-oauth-server", "api", "v1", "blacklist"}, "")) + pattern_M2MOAuthService_BlacklistTokens_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"m2m-oauth-server", "api", "v1", "blacklist"}, "")) ) var ( - forward_M2MOAuthServer_GetTokens_0 = runtime.ForwardResponseMessage + forward_M2MOAuthService_CreateToken_0 = runtime.ForwardResponseMessage - forward_M2MOAuthServer_GetBlacklistedTokens_0 = runtime.ForwardResponseMessage + forward_M2MOAuthService_GetTokens_0 = runtime.ForwardResponseStream - forward_M2MOAuthServer_BlacklistTokens_0 = runtime.ForwardResponseMessage + forward_M2MOAuthService_BlacklistTokens_0 = runtime.ForwardResponseMessage ) diff --git a/m2m-oauth-server/pb/service.proto b/m2m-oauth-server/pb/service.proto index 3d1485bf6..d0c8a0a52 100644 --- a/m2m-oauth-server/pb/service.proto +++ b/m2m-oauth-server/pb/service.proto @@ -32,18 +32,18 @@ option go_package = "github.com/plgd-dev/hub/v2/m2m-oauth-server/pb;pb"; message Token { // driven by resource change event // Token ID / jti string id = 1; // @gotags: bson:"_id" - // Incremetal version for update + // Incremental version for update uint64 version = 2; // User-friendly token name string name = 3; // Owner of the token string owner = 4; // Unix timestamp in ns when the condition has been created/updated - int64 timestamp = 5; + int64 issued_at = 5; // Token Audience repeated string audience = 6; // Token scopes - repeated string scopes = 7; + repeated string scope = 7; // Original token expiration int64 expiration = 8; // Client ID @@ -60,20 +60,7 @@ message Token { // driven by resource change event BlackListed blacklisted = 11; } -message GetBlacklistedTokensRequest { - // Returns all blacklisted/revoked not expired tokens of the owner from the given timestamp - int64 timestamp = 1; -} -message GetBlacklistedTokensResponse { - message Item { - string audience = 1; - string id = 2; - } - repeated Item black_list = 1; - // The biggest version of the blacklisted/revoked token over all tokens of owner - int64 timestamp = 2; -} message GetTokensRequest { repeated string id_filter = 1; @@ -89,21 +76,41 @@ message BlacklistTokensResponse { int64 count = 1; } -service M2MOAuthServer { - // Returns all tokens of the owner - rpc GetTokens(GetTokensRequest) returns (Token) { +message CreateTokenRequest { + string client_id = 1; + string client_secret = 2; + repeated string audience = 3; + repeated string scope = 4; + int64 time_to_live = 5; + string client_assertion_type = 6; + string client_assertion = 7; + string token_name = 8; + string grant_type = 9; +} + +message CreateTokenResponse { + string access_token = 1; + string token_type = 2; + int64 expires_in = 3; + repeated string scope = 4; +} + +service M2MOAuthService { + // Creates a new token + rpc CreateToken(CreateTokenRequest) returns (CreateTokenResponse) { option (google.api.http) = { - get: "/m2m-oauth-server/api/v1/tokens"; + post: "/m2m-oauth-server/api/v1/tokens"; + body: "*"; }; option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { tags: [ "Tokens" ]; }; } - // Returns all blacklisted/revoked not expired tokens - rpc GetBlacklistedTokens(GetBlacklistedTokensRequest) returns (GetBlacklistedTokensResponse) { + // Returns all tokens of the owner + rpc GetTokens(GetTokensRequest) returns (stream Token) { option (google.api.http) = { - get: "/m2m-oauth-server/api/v1/blacklist"; + get: "/m2m-oauth-server/api/v1/tokens"; }; option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { tags: [ "Tokens" ]; diff --git a/m2m-oauth-server/pb/service.swagger.json b/m2m-oauth-server/pb/service.swagger.json index fb34c96c4..1123e66fe 100644 --- a/m2m-oauth-server/pb/service.swagger.json +++ b/m2m-oauth-server/pb/service.swagger.json @@ -16,7 +16,7 @@ }, "tags": [ { - "name": "M2MOAuthServer" + "name": "M2MOAuthService" } ], "schemes": [ @@ -32,40 +32,9 @@ ], "paths": { "/m2m-oauth-server/api/v1/blacklist": { - "get": { - "summary": "Returns all blacklisted/revoked not expired tokens", - "operationId": "M2MOAuthServer_GetBlacklistedTokens", - "responses": { - "200": { - "description": "A successful response.", - "schema": { - "$ref": "#/definitions/pbGetBlacklistedTokensResponse" - } - }, - "default": { - "description": "An unexpected error response.", - "schema": { - "$ref": "#/definitions/rpcStatus" - } - } - }, - "parameters": [ - { - "name": "timestamp", - "description": "Returns all blacklisted/revoked not expired tokens of the owner from the given timestamp", - "in": "query", - "required": false, - "type": "string", - "format": "int64" - } - ], - "tags": [ - "Tokens" - ] - }, "post": { "summary": "Blacklists/revokes tokens", - "operationId": "M2MOAuthServer_BlacklistTokens", + "operationId": "M2MOAuthService_BlacklistTokens", "responses": { "200": { "description": "A successful response.", @@ -98,12 +67,21 @@ "/m2m-oauth-server/api/v1/tokens": { "get": { "summary": "Returns all tokens of the owner", - "operationId": "M2MOAuthServer_GetTokens", + "operationId": "M2MOAuthService_GetTokens", "responses": { "200": { - "description": "A successful response.", + "description": "A successful response.(streaming responses)", "schema": { - "$ref": "#/definitions/pbToken" + "type": "object", + "properties": { + "result": { + "$ref": "#/definitions/pbToken" + }, + "error": { + "$ref": "#/definitions/rpcStatus" + } + }, + "title": "Stream result of pbToken" } }, "default": { @@ -144,21 +122,41 @@ "tags": [ "Tokens" ] + }, + "post": { + "summary": "Creates a new token", + "operationId": "M2MOAuthService_CreateToken", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/pbCreateTokenResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/pbCreateTokenRequest" + } + } + ], + "tags": [ + "Tokens" + ] } } }, "definitions": { - "GetBlacklistedTokensResponseItem": { - "type": "object", - "properties": { - "audience": { - "type": "string" - }, - "id": { - "type": "string" - } - } - }, "TokenBlackListed": { "type": "object", "properties": { @@ -193,20 +191,63 @@ } } }, - "pbGetBlacklistedTokensResponse": { + "pbCreateTokenRequest": { "type": "object", "properties": { - "blackList": { + "clientId": { + "type": "string" + }, + "clientSecret": { + "type": "string" + }, + "audience": { "type": "array", "items": { - "type": "object", - "$ref": "#/definitions/GetBlacklistedTokensResponseItem" + "type": "string" } }, - "timestamp": { + "scope": { + "type": "array", + "items": { + "type": "string" + } + }, + "timeToLive": { "type": "string", - "format": "int64", - "title": "The biggest version of the blacklisted/revoked token over all tokens of owner" + "format": "int64" + }, + "clientAssertionType": { + "type": "string" + }, + "clientAssertion": { + "type": "string" + }, + "tokenName": { + "type": "string" + }, + "grantType": { + "type": "string" + } + } + }, + "pbCreateTokenResponse": { + "type": "object", + "properties": { + "accessToken": { + "type": "string" + }, + "tokenType": { + "type": "string" + }, + "expiresIn": { + "type": "string", + "format": "int64" + }, + "scope": { + "type": "array", + "items": { + "type": "string" + } } } }, @@ -221,7 +262,7 @@ "version": { "type": "string", "format": "uint64", - "title": "Incremetal version for update" + "title": "Incremental version for update" }, "name": { "type": "string", @@ -231,7 +272,7 @@ "type": "string", "title": "Owner of the token" }, - "timestamp": { + "issuedAt": { "type": "string", "format": "int64", "title": "Unix timestamp in ns when the condition has been created/updated" @@ -243,7 +284,7 @@ }, "title": "Token Audience" }, - "scopes": { + "scope": { "type": "array", "items": { "type": "string" diff --git a/m2m-oauth-server/pb/service_grpc.pb.go b/m2m-oauth-server/pb/service_grpc.pb.go index 8a85f1a87..900be79a9 100644 --- a/m2m-oauth-server/pb/service_grpc.pb.go +++ b/m2m-oauth-server/pb/service_grpc.pb.go @@ -19,171 +19,199 @@ import ( const _ = grpc.SupportPackageIsVersion7 const ( - M2MOAuthServer_GetTokens_FullMethodName = "/m2moauthserver.pb.M2MOAuthServer/GetTokens" - M2MOAuthServer_GetBlacklistedTokens_FullMethodName = "/m2moauthserver.pb.M2MOAuthServer/GetBlacklistedTokens" - M2MOAuthServer_BlacklistTokens_FullMethodName = "/m2moauthserver.pb.M2MOAuthServer/BlacklistTokens" + M2MOAuthService_CreateToken_FullMethodName = "/m2moauthserver.pb.M2MOAuthService/CreateToken" + M2MOAuthService_GetTokens_FullMethodName = "/m2moauthserver.pb.M2MOAuthService/GetTokens" + M2MOAuthService_BlacklistTokens_FullMethodName = "/m2moauthserver.pb.M2MOAuthService/BlacklistTokens" ) -// M2MOAuthServerClient is the client API for M2MOAuthServer service. +// M2MOAuthServiceClient is the client API for M2MOAuthService service. // // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. -type M2MOAuthServerClient interface { +type M2MOAuthServiceClient interface { + // Creates a new token + CreateToken(ctx context.Context, in *CreateTokenRequest, opts ...grpc.CallOption) (*CreateTokenResponse, error) // Returns all tokens of the owner - GetTokens(ctx context.Context, in *GetTokensRequest, opts ...grpc.CallOption) (*Token, error) - // Returns all blacklisted/revoked not expired tokens - GetBlacklistedTokens(ctx context.Context, in *GetBlacklistedTokensRequest, opts ...grpc.CallOption) (*GetBlacklistedTokensResponse, error) + GetTokens(ctx context.Context, in *GetTokensRequest, opts ...grpc.CallOption) (M2MOAuthService_GetTokensClient, error) // Blacklists/revokes tokens BlacklistTokens(ctx context.Context, in *BlacklistTokensRequest, opts ...grpc.CallOption) (*BlacklistTokensResponse, error) } -type m2MOAuthServerClient struct { +type m2MOAuthServiceClient struct { cc grpc.ClientConnInterface } -func NewM2MOAuthServerClient(cc grpc.ClientConnInterface) M2MOAuthServerClient { - return &m2MOAuthServerClient{cc} +func NewM2MOAuthServiceClient(cc grpc.ClientConnInterface) M2MOAuthServiceClient { + return &m2MOAuthServiceClient{cc} } -func (c *m2MOAuthServerClient) GetTokens(ctx context.Context, in *GetTokensRequest, opts ...grpc.CallOption) (*Token, error) { - out := new(Token) - err := c.cc.Invoke(ctx, M2MOAuthServer_GetTokens_FullMethodName, in, out, opts...) +func (c *m2MOAuthServiceClient) CreateToken(ctx context.Context, in *CreateTokenRequest, opts ...grpc.CallOption) (*CreateTokenResponse, error) { + out := new(CreateTokenResponse) + err := c.cc.Invoke(ctx, M2MOAuthService_CreateToken_FullMethodName, in, out, opts...) if err != nil { return nil, err } return out, nil } -func (c *m2MOAuthServerClient) GetBlacklistedTokens(ctx context.Context, in *GetBlacklistedTokensRequest, opts ...grpc.CallOption) (*GetBlacklistedTokensResponse, error) { - out := new(GetBlacklistedTokensResponse) - err := c.cc.Invoke(ctx, M2MOAuthServer_GetBlacklistedTokens_FullMethodName, in, out, opts...) +func (c *m2MOAuthServiceClient) GetTokens(ctx context.Context, in *GetTokensRequest, opts ...grpc.CallOption) (M2MOAuthService_GetTokensClient, error) { + stream, err := c.cc.NewStream(ctx, &M2MOAuthService_ServiceDesc.Streams[0], M2MOAuthService_GetTokens_FullMethodName, opts...) if err != nil { return nil, err } - return out, nil + x := &m2MOAuthServiceGetTokensClient{stream} + if err := x.ClientStream.SendMsg(in); err != nil { + return nil, err + } + if err := x.ClientStream.CloseSend(); err != nil { + return nil, err + } + return x, nil +} + +type M2MOAuthService_GetTokensClient interface { + Recv() (*Token, error) + grpc.ClientStream +} + +type m2MOAuthServiceGetTokensClient struct { + grpc.ClientStream +} + +func (x *m2MOAuthServiceGetTokensClient) Recv() (*Token, error) { + m := new(Token) + if err := x.ClientStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil } -func (c *m2MOAuthServerClient) BlacklistTokens(ctx context.Context, in *BlacklistTokensRequest, opts ...grpc.CallOption) (*BlacklistTokensResponse, error) { +func (c *m2MOAuthServiceClient) BlacklistTokens(ctx context.Context, in *BlacklistTokensRequest, opts ...grpc.CallOption) (*BlacklistTokensResponse, error) { out := new(BlacklistTokensResponse) - err := c.cc.Invoke(ctx, M2MOAuthServer_BlacklistTokens_FullMethodName, in, out, opts...) + err := c.cc.Invoke(ctx, M2MOAuthService_BlacklistTokens_FullMethodName, in, out, opts...) if err != nil { return nil, err } return out, nil } -// M2MOAuthServerServer is the server API for M2MOAuthServer service. -// All implementations must embed UnimplementedM2MOAuthServerServer +// M2MOAuthServiceServer is the server API for M2MOAuthService service. +// All implementations must embed UnimplementedM2MOAuthServiceServer // for forward compatibility -type M2MOAuthServerServer interface { +type M2MOAuthServiceServer interface { + // Creates a new token + CreateToken(context.Context, *CreateTokenRequest) (*CreateTokenResponse, error) // Returns all tokens of the owner - GetTokens(context.Context, *GetTokensRequest) (*Token, error) - // Returns all blacklisted/revoked not expired tokens - GetBlacklistedTokens(context.Context, *GetBlacklistedTokensRequest) (*GetBlacklistedTokensResponse, error) + GetTokens(*GetTokensRequest, M2MOAuthService_GetTokensServer) error // Blacklists/revokes tokens BlacklistTokens(context.Context, *BlacklistTokensRequest) (*BlacklistTokensResponse, error) - mustEmbedUnimplementedM2MOAuthServerServer() + mustEmbedUnimplementedM2MOAuthServiceServer() } -// UnimplementedM2MOAuthServerServer must be embedded to have forward compatible implementations. -type UnimplementedM2MOAuthServerServer struct { +// UnimplementedM2MOAuthServiceServer must be embedded to have forward compatible implementations. +type UnimplementedM2MOAuthServiceServer struct { } -func (UnimplementedM2MOAuthServerServer) GetTokens(context.Context, *GetTokensRequest) (*Token, error) { - return nil, status.Errorf(codes.Unimplemented, "method GetTokens not implemented") +func (UnimplementedM2MOAuthServiceServer) CreateToken(context.Context, *CreateTokenRequest) (*CreateTokenResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method CreateToken not implemented") } -func (UnimplementedM2MOAuthServerServer) GetBlacklistedTokens(context.Context, *GetBlacklistedTokensRequest) (*GetBlacklistedTokensResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method GetBlacklistedTokens not implemented") +func (UnimplementedM2MOAuthServiceServer) GetTokens(*GetTokensRequest, M2MOAuthService_GetTokensServer) error { + return status.Errorf(codes.Unimplemented, "method GetTokens not implemented") } -func (UnimplementedM2MOAuthServerServer) BlacklistTokens(context.Context, *BlacklistTokensRequest) (*BlacklistTokensResponse, error) { +func (UnimplementedM2MOAuthServiceServer) BlacklistTokens(context.Context, *BlacklistTokensRequest) (*BlacklistTokensResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method BlacklistTokens not implemented") } -func (UnimplementedM2MOAuthServerServer) mustEmbedUnimplementedM2MOAuthServerServer() {} +func (UnimplementedM2MOAuthServiceServer) mustEmbedUnimplementedM2MOAuthServiceServer() {} -// UnsafeM2MOAuthServerServer may be embedded to opt out of forward compatibility for this service. -// Use of this interface is not recommended, as added methods to M2MOAuthServerServer will +// UnsafeM2MOAuthServiceServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to M2MOAuthServiceServer will // result in compilation errors. -type UnsafeM2MOAuthServerServer interface { - mustEmbedUnimplementedM2MOAuthServerServer() +type UnsafeM2MOAuthServiceServer interface { + mustEmbedUnimplementedM2MOAuthServiceServer() } -func RegisterM2MOAuthServerServer(s grpc.ServiceRegistrar, srv M2MOAuthServerServer) { - s.RegisterService(&M2MOAuthServer_ServiceDesc, srv) +func RegisterM2MOAuthServiceServer(s grpc.ServiceRegistrar, srv M2MOAuthServiceServer) { + s.RegisterService(&M2MOAuthService_ServiceDesc, srv) } -func _M2MOAuthServer_GetTokens_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(GetTokensRequest) +func _M2MOAuthService_CreateToken_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(CreateTokenRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { - return srv.(M2MOAuthServerServer).GetTokens(ctx, in) + return srv.(M2MOAuthServiceServer).CreateToken(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: M2MOAuthServer_GetTokens_FullMethodName, + FullMethod: M2MOAuthService_CreateToken_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(M2MOAuthServerServer).GetTokens(ctx, req.(*GetTokensRequest)) + return srv.(M2MOAuthServiceServer).CreateToken(ctx, req.(*CreateTokenRequest)) } return interceptor(ctx, in, info, handler) } -func _M2MOAuthServer_GetBlacklistedTokens_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(GetBlacklistedTokensRequest) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(M2MOAuthServerServer).GetBlacklistedTokens(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: M2MOAuthServer_GetBlacklistedTokens_FullMethodName, +func _M2MOAuthService_GetTokens_Handler(srv interface{}, stream grpc.ServerStream) error { + m := new(GetTokensRequest) + if err := stream.RecvMsg(m); err != nil { + return err } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(M2MOAuthServerServer).GetBlacklistedTokens(ctx, req.(*GetBlacklistedTokensRequest)) - } - return interceptor(ctx, in, info, handler) + return srv.(M2MOAuthServiceServer).GetTokens(m, &m2MOAuthServiceGetTokensServer{stream}) +} + +type M2MOAuthService_GetTokensServer interface { + Send(*Token) error + grpc.ServerStream +} + +type m2MOAuthServiceGetTokensServer struct { + grpc.ServerStream +} + +func (x *m2MOAuthServiceGetTokensServer) Send(m *Token) error { + return x.ServerStream.SendMsg(m) } -func _M2MOAuthServer_BlacklistTokens_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { +func _M2MOAuthService_BlacklistTokens_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(BlacklistTokensRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { - return srv.(M2MOAuthServerServer).BlacklistTokens(ctx, in) + return srv.(M2MOAuthServiceServer).BlacklistTokens(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: M2MOAuthServer_BlacklistTokens_FullMethodName, + FullMethod: M2MOAuthService_BlacklistTokens_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(M2MOAuthServerServer).BlacklistTokens(ctx, req.(*BlacklistTokensRequest)) + return srv.(M2MOAuthServiceServer).BlacklistTokens(ctx, req.(*BlacklistTokensRequest)) } return interceptor(ctx, in, info, handler) } -// M2MOAuthServer_ServiceDesc is the grpc.ServiceDesc for M2MOAuthServer service. +// M2MOAuthService_ServiceDesc is the grpc.ServiceDesc for M2MOAuthService service. // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) -var M2MOAuthServer_ServiceDesc = grpc.ServiceDesc{ - ServiceName: "m2moauthserver.pb.M2MOAuthServer", - HandlerType: (*M2MOAuthServerServer)(nil), +var M2MOAuthService_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "m2moauthserver.pb.M2MOAuthService", + HandlerType: (*M2MOAuthServiceServer)(nil), Methods: []grpc.MethodDesc{ { - MethodName: "GetTokens", - Handler: _M2MOAuthServer_GetTokens_Handler, + MethodName: "CreateToken", + Handler: _M2MOAuthService_CreateToken_Handler, }, { - MethodName: "GetBlacklistedTokens", - Handler: _M2MOAuthServer_GetBlacklistedTokens_Handler, + MethodName: "BlacklistTokens", + Handler: _M2MOAuthService_BlacklistTokens_Handler, }, + }, + Streams: []grpc.StreamDesc{ { - MethodName: "BlacklistTokens", - Handler: _M2MOAuthServer_BlacklistTokens_Handler, + StreamName: "GetTokens", + Handler: _M2MOAuthService_GetTokens_Handler, + ServerStreams: true, }, }, - Streams: []grpc.StreamDesc{}, Metadata: "m2m-oauth-server/pb/service.proto", } diff --git a/m2m-oauth-server/pb/token.go b/m2m-oauth-server/pb/token.go index f4ba31187..480e2a06a 100644 --- a/m2m-oauth-server/pb/token.go +++ b/m2m-oauth-server/pb/token.go @@ -23,7 +23,7 @@ func (x *Token) Validate() error { if x.GetClientId() == "" { return errors.New("Token.ClientId is empty") } - if x.GetTimestamp() == 0 { + if x.GetIssuedAt() == 0 { return errors.New("Token.Timestamp is empty") } return nil diff --git a/m2m-oauth-server/service/config.go b/m2m-oauth-server/service/config.go index 24ec28cf6..28a6e16cc 100644 --- a/m2m-oauth-server/service/config.go +++ b/m2m-oauth-server/service/config.go @@ -2,103 +2,17 @@ package service import ( "fmt" - "time" + "net" + oauthsigner "github.com/plgd-dev/hub/v2/m2m-oauth-server/oauthSigner" + grpcService "github.com/plgd-dev/hub/v2/m2m-oauth-server/service/grpc" storeConfig "github.com/plgd-dev/hub/v2/m2m-oauth-server/store/config" "github.com/plgd-dev/hub/v2/pkg/config" - "github.com/plgd-dev/hub/v2/pkg/config/property/urischeme" "github.com/plgd-dev/hub/v2/pkg/log" "github.com/plgd-dev/hub/v2/pkg/net/http" "github.com/plgd-dev/hub/v2/pkg/net/http/server" - "github.com/plgd-dev/hub/v2/pkg/net/listener" - "github.com/plgd-dev/hub/v2/pkg/security/jwt/validator" ) -type AsymmetricKey struct { - PrivateFile string - PublicFile string -} - -type AccessTokenType string - -const AccessTokenType_JWT AccessTokenType = "jwt" - -type GrantType string - -const ( - GrantTypeClientCredentials GrantType = "client_credentials" -) - -type PrivateKeyJWTConfig struct { - Enabled bool `yaml:"enabled"` - Authorization validator.Config `yaml:"authorization,omitempty"` -} - -func (c *PrivateKeyJWTConfig) Validate() error { - if !c.Enabled { - return nil - } - if err := c.Authorization.Validate(); err != nil { - return fmt.Errorf("authorization.%w", err) - } - return nil -} - -type Client struct { - ID string `yaml:"id"` - SecretFile urischeme.URIScheme `yaml:"secretFile"` - AccessTokenLifetime time.Duration `yaml:"accessTokenLifetime"` - AllowedGrantTypes []GrantType `yaml:"allowedGrantTypes"` - AllowedAudiences []string `yaml:"allowedAudiences"` - AllowedScopes []string `yaml:"allowedScopes"` - JWTPrivateKey PrivateKeyJWTConfig `yaml:"jwtPrivateKey"` - InsertTokenClaims map[string]interface{} `yaml:"insertTokenClaims"` - - // runtime - secret string `yaml:"-"` -} - -func (c *Client) Validate() error { - if c.ID == "" { - return fmt.Errorf("id('%v')", c.ID) - } - if !c.JWTPrivateKey.Enabled { - if c.SecretFile == "" { - return fmt.Errorf("secretFile('%v') - one of [secretFile, privateKeyJWT] need to be set", c.SecretFile) - } - data, err := c.SecretFile.Read() - if err != nil { - return fmt.Errorf("secretFile('%v') - %w", c.SecretFile, err) - } - c.secret = string(data) - } - if len(c.AllowedGrantTypes) == 0 { - return fmt.Errorf("allowedGrantTypes('%v') - is empty", c.AllowedGrantTypes) - } - for _, gt := range c.AllowedGrantTypes { - switch gt { - case GrantTypeClientCredentials: - default: - return fmt.Errorf("allowedGrantTypes('%v') - only %v is supported", c.AllowedGrantTypes, GrantTypeClientCredentials) - } - } - if err := c.JWTPrivateKey.Validate(); err != nil { - return fmt.Errorf("privateKeyJWT.%w", err) - } - return nil -} - -type OAuthClientsConfig []*Client - -func (c OAuthClientsConfig) Find(id string) *Client { - for _, client := range c { - if client.ID == id { - return client - } - } - return nil -} - type ClientsConfig struct { Storage storeConfig.Config `yaml:"storage" json:"storage"` OpenTelemetryCollector http.OpenTelemetryCollectorConfig `yaml:"openTelemetryCollector" json:"openTelemetryCollector"` @@ -116,10 +30,14 @@ func (c *ClientsConfig) Validate() error { // Config represents application configuration type Config struct { - Log log.Config `yaml:"log" json:"log"` - APIs APIsConfig `yaml:"apis" json:"apis"` - Clients ClientsConfig `yaml:"clients" json:"clients"` - OAuthSigner OAuthSignerConfig `yaml:"oauthSigner" json:"oauthSigner"` + Log log.Config `yaml:"log" json:"log"` + APIs APIsConfig `yaml:"apis" json:"apis"` + Clients ClientsConfig `yaml:"clients" json:"clients"` + OAuthSigner oauthsigner.Config `yaml:"oauthSigner" json:"oauthSigner"` +} + +func (c *Config) String() string { + return config.ToString(c) } func (c *Config) Validate() error { @@ -140,51 +58,28 @@ func (c *Config) Validate() error { // Config represent application configuration type APIsConfig struct { - HTTP HTTPConfig `yaml:"http" json:"http"` + HTTP HTTPConfig `yaml:"http" json:"http"` + GRPC grpcService.Config `yaml:"grpc" json:"grpc"` } func (c *APIsConfig) Validate() error { if err := c.HTTP.Validate(); err != nil { return fmt.Errorf("http.%w", err) } + if err := c.GRPC.Validate(); err != nil { + return fmt.Errorf("grpc.%w", err) + } return nil } type HTTPConfig struct { - Connection listener.Config `yaml:",inline" json:",inline"` - Server server.Config `yaml:",inline" json:",inline"` + Addr string `yaml:"address" json:"address"` + Server server.Config `yaml:",inline" json:",inline"` } func (c *HTTPConfig) Validate() error { - return c.Connection.Validate() -} - -type OAuthSignerConfig struct { - PrivateKeyFile urischeme.URIScheme `yaml:"privateKeyFile" json:"privateKeyFile"` - Domain string `yaml:"domain" json:"domain"` - OwnerClaim string `yaml:"ownerClaim" json:"ownerClaim"` - DeviceIDClaim string `yaml:"deviceIDClaim" json:"deviceIDClaim"` - Clients OAuthClientsConfig `yaml:"clients" json:"clients"` -} - -func (c *OAuthSignerConfig) Validate() error { - if c.PrivateKeyFile == "" { - return fmt.Errorf("privateKeyFile('%v')", c.PrivateKeyFile) - } - if c.Domain == "" { - return fmt.Errorf("domain('%v')", c.Domain) - } - if len(c.Clients) == 0 { - return fmt.Errorf("clients('%v')", c.Clients) - } - for idx, client := range c.Clients { - if err := client.Validate(); err != nil { - return fmt.Errorf("clients[%v].%w", idx, err) - } + if _, err := net.ResolveTCPAddr("tcp", c.Addr); err != nil { + return fmt.Errorf("address('%v') - %w", c.Addr, err) } return nil } - -func (c Config) String() string { - return config.ToString(c) -} diff --git a/m2m-oauth-server/service/expiredUpdatesChecker.go b/m2m-oauth-server/service/expiredUpdatesChecker.go new file mode 100644 index 000000000..779587676 --- /dev/null +++ b/m2m-oauth-server/service/expiredUpdatesChecker.go @@ -0,0 +1,21 @@ +package service + +import ( + "fmt" + "time" + + "github.com/go-co-op/gocron/v2" +) + +func NewExpiredUpdatesChecker(cleanUpExpiredUpdates string, withSeconds bool, onCheckTimeout func()) (gocron.Scheduler, error) { + s, err := gocron.NewScheduler(gocron.WithLocation(time.Local)) //nolint:gosmopolitan + if err != nil { + return nil, fmt.Errorf("cannot create cron job: %w", err) + } + _, err = s.NewJob(gocron.CronJob(cleanUpExpiredUpdates, withSeconds), gocron.NewTask(onCheckTimeout)) + if err != nil { + return nil, fmt.Errorf("cannot create cron job: %w", err) + } + s.Start() + return s, nil +} diff --git a/m2m-oauth-server/service/grpc/config.go b/m2m-oauth-server/service/grpc/config.go new file mode 100644 index 000000000..655e8170e --- /dev/null +++ b/m2m-oauth-server/service/grpc/config.go @@ -0,0 +1,7 @@ +package grpc + +import ( + "github.com/plgd-dev/hub/v2/pkg/net/grpc/server" +) + +type Config = server.Config diff --git a/m2m-oauth-server/service/grpc/server.go b/m2m-oauth-server/service/grpc/server.go new file mode 100644 index 000000000..e4aa6454a --- /dev/null +++ b/m2m-oauth-server/service/grpc/server.go @@ -0,0 +1,164 @@ +package grpc + +import ( + "context" + "errors" + "fmt" + "strings" + "time" + + "github.com/google/uuid" + "github.com/lestrrat-go/jwx/v2/jwk" + oauthsigner "github.com/plgd-dev/hub/v2/m2m-oauth-server/oauthSigner" + "github.com/plgd-dev/hub/v2/m2m-oauth-server/pb" + "github.com/plgd-dev/hub/v2/m2m-oauth-server/store" + "github.com/plgd-dev/hub/v2/pkg/log" + pkgGrpc "github.com/plgd-dev/hub/v2/pkg/net/grpc" + pkgTime "github.com/plgd-dev/hub/v2/pkg/time" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + "google.golang.org/protobuf/types/known/structpb" +) + +// M2MOAuthServiceServer handles incoming requests. +type M2MOAuthServiceServer struct { + pb.UnimplementedM2MOAuthServiceServer + + signer *oauthsigner.OAuthSigner + store store.Store + logger log.Logger +} + +func NewM2MOAuthServerServer(store store.Store, signer *oauthsigner.OAuthSigner, logger log.Logger) *M2MOAuthServiceServer { + return &M2MOAuthServiceServer{ + store: store, + logger: logger, + signer: signer, + } +} + +func (s *M2MOAuthServiceServer) getOwner(ctx context.Context) (string, error) { + ownerFromToken, err := pkgGrpc.OwnerFromTokenMD(ctx, s.signer.Config.OwnerClaim) + if err != nil { + return "", err + } + return ownerFromToken, nil +} + +func getGRPCErrorCode(err error) codes.Code { + if errors.Is(err, store.ErrInvalidArgument) { + return codes.InvalidArgument + } + return codes.Internal +} + +func errCannotCreateConfiguration(err error) error { + return fmt.Errorf("cannot get configuration: %w", err) +} + +func errCannotCreateToken(err error) error { + return fmt.Errorf("cannot create token: %w", err) +} + +func (s *M2MOAuthServiceServer) CreateToken(ctx context.Context, req *pb.CreateTokenRequest) (*pb.CreateTokenResponse, error) { + tokenReq := tokenRequest{ + host: s.signer.Config.GetDomain(), + tokenType: oauthsigner.AccessTokenType_JWT, + issuedAt: time.Now(), + CreateTokenRequest: req, + } + clientCfg := s.signer.Config.Clients.Find(tokenReq.CreateTokenRequest.GetClientId()) + if clientCfg == nil { + return nil, status.Errorf(codes.Unauthenticated, "%v", errCannotCreateToken(fmt.Errorf("client(%v) not found", tokenReq.CreateTokenRequest.GetClientId()))) + } + tokenReq.owner = clientCfg.Owner + if err := s.validateTokenRequest(ctx, clientCfg, &tokenReq); err != nil { + return nil, status.Errorf(codes.Unauthenticated, "%v", errCannotCreateToken(fmt.Errorf("invalid request: %w", err))) + } + var originalTokenClaims *structpb.Value + if len(tokenReq.originalTokenClaims) > 0 { + var err error + originalTokenClaims, err = structpb.NewValue(map[string]interface{}(tokenReq.originalTokenClaims)) + if err != nil { + return nil, status.Errorf(codes.InvalidArgument, "%v", errCannotCreateToken(fmt.Errorf("cannot convert original token claims: %w", err))) + } + } + tokenReq.scopes = strings.Join(req.GetScope(), " ") + tokenReq.deviceIDClaim = s.signer.Config.DeviceIDClaim + tokenReq.ownerClaim = s.signer.Config.OwnerClaim + tokenReq.id = uuid.NewString() + tokenReq.expiration = getExpirationTime(clientCfg, tokenReq) + accessToken, err := s.generateAccessToken( + clientCfg, + tokenReq) + if err != nil { + return nil, status.Errorf(codes.InvalidArgument, "%v", errCannotCreateToken(err)) + } + + token, err := s.store.CreateToken(ctx, tokenReq.owner, &pb.Token{ + Id: tokenReq.id, + Name: tokenReq.CreateTokenRequest.GetTokenName(), + Owner: tokenReq.owner, + IssuedAt: tokenReq.issuedAt.UnixNano(), + Audience: tokenReq.CreateTokenRequest.GetAudience(), + Scope: tokenReq.CreateTokenRequest.GetScope(), + Expiration: pkgTime.UnixNano(tokenReq.expiration), + ClientId: tokenReq.CreateTokenRequest.GetClientId(), + OriginalTokenClaims: originalTokenClaims, + }) + if err != nil { + return nil, status.Errorf(getGRPCErrorCode(err), "%v", errCannotCreateConfiguration(err)) + } + var expiresIn int64 + if !tokenReq.expiration.IsZero() { + expiresIn = int64(time.Until(tokenReq.expiration).Seconds()) + } + return &pb.CreateTokenResponse{ + AccessToken: accessToken, + TokenType: "Bearer", + ExpiresIn: expiresIn, + Scope: token.GetScope(), + }, nil +} + +func errCannotGetTokens(err error) error { + return fmt.Errorf("cannot get tokens: %w", err) +} + +func (s *M2MOAuthServiceServer) GetTokens(req *pb.GetTokensRequest, srv pb.M2MOAuthService_GetTokensServer) error { + owner, err := s.getOwner(srv.Context()) + if err != nil { + return err + } + err = s.store.GetTokens(srv.Context(), owner, req, func(v *pb.Token) error { + return srv.Send(v) + }) + if err != nil { + return status.Errorf(getGRPCErrorCode(err), "%v", errCannotGetTokens(err)) + } + return nil +} + +func errCannotBlacklistTokens(err error) error { + return fmt.Errorf("cannot blacklist tokens: %w", err) +} + +func (s *M2MOAuthServiceServer) BlacklistTokens(ctx context.Context, req *pb.BlacklistTokensRequest) (*pb.BlacklistTokensResponse, error) { + owner, err := s.getOwner(ctx) + if err != nil { + return nil, err + } + resp, err := s.store.BlacklistTokens(ctx, owner, req) + if err != nil { + return nil, status.Errorf(getGRPCErrorCode(err), "%v", errCannotBlacklistTokens(err)) + } + return resp, nil +} + +func (s *M2MOAuthServiceServer) GetJWK() jwk.Key { + return s.signer.GetJWK() +} + +func (s *M2MOAuthServiceServer) GetDomain() string { + return s.signer.Config.GetDomain() +} diff --git a/m2m-oauth-server/service/grpc/service.go b/m2m-oauth-server/service/grpc/service.go new file mode 100644 index 000000000..c6c5f3b06 --- /dev/null +++ b/m2m-oauth-server/service/grpc/service.go @@ -0,0 +1,35 @@ +package grpc + +import ( + "fmt" + + "github.com/plgd-dev/hub/v2/m2m-oauth-server/pb" + "github.com/plgd-dev/hub/v2/pkg/fsnotify" + "github.com/plgd-dev/hub/v2/pkg/log" + "github.com/plgd-dev/hub/v2/pkg/net/grpc/server" + "github.com/plgd-dev/hub/v2/pkg/security/jwt/validator" + "go.opentelemetry.io/otel/trace" +) + +type Service struct { + *server.Server +} + +func New(config Config, m2mOAuthServiceServer *M2MOAuthServiceServer, validator *validator.Validator, fileWatcher *fsnotify.Watcher, logger log.Logger, tracerProvider trace.TracerProvider) (*Service, error) { + opts, err := server.MakeDefaultOptions(server.NewAuth(validator, server.WithWhiteListedMethods(pb.M2MOAuthService_CreateToken_FullMethodName)), logger, tracerProvider) + if err != nil { + return nil, fmt.Errorf("cannot create grpc server options: %w", err) + } + server, err := server.New(config, fileWatcher, logger, opts...) + if err != nil { + return nil, err + } + pb.RegisterM2MOAuthServiceServer(server.Server, m2mOAuthServiceServer) + + // SnippetService needs to stop gracefully to ensure that all commands are processed. + server.SetGracefulStop(true) + + return &Service{ + Server: server, + }, nil +} diff --git a/m2m-oauth-server/service/grpc/token.go b/m2m-oauth-server/service/grpc/token.go new file mode 100644 index 000000000..c30ebd745 --- /dev/null +++ b/m2m-oauth-server/service/grpc/token.go @@ -0,0 +1,289 @@ +package grpc + +import ( + "context" + "errors" + "fmt" + "time" + + goJwt "github.com/golang-jwt/jwt/v5" + "github.com/lestrrat-go/jwx/v2/jwk" + "github.com/lestrrat-go/jwx/v2/jws" + "github.com/lestrrat-go/jwx/v2/jwt" + oauthsigner "github.com/plgd-dev/hub/v2/m2m-oauth-server/oauthSigner" + "github.com/plgd-dev/hub/v2/m2m-oauth-server/pb" + "github.com/plgd-dev/hub/v2/m2m-oauth-server/uri" + pkgJwt "github.com/plgd-dev/hub/v2/pkg/security/jwt" +) + +func setKeyError(key string, err error) error { + return fmt.Errorf("failed to set %v: %w", key, err) +} + +func setKeyErrorExt(key, info interface{}, err error) error { + return fmt.Errorf("failed to set %v('%v'): %w", key, info, err) +} + +func makeAccessToken(clientCfg *oauthsigner.Client, tokenReq tokenRequest) (jwt.Token, error) { + token := jwt.New() + + if err := token.Set(jwt.JwtIDKey, tokenReq.id); err != nil { + return nil, setKeyError(jwt.JwtIDKey, err) + } + sub := getSubject(clientCfg, tokenReq) + if err := token.Set(jwt.SubjectKey, sub); err != nil { + return nil, setKeyError(jwt.SubjectKey, err) + } + if err := token.Set(jwt.AudienceKey, tokenReq.host); err != nil { + return nil, setKeyError(jwt.AudienceKey, err) + } + if err := token.Set(jwt.IssuedAtKey, tokenReq.issuedAt); err != nil { + return nil, setKeyError(jwt.IssuedAtKey, err) + } + if !tokenReq.expiration.IsZero() { + if err := token.Set(jwt.ExpirationKey, tokenReq.expiration); err != nil { + return nil, setKeyError(jwt.ExpirationKey, err) + } + } + if err := token.Set(uri.ScopeKey, tokenReq.scopes); err != nil { + return nil, setKeyError(uri.ScopeKey, err) + } + if err := token.Set(uri.ClientIDKey, clientCfg.ID); err != nil { + return nil, setKeyError(uri.ClientIDKey, err) + } + if err := token.Set(jwt.IssuerKey, tokenReq.host); err != nil { + return nil, setKeyError(jwt.IssuerKey, err) + } + if err := setDeviceIDClaim(token, tokenReq); err != nil { + return nil, err + } + if err := setName(token, tokenReq); err != nil { + return nil, err + } + if err := setOwnerClaim(token, tokenReq); err != nil { + return nil, err + } + if err := setOriginTokenClaims(token, tokenReq); err != nil { + return nil, err + } + + for k, v := range clientCfg.InsertTokenClaims { + if _, ok := token.Get(k); ok { + continue + } + if err := token.Set(k, v); err != nil { + return nil, setKeyErrorExt(k, v, err) + } + } + + return token, nil +} + +func getSubject(clientCfg *oauthsigner.Client, tokenReq tokenRequest) string { + if tokenReq.subject != "" { + return tokenReq.subject + } + if tokenReq.owner != "" { + return tokenReq.owner + } + return clientCfg.ID +} + +func setDeviceIDClaim(token jwt.Token, tokenReq tokenRequest) error { + if tokenReq.deviceID != "" && tokenReq.deviceIDClaim != "" { + return token.Set(tokenReq.deviceIDClaim, tokenReq.deviceID) + } + return nil +} + +func setOwnerClaim(token jwt.Token, tokenReq tokenRequest) error { + if tokenReq.owner != "" && tokenReq.ownerClaim != "" { + return token.Set(tokenReq.ownerClaim, tokenReq.owner) + } + return nil +} + +func setName(token jwt.Token, tokenReq tokenRequest) error { + if tokenReq.CreateTokenRequest.GetTokenName() != "" && tokenReq.ownerClaim != "name" { + return token.Set("name", tokenReq.CreateTokenRequest.GetTokenName()) + } + return nil +} + +func setOriginTokenClaims(token jwt.Token, tokenReq tokenRequest) error { + if len(tokenReq.originalTokenClaims) > 0 { + return token.Set(uri.OriginalTokenClaims, tokenReq.originalTokenClaims) + } + return nil +} + +func makeJWTPayload(key interface{}, jwkKey jwk.Key, data []byte) ([]byte, error) { + hdr := jws.NewHeaders() + if err := hdr.Set(jws.TypeKey, `JWT`); err != nil { + return nil, setKeyError(jws.TypeKey, err) + } + if err := hdr.Set(jws.KeyIDKey, jwkKey.KeyID()); err != nil { + return nil, setKeyError(jws.KeyIDKey, err) + } + + payload, err := jws.Sign(data, jws.WithKey(jwkKey.Algorithm(), key, jws.WithProtectedHeaders(hdr))) + if err != nil { + return nil, fmt.Errorf("failed to create UserToken: %w", err) + } + return payload, nil +} + +func getExpirationTime(clientCfg *oauthsigner.Client, tokenReq tokenRequest) time.Time { + var expires time.Time + ttl := time.Duration(tokenReq.CreateTokenRequest.GetTimeToLive()) * time.Nanosecond + if ttl == 0 || (ttl > clientCfg.AccessTokenLifetime && clientCfg.AccessTokenLifetime > 0) { + ttl = clientCfg.AccessTokenLifetime + } + if ttl > 0 { + expires = tokenReq.issuedAt.Add(clientCfg.AccessTokenLifetime) + } + return expires +} + +func (s *M2MOAuthServiceServer) generateAccessToken(clientCfg *oauthsigner.Client, tokenReq tokenRequest) (string, error) { + token, err := makeAccessToken(clientCfg, tokenReq) + if err != nil { + return "", fmt.Errorf("failed to make token: %w", err) + } + payload, err := s.signer.Sign(token) + if err != nil { + return "", fmt.Errorf("failed to sign token: %w", err) + } + return string(payload), nil +} + +type tokenRequest struct { + *pb.CreateTokenRequest + + id string `json:"-"` + deviceID string `json:"-"` + owner string `json:"-"` + subject string `json:"-"` + host string `json:"-"` + scopes string `json:"-"` + ownerClaim string `json:"-"` + deviceIDClaim string `json:"-"` + tokenType oauthsigner.AccessTokenType `json:"-"` + originalTokenClaims goJwt.MapClaims `json:"-"` + issuedAt time.Time `json:"-"` + expiration time.Time `json:"-"` +} + +func sliceContains[T comparable](s []T, sub []T) bool { + // sub must be non-empty + if len(s) > 0 && len(sub) == 0 { + return false + } + check := make(map[T]struct{}, len(sub)) + for _, e := range sub { + check[e] = struct{}{} + } + for _, e := range s { + delete(check, e) + } + return len(check) == 0 +} + +func (s *M2MOAuthServiceServer) validateTokenRequest(ctx context.Context, clientCfg *oauthsigner.Client, tokenReq *tokenRequest) error { + if err := validateGrantType(clientCfg, tokenReq); err != nil { + return err + } + if err := validateClient(clientCfg, tokenReq); err != nil { + return err + } + if err := validateClientAssertionType(clientCfg, tokenReq); err != nil { + return err + } + if err := s.validateClientAssertion(ctx, tokenReq); err != nil { + return err + } + if err := validateAudience(clientCfg, tokenReq); err != nil { + return err + } + if err := validateScopes(clientCfg, tokenReq); err != nil { + return err + } + + return nil +} + +func validateClient(clientCfg *oauthsigner.Client, tokenReq *tokenRequest) error { + if clientCfg == nil { + return fmt.Errorf("client(%v) not found", tokenReq.CreateTokenRequest.GetClientId()) + } + if clientCfg.Secret != "" && !clientCfg.JWTPrivateKey.Enabled && clientCfg.Secret != tokenReq.CreateTokenRequest.GetClientSecret() { + return errors.New("invalid client secret") + } + return nil +} + +func validateGrantType(clientCfg *oauthsigner.Client, tokenReq *tokenRequest) error { + // clientCfg.AllowedGrantTypes is always non-empty + if !sliceContains(clientCfg.AllowedGrantTypes, []oauthsigner.GrantType{oauthsigner.GrantType(tokenReq.CreateTokenRequest.GetGrantType())}) { + return fmt.Errorf("invalid grant type(%v)", tokenReq.CreateTokenRequest.GetGrantType()) + } + return nil +} + +func validateAudience(clientCfg *oauthsigner.Client, tokenReq *tokenRequest) error { + if !sliceContains(clientCfg.AllowedAudiences, tokenReq.CreateTokenRequest.GetAudience()) { + return fmt.Errorf("invalid audience(%v)", tokenReq.CreateTokenRequest.GetAudience()) + } + return nil +} + +func validateScopes(clientCfg *oauthsigner.Client, tokenReq *tokenRequest) error { + if len(tokenReq.CreateTokenRequest.GetScope()) == 0 { + tokenReq.CreateTokenRequest.Scope = clientCfg.AllowedScopes + } + if !sliceContains(clientCfg.AllowedScopes, tokenReq.CreateTokenRequest.GetScope()) { + return fmt.Errorf("invalid scope(%v)", tokenReq.CreateTokenRequest.GetScope()) + } + return nil +} + +func validateClientAssertionType(clientCfg *oauthsigner.Client, tokenReq *tokenRequest) error { + if tokenReq.CreateTokenRequest.GetClientAssertionType() != "" && clientCfg.JWTPrivateKey.Enabled && tokenReq.CreateTokenRequest.GetClientAssertionType() != uri.ClientAssertionTypeJWT { + return fmt.Errorf("invalid client assertion type(%v)", tokenReq.CreateTokenRequest.GetClientAssertionType()) + } + return nil +} + +func (s *M2MOAuthServiceServer) validateClientAssertion(ctx context.Context, tokenReq *tokenRequest) error { + if tokenReq.CreateTokenRequest.GetClientAssertion() == "" { + return nil + } + v, ok := s.signer.GetValidator(tokenReq.CreateTokenRequest.GetClientId()) + if !ok { + return errors.New("invalid client assertion") + } + token, err := v.GetParser().ParseWithContext(ctx, tokenReq.CreateTokenRequest.GetClientAssertion()) + if err != nil { + return fmt.Errorf("invalid client assertion: %w", err) + } + tokenReq.originalTokenClaims = token + claims := pkgJwt.Claims(token) + owner, err := claims.GetOwner(s.signer.Config.OwnerClaim) + if err != nil { + return fmt.Errorf("invalid client assertion - claim owner: %w", err) + } + tokenReq.owner = owner + sub, err := claims.GetSubject() + if err != nil { + return fmt.Errorf("invalid client assertion - claim sub: %w", err) + } + tokenReq.subject = sub + if s.signer.Config.DeviceIDClaim == "" { + return nil + } + deviceID, err := claims.GetDeviceID(s.signer.Config.DeviceIDClaim) + if err == nil { + tokenReq.deviceID = deviceID + } + return nil +} diff --git a/m2m-oauth-server/service/http/config.go b/m2m-oauth-server/service/http/config.go new file mode 100644 index 000000000..5ae74d064 --- /dev/null +++ b/m2m-oauth-server/service/http/config.go @@ -0,0 +1,13 @@ +package http + +import ( + "github.com/plgd-dev/hub/v2/pkg/net/http/server" + "github.com/plgd-dev/hub/v2/pkg/net/listener" + "github.com/plgd-dev/hub/v2/pkg/security/jwt/validator" +) + +type Config struct { + Connection listener.Config `yaml:",inline" json:",inline"` + Authorization validator.Config `yaml:"authorization" json:"authorization"` + Server server.Config `yaml:",inline" json:",inline"` +} diff --git a/m2m-oauth-server/service/http/createToken_test.go b/m2m-oauth-server/service/http/createToken_test.go new file mode 100644 index 000000000..ddb9cb1c9 --- /dev/null +++ b/m2m-oauth-server/service/http/createToken_test.go @@ -0,0 +1,82 @@ +package http_test + +import ( + "bytes" + "context" + "net/http" + "testing" + + "github.com/plgd-dev/go-coap/v3/message" + grpcPb "github.com/plgd-dev/hub/v2/grpc-gateway/pb" + oauthsigner "github.com/plgd-dev/hub/v2/m2m-oauth-server/oauthSigner" + "github.com/plgd-dev/hub/v2/m2m-oauth-server/pb" + m2mOauthServerTest "github.com/plgd-dev/hub/v2/m2m-oauth-server/test" + "github.com/plgd-dev/hub/v2/m2m-oauth-server/uri" + "github.com/plgd-dev/hub/v2/test" + "github.com/plgd-dev/hub/v2/test/config" + testHttp "github.com/plgd-dev/hub/v2/test/http" + testService "github.com/plgd-dev/hub/v2/test/service" + "github.com/stretchr/testify/require" +) + +func TestCreateToken(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), config.TEST_TIMEOUT) + defer cancel() + tearDown := testService.SetUp(ctx, t) + defer tearDown() + + type args struct { + req *pb.CreateTokenRequest + } + tests := []struct { + name string + args args + wantHTTPCode int + want *pb.CreateTokenResponse + wantErr bool + }{ + { + name: "create token", + args: args{ + req: &pb.CreateTokenRequest{ + ClientId: m2mOauthServerTest.ServiceOAuthClient.ID, + ClientSecret: m2mOauthServerTest.GetSecret(t, m2mOauthServerTest.ServiceOAuthClient.ID), + GrantType: string(oauthsigner.GrantTypeClientCredentials), + TokenName: "service token", + }, + }, + wantHTTPCode: http.StatusOK, + want: &pb.CreateTokenResponse{ + TokenType: "Bearer", + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + data, err := testHttp.GetContentData(&grpcPb.Content{ + ContentType: message.AppOcfCbor.String(), + Data: test.EncodeToCbor(t, tt.args.req), + }, message.AppJSON.String()) + require.NoError(t, err) + rb := testHttp.NewRequest(http.MethodPost, m2mOauthServerTest.HTTPURI(uri.Tokens), bytes.NewReader(data)) + rb = rb.ContentType(message.AppOcfCbor.String()) + resp := testHttp.Do(t, rb.Build(ctx, t)) + defer func() { + _ = resp.Body.Close() + }() + require.Equal(t, tt.wantHTTPCode, resp.StatusCode) + + var got pb.CreateTokenResponse + err = testHttp.Unmarshal(resp.StatusCode, resp.Body, &got) + if tt.wantErr { + require.Error(t, err) + return + } + require.NoError(t, err) + require.NotEmpty(t, got.GetAccessToken()) + require.Equal(t, tt.want.GetTokenType(), got.GetTokenType()) + }) + } + +} diff --git a/m2m-oauth-server/service/getJWKs.go b/m2m-oauth-server/service/http/getJWKs.go similarity index 85% rename from m2m-oauth-server/service/getJWKs.go rename to m2m-oauth-server/service/http/getJWKs.go index 420766868..461f6ac85 100644 --- a/m2m-oauth-server/service/getJWKs.go +++ b/m2m-oauth-server/service/http/getJWKs.go @@ -1,4 +1,4 @@ -package service +package http import ( "net/http" @@ -10,7 +10,7 @@ import ( func (requestHandler *RequestHandler) getJWKs(w http.ResponseWriter, _ *http.Request) { resp := map[string]interface{}{ "keys": []jwk.Key{ - requestHandler.accessTokenJwkKey, + requestHandler.m2mOAuthServiceServer.GetJWK(), }, } diff --git a/m2m-oauth-server/service/getJWKs_test.go b/m2m-oauth-server/service/http/getJWKs_test.go similarity index 62% rename from m2m-oauth-server/service/getJWKs_test.go rename to m2m-oauth-server/service/http/getJWKs_test.go index b5ef793ff..1ee23a3da 100644 --- a/m2m-oauth-server/service/getJWKs_test.go +++ b/m2m-oauth-server/service/http/getJWKs_test.go @@ -1,31 +1,29 @@ -package service_test +package http_test import ( "context" "net/http" "testing" - m2mOauthServerTest "github.com/plgd-dev/hub/v2/m2m-oauth-server/test" "github.com/plgd-dev/hub/v2/m2m-oauth-server/uri" "github.com/plgd-dev/hub/v2/test/config" testHttp "github.com/plgd-dev/hub/v2/test/http" - "github.com/plgd-dev/hub/v2/test/oauth-server/test" + testService "github.com/plgd-dev/hub/v2/test/service" "github.com/plgd-dev/kit/v2/codec/json" "github.com/stretchr/testify/require" ) func TestRequestHandlerGetJWKs(t *testing.T) { - oauthServerTeardown := test.SetUp(t) - defer oauthServerTeardown() - - webTearDown := m2mOauthServerTest.SetUp(t) + ctx, cancel := context.WithTimeout(context.Background(), config.TEST_TIMEOUT) + defer cancel() + webTearDown := testService.SetUp(ctx, t) defer webTearDown() - getJWKs(t) + getJWKs(ctx, t) } -func getJWKs(t *testing.T) map[string]interface{} { - getReq := testHttp.NewRequest(http.MethodGet, testHttp.HTTPS_SCHEME+config.M2M_OAUTH_SERVER_HTTP_HOST+uri.JWKs, nil).Build(context.Background(), t) +func getJWKs(ctx context.Context, t *testing.T) map[string]interface{} { + getReq := testHttp.NewRequest(http.MethodGet, testHttp.HTTPS_SCHEME+config.M2M_OAUTH_SERVER_HTTP_HOST+uri.JWKs, nil).Build(ctx, t) res := testHttp.Do(t, getReq) defer func() { _ = res.Body.Close() diff --git a/m2m-oauth-server/service/getOpenIDConfiguration.go b/m2m-oauth-server/service/http/getOpenIDConfiguration.go similarity index 53% rename from m2m-oauth-server/service/getOpenIDConfiguration.go rename to m2m-oauth-server/service/http/getOpenIDConfiguration.go index 2fa8bc861..7d6c96478 100644 --- a/m2m-oauth-server/service/getOpenIDConfiguration.go +++ b/m2m-oauth-server/service/http/getOpenIDConfiguration.go @@ -1,4 +1,4 @@ -package service +package http import ( "net/http" @@ -8,12 +8,17 @@ import ( "github.com/plgd-dev/hub/v2/pkg/security/openid" ) -func (requestHandler *RequestHandler) getOpenIDConfiguration(w http.ResponseWriter, _ *http.Request) { - v := openid.Config{ - Issuer: requestHandler.getDomain(), - TokenURL: requestHandler.getDomain() + uri.Token, - JWKSURL: requestHandler.getDomain() + uri.JWKs, +func GetOpenIDConfiguration(domain string) openid.Config { + return openid.Config{ + Issuer: domain, + TokenURL: domain + uri.Token, + JWKSURL: domain + uri.JWKs, + PlgdTokensEndpoint: domain + uri.Tokens, } +} + +func (requestHandler *RequestHandler) getOpenIDConfiguration(w http.ResponseWriter, _ *http.Request) { + v := GetOpenIDConfiguration(requestHandler.m2mOAuthServiceServer.GetDomain()) if err := jsonResponseWriter(w, v); err != nil { log.Errorf("failed to write response: %v", err) diff --git a/m2m-oauth-server/service/http/getOpenIDConfiguration_test.go b/m2m-oauth-server/service/http/getOpenIDConfiguration_test.go new file mode 100644 index 000000000..d28e81584 --- /dev/null +++ b/m2m-oauth-server/service/http/getOpenIDConfiguration_test.go @@ -0,0 +1,39 @@ +package http_test + +import ( + "context" + "net/http" + "testing" + + "github.com/plgd-dev/hub/v2/m2m-oauth-server/uri" + "github.com/plgd-dev/hub/v2/test/config" + testHttp "github.com/plgd-dev/hub/v2/test/http" + testService "github.com/plgd-dev/hub/v2/test/service" + "github.com/plgd-dev/kit/v2/codec/json" + "github.com/stretchr/testify/require" +) + +func TestGetOpenIDConfiguration(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), config.TEST_TIMEOUT) + defer cancel() + webTearDown := testService.SetUp(ctx, t) + defer webTearDown() + + getOpenIDConfiguration(ctx, t) +} + +func getOpenIDConfiguration(ctx context.Context, t *testing.T) { + getReq := testHttp.NewRequest(http.MethodGet, testHttp.HTTPS_SCHEME+config.M2M_OAUTH_SERVER_HTTP_HOST+uri.OpenIDConfiguration, nil).Build(ctx, t) + res := testHttp.Do(t, getReq) + defer func() { + _ = res.Body.Close() + }() + + var body map[string]interface{} + err := json.ReadFrom(res.Body, &body) + require.NoError(t, err) + require.NotEmpty(t, body["issuer"]) + require.NotEmpty(t, body["token_endpoint"]) + require.NotEmpty(t, body["jwks_uri"]) + require.NotEmpty(t, body["plgd_tokens_endpoint"]) +} diff --git a/m2m-oauth-server/service/http/getTokens_test.go b/m2m-oauth-server/service/http/getTokens_test.go new file mode 100644 index 000000000..c81718fd1 --- /dev/null +++ b/m2m-oauth-server/service/http/getTokens_test.go @@ -0,0 +1,257 @@ +package http_test + +import ( + "bytes" + "context" + "errors" + "io" + "net/http" + "testing" + + "github.com/plgd-dev/go-coap/v3/message" + grpcPb "github.com/plgd-dev/hub/v2/grpc-gateway/pb" + oauthsigner "github.com/plgd-dev/hub/v2/m2m-oauth-server/oauthSigner" + "github.com/plgd-dev/hub/v2/m2m-oauth-server/pb" + m2mOauthServerTest "github.com/plgd-dev/hub/v2/m2m-oauth-server/test" + "github.com/plgd-dev/hub/v2/m2m-oauth-server/uri" + "github.com/plgd-dev/hub/v2/pkg/security/jwt" + "github.com/plgd-dev/hub/v2/test" + "github.com/plgd-dev/hub/v2/test/config" + testHttp "github.com/plgd-dev/hub/v2/test/http" + testOAuthTest "github.com/plgd-dev/hub/v2/test/oauth-server/test" + testService "github.com/plgd-dev/hub/v2/test/service" + "github.com/stretchr/testify/require" +) + +func createTokens(ctx context.Context, t *testing.T, createTokens []*pb.CreateTokenRequest) []string { + accessTokensIDs := make([]string, 0, len(createTokens)) + for _, createToken := range createTokens { + data, err := testHttp.GetContentData(&grpcPb.Content{ + ContentType: message.AppOcfCbor.String(), + Data: test.EncodeToCbor(t, createToken), + }, message.AppJSON.String()) + require.NoError(t, err) + rb := testHttp.NewRequest(http.MethodPost, m2mOauthServerTest.HTTPURI(uri.Tokens), bytes.NewReader(data)) + rb = rb.ContentType(message.AppOcfCbor.String()) + resp := testHttp.Do(t, rb.Build(ctx, t)) + defer func() { + _ = resp.Body.Close() + }() + require.Equal(t, http.StatusOK, resp.StatusCode) + var got pb.CreateTokenResponse + err = testHttp.Unmarshal(resp.StatusCode, resp.Body, &got) + require.NoError(t, err) + claims, err := jwt.ParseToken(got.GetAccessToken()) + require.NoError(t, err) + name, err := claims.GetName() + require.NoError(t, err) + require.Equal(t, createToken.GetTokenName(), name) + id, err := claims.GetID() + require.NoError(t, err) + accessTokensIDs = append(accessTokensIDs, id) + } + return accessTokensIDs +} + +func blacklistTokens(ctx context.Context, t *testing.T, tokenIDs []string, token string) { + data, err := testHttp.GetContentData(&grpcPb.Content{ + ContentType: message.AppOcfCbor.String(), + Data: test.EncodeToCbor(t, &pb.BlacklistTokensRequest{ + IdFilter: tokenIDs, + }), + }, message.AppJSON.String()) + require.NoError(t, err) + rb := testHttp.NewRequest(http.MethodPost, m2mOauthServerTest.HTTPURI(uri.BlacklistTokens), bytes.NewReader(data)).AuthToken(token) + rb = rb.ContentType(message.AppOcfCbor.String()) + resp := testHttp.Do(t, rb.Build(ctx, t)) + defer func() { + _ = resp.Body.Close() + }() + require.Equal(t, http.StatusOK, resp.StatusCode) +} + +func TestGetTokens(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), config.TEST_TIMEOUT) + defer cancel() + tearDown := testService.SetUp(ctx, t) + defer tearDown() + + token := testOAuthTest.GetDefaultAccessToken(t) + tokens := []*pb.CreateTokenRequest{ + { + ClientId: m2mOauthServerTest.ServiceOAuthClient.ID, + ClientSecret: m2mOauthServerTest.GetSecret(t, m2mOauthServerTest.ServiceOAuthClient.ID), + GrantType: string(oauthsigner.GrantTypeClientCredentials), + TokenName: "service token", + }, + { + ClientId: m2mOauthServerTest.JWTPrivateKeyOAuthClient.ID, + GrantType: string(oauthsigner.GrantTypeClientCredentials), + ClientAssertionType: string(uri.ClientAssertionTypeJWT), + ClientAssertion: token, + }, + } + blacklistedTokens := []*pb.CreateTokenRequest{ + { + ClientId: m2mOauthServerTest.ServiceOAuthClient.ID, + ClientSecret: m2mOauthServerTest.GetSecret(t, m2mOauthServerTest.ServiceOAuthClient.ID), + GrantType: string(oauthsigner.GrantTypeClientCredentials), + TokenName: "service token blacklisted", + }, + } + tokenIDs := createTokens(ctx, t, tokens) + blacklistTokenIDs := createTokens(ctx, t, blacklistedTokens) + + blacklistTokens(ctx, t, blacklistTokenIDs, token) + + type args struct { + req *pb.GetTokensRequest + token string + } + tests := []struct { + name string + args args + wantHTTPCode int + want map[string]*pb.Token + wantErr bool + }{ + { + name: "get all tokens included blacklisted", + args: args{ + req: &pb.GetTokensRequest{ + IncludeBlacklisted: true, + }, + token: token, + }, + wantHTTPCode: http.StatusOK, + want: map[string]*pb.Token{ + tokenIDs[0]: { + ClientId: m2mOauthServerTest.ServiceOAuthClient.ID, + Id: tokenIDs[0], + Name: tokens[0].GetTokenName(), + }, + tokenIDs[1]: { + ClientId: m2mOauthServerTest.JWTPrivateKeyOAuthClient.ID, + Id: tokenIDs[1], + Name: tokens[1].GetTokenName(), + }, + blacklistTokenIDs[0]: { + ClientId: m2mOauthServerTest.ServiceOAuthClient.ID, + Id: blacklistTokenIDs[0], + Name: blacklistedTokens[0].GetTokenName(), + Blacklisted: &pb.Token_BlackListed{ + Flag: true, + }, + }, + }, + }, + { + name: "get all tokens excluded blacklisted", + args: args{ + req: &pb.GetTokensRequest{}, + token: token, + }, + wantHTTPCode: http.StatusOK, + want: map[string]*pb.Token{ + tokenIDs[0]: { + ClientId: m2mOauthServerTest.ServiceOAuthClient.ID, + Id: tokenIDs[0], + Name: tokens[0].GetTokenName(), + }, + tokenIDs[1]: { + ClientId: m2mOauthServerTest.JWTPrivateKeyOAuthClient.ID, + Id: tokenIDs[1], + Name: tokens[1].GetTokenName(), + }, + }, + }, + { + name: "get certain tokens with excluded blacklisted", + args: args{ + req: &pb.GetTokensRequest{ + IdFilter: []string{tokenIDs[0], blacklistTokenIDs[0]}, + }, + token: token, + }, + wantHTTPCode: http.StatusOK, + want: map[string]*pb.Token{ + tokenIDs[0]: { + ClientId: m2mOauthServerTest.ServiceOAuthClient.ID, + Id: tokenIDs[0], + Name: tokens[0].GetTokenName(), + }, + }, + }, + { + name: "get certain tokens with included blacklisted", + args: args{ + req: &pb.GetTokensRequest{ + IdFilter: []string{tokenIDs[0], blacklistTokenIDs[0]}, + IncludeBlacklisted: true, + }, + token: token, + }, + wantHTTPCode: http.StatusOK, + want: map[string]*pb.Token{ + tokenIDs[0]: { + ClientId: m2mOauthServerTest.ServiceOAuthClient.ID, + Id: tokenIDs[0], + Name: tokens[0].GetTokenName(), + }, + blacklistTokenIDs[0]: { + ClientId: m2mOauthServerTest.ServiceOAuthClient.ID, + Id: blacklistTokenIDs[0], + Name: blacklistedTokens[0].GetTokenName(), + Blacklisted: &pb.Token_BlackListed{ + Flag: true, + }, + }, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + rb := testHttp.NewRequest(http.MethodGet, m2mOauthServerTest.HTTPURI(uri.Tokens), nil) + if tt.args.token != "" { + rb = rb.AuthToken(tt.args.token) + } + if tt.args.req.GetIncludeBlacklisted() { + rb = rb.AddQuery("includeBlacklisted", "true") + } + for _, id := range tt.args.req.GetIdFilter() { + rb = rb.AddQuery("idFilter", id) + } + rb = rb.ContentType(message.AppOcfCbor.String()) + resp := testHttp.Do(t, rb.Build(ctx, t)) + defer func() { + _ = resp.Body.Close() + }() + require.Equal(t, tt.wantHTTPCode, resp.StatusCode) + + var got []*pb.Token + for { + var gotToken pb.Token + err := testHttp.Unmarshal(resp.StatusCode, resp.Body, &gotToken) + if errors.Is(err, io.EOF) { + break + } + require.NoError(t, err) + got = append(got, &gotToken) + } + require.Len(t, got, len(tt.want)) + for _, gotToken := range got { + want, ok := tt.want[gotToken.GetId()] + require.True(t, ok) + require.Equal(t, want.GetClientId(), gotToken.GetClientId()) + require.Equal(t, want.GetId(), gotToken.GetId()) + require.Equal(t, want.GetName(), gotToken.GetName()) + if gotToken.GetId() == blacklistTokenIDs[0] { + require.NotNil(t, gotToken.GetBlacklisted()) + } + require.Equal(t, want.GetBlacklisted().GetFlag(), gotToken.GetBlacklisted().GetFlag()) + } + }) + } + +} diff --git a/m2m-oauth-server/service/jsonWriter.go b/m2m-oauth-server/service/http/jsonWriter.go similarity index 96% rename from m2m-oauth-server/service/jsonWriter.go rename to m2m-oauth-server/service/http/jsonWriter.go index 2f5966dbe..ecd46a57c 100644 --- a/m2m-oauth-server/service/jsonWriter.go +++ b/m2m-oauth-server/service/http/jsonWriter.go @@ -1,4 +1,4 @@ -package service +package http import ( "net/http" diff --git a/m2m-oauth-server/service/http/postToken.go b/m2m-oauth-server/service/http/postToken.go new file mode 100644 index 000000000..795b97a92 --- /dev/null +++ b/m2m-oauth-server/service/http/postToken.go @@ -0,0 +1,113 @@ +package http + +import ( + "net/http" + "strconv" + "strings" + "time" + + "github.com/plgd-dev/hub/v2/http-gateway/serverMux" + "github.com/plgd-dev/hub/v2/m2m-oauth-server/pb" + "github.com/plgd-dev/hub/v2/m2m-oauth-server/uri" + "github.com/plgd-dev/hub/v2/pkg/log" + pkgGrpc "github.com/plgd-dev/hub/v2/pkg/net/grpc" + "github.com/plgd-dev/kit/v2/codec/json" + "google.golang.org/grpc/codes" +) + +type postRequest struct { + ClientID string `json:"client_id"` + Secret string `json:"client_secret"` + Audience string `json:"audience"` + GrantType string `json:"grant_type"` + ClientAssertionType string `json:"client_assertion_type"` + ClientAssertion string `json:"client_assertion"` + TokenName string `json:"token_name"` + Scope string `json:"scope"` + TimeToLive time.Duration `json:"time_to_live"` +} + +func postFormToCreateTokenRequest(r *http.Request, createTokenRequest *pb.CreateTokenRequest) { + createTokenRequest.GrantType = r.PostFormValue(uri.GrantTypeKey) + createTokenRequest.ClientId = r.PostFormValue(uri.ClientIDKey) + audience := r.PostFormValue(uri.AudienceKey) + if audience != "" { + createTokenRequest.Audience = strings.Split(audience, " ") + } + scope := r.PostFormValue(uri.ScopeKey) + if scope != "" { + createTokenRequest.Scope = strings.Split(scope, " ") + } + createTokenRequest.ClientSecret = r.PostFormValue(uri.ClientSecretKey) + createTokenRequest.ClientAssertionType = r.PostFormValue(uri.ClientAssertionTypeKey) + createTokenRequest.ClientAssertion = r.PostFormValue(uri.ClientAssertionKey) + createTokenRequest.TokenName = r.PostFormValue(uri.TokenNameKey) + ttl := r.PostFormValue(uri.TimeToLiveKey) + if ttl == "" { + return + } + if ttlVal, err := strconv.ParseInt(ttl, 10, 64); err == nil { + createTokenRequest.TimeToLive = ttlVal + } +} + +func jsonToCreateTokenRequest(req postRequest, createTokenRequest *pb.CreateTokenRequest) { + createTokenRequest.GrantType = req.GrantType + createTokenRequest.ClientId = req.ClientID + audience := req.Audience + if audience != "" { + createTokenRequest.Audience = strings.Split(audience, " ") + } + scope := req.Scope + if scope != "" { + createTokenRequest.Scope = strings.Split(scope, " ") + } + createTokenRequest.ClientSecret = req.Secret + createTokenRequest.ClientAssertionType = req.ClientAssertionType + createTokenRequest.ClientAssertion = req.ClientAssertion + createTokenRequest.TokenName = req.TokenName + createTokenRequest.TimeToLive = req.TimeToLive.Nanoseconds() +} + +func (requestHandler *RequestHandler) postToken(w http.ResponseWriter, r *http.Request) { + var createTokenRequest pb.CreateTokenRequest + const cannotCreateTokenFmt = "cannot create token: %v" + if strings.Contains(r.Header.Get("Content-Type"), "application/x-www-form-urlencoded") { + err := r.ParseForm() + if err != nil { + serverMux.WriteError(w, pkgGrpc.ForwardErrorf(codes.InvalidArgument, cannotCreateTokenFmt, err)) + return + } + postFormToCreateTokenRequest(r, &createTokenRequest) + } else { + var req postRequest + err := json.ReadFrom(r.Body, &req) + if err != nil { + serverMux.WriteError(w, pkgGrpc.ForwardErrorf(codes.InvalidArgument, cannotCreateTokenFmt, err)) + return + } + jsonToCreateTokenRequest(req, &createTokenRequest) + } + clientID, secret, ok := r.BasicAuth() + if ok { + createTokenRequest.ClientId = clientID + createTokenRequest.ClientSecret = secret + } + grpcResp, err := requestHandler.m2mOAuthServiceServer.CreateToken(r.Context(), &createTokenRequest) + if err != nil { + serverMux.WriteError(w, pkgGrpc.ForwardErrorf(codes.InvalidArgument, cannotCreateTokenFmt, err)) + return + } + resp := map[string]interface{}{ + uri.AccessTokenKey: grpcResp.GetAccessToken(), + uri.ScopeKey: strings.Join(grpcResp.GetScope(), " "), + uri.TokenTypeKey: grpcResp.GetTokenType(), + } + if grpcResp.GetExpiresIn() > 0 { + resp[uri.ExpiresInKey] = grpcResp.GetExpiresIn() + } + + if err = jsonResponseWriter(w, resp); err != nil { + log.Errorf("failed to write response: %v", err) + } +} diff --git a/m2m-oauth-server/service/token_test.go b/m2m-oauth-server/service/http/postToken_test.go similarity index 86% rename from m2m-oauth-server/service/token_test.go rename to m2m-oauth-server/service/http/postToken_test.go index 5f20eae52..411b70dc5 100644 --- a/m2m-oauth-server/service/token_test.go +++ b/m2m-oauth-server/service/http/postToken_test.go @@ -1,4 +1,4 @@ -package service_test +package http_test import ( "context" @@ -7,7 +7,7 @@ import ( "testing" "github.com/golang-jwt/jwt/v5" - "github.com/plgd-dev/hub/v2/m2m-oauth-server/service" + oauthsigner "github.com/plgd-dev/hub/v2/m2m-oauth-server/oauthSigner" m2mOauthServerTest "github.com/plgd-dev/hub/v2/m2m-oauth-server/test" "github.com/plgd-dev/hub/v2/m2m-oauth-server/uri" "github.com/plgd-dev/hub/v2/test/config" @@ -15,7 +15,7 @@ import ( "github.com/stretchr/testify/require" ) -func TestGetToken(t *testing.T) { +func TestPostToken(t *testing.T) { type want struct { owner interface{} existOriginalTokenClaims bool @@ -43,10 +43,13 @@ func TestGetToken(t *testing.T) { Ctx: context.Background(), ClientID: m2mOauthServerTest.ServiceOAuthClient.ID, ClientSecret: m2mOauthServerTest.GetSecret(t, m2mOauthServerTest.ServiceOAuthClient.ID), - GrantType: string(service.GrantTypeClientCredentials), + GrantType: string(oauthsigner.GrantTypeClientCredentials), Host: config.M2M_OAUTH_SERVER_HTTP_HOST, }, wantCode: http.StatusOK, + want: want{ + owner: "1", + }, }, { name: "serviceToken - postForm", @@ -54,18 +57,21 @@ func TestGetToken(t *testing.T) { Ctx: context.Background(), ClientID: m2mOauthServerTest.ServiceOAuthClient.ID, ClientSecret: m2mOauthServerTest.GetSecret(t, m2mOauthServerTest.ServiceOAuthClient.ID), - GrantType: string(service.GrantTypeClientCredentials), + GrantType: string(oauthsigner.GrantTypeClientCredentials), Host: config.M2M_OAUTH_SERVER_HTTP_HOST, PostForm: true, }, wantCode: http.StatusOK, + want: want{ + owner: "1", + }, }, { name: "snippetServiceToken - JWT", args: m2mOauthServerTest.AccessTokenOptions{ Ctx: context.Background(), ClientID: m2mOauthServerTest.JWTPrivateKeyOAuthClient.ID, - GrantType: string(service.GrantTypeClientCredentials), + GrantType: string(oauthsigner.GrantTypeClientCredentials), Host: config.M2M_OAUTH_SERVER_HTTP_HOST, JWT: token, }, @@ -80,7 +86,7 @@ func TestGetToken(t *testing.T) { args: m2mOauthServerTest.AccessTokenOptions{ Ctx: context.Background(), ClientID: "invalid client", - GrantType: string(service.GrantTypeClientCredentials), + GrantType: string(oauthsigner.GrantTypeClientCredentials), Host: config.M2M_OAUTH_SERVER_HTTP_HOST, JWT: invalidToken, }, @@ -91,7 +97,7 @@ func TestGetToken(t *testing.T) { args: m2mOauthServerTest.AccessTokenOptions{ Ctx: context.Background(), ClientID: m2mOauthServerTest.JWTPrivateKeyOAuthClient.ID, - GrantType: string(service.GrantTypeClientCredentials), + GrantType: string(oauthsigner.GrantTypeClientCredentials), Host: config.M2M_OAUTH_SERVER_HTTP_HOST, JWT: invalidToken, }, @@ -99,9 +105,6 @@ func TestGetToken(t *testing.T) { }, } - cfg := m2mOauthServerTest.MakeConfig(t) - fmt.Printf("cfg: %v\n", cfg) - webTearDown := m2mOauthServerTest.SetUp(t) defer webTearDown() diff --git a/m2m-oauth-server/service/http/requestHandler.go b/m2m-oauth-server/service/http/requestHandler.go new file mode 100644 index 000000000..b264e0230 --- /dev/null +++ b/m2m-oauth-server/service/http/requestHandler.go @@ -0,0 +1,46 @@ +package http + +import ( + "context" + "fmt" + "net/http" + + "github.com/fullstorydev/grpchan/inprocgrpc" + "github.com/gorilla/mux" + "github.com/grpc-ecosystem/grpc-gateway/v2/runtime" + "github.com/plgd-dev/hub/v2/http-gateway/serverMux" + "github.com/plgd-dev/hub/v2/m2m-oauth-server/pb" + grpcService "github.com/plgd-dev/hub/v2/m2m-oauth-server/service/grpc" + "github.com/plgd-dev/hub/v2/m2m-oauth-server/uri" +) + +// RequestHandler for handling incoming request +type RequestHandler struct { + config *Config + m2mOAuthServiceServer *grpcService.M2MOAuthServiceServer + mux *runtime.ServeMux +} + +// NewHTTP returns HTTP handler +func NewRequestHandler(config *Config, r *mux.Router, m2mOAuthServiceServer *grpcService.M2MOAuthServiceServer) (*RequestHandler, error) { + requestHandler := &RequestHandler{ + config: config, + mux: serverMux.New(), + m2mOAuthServiceServer: m2mOAuthServiceServer, + } + + r.HandleFunc(uri.OpenIDConfiguration, requestHandler.getOpenIDConfiguration).Methods(http.MethodGet) + r.HandleFunc(uri.JWKs, requestHandler.getJWKs).Methods(http.MethodGet) + r.HandleFunc(uri.Token, requestHandler.postToken).Methods(http.MethodPost) + + ch := new(inprocgrpc.Channel) + pb.RegisterM2MOAuthServiceServer(ch, m2mOAuthServiceServer) + grpcClient := pb.NewM2MOAuthServiceClient(ch) + // register grpc-proxy handler + if err := pb.RegisterM2MOAuthServiceHandlerClient(context.Background(), requestHandler.mux, grpcClient); err != nil { + return nil, fmt.Errorf("failed to register m2m-oauth-server handler: %w", err) + } + r.PathPrefix("/").Handler(requestHandler.mux) + + return requestHandler, nil +} diff --git a/m2m-oauth-server/service/http/service.go b/m2m-oauth-server/service/http/service.go new file mode 100644 index 000000000..a95b449fc --- /dev/null +++ b/m2m-oauth-server/service/http/service.go @@ -0,0 +1,69 @@ +package http + +import ( + "fmt" + "net/http" + "regexp" + + grpcService "github.com/plgd-dev/hub/v2/m2m-oauth-server/service/grpc" + "github.com/plgd-dev/hub/v2/m2m-oauth-server/uri" + "github.com/plgd-dev/hub/v2/pkg/fsnotify" + "github.com/plgd-dev/hub/v2/pkg/log" + pkgHttp "github.com/plgd-dev/hub/v2/pkg/net/http" + httpService "github.com/plgd-dev/hub/v2/pkg/net/http/service" + "github.com/plgd-dev/hub/v2/pkg/security/jwt/validator" + "go.opentelemetry.io/otel/trace" +) + +// Service handle HTTP request +type Service struct { + *httpService.Service + requestHandler *RequestHandler +} + +// New parses configuration and creates new Server with provided store and bus +func New(serviceName string, config Config, m2mOAuthServiceServer *grpcService.M2MOAuthServiceServer, validator *validator.Validator, fileWatcher *fsnotify.Watcher, logger log.Logger, tracerProvider trace.TracerProvider) (*Service, error) { + whiteList := []pkgHttp.RequestMatcher{ + { + Method: http.MethodGet, + URI: regexp.MustCompile(regexp.QuoteMeta(uri.JWKs)), + }, + { + Method: http.MethodGet, + URI: regexp.MustCompile(regexp.QuoteMeta(uri.OpenIDConfiguration)), + }, + { + Method: http.MethodPost, + URI: regexp.MustCompile(regexp.QuoteMeta(uri.Token)), + }, + { + Method: http.MethodPost, + URI: regexp.MustCompile(regexp.QuoteMeta(uri.Tokens)), + }, + } + service, err := httpService.New(httpService.Config{ + HTTPConnection: config.Connection, + HTTPServer: config.Server, + ServiceName: serviceName, + AuthRules: pkgHttp.NewDefaultAuthorizationRules(uri.API), + WhiteEndpointList: whiteList, + FileWatcher: fileWatcher, + Logger: logger, + TraceProvider: tracerProvider, + Validator: validator, + }) + if err != nil { + return nil, fmt.Errorf("cannot create http service: %w", err) + } + + requestHandler, err := NewRequestHandler(&config, service.GetRouter(), m2mOAuthServiceServer) + if err != nil { + _ = service.Close() + return nil, err + } + + return &Service{ + Service: service, + requestHandler: requestHandler, + }, nil +} diff --git a/m2m-oauth-server/service/httpApi.go b/m2m-oauth-server/service/httpApi.go deleted file mode 100644 index cbb50422c..000000000 --- a/m2m-oauth-server/service/httpApi.go +++ /dev/null @@ -1,86 +0,0 @@ -package service - -import ( - "context" - "fmt" - "net/http" - "time" - - router "github.com/gorilla/mux" - "github.com/lestrrat-go/jwx/v2/jwk" - "github.com/plgd-dev/go-coap/v3/pkg/cache" - "github.com/plgd-dev/go-coap/v3/pkg/runner/periodic" - "github.com/plgd-dev/hub/v2/m2m-oauth-server/uri" - "github.com/plgd-dev/hub/v2/pkg/fn" - "github.com/plgd-dev/hub/v2/pkg/fsnotify" - "github.com/plgd-dev/hub/v2/pkg/log" - kitHttp "github.com/plgd-dev/hub/v2/pkg/net/http" - pkgJwt "github.com/plgd-dev/hub/v2/pkg/security/jwt" - "github.com/plgd-dev/hub/v2/pkg/security/jwt/validator" - "go.opentelemetry.io/otel/trace" -) - -// RequestHandler for handling incoming request -type RequestHandler struct { - config *Config - authRestriction *cache.Cache[string, struct{}] - accessTokenKey interface{} - accessTokenJwkKey jwk.Key - refreshRestriction *cache.Cache[string, struct{}] - privateKeyJWTValidators map[string]*validator.Validator - logger log.Logger -} - -// NewRequestHandler factory for new RequestHandler -func NewRequestHandler(ctx context.Context, config *Config, accessTokenKey interface{}, fileWatcher *fsnotify.Watcher, logger log.Logger, tracerProvider trace.TracerProvider) (*RequestHandler, func(), error) { - accessTokenJwkKey, err := pkgJwt.CreateJwkKey(accessTokenKey) - var closer fn.FuncList - if err != nil { - return nil, nil, fmt.Errorf("cannot create jwk for idToken: %w", err) - } - authRestriction := cache.NewCache[string, struct{}]() - refreshRestriction := cache.NewCache[string, struct{}]() - add := periodic.New(ctx.Done(), time.Second*5) - add(func(now time.Time) bool { - authRestriction.CheckExpirations(now) - refreshRestriction.CheckExpirations(now) - return true - }) - - privateKeyJWTValidators := make(map[string]*validator.Validator, len(config.OAuthSigner.Clients)) - for _, c := range config.OAuthSigner.Clients { - if !c.JWTPrivateKey.Enabled { - continue - } - validator, err := validator.New(ctx, c.JWTPrivateKey.Authorization, fileWatcher, logger, tracerProvider) - if err != nil { - closer.Execute() - return nil, nil, fmt.Errorf("cannot create validator: %w", err) - } - privateKeyJWTValidators[c.ID] = validator - closer.AddFunc(validator.Close) - } - - return &RequestHandler{ - config: config, - authRestriction: authRestriction, - accessTokenJwkKey: accessTokenJwkKey, - accessTokenKey: accessTokenKey, - refreshRestriction: refreshRestriction, - privateKeyJWTValidators: privateKeyJWTValidators, - logger: logger, - }, closer.Execute, nil -} - -// NewHTTP returns HTTP handler -func NewHTTP(requestHandler *RequestHandler, logger log.Logger) http.Handler { - r := router.NewRouter() - r.Use(kitHttp.CreateLoggingMiddleware(kitHttp.WithLogger(logger))) - r.StrictSlash(true) - - r.HandleFunc(uri.JWKs, requestHandler.getJWKs).Methods(http.MethodGet) - r.HandleFunc(uri.OpenIDConfiguration, requestHandler.getOpenIDConfiguration).Methods(http.MethodGet) - r.HandleFunc(uri.Token, requestHandler.postToken).Methods(http.MethodPost) - - return r -} diff --git a/m2m-oauth-server/service/service.go b/m2m-oauth-server/service/service.go index f9bc2fff2..9bc0d9c59 100644 --- a/m2m-oauth-server/service/service.go +++ b/m2m-oauth-server/service/service.go @@ -4,82 +4,156 @@ import ( "context" "fmt" "net/http" + "time" + oauthsigner "github.com/plgd-dev/hub/v2/m2m-oauth-server/oauthSigner" + grpcService "github.com/plgd-dev/hub/v2/m2m-oauth-server/service/grpc" + httpService "github.com/plgd-dev/hub/v2/m2m-oauth-server/service/http" + "github.com/plgd-dev/hub/v2/m2m-oauth-server/store" + storeConfig "github.com/plgd-dev/hub/v2/m2m-oauth-server/store/config" + "github.com/plgd-dev/hub/v2/m2m-oauth-server/store/mongodb" + "github.com/plgd-dev/hub/v2/pkg/config/database" + "github.com/plgd-dev/hub/v2/pkg/fn" "github.com/plgd-dev/hub/v2/pkg/fsnotify" "github.com/plgd-dev/hub/v2/pkg/log" - kitNetHttp "github.com/plgd-dev/hub/v2/pkg/net/http" "github.com/plgd-dev/hub/v2/pkg/net/listener" otelClient "github.com/plgd-dev/hub/v2/pkg/opentelemetry/collector/client" + certManagerServer "github.com/plgd-dev/hub/v2/pkg/security/certManager/server" + "github.com/plgd-dev/hub/v2/pkg/security/jwt/validator" + "github.com/plgd-dev/hub/v2/pkg/security/openid" + "github.com/plgd-dev/hub/v2/pkg/service" + "go.opentelemetry.io/otel/trace" ) const serviceName = "m2m-oauth-server" -// Server handle HTTP request type Service struct { - server *http.Server - requestHandler *RequestHandler - listener *listener.Server + *service.Service + + store store.Store +} + +func createStore(ctx context.Context, config storeConfig.Config, fileWatcher *fsnotify.Watcher, logger log.Logger, tracerProvider trace.TracerProvider) (store.Store, error) { + if config.Use != database.MongoDB { + return nil, fmt.Errorf("invalid store use('%v')", config.Use) + } + s, err := mongodb.New(ctx, config.MongoDB, fileWatcher, logger, tracerProvider) + if err != nil { + return nil, fmt.Errorf("mongodb: %w", err) + } + if config.CleanUpDeletedTokens != "" { + scheduler, err := NewExpiredUpdatesChecker(config.CleanUpDeletedTokens, config.ExtendCronParserBySeconds, func() { + err = s.DeleteTokens(ctx, time.Now()) + if err != nil { + log.Errorf("cannot delete expired tokens: %v", err) + } + }) + if err != nil { + return nil, fmt.Errorf("cannot create scheduler: %w", err) + } + s.AddCloseFunc(func() { + err2 := scheduler.Shutdown() + if err2 != nil { + log.Errorf("failed to shutdown scheduler: %w", err2) + } + }) + } + return s, nil +} + +func newHttpService(ctx context.Context, config HTTPConfig, validatorConfig validator.Config, getOpenIDConfiguration validator.GetOpenIDConfigurationFunc, tlsConfig certManagerServer.Config, ss *grpcService.M2MOAuthServiceServer, fileWatcher *fsnotify.Watcher, logger log.Logger, tracerProvider trace.TracerProvider) (*httpService.Service, func(), error) { + httpValidator, err := validator.New(ctx, validatorConfig, fileWatcher, logger, tracerProvider, validator.WithGetOpenIDConfiguration(getOpenIDConfiguration)) + if err != nil { + return nil, nil, fmt.Errorf("cannot create http validator: %w", err) + } + httpService, err := httpService.New(serviceName, httpService.Config{ + Connection: listener.Config{ + Addr: config.Addr, + TLS: tlsConfig, + }, + Authorization: validatorConfig, + Server: config.Server, + }, ss, httpValidator, fileWatcher, logger, tracerProvider) + if err != nil { + httpValidator.Close() + return nil, nil, fmt.Errorf("cannot create http service: %w", err) + } + return httpService, httpValidator.Close, nil +} + +func newGrpcService(ctx context.Context, config grpcService.Config, getOpenIDConfiguration validator.GetOpenIDConfigurationFunc, ss *grpcService.M2MOAuthServiceServer, fileWatcher *fsnotify.Watcher, logger log.Logger, tracerProvider trace.TracerProvider) (*grpcService.Service, func(), error) { + grpcValidator, err := validator.New(ctx, config.Authorization.Config, fileWatcher, logger, tracerProvider, validator.WithGetOpenIDConfiguration(getOpenIDConfiguration)) + if err != nil { + return nil, nil, fmt.Errorf("cannot create grpc validator: %w", err) + } + grpcService, err := grpcService.New(config, ss, grpcValidator, fileWatcher, logger, tracerProvider) + if err != nil { + grpcValidator.Close() + return nil, nil, fmt.Errorf("cannot create grpc service: %w", err) + } + return grpcService, grpcValidator.Close, nil } -// New parses configuration and creates new Server with provided store and bus func New(ctx context.Context, config Config, fileWatcher *fsnotify.Watcher, logger log.Logger) (*Service, error) { - ctx, cancel := context.WithCancel(ctx) otelClient, err := otelClient.New(ctx, config.Clients.OpenTelemetryCollector.Config, serviceName, fileWatcher, logger) if err != nil { - cancel() return nil, fmt.Errorf("cannot create open telemetry collector client: %w", err) } - otelClient.AddCloseFunc(cancel) + var closerFn fn.FuncList + closerFn.AddFunc(otelClient.Close) tracerProvider := otelClient.GetTracerProvider() - listener, err := listener.New(config.APIs.HTTP.Connection, fileWatcher, logger) - if err != nil { - otelClient.Close() - return nil, fmt.Errorf("cannot create http server: %w", err) - } - listener.AddCloseFunc(otelClient.Close) - closeListener := func() { - if errC := listener.Close(); errC != nil { - logger.Errorf("cannot close listener: %w", errC) + getOpenIDCfg := func(ctx context.Context, c *http.Client, authority string) (openid.Config, error) { + if authority == config.OAuthSigner.GetAuthority() { + return httpService.GetOpenIDConfiguration(config.OAuthSigner.GetDomain()), nil } + return openid.GetConfiguration(ctx, c, authority) } - accessTokenPrivateKeyI, err := LoadPrivateKey(config.OAuthSigner.PrivateKeyFile) + db, err := createStore(ctx, config.Clients.Storage, fileWatcher, logger, tracerProvider) if err != nil { - closeListener() - return nil, fmt.Errorf("cannot load private privateKeyFile(%v): %w", config.OAuthSigner.PrivateKeyFile, err) + closerFn.Execute() + return nil, fmt.Errorf("cannot create store: %w", err) } + closerFn.AddFunc(func() { + if errC := db.Close(ctx); errC != nil { + log.Errorf("failed to close store: %w", errC) + } + }) - requestHandler, closeHandler, err := NewRequestHandler(ctx, &config, accessTokenPrivateKeyI, fileWatcher, logger, tracerProvider) + signer, err := oauthsigner.New(ctx, config.OAuthSigner, getOpenIDCfg, fileWatcher, logger, tracerProvider) if err != nil { - closeListener() - return nil, fmt.Errorf("cannot create request handler: %w", err) - } - listener.AddCloseFunc(closeHandler) - - httpServer := http.Server{ - Handler: kitNetHttp.OpenTelemetryNewHandler(NewHTTP(requestHandler, logger), serviceName, tracerProvider), - ReadTimeout: config.APIs.HTTP.Server.ReadTimeout, - ReadHeaderTimeout: config.APIs.HTTP.Server.ReadHeaderTimeout, - WriteTimeout: config.APIs.HTTP.Server.WriteTimeout, - IdleTimeout: config.APIs.HTTP.Server.IdleTimeout, + closerFn.Execute() + return nil, fmt.Errorf("cannot create oauth signer: %w", err) } + closerFn.AddFunc(signer.Close) - server := Service{ - server: &httpServer, - requestHandler: requestHandler, - listener: listener, + m2mOAuthService := grpcService.NewM2MOAuthServerServer(db, signer, logger) + + grpcService, grpcServiceClose, err := newGrpcService(ctx, config.APIs.GRPC, getOpenIDCfg, m2mOAuthService, fileWatcher, logger, tracerProvider) + if err != nil { + closerFn.Execute() + return nil, err } + closerFn.AddFunc(grpcServiceClose) - return &server, nil -} + httpService, httpServiceClose, err := newHttpService(ctx, config.APIs.HTTP, config.APIs.GRPC.Authorization.Config, getOpenIDCfg, config.APIs.GRPC.TLS, + m2mOAuthService, fileWatcher, logger, tracerProvider) + if err != nil { + grpcService.Close() + closerFn.Execute() + return nil, err + } + closerFn.AddFunc(httpServiceClose) -// Serve starts the service's HTTP server and blocks -func (s *Service) Serve() error { - return s.server.Serve(s.listener) + s := service.New(grpcService, httpService) + s.AddCloseFunc(closerFn.Execute) + return &Service{ + Service: s, + store: db, + }, nil } -// Shutdown ends serving -func (s *Service) Close() error { - return s.server.Shutdown(context.Background()) +func (s *Service) SnippetServiceStore() store.Store { + return s.store } diff --git a/m2m-oauth-server/service/service_test.go b/m2m-oauth-server/service/service_test.go new file mode 100644 index 000000000..98227d661 --- /dev/null +++ b/m2m-oauth-server/service/service_test.go @@ -0,0 +1,22 @@ +package service_test + +import ( + "context" + "fmt" + "testing" + + "github.com/plgd-dev/hub/v2/m2m-oauth-server/test" + "github.com/plgd-dev/hub/v2/test/config" + testService "github.com/plgd-dev/hub/v2/test/service" +) + +func TestService(t *testing.T) { + cfg := test.MakeConfig(t) + + ctx, cancel := context.WithTimeout(context.Background(), config.TEST_TIMEOUT) + defer cancel() + webTearDown := testService.SetUp(ctx, t, testService.WithM2MOAuthConfig(cfg)) + defer webTearDown() + + fmt.Printf("cfg: %v\n", cfg.String()) +} diff --git a/m2m-oauth-server/service/token.go b/m2m-oauth-server/service/token.go deleted file mode 100644 index 1fe097ef3..000000000 --- a/m2m-oauth-server/service/token.go +++ /dev/null @@ -1,361 +0,0 @@ -package service - -import ( - "context" - "errors" - "fmt" - "net/http" - "strings" - "time" - - goJwt "github.com/golang-jwt/jwt/v5" - "github.com/lestrrat-go/jwx/v2/jwk" - "github.com/lestrrat-go/jwx/v2/jws" - "github.com/lestrrat-go/jwx/v2/jwt" - "github.com/plgd-dev/hub/v2/m2m-oauth-server/uri" - "github.com/plgd-dev/hub/v2/pkg/log" - pkgJwt "github.com/plgd-dev/hub/v2/pkg/security/jwt" - "github.com/plgd-dev/kit/v2/codec/json" -) - -func setKeyError(key string, err error) error { - return fmt.Errorf("failed to set %v: %w", key, err) -} - -func setKeyErrorExt(key, info interface{}, err error) error { - return fmt.Errorf("failed to set %v('%v'): %w", key, info, err) -} - -func makeAccessToken(clientCfg *Client, tokenReq tokenRequest, issuedAt, expires time.Time) (jwt.Token, error) { - token := jwt.New() - - sub := getSubject(clientCfg, tokenReq) - if err := token.Set(jwt.SubjectKey, sub); err != nil { - return nil, setKeyError(jwt.SubjectKey, err) - } - if err := token.Set(jwt.AudienceKey, tokenReq.host); err != nil { - return nil, setKeyError(jwt.AudienceKey, err) - } - if err := token.Set(jwt.IssuedAtKey, issuedAt); err != nil { - return nil, setKeyError(jwt.IssuedAtKey, err) - } - if !expires.IsZero() { - if err := token.Set(jwt.ExpirationKey, expires); err != nil { - return nil, setKeyError(jwt.ExpirationKey, err) - } - } - if err := token.Set(uri.ScopeKey, tokenReq.scopes); err != nil { - return nil, setKeyError(uri.ScopeKey, err) - } - if err := token.Set(uri.ClientIDKey, clientCfg.ID); err != nil { - return nil, setKeyError(uri.ClientIDKey, err) - } - if err := token.Set(jwt.IssuerKey, tokenReq.host); err != nil { - return nil, setKeyError(jwt.IssuerKey, err) - } - if err := setDeviceIDClaim(token, tokenReq); err != nil { - return nil, err - } - if err := setName(token, tokenReq); err != nil { - return nil, err - } - if err := setOwnerClaim(token, tokenReq); err != nil { - return nil, err - } - if err := setOriginTokenClaims(token, tokenReq); err != nil { - return nil, err - } - - for k, v := range clientCfg.InsertTokenClaims { - if _, ok := token.Get(k); ok { - continue - } - if err := token.Set(k, v); err != nil { - return nil, setKeyErrorExt(k, v, err) - } - } - - return token, nil -} - -func getSubject(clientCfg *Client, tokenReq tokenRequest) string { - if tokenReq.subject != "" { - return tokenReq.subject - } - if tokenReq.owner != "" { - return tokenReq.owner - } - return clientCfg.ID -} - -func setDeviceIDClaim(token jwt.Token, tokenReq tokenRequest) error { - if tokenReq.deviceID != "" && tokenReq.deviceIDClaim != "" { - return token.Set(tokenReq.deviceIDClaim, tokenReq.deviceID) - } - return nil -} - -func setOwnerClaim(token jwt.Token, tokenReq tokenRequest) error { - if tokenReq.owner != "" && tokenReq.ownerClaim != "" { - return token.Set(tokenReq.ownerClaim, tokenReq.owner) - } - return nil -} - -func setName(token jwt.Token, tokenReq tokenRequest) error { - if tokenReq.TokenName != "" && tokenReq.ownerClaim != "name" { - return token.Set("name", tokenReq.TokenName) - } - return nil -} - -func setOriginTokenClaims(token jwt.Token, tokenReq tokenRequest) error { - if len(tokenReq.originalTokenClaims) > 0 { - return token.Set(uri.OriginalTokenClaims, tokenReq.originalTokenClaims) - } - return nil -} - -func makeJWTPayload(key interface{}, jwkKey jwk.Key, data []byte) ([]byte, error) { - hdr := jws.NewHeaders() - if err := hdr.Set(jws.TypeKey, `JWT`); err != nil { - return nil, setKeyError(jws.TypeKey, err) - } - if err := hdr.Set(jws.KeyIDKey, jwkKey.KeyID()); err != nil { - return nil, setKeyError(jws.KeyIDKey, err) - } - - payload, err := jws.Sign(data, jws.WithKey(jwkKey.Algorithm(), key, jws.WithProtectedHeaders(hdr))) - if err != nil { - return nil, fmt.Errorf("failed to create UserToken: %w", err) - } - return payload, nil -} - -func generateAccessToken(clientCfg *Client, tokenReq tokenRequest, key interface{}, jwkKey jwk.Key) (string, time.Time, error) { - now := time.Now() - var expires time.Time - if clientCfg.AccessTokenLifetime != 0 { - expires = now.Add(clientCfg.AccessTokenLifetime) - } - token, err := makeAccessToken(clientCfg, tokenReq, now, expires) - if err != nil { - return "", time.Time{}, fmt.Errorf("failed to make token: %w", err) - } - - buf, err := json.Encode(token) - if err != nil { - return "", time.Time{}, fmt.Errorf("failed to encode token: %w", err) - } - - payload, err := makeJWTPayload(key, jwkKey, buf) - if err != nil { - return "", time.Time{}, fmt.Errorf("failed to make payload: %w", err) - } - return string(payload), expires, nil -} - -type tokenRequest struct { - ClientID string `json:"client_id"` - Secret string `json:"client_secret"` - Audience string `json:"audience"` - GrantType GrantType `json:"grant_type"` - ClientAssertionType string `json:"client_assertion_type"` - ClientAssertion string `json:"client_assertion"` - TokenName string `json:"token_name"` - - deviceID string `json:"-"` - owner string `json:"-"` - subject string `json:"-"` - host string `json:"-"` - scopes string `json:"-"` - ownerClaim string `json:"-"` - deviceIDClaim string `json:"-"` - tokenType AccessTokenType `json:"-"` - originalTokenClaims goJwt.MapClaims `json:"-"` -} - -func (requestHandler *RequestHandler) getDomain() string { - return "https://" + requestHandler.config.OAuthSigner.Domain -} - -func (requestHandler *RequestHandler) postToken(w http.ResponseWriter, r *http.Request) { - tokenReq := tokenRequest{ - host: requestHandler.getDomain(), - tokenType: AccessTokenType_JWT, - } - - if strings.Contains(r.Header.Get("Content-Type"), "application/x-www-form-urlencoded") { - err := r.ParseForm() - if err != nil { - writeError(w, err, http.StatusBadRequest) - return - } - tokenReq.GrantType = GrantType(r.PostFormValue(uri.GrantTypeKey)) - tokenReq.ClientID = r.PostFormValue(uri.ClientIDKey) - tokenReq.Audience = r.PostFormValue(uri.AudienceKey) - tokenReq.Secret = r.PostFormValue(uri.ClientSecretKey) - tokenReq.ClientAssertionType = r.PostFormValue(uri.ClientAssertionTypeKey) - tokenReq.ClientAssertion = r.PostFormValue(uri.ClientAssertionKey) - tokenReq.TokenName = r.PostFormValue(uri.TokenName) - } else { - err := json.ReadFrom(r.Body, &tokenReq) - if err != nil { - writeError(w, err, http.StatusBadRequest) - return - } - } - clientID, secret, ok := r.BasicAuth() - if ok { - tokenReq.ClientID = clientID - tokenReq.Secret = secret - } - requestHandler.processResponse(r.Context(), w, tokenReq) -} - -func sliceContains[T comparable](s []T, sub []T) bool { - // sub must be non-empty - if len(s) > 0 && len(sub) == 0 { - return false - } - check := make(map[T]struct{}, len(sub)) - for _, e := range sub { - check[e] = struct{}{} - } - for _, e := range s { - delete(check, e) - } - return len(check) == 0 -} - -func (requestHandler *RequestHandler) validateTokenRequest(ctx context.Context, clientCfg *Client, tokenReq *tokenRequest) error { - if err := validateGrantType(clientCfg, tokenReq); err != nil { - return err - } - if err := validateClient(clientCfg, tokenReq); err != nil { - return err - } - if err := validateClientAssertionType(clientCfg, tokenReq); err != nil { - return err - } - if err := requestHandler.validateClientAssertion(ctx, tokenReq); err != nil { - return err - } - if err := validateAudience(clientCfg, tokenReq); err != nil { - return err - } - - return nil -} - -func validateClient(clientCfg *Client, tokenReq *tokenRequest) error { - if clientCfg == nil { - return fmt.Errorf("client(%v) not found", tokenReq.ClientID) - } - if clientCfg.secret != "" && !clientCfg.JWTPrivateKey.Enabled && clientCfg.secret != tokenReq.Secret { - return errors.New("invalid client secret") - } - return nil -} - -func validateGrantType(clientCfg *Client, tokenReq *tokenRequest) error { - // clientCfg.AllowedGrantTypes is always non-empty - if !sliceContains(clientCfg.AllowedGrantTypes, []GrantType{tokenReq.GrantType}) { - return fmt.Errorf("invalid grant type(%v)", tokenReq.GrantType) - } - return nil -} - -func validateAudience(clientCfg *Client, tokenReq *tokenRequest) error { - var audiences []string - if tokenReq.Audience != "" { - audiences = []string{tokenReq.Audience} - } - if !sliceContains(clientCfg.AllowedAudiences, audiences) { - return fmt.Errorf("invalid audience(%v)", tokenReq.Audience) - } - return nil -} - -func validateClientAssertionType(clientCfg *Client, tokenReq *tokenRequest) error { - if tokenReq.ClientAssertionType != "" && clientCfg.JWTPrivateKey.Enabled && tokenReq.ClientAssertionType != uri.ClientAssertionTypeJWT { - return errors.New("invalid client assertion type") - } - return nil -} - -func (requestHandler *RequestHandler) validateClientAssertion(ctx context.Context, tokenReq *tokenRequest) error { - if tokenReq.ClientAssertionType == "" { - return nil - } - v, ok := requestHandler.privateKeyJWTValidators[tokenReq.ClientID] - if !ok { - return errors.New("invalid client assertion") - } - token, err := v.GetParser().ParseWithContext(ctx, tokenReq.ClientAssertion) - if err != nil { - return fmt.Errorf("invalid client assertion: %w", err) - } - tokenReq.originalTokenClaims = token - claims := pkgJwt.Claims(token) - owner, err := claims.GetOwner(requestHandler.config.OAuthSigner.OwnerClaim) - if err != nil { - return fmt.Errorf("invalid client assertion - claim owner: %w", err) - } - tokenReq.owner = owner - sub, err := claims.GetSubject() - if err != nil { - return fmt.Errorf("invalid client assertion - claim sub: %w", err) - } - tokenReq.subject = sub - if requestHandler.config.OAuthSigner.DeviceIDClaim == "" { - return nil - } - deviceID, err := claims.GetDeviceID(requestHandler.config.OAuthSigner.DeviceIDClaim) - if err == nil { - tokenReq.deviceID = deviceID - } - return nil -} - -func (requestHandler *RequestHandler) processResponse(ctx context.Context, w http.ResponseWriter, tokenReq tokenRequest) { - clientCfg := requestHandler.config.OAuthSigner.Clients.Find(tokenReq.ClientID) - if clientCfg == nil { - requestHandler.logger.Errorf("client(%v) not found - sending unauthorized", tokenReq.ClientID) - writeError(w, errors.New("invalid client"), http.StatusUnauthorized) - return - } - if err := requestHandler.validateTokenRequest(ctx, clientCfg, &tokenReq); err != nil { - requestHandler.logger.Errorf("failed to validate token request - sending unauthorized: %w", err) - writeError(w, errors.New("invalid client"), http.StatusUnauthorized) - return - } - - tokenReq.scopes = strings.Join(clientCfg.AllowedScopes, " ") - tokenReq.deviceIDClaim = requestHandler.config.OAuthSigner.DeviceIDClaim - tokenReq.ownerClaim = requestHandler.config.OAuthSigner.OwnerClaim - - accessToken, accessTokenExpires, err := generateAccessToken( - clientCfg, - tokenReq, - requestHandler.accessTokenKey, - requestHandler.accessTokenJwkKey) - if err != nil { - writeError(w, err, http.StatusInternalServerError) - return - } - - resp := map[string]interface{}{ - uri.AccessTokenKey: accessToken, - uri.ScopeKey: tokenReq.scopes, - "token_type": "Bearer", - } - if !accessTokenExpires.IsZero() { - resp["expires_in"] = int64(time.Until(accessTokenExpires).Seconds()) - } - - if err = jsonResponseWriter(w, resp); err != nil { - log.Errorf("failed to write response: %v", err) - return - } -} diff --git a/m2m-oauth-server/service/writeError.go b/m2m-oauth-server/service/writeError.go deleted file mode 100644 index 1c70693f9..000000000 --- a/m2m-oauth-server/service/writeError.go +++ /dev/null @@ -1,22 +0,0 @@ -package service - -import ( - netHttp "net/http" - - "github.com/plgd-dev/hub/v2/pkg/log" - "github.com/plgd-dev/kit/v2/codec/json" -) - -func errToJsonRes(err error) map[string]string { - return map[string]string{"err": err.Error()} -} - -func writeError(w netHttp.ResponseWriter, err error, status int) { - if err == nil { - w.WriteHeader(netHttp.StatusNoContent) - return - } - log.Errorf("%v", err) - b, _ := json.Encode(errToJsonRes(err)) - netHttp.Error(w, string(b), status) -} diff --git a/m2m-oauth-server/store/cqldb/tokens.go b/m2m-oauth-server/store/cqldb/tokens.go index b3bd34e74..bbc8f6d80 100644 --- a/m2m-oauth-server/store/cqldb/tokens.go +++ b/m2m-oauth-server/store/cqldb/tokens.go @@ -16,9 +16,6 @@ func (s *Store) GetTokens(context.Context, string, *pb.GetTokensRequest, store.P func (s *Store) DeleteTokens(context.Context) error { return store.ErrNotSupported } -func (s *Store) GetBlacklistedTokens(context.Context, string, *pb.GetBlacklistedTokensRequest, store.ProcessTokens) error { - return store.ErrNotSupported -} func (s *Store) BlacklistTokens(context.Context, string, *pb.BlacklistTokensRequest) (*pb.BlacklistTokensResponse, error) { return nil, store.ErrNotSupported } diff --git a/m2m-oauth-server/store/mongodb/tokens.go b/m2m-oauth-server/store/mongodb/tokens.go index 93d3e28e1..26f69e02e 100644 --- a/m2m-oauth-server/store/mongodb/tokens.go +++ b/m2m-oauth-server/store/mongodb/tokens.go @@ -11,11 +11,10 @@ import ( "github.com/plgd-dev/hub/v2/pkg/mongodb" "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/mongo" - "go.mongodb.org/mongo-driver/mongo/options" ) func (s *Store) CreateToken(ctx context.Context, owner string, token *pb.Token) (*pb.Token, error) { - if owner == "" { + if token.Owner == "" { token.Owner = owner } if token.GetId() == "" { @@ -111,35 +110,6 @@ func (s *Store) DeleteTokens(ctx context.Context, now time.Time) error { return err } -func (s *Store) GetBlacklistedTokens(ctx context.Context, owner string, req *pb.GetBlacklistedTokensRequest, process store.ProcessTokens) error { - if owner == "" { - return store.ErrInvalidArgument - } - filter := bson.D{ - {Key: pb.OwnerKey, Value: owner}, - { - Key: mongodb.Or, Value: bson.A{ - bson.M{ - pb.ExpirationKey: bson.M{"$gte": time.Now().UnixNano()}, - }, - bson.M{ - pb.ExpirationKey: bson.M{mongodb.Exists: false}, - }, - bson.M{ - pb.ExpirationKey: int64(0), - }, - }, - }, - {Key: pb.BlackListedTimestampKey, Value: bson.M{"$gt": req.GetTimestamp()}}, - {Key: pb.BlackListedFlagKey, Value: true}, - } - cur, err := s.Store.Collection(tokensCol).Find(ctx, filter, options.Find().SetHint(expirationOwnerBlacklistedIndex.Keys)) - if err != nil { - return err - } - return processCursor(ctx, cur, process) -} - func (s *Store) BlacklistTokens(ctx context.Context, owner string, req *pb.BlacklistTokensRequest) (*pb.BlacklistTokensResponse, error) { if owner == "" { return nil, store.ErrInvalidArgument diff --git a/m2m-oauth-server/store/mongodb/tokens_test.go b/m2m-oauth-server/store/mongodb/tokens_test.go index face3fe41..d37bf4698 100644 --- a/m2m-oauth-server/store/mongodb/tokens_test.go +++ b/m2m-oauth-server/store/mongodb/tokens_test.go @@ -22,20 +22,20 @@ func TestGetTokens(t *testing.T) { owner := "testOwner" tokens := []*pb.Token{ { - Id: "token1", - Owner: owner, - Version: 0, - Name: "name1", - Timestamp: time.Now().UnixNano(), - ClientId: "client1", + Id: "token1", + Owner: owner, + Version: 0, + Name: "name1", + IssuedAt: time.Now().UnixNano(), + ClientId: "client1", }, { - Id: "token2", - Owner: owner, - Version: 0, - Name: "name2", - Timestamp: time.Now().UnixNano(), - ClientId: "client1", + Id: "token2", + Owner: owner, + Version: 0, + Name: "name2", + IssuedAt: time.Now().UnixNano(), + ClientId: "client1", Blacklisted: &pb.Token_BlackListed{ Flag: true, Timestamp: time.Now().UnixNano(), @@ -119,132 +119,6 @@ func TestGetTokens(t *testing.T) { } } -func TestGetBlacklistedTokens(t *testing.T) { - s, cleanUpStore := test.NewMongoStore(t) - defer cleanUpStore() - - ctx, cancel := context.WithTimeout(context.Background(), config.TEST_TIMEOUT*100) - defer cancel() - - owner := "testOwner" - tokens := []*pb.Token{ - { - Id: "token1", - Owner: owner, - Version: 0, - Name: "name1", - Timestamp: time.Now().UnixNano(), - ClientId: "client1", - Blacklisted: &pb.Token_BlackListed{ - Flag: true, - Timestamp: time.Now().UnixNano(), - }, - }, - { - Id: "token2", - Owner: owner, - Version: 0, - Name: "name2", - Timestamp: time.Now().UnixNano(), - ClientId: "client1", - Blacklisted: &pb.Token_BlackListed{ - Flag: true, - Timestamp: time.Now().Add(time.Hour).UnixNano(), - }, - }, - { - Id: "token3", - Owner: owner, - Version: 0, - Name: "name3", - Timestamp: time.Now().UnixNano(), - ClientId: "client1", - }, - } - - type args struct { - ctx context.Context - owner string - req *pb.GetBlacklistedTokensRequest - } - tests := []struct { - name string - args args - wantLen int - }{ - { - name: "all blacklisted tokens", - args: args{ - ctx: ctx, - owner: owner, - req: &pb.GetBlacklistedTokensRequest{}, - }, - wantLen: 2, - }, - { - name: "all blacklisted tokens with timestamp", - args: args{ - ctx: ctx, - owner: owner, - req: &pb.GetBlacklistedTokensRequest{ - Timestamp: time.Now().UnixNano(), - }, - }, - wantLen: 1, - }, - { - name: "all blacklisted tokens with timestamp in the future", - args: args{ - ctx: ctx, - owner: owner, - req: &pb.GetBlacklistedTokensRequest{ - Timestamp: time.Now().Add(2 * time.Hour).UnixNano(), - }, - }, - wantLen: 0, - }, - { - name: "all blacklisted tokens with timestamp in the past", - args: args{ - ctx: ctx, - owner: owner, - req: &pb.GetBlacklistedTokensRequest{ - Timestamp: time.Now().Add(-2 * time.Hour).UnixNano(), - }, - }, - wantLen: 2, - }, - { - name: "another owner", - args: args{ - ctx: ctx, - owner: "anotherOwner", - req: &pb.GetBlacklistedTokensRequest{}, - }, - wantLen: 0, - }, - } - - for _, token := range tokens { - _, err := s.CreateToken(ctx, owner, token) - require.NoError(t, err) - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - result := make(map[string]*pb.Token) - process := func(token *pb.Token) error { - result[token.GetId()] = token - return nil - } - - err := s.GetBlacklistedTokens(tt.args.ctx, tt.args.owner, tt.args.req, process) - require.NoError(t, err) - require.Len(t, result, tt.wantLen) - }) - } -} - func TestBlacklistTokens(t *testing.T) { s, cleanUpStore := test.NewMongoStore(t) defer cleanUpStore() @@ -255,28 +129,28 @@ func TestBlacklistTokens(t *testing.T) { owner := "testOwner" tokens := []*pb.Token{ { - Id: "token1", - Owner: owner, - Version: 0, - Name: "name1", - Timestamp: time.Now().UnixNano(), - ClientId: "client1", + Id: "token1", + Owner: owner, + Version: 0, + Name: "name1", + IssuedAt: time.Now().UnixNano(), + ClientId: "client1", }, { - Id: "token2", - Owner: owner, - Version: 0, - Name: "name2", - Timestamp: time.Now().UnixNano(), - ClientId: "client1", + Id: "token2", + Owner: owner, + Version: 0, + Name: "name2", + IssuedAt: time.Now().UnixNano(), + ClientId: "client1", }, { - Id: "token3", - Owner: owner, - Version: 0, - Name: "name3", - Timestamp: time.Now().UnixNano(), - ClientId: "client1", + Id: "token3", + Owner: owner, + Version: 0, + Name: "name3", + IssuedAt: time.Now().UnixNano(), + ClientId: "client1", }, } @@ -295,24 +169,24 @@ func TestBlacklistTokens(t *testing.T) { blacklistedTokens := []*pb.Token{ { - Id: "token1", - Owner: owner, - Version: 0, - Name: "name1", - Timestamp: time.Now().UnixNano(), - ClientId: "client1", + Id: "token1", + Owner: owner, + Version: 0, + Name: "name1", + IssuedAt: time.Now().UnixNano(), + ClientId: "client1", Blacklisted: &pb.Token_BlackListed{ Flag: true, Timestamp: time.Now().UnixNano(), }, }, { - Id: "token2", - Owner: owner, - Version: 0, - Name: "name2", - Timestamp: time.Now().UnixNano(), - ClientId: "client1", + Id: "token2", + Owner: owner, + Version: 0, + Name: "name2", + IssuedAt: time.Now().UnixNano(), + ClientId: "client1", Blacklisted: &pb.Token_BlackListed{ Flag: true, Timestamp: time.Now().UnixNano(), @@ -352,7 +226,7 @@ func TestDeleteTokens(t *testing.T) { Owner: owner, Version: 0, Name: "name1", - Timestamp: time.Now().UnixNano(), + IssuedAt: time.Now().UnixNano(), ClientId: "client1", Expiration: time.Now().Add(time.Minute * 10).UnixNano(), Blacklisted: &pb.Token_BlackListed{ @@ -365,7 +239,7 @@ func TestDeleteTokens(t *testing.T) { Owner: owner, Version: 0, Name: "name2", - Timestamp: time.Now().UnixNano(), + IssuedAt: time.Now().UnixNano(), ClientId: "client1", Expiration: time.Now().Add(time.Minute * 10).UnixNano(), Blacklisted: &pb.Token_BlackListed{ @@ -374,12 +248,12 @@ func TestDeleteTokens(t *testing.T) { }, }, { - Id: "token3", - Owner: owner, - Version: 0, - Name: "name3", - Timestamp: time.Now().UnixNano(), - ClientId: "client1", + Id: "token3", + Owner: owner, + Version: 0, + Name: "name3", + IssuedAt: time.Now().UnixNano(), + ClientId: "client1", }, } @@ -393,12 +267,12 @@ func TestDeleteTokens(t *testing.T) { remainingTokens := []*pb.Token{ { - Id: "token3", - Owner: owner, - Version: 0, - Name: "name3", - Timestamp: time.Now().UnixNano(), - ClientId: "client1", + Id: "token3", + Owner: owner, + Version: 0, + Name: "name3", + IssuedAt: time.Now().UnixNano(), + ClientId: "client1", }, } diff --git a/m2m-oauth-server/store/store.go b/m2m-oauth-server/store/store.go index 6aeac71ba..3a44d7c89 100644 --- a/m2m-oauth-server/store/store.go +++ b/m2m-oauth-server/store/store.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + "time" "github.com/plgd-dev/hub/v2/m2m-oauth-server/pb" "go.mongodb.org/mongo-driver/mongo" @@ -66,15 +67,12 @@ func (i *MongoIterator[T]) Err() error { type Store interface { // CreateToken creates a new token. If the token already exists, it will throw an error. - CreateToken(ctx context.Context, token *pb.Token) (*pb.Token, error) + CreateToken(ctx context.Context, owner string, token *pb.Token) (*pb.Token, error) // GetTokens loads tokens from the database. GetTokens(ctx context.Context, owner string, query *pb.GetTokensRequest, p ProcessTokens) error // DeleteTokens deletes blacklisted expired tokens from the database. - DeleteTokens(ctx context.Context) error - - // GetBlacklistedTokens get blacklisted tokens which are not expired. - GetBlacklistedTokens(ctx context.Context, owner string, query *pb.GetBlacklistedTokensRequest, p ProcessTokens) error + DeleteTokens(ctx context.Context, now time.Time) error // Set tokens as blacklisted BlacklistTokens(ctx context.Context, owner string, req *pb.BlacklistTokensRequest) (*pb.BlacklistTokensResponse, error) diff --git a/m2m-oauth-server/swagger.yaml b/m2m-oauth-server/swagger.yaml index 8ea84b2a3..42000bc1e 100644 --- a/m2m-oauth-server/swagger.yaml +++ b/m2m-oauth-server/swagger.yaml @@ -1,11 +1,24 @@ -openapi: 3.0.0 +openapi: 3.0.1 info: - title: m2m-oauth-server API - description: API documentation for m2m-oauth-server - version: 1.0.0 + title: PLGD M2M API + description: API for to manage m2m tokens in PLGD + contact: + name: plgd.dev + url: https://github.com/plgd-dev/hub + email: info@plgd.dev + license: + name: Apache License 2.0 + url: https://github.com/plgd-dev/hub/blob/v2/LICENSE + version: "1.0" +servers: +- url: / +tags: +- name: M2MOAuthService paths: /m2m-oauth-server/oauth/token: post: + tags: + - Native OAuth summary: Obtain an OAuth token description: This endpoint is used to obtain an OAuth token by providing the necessary credentials and parameters. requestBody: @@ -58,9 +71,10 @@ paths: description: "The scopes granted for the token." '401': description: Unauthorized. The request requires valid user authentication. - /m2m-oauth-server/.well-known/jwks.json: get: + tags: + - Native OAuth summary: Retrieve JSON Web Key Set (JWKS) description: This endpoint retrieves the JSON Web Key Set (JWKS), which contains the public keys used to verify the JWT tokens. responses: @@ -68,9 +82,10 @@ paths: description: JSON Web Key Set retrieved successfully '404': description: JWKS not found. The requested JWKS does not exist. - /m2m-oauth-server/.well-known/openid-configuration: get: + tags: + - Native OAuth summary: Retrieve OpenID Configuration description: This endpoint retrieves the OpenID Configuration, which contains the necessary information for clients to interact with the OAuth server. responses: @@ -78,3 +93,272 @@ paths: description: OpenID Configuration retrieved successfully '404': description: OpenID Configuration not found. The requested OpenID Configuration does not exist. + /m2m-oauth-server/api/v1/blacklist: + post: + tags: + - Tokens + summary: Blacklists/revokes tokens + operationId: M2MOAuthService_BlacklistTokens + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/pbBlacklistTokensRequest' + application/protojson: + schema: + $ref: '#/components/schemas/pbBlacklistTokensRequest' + required: true + responses: + "200": + description: A successful response. + content: + application/json: + schema: + $ref: '#/components/schemas/pbBlacklistTokensResponse' + application/protojson: + schema: + $ref: '#/components/schemas/pbBlacklistTokensResponse' + default: + description: An unexpected error response. + content: + application/json: + schema: + $ref: '#/components/schemas/rpcStatus' + application/protojson: + schema: + $ref: '#/components/schemas/rpcStatus' + x-codegen-request-body-name: body + /m2m-oauth-server/api/v1/tokens: + get: + tags: + - Tokens + summary: Returns all tokens of the owner + operationId: M2MOAuthService_GetTokens + parameters: + - name: idFilter + in: query + style: form + explode: true + schema: + type: array + items: + type: string + - name: audienceFilter + in: query + style: form + explode: true + schema: + type: array + items: + type: string + - name: includeBlacklisted + in: query + schema: + type: boolean + responses: + "200": + description: A successful response.(streaming responses) + content: + application/json: + schema: + title: Stream result of pbToken + type: object + properties: + result: + $ref: '#/components/schemas/pbToken' + error: + $ref: '#/components/schemas/rpcStatus' + application/protojson: + schema: + title: Stream result of pbToken + type: object + properties: + result: + $ref: '#/components/schemas/pbToken' + error: + $ref: '#/components/schemas/rpcStatus' + default: + description: An unexpected error response. + content: + application/json: + schema: + $ref: '#/components/schemas/rpcStatus' + application/protojson: + schema: + $ref: '#/components/schemas/rpcStatus' + post: + tags: + - Tokens + summary: Creates a new token + operationId: M2MOAuthService_CreateToken + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/pbCreateTokenRequest' + application/protojson: + schema: + $ref: '#/components/schemas/pbCreateTokenRequest' + required: true + responses: + "200": + description: A successful response. + content: + application/json: + schema: + $ref: '#/components/schemas/pbCreateTokenResponse' + application/protojson: + schema: + $ref: '#/components/schemas/pbCreateTokenResponse' + default: + description: An unexpected error response. + content: + application/json: + schema: + $ref: '#/components/schemas/rpcStatus' + application/protojson: + schema: + $ref: '#/components/schemas/rpcStatus' + x-codegen-request-body-name: body +components: + schemas: + TokenBlackListed: + type: object + properties: + flag: + title: "Blacklisted/revoked enabled flag, if once token has been blacklisted/revoked\ + \ then it can't be unblacklisted/unrevoked" + type: boolean + timestamp: + title: Unix timestamp in ns when the token has been blacklisted/revoked + type: string + format: int64 + pbBlacklistTokensRequest: + type: object + properties: + idFilter: + type: array + items: + type: string + pbBlacklistTokensResponse: + type: object + properties: + count: + type: string + format: int64 + pbCreateTokenRequest: + type: object + properties: + clientId: + type: string + clientSecret: + type: string + audience: + type: array + items: + type: string + scope: + type: array + items: + type: string + timeToLive: + type: string + format: int64 + clientAssertionType: + type: string + clientAssertion: + type: string + tokenName: + type: string + grantType: + type: string + pbCreateTokenResponse: + type: object + properties: + accessToken: + type: string + tokenType: + type: string + expiresIn: + type: string + format: int64 + scope: + type: array + items: + type: string + pbToken: + title: Tokens are deleted from DB after they are expired and blacklisted/revoked + type: object + properties: + id: + title: Token ID / jti + type: string + description: "@gotags: bson:\"_id\"" + version: + title: Incremental version for update + type: string + format: uint64 + name: + title: User-friendly token name + type: string + owner: + title: Owner of the token + type: string + issuedAt: + title: Unix timestamp in ns when the condition has been created/updated + type: string + format: int64 + audience: + title: Token Audience + type: array + items: + type: string + scope: + title: Token scopes + type: array + items: + type: string + expiration: + title: Original token expiration + type: string + format: int64 + clientId: + title: Client ID + type: string + originalTokenClaims: + title: Original token claims + type: object + blacklisted: + $ref: '#/components/schemas/TokenBlackListed' + description: driven by resource change event + protobufAny: + type: object + properties: + '@type': + type: string + additionalProperties: + type: object + protobufNullValue: + type: string + description: |- + `NullValue` is a singleton enumeration to represent the null value for the + `Value` type union. + + The JSON representation for `NullValue` is JSON `null`. + + - NULL_VALUE: Null value. + default: NULL_VALUE + enum: + - NULL_VALUE + rpcStatus: + type: object + properties: + code: + type: integer + format: int32 + message: + type: string + details: + type: array + items: + $ref: '#/components/schemas/protobufAny' diff --git a/m2m-oauth-server/test/service.go b/m2m-oauth-server/test/service.go index 02db193dc..aeef80772 100644 --- a/m2m-oauth-server/test/service.go +++ b/m2m-oauth-server/test/service.go @@ -12,6 +12,7 @@ import ( "github.com/plgd-dev/hub/v2/pkg/log" "github.com/plgd-dev/hub/v2/pkg/mongodb" "github.com/plgd-dev/hub/v2/test/config" + testHttp "github.com/plgd-dev/hub/v2/test/http" "github.com/stretchr/testify/require" "go.opentelemetry.io/otel/trace/noop" ) @@ -63,3 +64,7 @@ func NewMongoStore(t require.TestingT) (*storeMongo.Store, func()) { return store, cleanUp } + +func HTTPURI(uri string) string { + return testHttp.HTTPS_SCHEME + config.M2M_OAUTH_SERVER_HTTP_HOST + uri +} diff --git a/m2m-oauth-server/test/test.go b/m2m-oauth-server/test/test.go index 1fe048bc8..40091d91e 100644 --- a/m2m-oauth-server/test/test.go +++ b/m2m-oauth-server/test/test.go @@ -13,6 +13,7 @@ import ( "testing" "time" + oauthsigner "github.com/plgd-dev/hub/v2/m2m-oauth-server/oauthSigner" "github.com/plgd-dev/hub/v2/m2m-oauth-server/service" "github.com/plgd-dev/hub/v2/m2m-oauth-server/uri" "github.com/plgd-dev/hub/v2/pkg/config/property/urischeme" @@ -32,30 +33,31 @@ const ( DeviceIDClaim = testOAuthUri.DeviceIDClaimKey ) -var ServiceOAuthClient = service.Client{ +var ServiceOAuthClient = oauthsigner.Client{ ID: "serviceClient", SecretFile: "data:,serviceClientSecret", + Owner: "1", AccessTokenLifetime: 0, - AllowedGrantTypes: []service.GrantType{service.GrantTypeClientCredentials}, + AllowedGrantTypes: []oauthsigner.GrantType{oauthsigner.GrantTypeClientCredentials}, AllowedAudiences: nil, AllowedScopes: nil, InsertTokenClaims: map[string]interface{}{"hardcodedClaim": true}, } -var JWTPrivateKeyOAuthClient = service.Client{ +var JWTPrivateKeyOAuthClient = oauthsigner.Client{ ID: "JWTPrivateKeyClient", SecretFile: "data:,JWTPrivateKeyClientSecret", AccessTokenLifetime: 0, - AllowedGrantTypes: []service.GrantType{service.GrantTypeClientCredentials}, + AllowedGrantTypes: []oauthsigner.GrantType{oauthsigner.GrantTypeClientCredentials}, AllowedAudiences: nil, AllowedScopes: nil, - JWTPrivateKey: service.PrivateKeyJWTConfig{ + JWTPrivateKey: oauthsigner.PrivateKeyJWTConfig{ Enabled: true, Authorization: config.MakeValidatorConfig(), }, } -var OAuthClients = service.OAuthClientsConfig{ +var OAuthClients = oauthsigner.OAuthClientsConfig{ &ServiceOAuthClient, &JWTPrivateKeyOAuthClient, } @@ -65,13 +67,13 @@ func MakeConfig(t require.TestingT) service.Config { cfg.Log = log.MakeDefaultConfig() - cfg.APIs.HTTP.Connection = config.MakeListenerConfig(config.M2M_OAUTH_SERVER_HTTP_HOST) - cfg.APIs.HTTP.Connection.TLS.ClientCertificateRequired = false + cfg.APIs.HTTP.Addr = config.M2M_OAUTH_SERVER_HTTP_HOST cfg.APIs.HTTP.Server = config.MakeHttpServerConfig() cfg.Clients.OpenTelemetryCollector = kitNetHttp.OpenTelemetryCollectorConfig{ Config: config.MakeOpenTelemetryCollectorClient(), } - + cfg.APIs.GRPC = config.MakeGrpcServerConfig(config.M2M_OAUTH_SERVER_HOST) + cfg.APIs.GRPC.TLS.ClientCertificateRequired = false cfg.Clients.Storage = MakeStoreConfig() cfg.OAuthSigner.PrivateKeyFile = urischeme.URIScheme(os.Getenv("M2M_OAUTH_SERVER_PRIVATE_KEY")) @@ -203,7 +205,7 @@ func GetAccessToken(t *testing.T, expectedCode int, opts ...func(opts *AccessTok Host: config.M2M_OAUTH_SERVER_HTTP_HOST, ClientID: ServiceOAuthClient.ID, ClientSecret: GetSecret(t, ServiceOAuthClient.ID), - GrantType: string(service.GrantTypeClientCredentials), + GrantType: string(oauthsigner.GrantTypeClientCredentials), Ctx: context.Background(), } for _, o := range opts { diff --git a/m2m-oauth-server/uri/uri.go b/m2m-oauth-server/uri/uri.go index 58c553495..d6013bcd9 100644 --- a/m2m-oauth-server/uri/uri.go +++ b/m2m-oauth-server/uri/uri.go @@ -5,19 +5,25 @@ import "github.com/lestrrat-go/jwx/v2/jwt" const ( ClientIDKey = "client_id" ClientSecretKey = "client_secret" - TokenName = "token_name" + TokenNameKey = "token_name" ScopeKey = "scope" GrantTypeKey = "grant_type" + TimeToLiveKey = "time_to_live" AudienceKey = jwt.AudienceKey AccessTokenKey = "access_token" ClientAssertionTypeKey = "client_assertion_type" ClientAssertionTypeJWT = "urn:ietf:params:oauth:client-assertion-type:jwt-bearer" ClientAssertionKey = "client_assertion" + TokenTypeKey = "token_type" + ExpiresInKey = "expires_in" OriginalTokenClaims = "https://plgd.dev/originalClaims" Base = "/m2m-oauth-server" + API = Base + "/api/v1" Token = Base + "/oauth/token" JWKs = Base + "/.well-known/jwks.json" OpenIDConfiguration = Base + "/.well-known/openid-configuration" + Tokens = API + "/tokens" + BlacklistTokens = API + "/blacklist" ) diff --git a/pkg/security/jwt/claims.go b/pkg/security/jwt/claims.go index d46071f2c..2e8aa9300 100644 --- a/pkg/security/jwt/claims.go +++ b/pkg/security/jwt/claims.go @@ -22,7 +22,7 @@ const ( ClaimID = "jti" ClaimEmail = "email" ClaimClientID = "client_id" - ClaimName = "n" + ClaimName = "name" ) var ErrOwnerClaimInvalid = errors.New("owner claim is invalid") diff --git a/pkg/security/jwt/validator/validator.go b/pkg/security/jwt/validator/validator.go index 30036f3a4..95c67dc49 100644 --- a/pkg/security/jwt/validator/validator.go +++ b/pkg/security/jwt/validator/validator.go @@ -2,7 +2,9 @@ package validator import ( "context" + "errors" "fmt" + "net/http" "github.com/golang-jwt/jwt/v5" "github.com/plgd-dev/hub/v2/pkg/fn" @@ -38,7 +40,26 @@ func (v *Validator) GetParser() *jwtValidator.Validator { return v.validator } -func New(ctx context.Context, config Config, fileWatcher *fsnotify.Watcher, logger log.Logger, tracerProvider trace.TracerProvider) (*Validator, error) { +type GetOpenIDConfigurationFunc func(ctx context.Context, c *http.Client, authority string) (openid.Config, error) + +type Options struct { + GetOpenIDConfiguration GetOpenIDConfigurationFunc +} + +func WithGetOpenIDConfiguration(f GetOpenIDConfigurationFunc) func(o *Options) { + return func(o *Options) { + o.GetOpenIDConfiguration = f + } +} + +func New(ctx context.Context, config Config, fileWatcher *fsnotify.Watcher, logger log.Logger, tracerProvider trace.TracerProvider, opts ...func(o *Options)) (*Validator, error) { + options := Options{ + GetOpenIDConfiguration: openid.GetConfiguration, + } + for _, o := range opts { + o(&options) + } + keys := jwtValidator.NewMultiKeyCache() var onClose fn.FuncList openIDConfigurations := make([]openid.Config, 0, len(config.Endpoints)) @@ -51,6 +72,10 @@ func New(ctx context.Context, config Config, fileWatcher *fsnotify.Watcher, logg ctx2, cancel := context.WithTimeout(ctx, authority.HTTP.Timeout) defer cancel() + if options.GetOpenIDConfiguration == nil { + return nil, errors.New("GetOpenIDConfiguration is nil") + } + openIDCfg, err := openid.GetConfiguration(ctx2, httpClient.HTTP(), authority.Authority) if err != nil { onClose.Execute() diff --git a/pkg/security/openid/config.go b/pkg/security/openid/config.go index 448fe8761..f66f4044a 100644 --- a/pkg/security/openid/config.go +++ b/pkg/security/openid/config.go @@ -10,6 +10,7 @@ type Config struct { UserInfoURL string `json:"userinfo_endpoint,omitempty"` Algorithms []string `json:"id_token_signing_alg_values_supported,omitempty"` EndSessionEndpoint string `json:"end_session_endpoint,omitempty"` + PlgdTokensEndpoint string `json:"plgd_tokens_endpoint,omitempty"` } func (c Config) Validate() error { diff --git a/snippet-service/service/http/service.go b/snippet-service/service/http/service.go index d9ca71cae..ed154de70 100644 --- a/snippet-service/service/http/service.go +++ b/snippet-service/service/http/service.go @@ -3,7 +3,6 @@ package http import ( "fmt" - "github.com/plgd-dev/hub/v2/http-gateway/uri" "github.com/plgd-dev/hub/v2/pkg/fsnotify" "github.com/plgd-dev/hub/v2/pkg/log" pkgHttp "github.com/plgd-dev/hub/v2/pkg/net/http" @@ -25,7 +24,7 @@ func New(serviceName string, config Config, snippetServiceServer *grpcService.Sn HTTPConnection: config.Connection, HTTPServer: config.Server, ServiceName: serviceName, - AuthRules: pkgHttp.NewDefaultAuthorizationRules(uri.API), + AuthRules: pkgHttp.NewDefaultAuthorizationRules(API), FileWatcher: fileWatcher, Logger: logger, TraceProvider: tracerProvider, diff --git a/test/config/config.go b/test/config/config.go index 9e87f2871..97e0df347 100644 --- a/test/config/config.go +++ b/test/config/config.go @@ -45,6 +45,7 @@ const ( CERTIFICATE_AUTHORITY_HOST = "localhost:20011" CERTIFICATE_AUTHORITY_HTTP_HOST = "localhost:20012" M2M_OAUTH_SERVER_HTTP_HOST = "localhost:20013" + M2M_OAUTH_SERVER_HOST = "localhost:20016" SNIPPET_SERVICE_HOST = "localhost:20014" SNIPPET_SERVICE_HTTP_HOST = "localhost:20015" GRPC_GW_HOST = "localhost:20005" From 2ae5d653af6ecf469c3cda95f120feffd5eef501 Mon Sep 17 00:00:00 2001 From: Jozef Kralik Date: Thu, 18 Jul 2024 07:59:25 +0000 Subject: [PATCH 06/31] store set index --- .../service/http/getTokens_test.go | 59 +++++++++++++------ m2m-oauth-server/store/mongodb/store.go | 8 +-- m2m-oauth-server/store/mongodb/tokens.go | 29 ++++++--- 3 files changed, 66 insertions(+), 30 deletions(-) diff --git a/m2m-oauth-server/service/http/getTokens_test.go b/m2m-oauth-server/service/http/getTokens_test.go index c81718fd1..91a559ad0 100644 --- a/m2m-oauth-server/service/http/getTokens_test.go +++ b/m2m-oauth-server/service/http/getTokens_test.go @@ -21,6 +21,7 @@ import ( testOAuthTest "github.com/plgd-dev/hub/v2/test/oauth-server/test" testService "github.com/plgd-dev/hub/v2/test/service" "github.com/stretchr/testify/require" + "google.golang.org/protobuf/types/known/structpb" ) func createTokens(ctx context.Context, t *testing.T, createTokens []*pb.CreateTokenRequest) []string { @@ -103,6 +104,8 @@ func TestGetTokens(t *testing.T) { blacklistTokenIDs := createTokens(ctx, t, blacklistedTokens) blacklistTokens(ctx, t, blacklistTokenIDs, token) + claims, err := jwt.ParseToken(token) + require.NoError(t, err) type args struct { req *pb.GetTokensRequest @@ -126,17 +129,22 @@ func TestGetTokens(t *testing.T) { wantHTTPCode: http.StatusOK, want: map[string]*pb.Token{ tokenIDs[0]: { - ClientId: m2mOauthServerTest.ServiceOAuthClient.ID, + ClientId: tokens[0].GetClientId(), Id: tokenIDs[0], Name: tokens[0].GetTokenName(), }, tokenIDs[1]: { - ClientId: m2mOauthServerTest.JWTPrivateKeyOAuthClient.ID, + ClientId: tokens[1].GetClientId(), Id: tokenIDs[1], Name: tokens[1].GetTokenName(), + OriginalTokenClaims: func() *structpb.Value { + v, err2 := structpb.NewValue(map[string]interface{}(claims)) + require.NoError(t, err2) + return v + }(), }, blacklistTokenIDs[0]: { - ClientId: m2mOauthServerTest.ServiceOAuthClient.ID, + ClientId: blacklistedTokens[0].GetClientId(), Id: blacklistTokenIDs[0], Name: blacklistedTokens[0].GetTokenName(), Blacklisted: &pb.Token_BlackListed{ @@ -154,14 +162,19 @@ func TestGetTokens(t *testing.T) { wantHTTPCode: http.StatusOK, want: map[string]*pb.Token{ tokenIDs[0]: { - ClientId: m2mOauthServerTest.ServiceOAuthClient.ID, + ClientId: tokens[0].GetClientId(), Id: tokenIDs[0], Name: tokens[0].GetTokenName(), }, tokenIDs[1]: { - ClientId: m2mOauthServerTest.JWTPrivateKeyOAuthClient.ID, + ClientId: tokens[1].GetClientId(), Id: tokenIDs[1], Name: tokens[1].GetTokenName(), + OriginalTokenClaims: func() *structpb.Value { + v, err2 := structpb.NewValue(map[string]interface{}(claims)) + require.NoError(t, err2) + return v + }(), }, }, }, @@ -169,16 +182,21 @@ func TestGetTokens(t *testing.T) { name: "get certain tokens with excluded blacklisted", args: args{ req: &pb.GetTokensRequest{ - IdFilter: []string{tokenIDs[0], blacklistTokenIDs[0]}, + IdFilter: []string{tokenIDs[1], blacklistTokenIDs[0]}, }, token: token, }, wantHTTPCode: http.StatusOK, want: map[string]*pb.Token{ - tokenIDs[0]: { - ClientId: m2mOauthServerTest.ServiceOAuthClient.ID, - Id: tokenIDs[0], - Name: tokens[0].GetTokenName(), + tokenIDs[1]: { + ClientId: tokens[1].GetClientId(), + Id: tokenIDs[1], + Name: tokens[1].GetTokenName(), + OriginalTokenClaims: func() *structpb.Value { + v, err2 := structpb.NewValue(map[string]interface{}(claims)) + require.NoError(t, err2) + return v + }(), }, }, }, @@ -186,20 +204,25 @@ func TestGetTokens(t *testing.T) { name: "get certain tokens with included blacklisted", args: args{ req: &pb.GetTokensRequest{ - IdFilter: []string{tokenIDs[0], blacklistTokenIDs[0]}, + IdFilter: []string{tokenIDs[1], blacklistTokenIDs[0]}, IncludeBlacklisted: true, }, token: token, }, wantHTTPCode: http.StatusOK, want: map[string]*pb.Token{ - tokenIDs[0]: { - ClientId: m2mOauthServerTest.ServiceOAuthClient.ID, - Id: tokenIDs[0], - Name: tokens[0].GetTokenName(), + tokenIDs[1]: { + ClientId: tokens[1].GetClientId(), + Id: tokenIDs[1], + Name: tokens[1].GetTokenName(), + OriginalTokenClaims: func() *structpb.Value { + v, err2 := structpb.NewValue(map[string]interface{}(claims)) + require.NoError(t, err2) + return v + }(), }, blacklistTokenIDs[0]: { - ClientId: m2mOauthServerTest.ServiceOAuthClient.ID, + ClientId: blacklistedTokens[0].GetClientId(), Id: blacklistTokenIDs[0], Name: blacklistedTokens[0].GetTokenName(), Blacklisted: &pb.Token_BlackListed{ @@ -246,8 +269,8 @@ func TestGetTokens(t *testing.T) { require.Equal(t, want.GetClientId(), gotToken.GetClientId()) require.Equal(t, want.GetId(), gotToken.GetId()) require.Equal(t, want.GetName(), gotToken.GetName()) - if gotToken.GetId() == blacklistTokenIDs[0] { - require.NotNil(t, gotToken.GetBlacklisted()) + if want.GetOriginalTokenClaims() != nil { + test.CheckProtobufs(t, want.GetOriginalTokenClaims(), gotToken.GetOriginalTokenClaims(), test.RequireToCheckFunc(require.Equal)) } require.Equal(t, want.GetBlacklisted().GetFlag(), gotToken.GetBlacklisted().GetFlag()) } diff --git a/m2m-oauth-server/store/mongodb/store.go b/m2m-oauth-server/store/mongodb/store.go index 10ebf1915..bca87a192 100644 --- a/m2m-oauth-server/store/mongodb/store.go +++ b/m2m-oauth-server/store/mongodb/store.go @@ -23,12 +23,10 @@ const ( tokensCol = "tokens" ) -var expirationOwnerBlacklistedIndex = mongo.IndexModel{ +var idOwnerIndex = mongo.IndexModel{ Keys: bson.D{ + {Key: "_id", Value: 1}, {Key: pb.OwnerKey, Value: 1}, - {Key: pb.ExpirationKey, Value: 1}, - {Key: pb.BlackListedTimestampKey, Value: 1}, - {Key: pb.BlackListedFlagKey, Value: 1}, }, } @@ -39,7 +37,7 @@ func New(ctx context.Context, cfg *Config, fileWatcher *fsnotify.Watcher, logger } m, err := pkgMongo.NewStoreWithCollections(ctx, &cfg.Mongo, certManager.GetTLSConfig(), tracerProvider, map[string][]mongo.IndexModel{ - tokensCol: {expirationOwnerBlacklistedIndex}, + tokensCol: {idOwnerIndex}, }) if err != nil { certManager.Close() diff --git a/m2m-oauth-server/store/mongodb/tokens.go b/m2m-oauth-server/store/mongodb/tokens.go index 26f69e02e..e0e5b864c 100644 --- a/m2m-oauth-server/store/mongodb/tokens.go +++ b/m2m-oauth-server/store/mongodb/tokens.go @@ -11,6 +11,7 @@ import ( "github.com/plgd-dev/hub/v2/pkg/mongodb" "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/mongo" + "go.mongodb.org/mongo-driver/mongo/options" ) func (s *Store) CreateToken(ctx context.Context, owner string, token *pb.Token) (*pb.Token, error) { @@ -38,17 +39,24 @@ func (s *Store) CreateToken(ctx context.Context, owner string, token *pb.Token) return token, nil } -func toFilter(owner string, req *pb.GetTokensRequest) (filter bson.D) { - if owner != "" { - filter = append(filter, bson.E{Key: pb.OwnerKey, Value: owner}) - } +func toFilter(owner string, req *pb.GetTokensRequest) (filter bson.D, hint interface{}) { + setIdOwnerHint := true if len(req.GetIdFilter()) > 0 { filter = append(filter, bson.E{Key: "_id", Value: bson.M{mongodb.In: req.GetIdFilter()}}) + } else { + setIdOwnerHint = false + } + if owner != "" { + filter = append(filter, bson.E{Key: pb.OwnerKey, Value: owner}) + } else { + setIdOwnerHint = false } if len(req.GetAudienceFilter()) > 0 { filter = append(filter, bson.E{Key: pb.AudienceKey, Value: bson.M{mongodb.In: req.GetAudienceFilter()}}) + setIdOwnerHint = false } if !req.GetIncludeBlacklisted() { + setIdOwnerHint = false filter = append(filter, bson.E{ Key: mongodb.Or, Value: bson.A{ @@ -63,7 +71,10 @@ func toFilter(owner string, req *pb.GetTokensRequest) (filter bson.D) { }, }) } - return filter + if setIdOwnerHint { + hint = idOwnerIndex.Keys + } + return filter, hint } func processCursor[T any](ctx context.Context, cr *mongo.Cursor, process store.Process[T]) error { @@ -92,8 +103,12 @@ func (s *Store) GetTokens(ctx context.Context, owner string, req *pb.GetTokensRe if owner == "" { return store.ErrInvalidArgument } - filter := toFilter(owner, req) - cur, err := s.Store.Collection(tokensCol).Find(ctx, filter) + filter, hint := toFilter(owner, req) + opts := options.Find() + if hint != nil { + opts.SetHint(hint) + } + cur, err := s.Store.Collection(tokensCol).Find(ctx, filter, opts) if err != nil { return err } From 2c526272774472f1eef60642460434279e58b18b Mon Sep 17 00:00:00 2001 From: Jozef Kralik Date: Thu, 18 Jul 2024 08:10:08 +0000 Subject: [PATCH 07/31] fix linter --- m2m-oauth-server/pb/token.go | 4 +- m2m-oauth-server/service/grpc/token.go | 55 ++++++------------- .../service/http/createToken_test.go | 1 - .../service/http/getTokens_test.go | 1 - m2m-oauth-server/service/service.go | 12 ++-- m2m-oauth-server/store/cqldb/tokens.go | 3 + m2m-oauth-server/store/mongodb/tokens.go | 2 +- m2m-oauth-server/store/store.go | 5 -- 8 files changed, 28 insertions(+), 55 deletions(-) diff --git a/m2m-oauth-server/pb/token.go b/m2m-oauth-server/pb/token.go index 480e2a06a..49c89ba40 100644 --- a/m2m-oauth-server/pb/token.go +++ b/m2m-oauth-server/pb/token.go @@ -56,9 +56,9 @@ func replaceStrToInt64(m map[string]interface{}, keys ...string) error { i, err := strconv.ParseInt(str, 10, 64) if err != nil { errs = multierror.Append(errs, fmt.Errorf("cannot convert key %v to int64, %w", k, err)) - return err + } else { + m[k] = i } - m[k] = i } } } diff --git a/m2m-oauth-server/service/grpc/token.go b/m2m-oauth-server/service/grpc/token.go index c30ebd745..128c9ff51 100644 --- a/m2m-oauth-server/service/grpc/token.go +++ b/m2m-oauth-server/service/grpc/token.go @@ -7,8 +7,6 @@ import ( "time" goJwt "github.com/golang-jwt/jwt/v5" - "github.com/lestrrat-go/jwx/v2/jwk" - "github.com/lestrrat-go/jwx/v2/jws" "github.com/lestrrat-go/jwx/v2/jwt" oauthsigner "github.com/plgd-dev/hub/v2/m2m-oauth-server/oauthSigner" "github.com/plgd-dev/hub/v2/m2m-oauth-server/pb" @@ -27,33 +25,28 @@ func setKeyErrorExt(key, info interface{}, err error) error { func makeAccessToken(clientCfg *oauthsigner.Client, tokenReq tokenRequest) (jwt.Token, error) { token := jwt.New() - if err := token.Set(jwt.JwtIDKey, tokenReq.id); err != nil { - return nil, setKeyError(jwt.JwtIDKey, err) - } - sub := getSubject(clientCfg, tokenReq) - if err := token.Set(jwt.SubjectKey, sub); err != nil { - return nil, setKeyError(jwt.SubjectKey, err) - } - if err := token.Set(jwt.AudienceKey, tokenReq.host); err != nil { - return nil, setKeyError(jwt.AudienceKey, err) - } - if err := token.Set(jwt.IssuedAtKey, tokenReq.issuedAt); err != nil { - return nil, setKeyError(jwt.IssuedAtKey, err) + simpleSets := []struct { + key string + val interface{} + }{ + {jwt.JwtIDKey, tokenReq.id}, + {jwt.SubjectKey, getSubject(clientCfg, tokenReq)}, + {jwt.AudienceKey, tokenReq.host}, + {jwt.IssuedAtKey, tokenReq.issuedAt}, + {uri.ScopeKey, tokenReq.scopes}, + {uri.ClientIDKey, clientCfg.ID}, + {jwt.IssuerKey, tokenReq.host}, + } + for _, set := range simpleSets { + if err := token.Set(set.key, set.val); err != nil { + return nil, setKeyError(set.key, err) + } } if !tokenReq.expiration.IsZero() { if err := token.Set(jwt.ExpirationKey, tokenReq.expiration); err != nil { return nil, setKeyError(jwt.ExpirationKey, err) } } - if err := token.Set(uri.ScopeKey, tokenReq.scopes); err != nil { - return nil, setKeyError(uri.ScopeKey, err) - } - if err := token.Set(uri.ClientIDKey, clientCfg.ID); err != nil { - return nil, setKeyError(uri.ClientIDKey, err) - } - if err := token.Set(jwt.IssuerKey, tokenReq.host); err != nil { - return nil, setKeyError(jwt.IssuerKey, err) - } if err := setDeviceIDClaim(token, tokenReq); err != nil { return nil, err } @@ -117,22 +110,6 @@ func setOriginTokenClaims(token jwt.Token, tokenReq tokenRequest) error { return nil } -func makeJWTPayload(key interface{}, jwkKey jwk.Key, data []byte) ([]byte, error) { - hdr := jws.NewHeaders() - if err := hdr.Set(jws.TypeKey, `JWT`); err != nil { - return nil, setKeyError(jws.TypeKey, err) - } - if err := hdr.Set(jws.KeyIDKey, jwkKey.KeyID()); err != nil { - return nil, setKeyError(jws.KeyIDKey, err) - } - - payload, err := jws.Sign(data, jws.WithKey(jwkKey.Algorithm(), key, jws.WithProtectedHeaders(hdr))) - if err != nil { - return nil, fmt.Errorf("failed to create UserToken: %w", err) - } - return payload, nil -} - func getExpirationTime(clientCfg *oauthsigner.Client, tokenReq tokenRequest) time.Time { var expires time.Time ttl := time.Duration(tokenReq.CreateTokenRequest.GetTimeToLive()) * time.Nanosecond diff --git a/m2m-oauth-server/service/http/createToken_test.go b/m2m-oauth-server/service/http/createToken_test.go index ddb9cb1c9..ce19727fb 100644 --- a/m2m-oauth-server/service/http/createToken_test.go +++ b/m2m-oauth-server/service/http/createToken_test.go @@ -78,5 +78,4 @@ func TestCreateToken(t *testing.T) { require.Equal(t, tt.want.GetTokenType(), got.GetTokenType()) }) } - } diff --git a/m2m-oauth-server/service/http/getTokens_test.go b/m2m-oauth-server/service/http/getTokens_test.go index 91a559ad0..d5eec0bf2 100644 --- a/m2m-oauth-server/service/http/getTokens_test.go +++ b/m2m-oauth-server/service/http/getTokens_test.go @@ -276,5 +276,4 @@ func TestGetTokens(t *testing.T) { } }) } - } diff --git a/m2m-oauth-server/service/service.go b/m2m-oauth-server/service/service.go index 9bc0d9c59..e8bb6fb7e 100644 --- a/m2m-oauth-server/service/service.go +++ b/m2m-oauth-server/service/service.go @@ -42,14 +42,14 @@ func createStore(ctx context.Context, config storeConfig.Config, fileWatcher *fs return nil, fmt.Errorf("mongodb: %w", err) } if config.CleanUpDeletedTokens != "" { - scheduler, err := NewExpiredUpdatesChecker(config.CleanUpDeletedTokens, config.ExtendCronParserBySeconds, func() { - err = s.DeleteTokens(ctx, time.Now()) - if err != nil { - log.Errorf("cannot delete expired tokens: %v", err) + scheduler, err2 := NewExpiredUpdatesChecker(config.CleanUpDeletedTokens, config.ExtendCronParserBySeconds, func() { + err2 := s.DeleteTokens(ctx, time.Now()) + if err2 != nil { + log.Errorf("cannot delete expired tokens: %v", err2) } }) - if err != nil { - return nil, fmt.Errorf("cannot create scheduler: %w", err) + if err2 != nil { + return nil, fmt.Errorf("cannot create scheduler: %w", err2) } s.AddCloseFunc(func() { err2 := scheduler.Shutdown() diff --git a/m2m-oauth-server/store/cqldb/tokens.go b/m2m-oauth-server/store/cqldb/tokens.go index bbc8f6d80..26bc183f1 100644 --- a/m2m-oauth-server/store/cqldb/tokens.go +++ b/m2m-oauth-server/store/cqldb/tokens.go @@ -10,12 +10,15 @@ import ( func (s *Store) CreateToken(context.Context, *pb.Token) (*pb.Token, error) { return nil, store.ErrNotSupported } + func (s *Store) GetTokens(context.Context, string, *pb.GetTokensRequest, store.ProcessTokens) error { return store.ErrNotSupported } + func (s *Store) DeleteTokens(context.Context) error { return store.ErrNotSupported } + func (s *Store) BlacklistTokens(context.Context, string, *pb.BlacklistTokensRequest) (*pb.BlacklistTokensResponse, error) { return nil, store.ErrNotSupported } diff --git a/m2m-oauth-server/store/mongodb/tokens.go b/m2m-oauth-server/store/mongodb/tokens.go index e0e5b864c..b670194d7 100644 --- a/m2m-oauth-server/store/mongodb/tokens.go +++ b/m2m-oauth-server/store/mongodb/tokens.go @@ -15,7 +15,7 @@ import ( ) func (s *Store) CreateToken(ctx context.Context, owner string, token *pb.Token) (*pb.Token, error) { - if token.Owner == "" { + if token.GetOwner() == "" { token.Owner = owner } if token.GetId() == "" { diff --git a/m2m-oauth-server/store/store.go b/m2m-oauth-server/store/store.go index 3a44d7c89..c46b2974b 100644 --- a/m2m-oauth-server/store/store.go +++ b/m2m-oauth-server/store/store.go @@ -3,7 +3,6 @@ package store import ( "context" "errors" - "fmt" "time" "github.com/plgd-dev/hub/v2/m2m-oauth-server/pb" @@ -28,10 +27,6 @@ var ( ErrPartialDelete = errors.New("some errors occurred while deleting") ) -func errInvalidArgument(err error) error { - return fmt.Errorf("%w: %w", ErrInvalidArgument, err) -} - func IsDuplicateKeyError(err error) bool { return mongo.IsDuplicateKeyError(err) } From 3a708eb084fed7a759f0e7c8a8fc7bf280b07151 Mon Sep 17 00:00:00 2001 From: Jozef Kralik Date: Thu, 18 Jul 2024 10:44:44 +0000 Subject: [PATCH 08/31] set helm charts --- .../templates/m2m-oauth-server/_helpers.tpl | 19 +- .../templates/m2m-oauth-server/config.yaml | 49 ++++- .../m2m-oauth-server/deployment.yaml | 12 +- .../m2m-oauth-server/grpc-ingress.yaml | 40 +++++ .../m2m-oauth-server/grpc-service.yaml | 25 +++ .../{ingress.yaml => http-ingress.yaml} | 12 +- .../{service.yaml => http-service.yaml} | 14 +- .../m2m-oauth-server/service-crt.yaml | 7 +- .../templates/snippet-service/config.yaml | 8 +- .../templates/snippet-service/deployment.yaml | 13 +- charts/plgd-hub/values.yaml | 167 ++++++++++++------ test/helm/mock.plgd.cloud.yaml | 20 +-- 12 files changed, 252 insertions(+), 134 deletions(-) create mode 100644 charts/plgd-hub/templates/m2m-oauth-server/grpc-ingress.yaml create mode 100644 charts/plgd-hub/templates/m2m-oauth-server/grpc-service.yaml rename charts/plgd-hub/templates/m2m-oauth-server/{ingress.yaml => http-ingress.yaml} (75%) rename charts/plgd-hub/templates/m2m-oauth-server/{service.yaml => http-service.yaml} (54%) diff --git a/charts/plgd-hub/templates/m2m-oauth-server/_helpers.tpl b/charts/plgd-hub/templates/m2m-oauth-server/_helpers.tpl index 0086309d3..1d5a02b9a 100644 --- a/charts/plgd-hub/templates/m2m-oauth-server/_helpers.tpl +++ b/charts/plgd-hub/templates/m2m-oauth-server/_helpers.tpl @@ -17,7 +17,7 @@ {{- end -}} {{- define "plgd-hub.m2moauthserver.createServiceCertByCm" }} - {{- $serviceTls := .Values.m2moauthserver.apis.http.tls.certFile }} + {{- $serviceTls := .Values.m2moauthserver.apis.grpc.tls.certFile }} {{- if $serviceTls }} {{- printf "" -}} {{- else }} @@ -78,14 +78,6 @@ true {{- end }} {{- end }} -{{- define "plgd-hub.m2moauthserver.clientServiceSecretEnabled" -}} -{{- if or .Values.global.m2mOAuthServer.clientServiceSecret .Values.m2moauthserver.clientServiceSecret.enabled }} -true -{{- else }} -{{- printf "" }} -{{- end }} -{{- end }} - {{- define "plgd-hub.m2moauthserver.getPrivateKeyFile" -}} {{- $privateKeyFile := .Values.m2moauthserver.oauthSigner.privateKeyFile }} {{- if and (not $privateKeyFile) (include "plgd-hub.m2moauthserver.privateKeySecretEnabled" $) }} @@ -94,15 +86,6 @@ true {{- printf "%s" $privateKeyFile }} {{- end -}} -{{- define "plgd-hub.m2moauthserver.getClientServiceSecretFile" -}} -{{- $file := "" }} -{{- if include "plgd-hub.m2moauthserver.clientServiceSecretEnabled" $ }} -{{- $file = printf "%s/%s" .Values.m2moauthserver.clientServiceSecret.mountPath .Values.m2moauthserver.clientServiceSecret.fileName }} -{{- end }} -{{- printf "%s" $file }} -{{- end -}} - - {{- define "plgd-hub.m2moauthserver.enabled" -}} {{- if and .Values.m2moauthserver.enabled (include "plgd-hub.m2moauthserver.privateKeySecretEnabled" .) }} true diff --git a/charts/plgd-hub/templates/m2m-oauth-server/config.yaml b/charts/plgd-hub/templates/m2m-oauth-server/config.yaml index a149a9d80..1afc9a45c 100644 --- a/charts/plgd-hub/templates/m2m-oauth-server/config.yaml +++ b/charts/plgd-hub/templates/m2m-oauth-server/config.yaml @@ -1,5 +1,5 @@ {{- if include "plgd-hub.m2moauthserver.enabled" . }} -{{- $oauthServerCertPath := "/certs" }} +{{- $cert := "/certs" }} apiVersion: v1 kind: ConfigMap metadata: @@ -17,16 +17,49 @@ data: encoderConfig: timeEncoder: {{ .log.encoderConfig.timeEncoder }} apis: + grpc: + address: {{ .apis.grpc.address | default (printf "0.0.0.0:%v" .port) | quote }} + sendMsgSize: {{ int64 .apis.grpc.sendMsgSize | default 4194304 }} + recvMsgSize: {{ int64 .apis.grpc.recvMsgSize | default 4194304 }} + enforcementPolicy: + minTime: {{ .apis.grpc.enforcementPolicy.minTime }} + permitWithoutStream: {{ .apis.grpc.enforcementPolicy.permitWithoutStream }} + keepAlive: + # 0s - means infinity + maxConnectionIdle: {{ .apis.grpc.keepAlive.maxConnectionIdle }} + # 0s - means infinity + maxConnectionAge: {{ .apis.grpc.keepAlive.maxConnectionIdle }} + # 0s - means infinity + maxConnectionAgeGrace: {{ .apis.grpc.keepAlive.maxConnectionAgeGrace }} + time: {{ .apis.grpc.keepAlive.maxConnectionIdle }} + timeout: {{ .apis.grpc.keepAlive.maxConnectionIdle }} + tls: + {{- $tls := .apis.grpc.tls }} + {{- include "plgd-hub.internalCertificateConfig" (list $ $tls $cert ) | indent 8 }} + clientCertificateRequired: {{ .apis.grpc.tls.clientCertificateRequired }} + authorization: + {{- $authorization := .apis.grpc.authorization }} + {{- include "plgd-hub.authorizationConfig" (list $ $authorization "m2moauthserver.apis.grpc.authorization" $cert ) | indent 8 }} http: - address: {{ .apis.http.address | default (printf "0.0.0.0:%v" .port) | quote }} + address: {{ .apis.http.address | default (printf "0.0.0.0:%v" .httpPort) | quote }} readTimeout: {{ .apis.http.readTimeout }} readHeaderTimeout: {{ .apis.http.readHeaderTimeout }} writeTimeout: {{ .apis.http.writeTimeout }} idleTimeout: {{ .apis.http.idleTimeout }} - tls: - {{- $tls := .apis.http.tls }} - {{- include "plgd-hub.internalCertificateConfig" (list $ $tls $oauthServerCertPath ) | indent 8 }} - clientCertificateRequired: {{ .apis.http.tls.clientCertificateRequired }} + clients: + storage: + cleanUpDeletedTokens: {{ .clients.storage.cleanUpDeletedTokens | quote }} + use: {{ include "plgd-hub.useDatabase" (list $ . .clients.storage.use) | quote }} + mongoDB: + uri: {{ include "plgd-hub.mongoDBUri" (list $ .clients.storage.mongoDB.uri ) | quote }} + database: {{ .clients.storage.mongoDB.database }} + maxPoolSize: {{ .clients.storage.mongoDB.maxPoolSize }} + maxConnIdleTime: {{ .clients.storage.mongoDB.maxConnIdleTime }} + tls: + {{- $mongoDbTls := .clients.storage.mongoDB.tls }} + {{- include "plgd-hub.internalCertificateConfig" (list $ $mongoDbTls $cert ) | indent 10 }} + useSystemCAPool: {{ .clients.storage.mongoDB.tls.useSystemCAPool }} + {{- include "plgd-hub.openTelemetryExporterConfig" (list $ $cert ) | nindent 6 }} oauthSigner: privateKeyFile: {{ include "plgd-hub.m2moauthserver.getPrivateKeyFile" $ }} domain: {{ include "plgd-hub.m2moauthserver.ingressDomain" $ }} @@ -69,7 +102,7 @@ data: jwtPrivateKey: enabled: {{ .jwtPrivateKey.enabled }} authorization: - {{- $authorization := include "plgd-hub.basicAuthorizationConfig" (list $ .jwtPrivateKey.authorization (printf "m2moauthserver.oauthSigner.clients[%v].jwtPrivateKey.authorization" $idx) $oauthServerCertPath) | fromYaml }} + {{- $authorization := include "plgd-hub.basicAuthorizationConfig" (list $ .jwtPrivateKey.authorization (printf "m2moauthserver.oauthSigner.clients[%v].jwtPrivateKey.authorization" $idx) $cert) | fromYaml }} {{- if $authorization.audience }} audience: {{ $authorization.audience | quote }} {{- end }} @@ -85,6 +118,6 @@ data: {{- end }} {{- end }} clients: - {{- include "plgd-hub.openTelemetryExporterConfig" (list $ $oauthServerCertPath) | nindent 6 }} + {{- include "plgd-hub.openTelemetryExporterConfig" (list $ $cert) | nindent 6 }} {{- end }} {{- end }} \ No newline at end of file diff --git a/charts/plgd-hub/templates/m2m-oauth-server/deployment.yaml b/charts/plgd-hub/templates/m2m-oauth-server/deployment.yaml index 8189d4da8..95a61fa37 100644 --- a/charts/plgd-hub/templates/m2m-oauth-server/deployment.yaml +++ b/charts/plgd-hub/templates/m2m-oauth-server/deployment.yaml @@ -78,6 +78,7 @@ spec: - name: service-crt mountPath: {{ $rdServiceCert }} {{- end }} + {{- include "plgd-hub.extraCAPoolMount" (list . .Values.extraCAPool.authorization) | nindent 12 }} {{- include "plgd-hub.extraCAPoolMount" (list . .Values.extraCAPool.internal) | nindent 12 }} {{- with .Values.m2moauthserver.extraVolumeMounts }} {{- toYaml . | nindent 12 }} @@ -87,22 +88,12 @@ spec: mountPath: {{ .Values.m2moauthserver.privateKey.mountPath }} readOnly: true {{- end }} - {{- if include "plgd-hub.m2moauthserver.clientServiceSecretEnabled" $ }} - - name: {{ .Values.m2moauthserver.clientServiceSecret.volume }} - mountPath: {{ .Values.m2moauthserver.clientServiceSecret.mountPath }} - readOnly: true - {{- end }} volumes: {{- if include "plgd-hub.m2moauthserver.privateKeySecretEnabled" $ }} - name: {{ .Values.m2moauthserver.privateKey.volume }} secret: secretName: {{ .Values.m2moauthserver.privateKey.secretName }} {{- end }} - {{- if include "plgd-hub.m2moauthserver.clientServiceSecretEnabled" $ }} - - name: {{ .Values.m2moauthserver.clientServiceSecret.volume }} - secret: - secretName: {{ .Values.m2moauthserver.clientServiceSecret.secretName }} - {{- end }} - name: {{ .Values.m2moauthserver.config.volume }} configMap: name: {{ include "plgd-hub.m2moauthserver.configName" . }} @@ -111,6 +102,7 @@ spec: secret: secretName: {{ include "plgd-hub.m2moauthserver.serviceCertName" . }} {{- end }} + {{- include "plgd-hub.extraCAPoolVolume" (list . .Values.extraCAPool.authorization) | nindent 8 }} {{- include "plgd-hub.extraCAPoolVolume" (list . .Values.extraCAPool.internal) | nindent 8 }} {{- with .Values.m2moauthserver.extraVolumes }} {{- toYaml . | nindent 8 }} diff --git a/charts/plgd-hub/templates/m2m-oauth-server/grpc-ingress.yaml b/charts/plgd-hub/templates/m2m-oauth-server/grpc-ingress.yaml new file mode 100644 index 000000000..c552baa9d --- /dev/null +++ b/charts/plgd-hub/templates/m2m-oauth-server/grpc-ingress.yaml @@ -0,0 +1,40 @@ +{{- if and (include "plgd-hub.m2moauthserver.enabled" .) .Values.m2moauthserver.ingress.grpc.enabled }} +{{- $fullname := include "plgd-hub.m2moauthserver.fullname" . }} +{{- $port := .Values.m2moauthserver.port }} +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: {{ $fullname }}-grpc + namespace: {{ .Release.Namespace }} + labels: + {{- include "plgd-hub.labels" . | nindent 4 }} + annotations: + {{- if .Values.m2moauthserver.ingress.grpc.annotations }} + {{- include "plgd-hub.tplvalues.render" ( dict "value" .Values.m2moauthserver.ingress.grpc.annotations "context" $ ) | nindent 4 }} + {{- end }} + {{- if .Values.m2moauthserver.ingress.grpc.customAnnotations }} + {{- include "plgd-hub.tplvalues.render" ( dict "value" .Values.m2moauthserver.ingress.grpc.customAnnotations "context" $ ) | nindent 4 }} + {{- end }} +spec: + tls: + - hosts: + - {{ include "plgd-hub.m2moauthserver.ingressDomain" . | quote }} + {{- if $.Values.global.enableWildCartCert }} + secretName: {{ include "plgd-hub.wildCardCertName" . | quote }} + {{- else }} + secretName: {{ include "plgd-hub.m2moauthserver.domainCertName" . | quote }} + {{- end }} + rules: + - host: {{ include "plgd-hub.m2moauthserver.ingressDomain" . | quote }} + http: + paths: + {{- range .Values.m2moauthserver.ingress.grpc.paths }} + - path: {{ . }} + pathType: Prefix + backend: + service: + name: {{ $fullname }}-grpc + port: + number: {{ $port }} + {{- end }} +{{- end }} diff --git a/charts/plgd-hub/templates/m2m-oauth-server/grpc-service.yaml b/charts/plgd-hub/templates/m2m-oauth-server/grpc-service.yaml new file mode 100644 index 000000000..53dcdb956 --- /dev/null +++ b/charts/plgd-hub/templates/m2m-oauth-server/grpc-service.yaml @@ -0,0 +1,25 @@ +{{- if and (include "plgd-hub.m2moauthserver.enabled" .) }} +apiVersion: v1 +kind: Service +metadata: + name: {{ include "plgd-hub.m2moauthserver.fullname" . }}-grpc + namespace: {{ .Release.Namespace }} + labels: + {{- include "plgd-hub.labels" . | nindent 4 }} + {{- with .Values.m2moauthserver.service.grpc.labels }} + {{- . | toYaml | nindent 4 }} + {{- end }} + {{- if .Values.m2moauthserver.service.grpc.annotations }} + annotations: + {{- include "plgd-hub.tplvalues.render" ( dict "value" .Values.m2moauthserver.service.grpc.annotations "context" $ ) | nindent 4 }} + {{- end }} +spec: + type: {{ .Values.m2moauthserver.service.grpc.type | default "ClusterIP" }} + ports: + - port: {{ .Values.m2moauthserver.port }} + targetPort: {{ .Values.m2moauthserver.service.grpc.targetPort }} + protocol: {{ .Values.m2moauthserver.service.grpc.protocol }} + name: {{ .Values.m2moauthserver.service.grpc.name }} + selector: + {{- include "plgd-hub.m2moauthserver.selectorLabels" . | nindent 4 }} +{{- end }} \ No newline at end of file diff --git a/charts/plgd-hub/templates/m2m-oauth-server/ingress.yaml b/charts/plgd-hub/templates/m2m-oauth-server/http-ingress.yaml similarity index 75% rename from charts/plgd-hub/templates/m2m-oauth-server/ingress.yaml rename to charts/plgd-hub/templates/m2m-oauth-server/http-ingress.yaml index ad4b8eea1..98ca80927 100644 --- a/charts/plgd-hub/templates/m2m-oauth-server/ingress.yaml +++ b/charts/plgd-hub/templates/m2m-oauth-server/http-ingress.yaml @@ -1,4 +1,4 @@ -{{- if and (include "plgd-hub.m2moauthserver.enabled" .) .Values.m2moauthserver.ingress.enabled }} +{{- if and (include "plgd-hub.m2moauthserver.enabled" .) .Values.m2moauthserver.ingress.http.enabled }} {{- $fullname := include "plgd-hub.m2moauthserver.fullname" . }} {{- $port := .Values.m2moauthserver.port }} apiVersion: networking.k8s.io/v1 @@ -9,11 +9,11 @@ metadata: labels: {{- include "plgd-hub.labels" . | nindent 4 }} annotations: - {{- if .Values.m2moauthserver.ingress.annotations }} - {{- include "plgd-hub.tplvalues.render" ( dict "value" .Values.m2moauthserver.ingress.annotations "context" $ ) | nindent 4 }} + {{- if .Values.m2moauthserver.ingress.http.annotations }} + {{- include "plgd-hub.tplvalues.render" ( dict "value" .Values.m2moauthserver.ingress.http.annotations "context" $ ) | nindent 4 }} {{- end }} - {{- if .Values.m2moauthserver.ingress.customAnnotations }} - {{- include "plgd-hub.tplvalues.render" ( dict "value" .Values.m2moauthserver.ingress.customAnnotations "context" $ ) | nindent 4 }} + {{- if .Values.m2moauthserver.ingress.http.customAnnotations }} + {{- include "plgd-hub.tplvalues.render" ( dict "value" .Values.m2moauthserver.ingress.http.customAnnotations "context" $ ) | nindent 4 }} {{- end }} spec: tls: @@ -28,7 +28,7 @@ spec: - host: {{ include "plgd-hub.m2moauthserver.ingressDomain" . | quote }} http: paths: - {{- range .Values.m2moauthserver.ingress.paths }} + {{- range .Values.m2moauthserver.ingress.http.paths }} - path: {{ . }} pathType: Prefix backend: diff --git a/charts/plgd-hub/templates/m2m-oauth-server/service.yaml b/charts/plgd-hub/templates/m2m-oauth-server/http-service.yaml similarity index 54% rename from charts/plgd-hub/templates/m2m-oauth-server/service.yaml rename to charts/plgd-hub/templates/m2m-oauth-server/http-service.yaml index a770904e8..ef9ef5a71 100644 --- a/charts/plgd-hub/templates/m2m-oauth-server/service.yaml +++ b/charts/plgd-hub/templates/m2m-oauth-server/http-service.yaml @@ -6,20 +6,20 @@ metadata: namespace: {{ .Release.Namespace }} labels: {{- include "plgd-hub.labels" . | nindent 4 }} - {{- with .Values.m2moauthserver.service.labels }} + {{- with .Values.m2moauthserver.service.http.labels }} {{- . | toYaml | nindent 4 }} {{- end }} - {{- if .Values.m2moauthserver.service.annotations }} + {{- if .Values.m2moauthserver.service.http.annotations }} annotations: - {{- include "plgd-hub.tplvalues.render" ( dict "value" .Values.m2moauthserver.service.annotations "context" $ ) | nindent 4 }} + {{- include "plgd-hub.tplvalues.render" ( dict "value" .Values.m2moauthserver.service.http.annotations "context" $ ) | nindent 4 }} {{- end }} spec: - type: {{ .Values.m2moauthserver.service.type | default "ClusterIP" }} + type: {{ .Values.m2moauthserver.service.http.type | default "ClusterIP" }} ports: - port: {{ .Values.m2moauthserver.port }} - targetPort: {{ .Values.m2moauthserver.service.targetPort }} - protocol: {{ .Values.m2moauthserver.service.protocol }} - name: {{ .Values.m2moauthserver.service.name }} + targetPort: {{ .Values.m2moauthserver.service.http.targetPort }} + protocol: {{ .Values.m2moauthserver.service.http.protocol }} + name: {{ .Values.m2moauthserver.service.http.name }} selector: {{- include "plgd-hub.m2moauthserver.selectorLabels" . | nindent 4 }} {{- end }} \ No newline at end of file diff --git a/charts/plgd-hub/templates/m2m-oauth-server/service-crt.yaml b/charts/plgd-hub/templates/m2m-oauth-server/service-crt.yaml index e5a53c67e..4eb579984 100644 --- a/charts/plgd-hub/templates/m2m-oauth-server/service-crt.yaml +++ b/charts/plgd-hub/templates/m2m-oauth-server/service-crt.yaml @@ -27,8 +27,11 @@ spec: dnsNames: - {{ printf "%s.%s.svc.%s" $serviceDns .Release.Namespace .Values.cluster.dns | quote }} - {{ $serviceDns | quote }} - {{- if .Values.m2moauthserver.service.crt.extraDnsNames }} - {{- toYaml .Values.m2moauthserver.service.crt.extraDnsNames | nindent 4 }} + {{- if .Values.m2moauthserver.service.grpc.crt.extraDnsNames }} + {{- toYaml .Values.m2moauthserver.service.grpc.crt.extraDnsNames | nindent 4}} + {{- end }} + {{- if .Values.m2moauthserver.service.http.crt.extraDnsNames }} + {{- toYaml .Values.m2moauthserver.service.http.crt.extraDnsNames | nindent 4}} {{- end }} duration: {{ .Values.certmanager.internal.cert.duration | default .Values.certmanager.default.cert.duration }} renewBefore: {{ .Values.certmanager.internal.cert.renewBefore | default .Values.certmanager.default.cert.renewBefore }} diff --git a/charts/plgd-hub/templates/snippet-service/config.yaml b/charts/plgd-hub/templates/snippet-service/config.yaml index 8f16a070d..9ed69d7bf 100644 --- a/charts/plgd-hub/templates/snippet-service/config.yaml +++ b/charts/plgd-hub/templates/snippet-service/config.yaml @@ -37,7 +37,7 @@ data: timeout: {{ .apis.grpc.keepAlive.maxConnectionIdle }} tls: {{- $tls := .apis.grpc.tls }} - {{- include "plgd-hub.certificateConfig" (list $ $tls $cert ) | indent 8 }} + {{- include "plgd-hub.internalCertificateConfig" (list $ $tls $cert ) | indent 8 }} clientCertificateRequired: {{ .apis.grpc.tls.clientCertificateRequired }} authorization: {{- $authorization := .apis.grpc.authorization }} @@ -58,7 +58,7 @@ data: bytesLimit: {{ printf "%v" .clients.eventBus.nats.pendingLimits.bytesLimit }} tls: {{- $natsTls := .clients.eventBus.nats.tls }} - {{- include "plgd-hub.certificateConfig" (list $ $natsTls $cert ) | indent 10 }} + {{- include "plgd-hub.internalCertificateConfig" (list $ $natsTls $cert ) | indent 10 }} useSystemCAPool: {{ .clients.eventBus.nats.tls.useSystemCAPool }} {{- if or .clients.eventBus.nats.leadResourceType $.Values.global.nats.leadResourceType }} {{- $leadResourceType := .clients.eventBus.nats.leadResourceType | default $.Values.global.nats.leadResourceType }} @@ -75,7 +75,7 @@ data: maxConnIdleTime: {{ .clients.storage.mongoDB.maxConnIdleTime }} tls: {{- $mongoDbTls := .clients.storage.mongoDB.tls }} - {{- include "plgd-hub.certificateConfig" (list $ $mongoDbTls $cert ) | indent 10 }} + {{- include "plgd-hub.internalCertificateConfig" (list $ $mongoDbTls $cert ) | indent 10 }} useSystemCAPool: {{ .clients.storage.mongoDB.tls.useSystemCAPool }} resourceAggregate: grpc: @@ -89,7 +89,7 @@ data: permitWithoutStream: {{ .clients.resourceAggregate.grpc.keepAlive.permitWithoutStream }} tls: {{- $raClientTls := .clients.resourceAggregate.grpc.tls }} - {{- include "plgd-hub.certificateConfig" (list $ $raClientTls $cert) | indent 10 }} + {{- include "plgd-hub.internalCertificateConfig" (list $ $raClientTls $cert ) | indent 10 }} useSystemCAPool: {{ .clients.resourceAggregate.grpc.tls.useSystemCAPool }} {{- include "plgd-hub.openTelemetryExporterConfig" (list $ $cert ) | nindent 6 }} {{- end }} diff --git a/charts/plgd-hub/templates/snippet-service/deployment.yaml b/charts/plgd-hub/templates/snippet-service/deployment.yaml index 0737a274d..3cbd5e62d 100644 --- a/charts/plgd-hub/templates/snippet-service/deployment.yaml +++ b/charts/plgd-hub/templates/snippet-service/deployment.yaml @@ -87,10 +87,8 @@ spec: - name: service-crt mountPath: {{ $cert }} {{- end }} - {{- if .Values.global.authorizationCAPool }} - - name: {{ .Values.extraAuthorizationCAPool.name }} - mountPath: {{ .Values.extraAuthorizationCAPool.mountPath }} - {{- end }} + {{- include "plgd-hub.extraCAPoolMount" (list . .Values.extraCAPool.authorization) | nindent 12 }} + {{- include "plgd-hub.extraCAPoolMount" (list . .Values.extraCAPool.internal) | nindent 12 }} {{- with .Values.snippetservice.extraVolumeMounts }} {{- toYaml . | nindent 12 }} {{- end }} @@ -106,11 +104,8 @@ spec: secret: secretName: {{ include "plgd-hub.snippetservice.serviceCertName" . }} {{- end }} - {{- if .Values.global.authorizationCAPool }} - - name: {{ .Values.extraAuthorizationCAPool.name }} - secret: - secretName: {{ .Values.extraAuthorizationCAPool.name }} - {{- end }} + {{- include "plgd-hub.extraCAPoolVolume" (list . .Values.extraCAPool.authorization) | nindent 8 }} + {{- include "plgd-hub.extraCAPoolVolume" (list . .Values.extraCAPool.internal) | nindent 8 }} {{- with .Values.snippetservice.extraVolumes }} {{- toYaml . | nindent 8 }} {{- end }} diff --git a/charts/plgd-hub/values.yaml b/charts/plgd-hub/values.yaml index 0985873f5..c35b802b7 100644 --- a/charts/plgd-hub/values.yaml +++ b/charts/plgd-hub/values.yaml @@ -22,8 +22,6 @@ global: m2mOAuthServer: # -- private key to sign JWT m2m tokens privateKey: "" - # -- service secret to sign JWT m2m tokens for the oauth service client - clientServiceSecret: "" # -- Default OAuth authorization for all services authorization: audience: '{{ include "plgd-hub.globalAudience" . }}' @@ -2342,9 +2340,9 @@ snippetservice: http: # -- Service type type: ClusterIP - # -- Labels for snippet service + # -- Labels for snippet-service labels: {} - # -- Annotations for snippet service + # -- Annotations for snippet-service annotations: {} # -- Target port targetPort: http @@ -2359,7 +2357,7 @@ snippetservice: rbac: # -- Enable RBAC enabled: false - # -- Name of snippet service SA + # -- Name of snippet-service SA serviceAccountName: snippet-service # -- Template definition for Role/binding etc.. roleBindingDefitionTpl: @@ -2745,20 +2743,39 @@ m2moauthserver: # -- Annotations for m2m-oauth-server pod podAnnotations: {} service: - type: ClusterIP - # -- Labels for m2m-oauth-server service - labels: {} - # -- Annotations for m2m-oauth-server service - annotations: {} - # -- Target port - targetPort: http - # -- Protocol - protocol: TCP - # -- Name - name: http - crt: - # -- Extra DNS names for service certificate - extraDnsNames: [] + grpc: + # -- Service type + type: ClusterIP + # -- Labels for m2m-oauth-server + labels: {} + # -- Annotations for m2m-oauth-server + annotations: {} + # -- Target port + targetPort: grpc + # -- Protocol + protocol: TCP + # -- Name + name: grpc + crt: + # -- Extra DNS names for service certificate + extraDnsNames: [] + http: + # -- Service type + type: ClusterIP + # -- Labels for m2m-oauth-server + labels: {} + # -- Annotations for m2m-oauth-server + annotations: {} + # -- Target port + targetPort: http + # -- Protocol + protocol: TCP + # -- Name + name: http + crt: + # -- Extra DNS names for service certificate + extraDnsNames: [] + # -- RBAC configuration securityContext: {} # -- Image pull secrets imagePullSecrets: {} @@ -2794,20 +2811,40 @@ m2moauthserver: # -- Domain for oauth. Default {{ global.domain }} domain: ingress: - # -- Enable ingress - enabled: true - # -- Pre defined map of Ingress annotation - annotations: - nginx.ingress.kubernetes.io/backend-protocol: "HTTPS" - ingress.kubernetes.io/force-ssl-redirect: "true" - nginx.ingress.kubernetes.io/enable-cors: "true" - cert-manager.io/private-key-rotation-policy: always - # -- Custom map of Ingress annotation - customAnnotations: {} - # -- Ingress path - paths: - - /m2m-oauth-server - allowHeaders: "Authortity,Method,Path,Scheme,Accept,Accept-Encoding,Accept-Language,Content-Type,auth0-client,Origin,Refer,Sec-Fetch-Dest,Sec-Fetch-Mode,Sec-Fetch-Site,Authorization,DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range" + http: + # -- Enable ingress + enabled: true + # -- Override name of host/tls secret. If not specified, it will be generated + secretName: + # -- Pre defined map of Ingress annotation + annotations: + nginx.ingress.kubernetes.io/backend-protocol: "HTTPS" + ingress.kubernetes.io/force-ssl-redirect: "true" + nginx.ingress.kubernetes.io/enable-cors: "true" + cert-manager.io/private-key-rotation-policy: always + # -- Custom map of Ingress annotation + customAnnotations: {} + # -- Ingress path + paths: + - /m2m-oauth-server + allowHeaders: "Authortity,Method,Path,Scheme,Accept,Accept-Encoding,Accept-Language,Content-Type,auth0-client,Origin,Refer,Sec-Fetch-Dest,Sec-Fetch-Mode,Sec-Fetch-Site,Authorization,DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range" + grpc: + # -- Enable ingress + enabled: true + # -- Override name of host/tls secret. If not specified, it will be generated + secretName: + # -- Pre defined map of Ingress annotation + annotations: + nginx.org/grpc-services: '{{ include "plgd-hub.m2moauthserver.fullname" . }}-grpc' + nginx.ingress.kubernetes.io/backend-protocol: "GRPCS" + ingress.kubernetes.io/force-ssl-redirect: "true" + nginx.ingress.kubernetes.io/enable-cors: "true" + cert-manager.io/private-key-rotation-policy: always + # -- Custom map of Ingress annotation + customAnnotations: {} + # -- Paths + paths: + - /m2moauthserver.pb.M2MOAuthService # -- m2m-oauth-server service yaml config section config: # -- Name of configuration file @@ -2818,6 +2855,7 @@ m2moauthserver: mountPath: /config # -- Port for service and POD port: 9100 + httpPort: 9101 privateKey: # -- Set deployment to use secret for private key enabled: false @@ -2829,17 +2867,6 @@ m2moauthserver: volume: private-key # -- Mount path mountPath: /secrets/keys - clientServiceSecret: - # -- Set deployment to use secret for service secret - enabled: false - # -- Name of private key file - fileName: secret.dat - # -- Name of secret - secretName: m2m-service-secret - # -- Volume name - volume: service-secret - # -- Mount path - mountPath: /secrets/clients/service log: # -- Logging enabled from level level: info @@ -2861,11 +2888,56 @@ m2moauthserver: readHeaderTimeout: 4s writeTimeout: 16s idleTimeout: 30s + grpc: + address: "" + sendMsgSize: 4194304 + recvMsgSize: 4194304 + enforcementPolicy: + minTime: 5s + permitWithoutStream: true + keepAlive: + # 0s - means infinity + maxConnectionIdle: 0s + # 0s - means infinity + maxConnectionAge: 0s + # 0s - means infinity + maxConnectionAgeGrace: 0s + time: 2h + timeout: 20s tls: caPool: keyFile: certFile: clientCertificateRequired: false + authorization: + ownerClaim: + authority: + audience: + http: + maxIdleConns: 16 + maxConnsPerHost: 32 + maxIdleConnsPerHost: 16 + idleConnTimeout: "30s" + timeout: "10s" + tls: + caPool: + keyFile: + certFile: + useSystemCAPool: false + clients: + storage: + cleanUpDeletedTokens: "0 * * * *" + use: mongoDB + mongoDB: + uri: + database: m2mOAuthServer + maxPoolSize: 16 + maxConnIdleTime: 4m0s + tls: + caPool: + keyFile: + certFile: + useSystemCAPool: false oauthSigner: privateKeyFile: domain: @@ -2883,10 +2955,3 @@ m2moauthserver: authorization: audience: endpoints: - - id: "service" - secretFile: '{{ include "plgd-hub.m2moauthserver.getClientServiceSecretFile" . }}' - accessTokenLifetime: 0s - allowedGrantTypes: - - client_credentials - allowedAudiences: [] - allowedScopes: [] diff --git a/test/helm/mock.plgd.cloud.yaml b/test/helm/mock.plgd.cloud.yaml index f6a500dbe..971934586 100644 --- a/test/helm/mock.plgd.cloud.yaml +++ b/test/helm/mock.plgd.cloud.yaml @@ -191,22 +191,4 @@ m2moauthserver: enabled: true authorization: audience: - endpoints: - - id: "service" - secretFile: "{{ include \"plgd-hub.m2moauthserver.getClientServiceSecretFile\" . }}" - accessTokenLifetime: 0s - allowedGrantTypes: - - client_credentials - allowedAudiences: [] - allowedScopes: [] - insertTokenClaims: - x-deviceid: "device1" - x-deviceowner: "owner1" - x-array: - - key: "key1" - value: "value1" - - key: "key2" - value: "value2" - x-map: - key1: "value1" - key2: "value2" \ No newline at end of file + endpoints: \ No newline at end of file From e3d3a413fed4c4d45d0dea68cb0f5002935ef7fd Mon Sep 17 00:00:00 2001 From: Jozef Kralik Date: Thu, 18 Jul 2024 10:53:58 +0000 Subject: [PATCH 09/31] fix chart --- charts/plgd-hub/templates/m2m-oauth-server/config.yaml | 2 -- 1 file changed, 2 deletions(-) diff --git a/charts/plgd-hub/templates/m2m-oauth-server/config.yaml b/charts/plgd-hub/templates/m2m-oauth-server/config.yaml index 1afc9a45c..36c98c9b8 100644 --- a/charts/plgd-hub/templates/m2m-oauth-server/config.yaml +++ b/charts/plgd-hub/templates/m2m-oauth-server/config.yaml @@ -117,7 +117,5 @@ data: {{- end }} {{- end }} {{- end }} - clients: - {{- include "plgd-hub.openTelemetryExporterConfig" (list $ $cert) | nindent 6 }} {{- end }} {{- end }} \ No newline at end of file From 653fb7547404b242e9d7f3b96ef1060ce0533c79 Mon Sep 17 00:00:00 2001 From: Jozef Kralik Date: Thu, 18 Jul 2024 11:07:27 +0000 Subject: [PATCH 10/31] fix validator --- m2m-oauth-server/test/test.go | 7 +++++++ pkg/security/jwt/validator/validator.go | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/m2m-oauth-server/test/test.go b/m2m-oauth-server/test/test.go index 40091d91e..a412c8c6e 100644 --- a/m2m-oauth-server/test/test.go +++ b/m2m-oauth-server/test/test.go @@ -21,6 +21,7 @@ import ( "github.com/plgd-dev/hub/v2/pkg/log" kitNetHttp "github.com/plgd-dev/hub/v2/pkg/net/http" "github.com/plgd-dev/hub/v2/pkg/security/jwt" + "github.com/plgd-dev/hub/v2/pkg/security/jwt/validator" "github.com/plgd-dev/hub/v2/test/config" testHttp "github.com/plgd-dev/hub/v2/test/http" testOAuthUri "github.com/plgd-dev/hub/v2/test/oauth-server/uri" @@ -74,6 +75,12 @@ func MakeConfig(t require.TestingT) service.Config { } cfg.APIs.GRPC = config.MakeGrpcServerConfig(config.M2M_OAUTH_SERVER_HOST) cfg.APIs.GRPC.TLS.ClientCertificateRequired = false + cfg.APIs.GRPC.Authorization.Endpoints = append(cfg.APIs.GRPC.Authorization.Endpoints, + validator.AuthorityConfig{ + Authority: testHttp.HTTPS_SCHEME + config.M2M_OAUTH_SERVER_HTTP_HOST + uri.Base, + HTTP: config.MakeHttpClientConfig(), + }, + ) cfg.Clients.Storage = MakeStoreConfig() cfg.OAuthSigner.PrivateKeyFile = urischeme.URIScheme(os.Getenv("M2M_OAUTH_SERVER_PRIVATE_KEY")) diff --git a/pkg/security/jwt/validator/validator.go b/pkg/security/jwt/validator/validator.go index 95c67dc49..87305d9c9 100644 --- a/pkg/security/jwt/validator/validator.go +++ b/pkg/security/jwt/validator/validator.go @@ -76,7 +76,7 @@ func New(ctx context.Context, config Config, fileWatcher *fsnotify.Watcher, logg return nil, errors.New("GetOpenIDConfiguration is nil") } - openIDCfg, err := openid.GetConfiguration(ctx2, httpClient.HTTP(), authority.Authority) + openIDCfg, err := options.GetOpenIDConfiguration(ctx2, httpClient.HTTP(), authority.Authority) if err != nil { onClose.Execute() httpClient.Close() From 6d3f7937d20fca5ffcd085b18695bafcf2348fa2 Mon Sep 17 00:00:00 2001 From: Jozef Kralik Date: Thu, 18 Jul 2024 11:13:17 +0000 Subject: [PATCH 11/31] cleanup --- m2m-oauth-server/Makefile | 2 +- m2m-oauth-server/pb/README.md | 4 +--- m2m-oauth-server/pb/doc.html | 4 +--- m2m-oauth-server/pb/service.pb.go | 2 +- m2m-oauth-server/pb/service.proto | 2 +- m2m-oauth-server/pb/service.swagger.json | 1 - m2m-oauth-server/swagger.yaml | 1 - 7 files changed, 5 insertions(+), 11 deletions(-) diff --git a/m2m-oauth-server/Makefile b/m2m-oauth-server/Makefile index f43d82fa5..04a971d21 100644 --- a/m2m-oauth-server/Makefile +++ b/m2m-oauth-server/Makefile @@ -17,7 +17,7 @@ BUILD_VERSION ?= $(shell git tag --sort version:refname | tail -1 | sed -e "s/^v default: build define build-docker-image - cd ../.. && \ + cd .. && \ mkdir -p .tmp/docker/$(SERVICE_NAME) && \ awk '{gsub("@NAME@","$(SERVICE_NAME)")} {gsub("@DIRECTORY@","m2m-oauth-server")} {print}' tools/docker/Dockerfile.in > .tmp/docker/$(SERVICE_NAME)/Dockerfile && \ docker build \ diff --git a/m2m-oauth-server/pb/README.md b/m2m-oauth-server/pb/README.md index a8c73a840..bb3f3674e 100644 --- a/m2m-oauth-server/pb/README.md +++ b/m2m-oauth-server/pb/README.md @@ -123,9 +123,7 @@ driven by resource change event | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | -| id | [string](#string) | | Token ID / jti - -@gotags: bson:"_id" | +| id | [string](#string) | | Token ID / jti | | version | [uint64](#uint64) | | Incremental version for update | | name | [string](#string) | | User-friendly token name | | owner | [string](#string) | | Owner of the token | diff --git a/m2m-oauth-server/pb/doc.html b/m2m-oauth-server/pb/doc.html index e2be77a3c..1b05ec46f 100644 --- a/m2m-oauth-server/pb/doc.html +++ b/m2m-oauth-server/pb/doc.html @@ -453,9 +453,7 @@

    Token

    - + diff --git a/m2m-oauth-server/pb/service.pb.go b/m2m-oauth-server/pb/service.pb.go index ecf7b3bf1..8c303d582 100644 --- a/m2m-oauth-server/pb/service.pb.go +++ b/m2m-oauth-server/pb/service.pb.go @@ -30,7 +30,7 @@ type Token struct { unknownFields protoimpl.UnknownFields // Token ID / jti - Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty" bson:"_id"` + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` // Incremental version for update Version uint64 `protobuf:"varint,2,opt,name=version,proto3" json:"version,omitempty"` // User-friendly token name diff --git a/m2m-oauth-server/pb/service.proto b/m2m-oauth-server/pb/service.proto index d0c8a0a52..c5cf5b73a 100644 --- a/m2m-oauth-server/pb/service.proto +++ b/m2m-oauth-server/pb/service.proto @@ -31,7 +31,7 @@ option go_package = "github.com/plgd-dev/hub/v2/m2m-oauth-server/pb;pb"; // Tokens are deleted from DB after they are expired and blacklisted/revoked message Token { // driven by resource change event // Token ID / jti - string id = 1; // @gotags: bson:"_id" + string id = 1; // Incremental version for update uint64 version = 2; // User-friendly token name diff --git a/m2m-oauth-server/pb/service.swagger.json b/m2m-oauth-server/pb/service.swagger.json index 1123e66fe..cc681b4aa 100644 --- a/m2m-oauth-server/pb/service.swagger.json +++ b/m2m-oauth-server/pb/service.swagger.json @@ -256,7 +256,6 @@ "properties": { "id": { "type": "string", - "description": "@gotags: bson:\"_id\"", "title": "Token ID / jti" }, "version": { diff --git a/m2m-oauth-server/swagger.yaml b/m2m-oauth-server/swagger.yaml index 42000bc1e..69460134d 100644 --- a/m2m-oauth-server/swagger.yaml +++ b/m2m-oauth-server/swagger.yaml @@ -293,7 +293,6 @@ components: id: title: Token ID / jti type: string - description: "@gotags: bson:\"_id\"" version: title: Incremental version for update type: string From 13248dc4b23cc3465cb36ecc50b5a333b0640e45 Mon Sep 17 00:00:00 2001 From: Jozef Kralik Date: Thu, 18 Jul 2024 11:32:08 +0000 Subject: [PATCH 12/31] fix ports in helm --- charts/plgd-hub/templates/m2m-oauth-server/http-ingress.yaml | 2 +- charts/plgd-hub/templates/m2m-oauth-server/http-service.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/charts/plgd-hub/templates/m2m-oauth-server/http-ingress.yaml b/charts/plgd-hub/templates/m2m-oauth-server/http-ingress.yaml index 98ca80927..a062cb3be 100644 --- a/charts/plgd-hub/templates/m2m-oauth-server/http-ingress.yaml +++ b/charts/plgd-hub/templates/m2m-oauth-server/http-ingress.yaml @@ -1,6 +1,6 @@ {{- if and (include "plgd-hub.m2moauthserver.enabled" .) .Values.m2moauthserver.ingress.http.enabled }} {{- $fullname := include "plgd-hub.m2moauthserver.fullname" . }} -{{- $port := .Values.m2moauthserver.port }} +{{- $port := .Values.m2moauthserver.httpPort }} apiVersion: networking.k8s.io/v1 kind: Ingress metadata: diff --git a/charts/plgd-hub/templates/m2m-oauth-server/http-service.yaml b/charts/plgd-hub/templates/m2m-oauth-server/http-service.yaml index ef9ef5a71..a9010d2b1 100644 --- a/charts/plgd-hub/templates/m2m-oauth-server/http-service.yaml +++ b/charts/plgd-hub/templates/m2m-oauth-server/http-service.yaml @@ -16,7 +16,7 @@ metadata: spec: type: {{ .Values.m2moauthserver.service.http.type | default "ClusterIP" }} ports: - - port: {{ .Values.m2moauthserver.port }} + - port: {{ .Values.m2moauthserver.httpPort }} targetPort: {{ .Values.m2moauthserver.service.http.targetPort }} protocol: {{ .Values.m2moauthserver.service.http.protocol }} name: {{ .Values.m2moauthserver.service.http.name }} From 8aa63425f216fc496c7be3c0781191e0c0b1dea6 Mon Sep 17 00:00:00 2001 From: Jozef Kralik Date: Thu, 18 Jul 2024 11:37:38 +0000 Subject: [PATCH 13/31] fix deployment --- charts/plgd-hub/templates/m2m-oauth-server/deployment.yaml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/charts/plgd-hub/templates/m2m-oauth-server/deployment.yaml b/charts/plgd-hub/templates/m2m-oauth-server/deployment.yaml index 95a61fa37..ee17f3a27 100644 --- a/charts/plgd-hub/templates/m2m-oauth-server/deployment.yaml +++ b/charts/plgd-hub/templates/m2m-oauth-server/deployment.yaml @@ -56,9 +56,12 @@ spec: - "--config" - {{ printf "%s/%s" .Values.m2moauthserver.config.mountPath .Values.m2moauthserver.config.fileName | quote }} ports: - - name: http + - name: grpc containerPort: {{ .Values.m2moauthserver.port }} protocol: TCP + - name: http + containerPort: {{ .Values.m2moauthserver.httpPort }} + protocol: TCP {{- with .Values.m2moauthserver.livenessProbe }} livenessProbe: {{- toYaml . | nindent 12 }} From dc8c87162d7a93398705b3a4448a11cd914cbb96 Mon Sep 17 00:00:00 2001 From: Jozef Kralik Date: Thu, 18 Jul 2024 12:23:29 +0000 Subject: [PATCH 14/31] use seconds instead of nanoseconds in token from grpc --- m2m-oauth-server/pb/README.md | 26 +-- m2m-oauth-server/pb/doc.html | 28 +-- m2m-oauth-server/pb/service.pb.go | 185 +++++++++--------- m2m-oauth-server/pb/service.proto | 19 +- m2m-oauth-server/pb/service.swagger.json | 37 ++-- m2m-oauth-server/service/grpc/server.go | 4 +- m2m-oauth-server/service/grpc/token.go | 68 ++++--- m2m-oauth-server/service/http/postToken.go | 29 ++- m2m-oauth-server/store/mongodb/tokens.go | 6 +- m2m-oauth-server/store/mongodb/tokens_test.go | 36 ++-- m2m-oauth-server/swagger.yaml | 25 ++- m2m-oauth-server/uri/uri.go | 2 +- pkg/time/unixnano.go | 8 + 13 files changed, 269 insertions(+), 204 deletions(-) diff --git a/m2m-oauth-server/pb/README.md b/m2m-oauth-server/pb/README.md index bb3f3674e..ea4e88250 100644 --- a/m2m-oauth-server/pb/README.md +++ b/m2m-oauth-server/pb/README.md @@ -63,15 +63,15 @@ | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | -| client_id | [string](#string) | | | -| client_secret | [string](#string) | | | -| audience | [string](#string) | repeated | | -| scope | [string](#string) | repeated | | -| time_to_live | [int64](#int64) | | | -| client_assertion_type | [string](#string) | | | -| client_assertion | [string](#string) | | | -| token_name | [string](#string) | | | -| grant_type | [string](#string) | | | +| client_id | [string](#string) | | Client ID | +| client_secret | [string](#string) | | Client Secret | +| audience | [string](#string) | repeated | Requested token Audience | +| scope | [string](#string) | repeated | Requested token scopes | +| expiration | [int64](#int64) | | Requested token expiration in Unix timestamp seconds | +| client_assertion_type | [string](#string) | | Client assertion type | +| client_assertion | [string](#string) | | Client assertion | +| token_name | [string](#string) | | Token name | +| grant_type | [string](#string) | | Grant type | @@ -127,10 +127,10 @@ driven by resource change event | version | [uint64](#uint64) | | Incremental version for update | | name | [string](#string) | | User-friendly token name | | owner | [string](#string) | | Owner of the token | -| issued_at | [int64](#int64) | | Unix timestamp in ns when the condition has been created/updated | +| issued_at | [int64](#int64) | | Unix timestamp in s when the condition has been created/updated | | audience | [string](#string) | repeated | Token Audience | | scope | [string](#string) | repeated | Token scopes | -| expiration | [int64](#int64) | | Original token expiration | +| expiration | [int64](#int64) | | Token expiration in Unix timestamp seconds | | client_id | [string](#string) | | Client ID | | original_token_claims | [google.protobuf.Value](#google-protobuf-Value) | | Original token claims | | blacklisted | [Token.BlackListed](#m2moauthserver-pb-Token-BlackListed) | | Token black list section | @@ -148,8 +148,8 @@ driven by resource change event | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | -| flag | [bool](#bool) | | Blacklisted/revoked enabled flag, if once token has been blacklisted/revoked then it can't be unblacklisted/unrevoked | -| timestamp | [int64](#int64) | | Unix timestamp in ns when the token has been blacklisted/revoked | +| flag | [bool](#bool) | | Blacklisted enabled flag, if once token has been blacklisted then it can't be unblacklisted/unrevoked | +| timestamp | [int64](#int64) | | Unix timestamp in s when the token has been blacklisted | diff --git a/m2m-oauth-server/pb/doc.html b/m2m-oauth-server/pb/doc.html index 1b05ec46f..04e5dc89c 100644 --- a/m2m-oauth-server/pb/doc.html +++ b/m2m-oauth-server/pb/doc.html @@ -290,63 +290,63 @@

    CreateTokenRequest

    - + - + - + - + - + - + - + - + - + - + @@ -481,7 +481,7 @@

    Token

    - + @@ -502,7 +502,7 @@

    Token

    - + @@ -547,14 +547,14 @@

    Token.BlackListed

    - + - + diff --git a/m2m-oauth-server/pb/service.pb.go b/m2m-oauth-server/pb/service.pb.go index 8c303d582..5c3a0539d 100644 --- a/m2m-oauth-server/pb/service.pb.go +++ b/m2m-oauth-server/pb/service.pb.go @@ -37,13 +37,13 @@ type Token struct { Name string `protobuf:"bytes,3,opt,name=name,proto3" json:"name,omitempty"` // Owner of the token Owner string `protobuf:"bytes,4,opt,name=owner,proto3" json:"owner,omitempty"` - // Unix timestamp in ns when the condition has been created/updated + // Unix timestamp in s when the condition has been created/updated IssuedAt int64 `protobuf:"varint,5,opt,name=issued_at,json=issuedAt,proto3" json:"issued_at,omitempty"` // Token Audience Audience []string `protobuf:"bytes,6,rep,name=audience,proto3" json:"audience,omitempty"` // Token scopes Scope []string `protobuf:"bytes,7,rep,name=scope,proto3" json:"scope,omitempty"` - // Original token expiration + // Token expiration in Unix timestamp seconds Expiration int64 `protobuf:"varint,8,opt,name=expiration,proto3" json:"expiration,omitempty"` // Client ID ClientId string `protobuf:"bytes,9,opt,name=client_id,json=clientId,proto3" json:"client_id,omitempty"` @@ -324,15 +324,24 @@ type CreateTokenRequest struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - ClientId string `protobuf:"bytes,1,opt,name=client_id,json=clientId,proto3" json:"client_id,omitempty"` - ClientSecret string `protobuf:"bytes,2,opt,name=client_secret,json=clientSecret,proto3" json:"client_secret,omitempty"` - Audience []string `protobuf:"bytes,3,rep,name=audience,proto3" json:"audience,omitempty"` - Scope []string `protobuf:"bytes,4,rep,name=scope,proto3" json:"scope,omitempty"` - TimeToLive int64 `protobuf:"varint,5,opt,name=time_to_live,json=timeToLive,proto3" json:"time_to_live,omitempty"` - ClientAssertionType string `protobuf:"bytes,6,opt,name=client_assertion_type,json=clientAssertionType,proto3" json:"client_assertion_type,omitempty"` - ClientAssertion string `protobuf:"bytes,7,opt,name=client_assertion,json=clientAssertion,proto3" json:"client_assertion,omitempty"` - TokenName string `protobuf:"bytes,8,opt,name=token_name,json=tokenName,proto3" json:"token_name,omitempty"` - GrantType string `protobuf:"bytes,9,opt,name=grant_type,json=grantType,proto3" json:"grant_type,omitempty"` + // Client ID + ClientId string `protobuf:"bytes,1,opt,name=client_id,json=clientId,proto3" json:"client_id,omitempty"` + // Client Secret + ClientSecret string `protobuf:"bytes,2,opt,name=client_secret,json=clientSecret,proto3" json:"client_secret,omitempty"` + // Requested token Audience + Audience []string `protobuf:"bytes,3,rep,name=audience,proto3" json:"audience,omitempty"` + // Requested token scopes + Scope []string `protobuf:"bytes,4,rep,name=scope,proto3" json:"scope,omitempty"` + // Requested token expiration in Unix timestamp seconds + Expiration int64 `protobuf:"varint,5,opt,name=expiration,proto3" json:"expiration,omitempty"` + // Client assertion type + ClientAssertionType string `protobuf:"bytes,6,opt,name=client_assertion_type,json=clientAssertionType,proto3" json:"client_assertion_type,omitempty"` + // Client assertion + ClientAssertion string `protobuf:"bytes,7,opt,name=client_assertion,json=clientAssertion,proto3" json:"client_assertion,omitempty"` + // Token name + TokenName string `protobuf:"bytes,8,opt,name=token_name,json=tokenName,proto3" json:"token_name,omitempty"` + // Grant type + GrantType string `protobuf:"bytes,9,opt,name=grant_type,json=grantType,proto3" json:"grant_type,omitempty"` } func (x *CreateTokenRequest) Reset() { @@ -395,9 +404,9 @@ func (x *CreateTokenRequest) GetScope() []string { return nil } -func (x *CreateTokenRequest) GetTimeToLive() int64 { +func (x *CreateTokenRequest) GetExpiration() int64 { if x != nil { - return x.TimeToLive + return x.Expiration } return 0 } @@ -506,9 +515,9 @@ type Token_BlackListed struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - // Blacklisted/revoked enabled flag, if once token has been blacklisted/revoked then it can't be unblacklisted/unrevoked + // Blacklisted enabled flag, if once token has been blacklisted then it can't be unblacklisted/unrevoked Flag bool `protobuf:"varint,1,opt,name=flag,proto3" json:"flag,omitempty"` - // Unix timestamp in ns when the token has been blacklisted/revoked + // Unix timestamp in s when the token has been blacklisted Timestamp int64 `protobuf:"varint,2,opt,name=timestamp,proto3" json:"timestamp,omitempty"` } @@ -614,7 +623,7 @@ var file_m2m_oauth_server_pb_service_proto_rawDesc = []byte{ 0x6c, 0x74, 0x65, 0x72, 0x22, 0x2f, 0x0a, 0x17, 0x42, 0x6c, 0x61, 0x63, 0x6b, 0x6c, 0x69, 0x73, 0x74, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, - 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0xc7, 0x02, 0x0a, 0x12, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, + 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0xc5, 0x02, 0x0a, 0x12, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x6c, 0x69, @@ -623,78 +632,78 @@ var file_m2m_oauth_server_pb_service_proto_rawDesc = []byte{ 0x0a, 0x08, 0x61, 0x75, 0x64, 0x69, 0x65, 0x6e, 0x63, 0x65, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x08, 0x61, 0x75, 0x64, 0x69, 0x65, 0x6e, 0x63, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x18, 0x04, 0x20, 0x03, 0x28, 0x09, 0x52, 0x05, 0x73, 0x63, 0x6f, 0x70, 0x65, - 0x12, 0x20, 0x0a, 0x0c, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x74, 0x6f, 0x5f, 0x6c, 0x69, 0x76, 0x65, - 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x74, 0x69, 0x6d, 0x65, 0x54, 0x6f, 0x4c, 0x69, - 0x76, 0x65, 0x12, 0x32, 0x0a, 0x15, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x61, 0x73, 0x73, - 0x65, 0x72, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x13, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x41, 0x73, 0x73, 0x65, 0x72, 0x74, 0x69, - 0x6f, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x12, 0x29, 0x0a, 0x10, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, - 0x5f, 0x61, 0x73, 0x73, 0x65, 0x72, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x0f, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x41, 0x73, 0x73, 0x65, 0x72, 0x74, 0x69, 0x6f, - 0x6e, 0x12, 0x1d, 0x0a, 0x0a, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, - 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x4e, 0x61, 0x6d, 0x65, - 0x12, 0x1d, 0x0a, 0x0a, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x09, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x22, - 0x8c, 0x01, 0x0a, 0x13, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x61, 0x63, 0x63, 0x65, 0x73, - 0x73, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x61, - 0x63, 0x63, 0x65, 0x73, 0x73, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x1d, 0x0a, 0x0a, 0x74, 0x6f, - 0x6b, 0x65, 0x6e, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, - 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x65, 0x78, 0x70, - 0x69, 0x72, 0x65, 0x73, 0x5f, 0x69, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x65, - 0x78, 0x70, 0x69, 0x72, 0x65, 0x73, 0x49, 0x6e, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x63, 0x6f, 0x70, - 0x65, 0x18, 0x04, 0x20, 0x03, 0x28, 0x09, 0x52, 0x05, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x32, 0xcf, - 0x03, 0x0a, 0x0f, 0x4d, 0x32, 0x4d, 0x4f, 0x41, 0x75, 0x74, 0x68, 0x53, 0x65, 0x72, 0x76, 0x69, - 0x63, 0x65, 0x12, 0x93, 0x01, 0x0a, 0x0b, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x6f, 0x6b, - 0x65, 0x6e, 0x12, 0x25, 0x2e, 0x6d, 0x32, 0x6d, 0x6f, 0x61, 0x75, 0x74, 0x68, 0x73, 0x65, 0x72, - 0x76, 0x65, 0x72, 0x2e, 0x70, 0x62, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x6f, 0x6b, - 0x65, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x26, 0x2e, 0x6d, 0x32, 0x6d, 0x6f, - 0x61, 0x75, 0x74, 0x68, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x70, 0x62, 0x2e, 0x43, 0x72, - 0x65, 0x61, 0x74, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x22, 0x35, 0x92, 0x41, 0x08, 0x0a, 0x06, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x82, 0xd3, - 0xe4, 0x93, 0x02, 0x24, 0x3a, 0x01, 0x2a, 0x22, 0x1f, 0x2f, 0x6d, 0x32, 0x6d, 0x2d, 0x6f, 0x61, - 0x75, 0x74, 0x68, 0x2d, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, - 0x31, 0x2f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x12, 0x80, 0x01, 0x0a, 0x09, 0x47, 0x65, 0x74, - 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x12, 0x23, 0x2e, 0x6d, 0x32, 0x6d, 0x6f, 0x61, 0x75, 0x74, - 0x68, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x70, 0x62, 0x2e, 0x47, 0x65, 0x74, 0x54, 0x6f, - 0x6b, 0x65, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x6d, 0x32, - 0x6d, 0x6f, 0x61, 0x75, 0x74, 0x68, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x70, 0x62, 0x2e, - 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0x32, 0x92, 0x41, 0x08, 0x0a, 0x06, 0x54, 0x6f, 0x6b, 0x65, - 0x6e, 0x73, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x21, 0x12, 0x1f, 0x2f, 0x6d, 0x32, 0x6d, 0x2d, 0x6f, - 0x61, 0x75, 0x74, 0x68, 0x2d, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2f, 0x61, 0x70, 0x69, 0x2f, - 0x76, 0x31, 0x2f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x30, 0x01, 0x12, 0xa2, 0x01, 0x0a, 0x0f, - 0x42, 0x6c, 0x61, 0x63, 0x6b, 0x6c, 0x69, 0x73, 0x74, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x12, - 0x29, 0x2e, 0x6d, 0x32, 0x6d, 0x6f, 0x61, 0x75, 0x74, 0x68, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, - 0x2e, 0x70, 0x62, 0x2e, 0x42, 0x6c, 0x61, 0x63, 0x6b, 0x6c, 0x69, 0x73, 0x74, 0x54, 0x6f, 0x6b, - 0x65, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x6d, 0x32, 0x6d, - 0x6f, 0x61, 0x75, 0x74, 0x68, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x70, 0x62, 0x2e, 0x42, - 0x6c, 0x61, 0x63, 0x6b, 0x6c, 0x69, 0x73, 0x74, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x38, 0x92, 0x41, 0x08, 0x0a, 0x06, 0x54, 0x6f, 0x6b, - 0x65, 0x6e, 0x73, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x27, 0x3a, 0x01, 0x2a, 0x22, 0x22, 0x2f, 0x6d, - 0x32, 0x6d, 0x2d, 0x6f, 0x61, 0x75, 0x74, 0x68, 0x2d, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2f, - 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x62, 0x6c, 0x61, 0x63, 0x6b, 0x6c, 0x69, 0x73, 0x74, - 0x42, 0xcb, 0x02, 0x92, 0x41, 0x94, 0x02, 0x12, 0xbc, 0x01, 0x0a, 0x0c, 0x50, 0x4c, 0x47, 0x44, - 0x20, 0x4d, 0x32, 0x4d, 0x20, 0x41, 0x50, 0x49, 0x12, 0x24, 0x41, 0x50, 0x49, 0x20, 0x66, 0x6f, - 0x72, 0x20, 0x74, 0x6f, 0x20, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x20, 0x6d, 0x32, 0x6d, 0x20, - 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x20, 0x69, 0x6e, 0x20, 0x50, 0x4c, 0x47, 0x44, 0x22, 0x3a, - 0x0a, 0x08, 0x70, 0x6c, 0x67, 0x64, 0x2e, 0x64, 0x65, 0x76, 0x12, 0x1f, 0x68, 0x74, 0x74, 0x70, - 0x73, 0x3a, 0x2f, 0x2f, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x70, - 0x6c, 0x67, 0x64, 0x2d, 0x64, 0x65, 0x76, 0x2f, 0x68, 0x75, 0x62, 0x1a, 0x0d, 0x69, 0x6e, 0x66, - 0x6f, 0x40, 0x70, 0x6c, 0x67, 0x64, 0x2e, 0x64, 0x65, 0x76, 0x2a, 0x45, 0x0a, 0x12, 0x41, 0x70, - 0x61, 0x63, 0x68, 0x65, 0x20, 0x4c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x20, 0x32, 0x2e, 0x30, - 0x12, 0x2f, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, - 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x70, 0x6c, 0x67, 0x64, 0x2d, 0x64, 0x65, 0x76, 0x2f, 0x68, 0x75, - 0x62, 0x2f, 0x62, 0x6c, 0x6f, 0x62, 0x2f, 0x76, 0x32, 0x2f, 0x4c, 0x49, 0x43, 0x45, 0x4e, 0x53, - 0x45, 0x32, 0x03, 0x31, 0x2e, 0x30, 0x2a, 0x01, 0x02, 0x32, 0x10, 0x61, 0x70, 0x70, 0x6c, 0x69, - 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x6a, 0x73, 0x6f, 0x6e, 0x32, 0x15, 0x61, 0x70, 0x70, - 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x6a, 0x73, - 0x6f, 0x6e, 0x3a, 0x10, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, - 0x6a, 0x73, 0x6f, 0x6e, 0x3a, 0x15, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x6a, 0x73, 0x6f, 0x6e, 0x5a, 0x31, 0x67, 0x69, 0x74, - 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x70, 0x6c, 0x67, 0x64, 0x2d, 0x64, 0x65, 0x76, - 0x2f, 0x68, 0x75, 0x62, 0x2f, 0x76, 0x32, 0x2f, 0x6d, 0x32, 0x6d, 0x2d, 0x6f, 0x61, 0x75, 0x74, - 0x68, 0x2d, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2f, 0x70, 0x62, 0x3b, 0x70, 0x62, 0x62, 0x06, - 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x12, 0x1e, 0x0a, 0x0a, 0x65, 0x78, 0x70, 0x69, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x05, + 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x65, 0x78, 0x70, 0x69, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x12, 0x32, 0x0a, 0x15, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x61, 0x73, 0x73, 0x65, 0x72, + 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x13, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x41, 0x73, 0x73, 0x65, 0x72, 0x74, 0x69, 0x6f, 0x6e, + 0x54, 0x79, 0x70, 0x65, 0x12, 0x29, 0x0a, 0x10, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x61, + 0x73, 0x73, 0x65, 0x72, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, + 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x41, 0x73, 0x73, 0x65, 0x72, 0x74, 0x69, 0x6f, 0x6e, 0x12, + 0x1d, 0x0a, 0x0a, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x08, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x09, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1d, + 0x0a, 0x0a, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x09, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x09, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x22, 0x8c, 0x01, + 0x0a, 0x13, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x5f, + 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x61, 0x63, 0x63, + 0x65, 0x73, 0x73, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x1d, 0x0a, 0x0a, 0x74, 0x6f, 0x6b, 0x65, + 0x6e, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x74, 0x6f, + 0x6b, 0x65, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x65, 0x78, 0x70, 0x69, 0x72, + 0x65, 0x73, 0x5f, 0x69, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x65, 0x78, 0x70, + 0x69, 0x72, 0x65, 0x73, 0x49, 0x6e, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x18, + 0x04, 0x20, 0x03, 0x28, 0x09, 0x52, 0x05, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x32, 0xcf, 0x03, 0x0a, + 0x0f, 0x4d, 0x32, 0x4d, 0x4f, 0x41, 0x75, 0x74, 0x68, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, + 0x12, 0x93, 0x01, 0x0a, 0x0b, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, + 0x12, 0x25, 0x2e, 0x6d, 0x32, 0x6d, 0x6f, 0x61, 0x75, 0x74, 0x68, 0x73, 0x65, 0x72, 0x76, 0x65, + 0x72, 0x2e, 0x70, 0x62, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x26, 0x2e, 0x6d, 0x32, 0x6d, 0x6f, 0x61, 0x75, + 0x74, 0x68, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x70, 0x62, 0x2e, 0x43, 0x72, 0x65, 0x61, + 0x74, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, + 0x35, 0x92, 0x41, 0x08, 0x0a, 0x06, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x82, 0xd3, 0xe4, 0x93, + 0x02, 0x24, 0x3a, 0x01, 0x2a, 0x22, 0x1f, 0x2f, 0x6d, 0x32, 0x6d, 0x2d, 0x6f, 0x61, 0x75, 0x74, + 0x68, 0x2d, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, + 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x12, 0x80, 0x01, 0x0a, 0x09, 0x47, 0x65, 0x74, 0x54, 0x6f, + 0x6b, 0x65, 0x6e, 0x73, 0x12, 0x23, 0x2e, 0x6d, 0x32, 0x6d, 0x6f, 0x61, 0x75, 0x74, 0x68, 0x73, + 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x70, 0x62, 0x2e, 0x47, 0x65, 0x74, 0x54, 0x6f, 0x6b, 0x65, + 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x6d, 0x32, 0x6d, 0x6f, + 0x61, 0x75, 0x74, 0x68, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x70, 0x62, 0x2e, 0x54, 0x6f, + 0x6b, 0x65, 0x6e, 0x22, 0x32, 0x92, 0x41, 0x08, 0x0a, 0x06, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, + 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x21, 0x12, 0x1f, 0x2f, 0x6d, 0x32, 0x6d, 0x2d, 0x6f, 0x61, 0x75, + 0x74, 0x68, 0x2d, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, + 0x2f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x30, 0x01, 0x12, 0xa2, 0x01, 0x0a, 0x0f, 0x42, 0x6c, + 0x61, 0x63, 0x6b, 0x6c, 0x69, 0x73, 0x74, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x12, 0x29, 0x2e, + 0x6d, 0x32, 0x6d, 0x6f, 0x61, 0x75, 0x74, 0x68, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x70, + 0x62, 0x2e, 0x42, 0x6c, 0x61, 0x63, 0x6b, 0x6c, 0x69, 0x73, 0x74, 0x54, 0x6f, 0x6b, 0x65, 0x6e, + 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x6d, 0x32, 0x6d, 0x6f, 0x61, + 0x75, 0x74, 0x68, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x70, 0x62, 0x2e, 0x42, 0x6c, 0x61, + 0x63, 0x6b, 0x6c, 0x69, 0x73, 0x74, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x38, 0x92, 0x41, 0x08, 0x0a, 0x06, 0x54, 0x6f, 0x6b, 0x65, 0x6e, + 0x73, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x27, 0x3a, 0x01, 0x2a, 0x22, 0x22, 0x2f, 0x6d, 0x32, 0x6d, + 0x2d, 0x6f, 0x61, 0x75, 0x74, 0x68, 0x2d, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2f, 0x61, 0x70, + 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x62, 0x6c, 0x61, 0x63, 0x6b, 0x6c, 0x69, 0x73, 0x74, 0x42, 0xcb, + 0x02, 0x92, 0x41, 0x94, 0x02, 0x12, 0xbc, 0x01, 0x0a, 0x0c, 0x50, 0x4c, 0x47, 0x44, 0x20, 0x4d, + 0x32, 0x4d, 0x20, 0x41, 0x50, 0x49, 0x12, 0x24, 0x41, 0x50, 0x49, 0x20, 0x66, 0x6f, 0x72, 0x20, + 0x74, 0x6f, 0x20, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x20, 0x6d, 0x32, 0x6d, 0x20, 0x74, 0x6f, + 0x6b, 0x65, 0x6e, 0x73, 0x20, 0x69, 0x6e, 0x20, 0x50, 0x4c, 0x47, 0x44, 0x22, 0x3a, 0x0a, 0x08, + 0x70, 0x6c, 0x67, 0x64, 0x2e, 0x64, 0x65, 0x76, 0x12, 0x1f, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, + 0x2f, 0x2f, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x70, 0x6c, 0x67, + 0x64, 0x2d, 0x64, 0x65, 0x76, 0x2f, 0x68, 0x75, 0x62, 0x1a, 0x0d, 0x69, 0x6e, 0x66, 0x6f, 0x40, + 0x70, 0x6c, 0x67, 0x64, 0x2e, 0x64, 0x65, 0x76, 0x2a, 0x45, 0x0a, 0x12, 0x41, 0x70, 0x61, 0x63, + 0x68, 0x65, 0x20, 0x4c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x20, 0x32, 0x2e, 0x30, 0x12, 0x2f, + 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, + 0x6f, 0x6d, 0x2f, 0x70, 0x6c, 0x67, 0x64, 0x2d, 0x64, 0x65, 0x76, 0x2f, 0x68, 0x75, 0x62, 0x2f, + 0x62, 0x6c, 0x6f, 0x62, 0x2f, 0x76, 0x32, 0x2f, 0x4c, 0x49, 0x43, 0x45, 0x4e, 0x53, 0x45, 0x32, + 0x03, 0x31, 0x2e, 0x30, 0x2a, 0x01, 0x02, 0x32, 0x10, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x6a, 0x73, 0x6f, 0x6e, 0x32, 0x15, 0x61, 0x70, 0x70, 0x6c, 0x69, + 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x6a, 0x73, 0x6f, 0x6e, + 0x3a, 0x10, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x6a, 0x73, + 0x6f, 0x6e, 0x3a, 0x15, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x6a, 0x73, 0x6f, 0x6e, 0x5a, 0x31, 0x67, 0x69, 0x74, 0x68, 0x75, + 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x70, 0x6c, 0x67, 0x64, 0x2d, 0x64, 0x65, 0x76, 0x2f, 0x68, + 0x75, 0x62, 0x2f, 0x76, 0x32, 0x2f, 0x6d, 0x32, 0x6d, 0x2d, 0x6f, 0x61, 0x75, 0x74, 0x68, 0x2d, + 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2f, 0x70, 0x62, 0x3b, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/m2m-oauth-server/pb/service.proto b/m2m-oauth-server/pb/service.proto index c5cf5b73a..345f04f2e 100644 --- a/m2m-oauth-server/pb/service.proto +++ b/m2m-oauth-server/pb/service.proto @@ -38,22 +38,22 @@ message Token { // driven by resource change event string name = 3; // Owner of the token string owner = 4; - // Unix timestamp in ns when the condition has been created/updated + // Unix timestamp in s when the condition has been created/updated int64 issued_at = 5; // Token Audience repeated string audience = 6; // Token scopes repeated string scope = 7; - // Original token expiration + // Token expiration in Unix timestamp seconds int64 expiration = 8; // Client ID string client_id = 9; // Original token claims google.protobuf.Value original_token_claims = 10; message BlackListed { - // Blacklisted/revoked enabled flag, if once token has been blacklisted/revoked then it can't be unblacklisted/unrevoked + // Blacklisted enabled flag, if once token has been blacklisted then it can't be unblacklisted/unrevoked bool flag = 1; - // Unix timestamp in ns when the token has been blacklisted/revoked + // Unix timestamp in s when the token has been blacklisted int64 timestamp = 2; } // Token black list section @@ -77,14 +77,23 @@ message BlacklistTokensResponse { } message CreateTokenRequest { + // Client ID string client_id = 1; + // Client Secret string client_secret = 2; + // Requested token Audience repeated string audience = 3; + // Requested token scopes repeated string scope = 4; - int64 time_to_live = 5; + // Requested token expiration in Unix timestamp seconds + int64 expiration = 5; + // Client assertion type string client_assertion_type = 6; + // Client assertion string client_assertion = 7; + // Token name string token_name = 8; + // Grant type string grant_type = 9; } diff --git a/m2m-oauth-server/pb/service.swagger.json b/m2m-oauth-server/pb/service.swagger.json index cc681b4aa..4a718640f 100644 --- a/m2m-oauth-server/pb/service.swagger.json +++ b/m2m-oauth-server/pb/service.swagger.json @@ -162,12 +162,12 @@ "properties": { "flag": { "type": "boolean", - "title": "Blacklisted/revoked enabled flag, if once token has been blacklisted/revoked then it can't be unblacklisted/unrevoked" + "title": "Blacklisted enabled flag, if once token has been blacklisted then it can't be unblacklisted/unrevoked" }, "timestamp": { "type": "string", "format": "int64", - "title": "Unix timestamp in ns when the token has been blacklisted/revoked" + "title": "Unix timestamp in s when the token has been blacklisted" } } }, @@ -195,38 +195,47 @@ "type": "object", "properties": { "clientId": { - "type": "string" + "type": "string", + "title": "Client ID" }, "clientSecret": { - "type": "string" + "type": "string", + "title": "Client Secret" }, "audience": { "type": "array", "items": { "type": "string" - } + }, + "title": "Requested token Audience" }, "scope": { "type": "array", "items": { "type": "string" - } + }, + "title": "Requested token scopes" }, - "timeToLive": { + "expiration": { "type": "string", - "format": "int64" + "format": "int64", + "title": "Requested token expiration in Unix timestamp seconds" }, "clientAssertionType": { - "type": "string" + "type": "string", + "title": "Client assertion type" }, "clientAssertion": { - "type": "string" + "type": "string", + "title": "Client assertion" }, "tokenName": { - "type": "string" + "type": "string", + "title": "Token name" }, "grantType": { - "type": "string" + "type": "string", + "title": "Grant type" } } }, @@ -274,7 +283,7 @@ "issuedAt": { "type": "string", "format": "int64", - "title": "Unix timestamp in ns when the condition has been created/updated" + "title": "Unix timestamp in s when the condition has been created/updated" }, "audience": { "type": "array", @@ -293,7 +302,7 @@ "expiration": { "type": "string", "format": "int64", - "title": "Original token expiration" + "title": "Token expiration in Unix timestamp seconds" }, "clientId": { "type": "string", diff --git a/m2m-oauth-server/service/grpc/server.go b/m2m-oauth-server/service/grpc/server.go index e4aa6454a..aad8aa466 100644 --- a/m2m-oauth-server/service/grpc/server.go +++ b/m2m-oauth-server/service/grpc/server.go @@ -99,10 +99,10 @@ func (s *M2MOAuthServiceServer) CreateToken(ctx context.Context, req *pb.CreateT Id: tokenReq.id, Name: tokenReq.CreateTokenRequest.GetTokenName(), Owner: tokenReq.owner, - IssuedAt: tokenReq.issuedAt.UnixNano(), + IssuedAt: tokenReq.issuedAt.Unix(), Audience: tokenReq.CreateTokenRequest.GetAudience(), Scope: tokenReq.CreateTokenRequest.GetScope(), - Expiration: pkgTime.UnixNano(tokenReq.expiration), + Expiration: pkgTime.UnixSec(tokenReq.expiration), ClientId: tokenReq.CreateTokenRequest.GetClientId(), OriginalTokenClaims: originalTokenClaims, }) diff --git a/m2m-oauth-server/service/grpc/token.go b/m2m-oauth-server/service/grpc/token.go index 128c9ff51..f56c9c1ba 100644 --- a/m2m-oauth-server/service/grpc/token.go +++ b/m2m-oauth-server/service/grpc/token.go @@ -25,21 +25,18 @@ func setKeyErrorExt(key, info interface{}, err error) error { func makeAccessToken(clientCfg *oauthsigner.Client, tokenReq tokenRequest) (jwt.Token, error) { token := jwt.New() - simpleSets := []struct { - key string - val interface{} - }{ - {jwt.JwtIDKey, tokenReq.id}, - {jwt.SubjectKey, getSubject(clientCfg, tokenReq)}, - {jwt.AudienceKey, tokenReq.host}, - {jwt.IssuedAtKey, tokenReq.issuedAt}, - {uri.ScopeKey, tokenReq.scopes}, - {uri.ClientIDKey, clientCfg.ID}, - {jwt.IssuerKey, tokenReq.host}, - } - for _, set := range simpleSets { - if err := token.Set(set.key, set.val); err != nil { - return nil, setKeyError(set.key, err) + claims := map[string]interface{}{ + jwt.JwtIDKey: tokenReq.id, + jwt.SubjectKey: getSubject(clientCfg, tokenReq), + jwt.AudienceKey: tokenReq.host, + jwt.IssuedAtKey: tokenReq.issuedAt, + uri.ScopeKey: tokenReq.scopes, + uri.ClientIDKey: clientCfg.ID, + jwt.IssuerKey: tokenReq.host, + } + for key, val := range claims { + if err := token.Set(key, val); err != nil { + return nil, setKeyError(key, err) } } if !tokenReq.expiration.IsZero() { @@ -111,15 +108,24 @@ func setOriginTokenClaims(token jwt.Token, tokenReq tokenRequest) error { } func getExpirationTime(clientCfg *oauthsigner.Client, tokenReq tokenRequest) time.Time { - var expires time.Time - ttl := time.Duration(tokenReq.CreateTokenRequest.GetTimeToLive()) * time.Nanosecond - if ttl == 0 || (ttl > clientCfg.AccessTokenLifetime && clientCfg.AccessTokenLifetime > 0) { - ttl = clientCfg.AccessTokenLifetime + if clientCfg.AccessTokenLifetime == 0 { + if tokenReq.CreateTokenRequest.GetExpiration() > 0 { + return time.Unix(tokenReq.CreateTokenRequest.GetExpiration(), 0) + } + return time.Time{} + } + if tokenReq.CreateTokenRequest.GetExpiration() == 0 { + if clientCfg.AccessTokenLifetime == 0 { + return time.Time{} + } + return tokenReq.issuedAt.Add(clientCfg.AccessTokenLifetime) } - if ttl > 0 { - expires = tokenReq.issuedAt.Add(clientCfg.AccessTokenLifetime) + wantExpiration := time.Unix(tokenReq.CreateTokenRequest.GetExpiration(), 0) + clientWantExpiration := tokenReq.issuedAt.Add(clientCfg.AccessTokenLifetime) + if clientWantExpiration.Before(wantExpiration) { + return clientWantExpiration } - return expires + return wantExpiration } func (s *M2MOAuthServiceServer) generateAccessToken(clientCfg *oauthsigner.Client, tokenReq tokenRequest) (string, error) { @@ -166,6 +172,20 @@ func sliceContains[T comparable](s []T, sub []T) bool { return len(check) == 0 } +func validateExpiration(clientCfg *oauthsigner.Client, tokenReq *tokenRequest) error { + if tokenReq.CreateTokenRequest.GetExpiration() > 0 { + if tokenReq.CreateTokenRequest.GetExpiration() < tokenReq.issuedAt.Unix() { + return fmt.Errorf("expiration(%v) must be greater than issuedAt(%v)", time.Unix(tokenReq.CreateTokenRequest.GetExpiration(), 0), tokenReq.issuedAt) + } + if clientCfg.AccessTokenLifetime > 0 { + if tokenReq.CreateTokenRequest.GetExpiration() > tokenReq.issuedAt.Add(clientCfg.AccessTokenLifetime).Unix() { + return fmt.Errorf("expiration(%v) must be less than or equal to issuedAt + client accessTokenLifetime(%v)", time.Unix(tokenReq.CreateTokenRequest.GetExpiration(), 0), tokenReq.issuedAt.Add(clientCfg.AccessTokenLifetime)) + } + } + } + return nil +} + func (s *M2MOAuthServiceServer) validateTokenRequest(ctx context.Context, clientCfg *oauthsigner.Client, tokenReq *tokenRequest) error { if err := validateGrantType(clientCfg, tokenReq); err != nil { return err @@ -173,6 +193,9 @@ func (s *M2MOAuthServiceServer) validateTokenRequest(ctx context.Context, client if err := validateClient(clientCfg, tokenReq); err != nil { return err } + if err := validateExpiration(clientCfg, tokenReq); err != nil { + return err + } if err := validateClientAssertionType(clientCfg, tokenReq); err != nil { return err } @@ -185,7 +208,6 @@ func (s *M2MOAuthServiceServer) validateTokenRequest(ctx context.Context, client if err := validateScopes(clientCfg, tokenReq); err != nil { return err } - return nil } diff --git a/m2m-oauth-server/service/http/postToken.go b/m2m-oauth-server/service/http/postToken.go index 795b97a92..6f1748fa7 100644 --- a/m2m-oauth-server/service/http/postToken.go +++ b/m2m-oauth-server/service/http/postToken.go @@ -4,7 +4,6 @@ import ( "net/http" "strconv" "strings" - "time" "github.com/plgd-dev/hub/v2/http-gateway/serverMux" "github.com/plgd-dev/hub/v2/m2m-oauth-server/pb" @@ -16,15 +15,15 @@ import ( ) type postRequest struct { - ClientID string `json:"client_id"` - Secret string `json:"client_secret"` - Audience string `json:"audience"` - GrantType string `json:"grant_type"` - ClientAssertionType string `json:"client_assertion_type"` - ClientAssertion string `json:"client_assertion"` - TokenName string `json:"token_name"` - Scope string `json:"scope"` - TimeToLive time.Duration `json:"time_to_live"` + ClientID string `json:"client_id"` + Secret string `json:"client_secret"` + Audience string `json:"audience"` + GrantType string `json:"grant_type"` + ClientAssertionType string `json:"client_assertion_type"` + ClientAssertion string `json:"client_assertion"` + TokenName string `json:"token_name"` + Scope string `json:"scope"` + Expiration int64 `json:"expiration"` } func postFormToCreateTokenRequest(r *http.Request, createTokenRequest *pb.CreateTokenRequest) { @@ -42,12 +41,12 @@ func postFormToCreateTokenRequest(r *http.Request, createTokenRequest *pb.Create createTokenRequest.ClientAssertionType = r.PostFormValue(uri.ClientAssertionTypeKey) createTokenRequest.ClientAssertion = r.PostFormValue(uri.ClientAssertionKey) createTokenRequest.TokenName = r.PostFormValue(uri.TokenNameKey) - ttl := r.PostFormValue(uri.TimeToLiveKey) - if ttl == "" { + expiration := r.PostFormValue(uri.ExpirationKey) + if expiration == "" { return } - if ttlVal, err := strconv.ParseInt(ttl, 10, 64); err == nil { - createTokenRequest.TimeToLive = ttlVal + if expirationVal, err := strconv.ParseInt(expiration, 10, 64); err == nil { + createTokenRequest.Expiration = expirationVal } } @@ -66,7 +65,7 @@ func jsonToCreateTokenRequest(req postRequest, createTokenRequest *pb.CreateToke createTokenRequest.ClientAssertionType = req.ClientAssertionType createTokenRequest.ClientAssertion = req.ClientAssertion createTokenRequest.TokenName = req.TokenName - createTokenRequest.TimeToLive = req.TimeToLive.Nanoseconds() + createTokenRequest.Expiration = req.Expiration } func (requestHandler *RequestHandler) postToken(w http.ResponseWriter, r *http.Request) { diff --git a/m2m-oauth-server/store/mongodb/tokens.go b/m2m-oauth-server/store/mongodb/tokens.go index b670194d7..87eeea0da 100644 --- a/m2m-oauth-server/store/mongodb/tokens.go +++ b/m2m-oauth-server/store/mongodb/tokens.go @@ -117,7 +117,7 @@ func (s *Store) GetTokens(ctx context.Context, owner string, req *pb.GetTokensRe func (s *Store) DeleteTokens(ctx context.Context, now time.Time) error { deleteFilter := bson.D{ - {Key: pb.ExpirationKey, Value: bson.M{"$lt": now.UnixNano()}}, + {Key: pb.ExpirationKey, Value: bson.M{"$lt": now.Unix()}}, {Key: pb.ExpirationKey, Value: bson.M{"$gt": int64(0)}}, {Key: pb.BlackListedFlagKey, Value: true}, } @@ -134,7 +134,7 @@ func (s *Store) BlacklistTokens(ctx context.Context, owner string, req *pb.Black { Key: mongodb.Or, Value: bson.A{ bson.M{ - pb.ExpirationKey: bson.M{"$gte": time.Now().UnixNano()}, + pb.ExpirationKey: bson.M{"$gte": time.Now().Unix()}, }, bson.M{ pb.ExpirationKey: bson.M{mongodb.Exists: false}, @@ -156,7 +156,7 @@ func (s *Store) BlacklistTokens(ctx context.Context, owner string, req *pb.Black } blacklisted := pb.Token_BlackListed{ Flag: true, - Timestamp: time.Now().UnixNano(), + Timestamp: time.Now().Unix(), } value, err := blacklisted.ToBsonMap() if err != nil { diff --git a/m2m-oauth-server/store/mongodb/tokens_test.go b/m2m-oauth-server/store/mongodb/tokens_test.go index d37bf4698..381fd0e12 100644 --- a/m2m-oauth-server/store/mongodb/tokens_test.go +++ b/m2m-oauth-server/store/mongodb/tokens_test.go @@ -26,7 +26,7 @@ func TestGetTokens(t *testing.T) { Owner: owner, Version: 0, Name: "name1", - IssuedAt: time.Now().UnixNano(), + IssuedAt: time.Now().Unix(), ClientId: "client1", }, { @@ -34,11 +34,11 @@ func TestGetTokens(t *testing.T) { Owner: owner, Version: 0, Name: "name2", - IssuedAt: time.Now().UnixNano(), + IssuedAt: time.Now().Unix(), ClientId: "client1", Blacklisted: &pb.Token_BlackListed{ Flag: true, - Timestamp: time.Now().UnixNano(), + Timestamp: time.Now().Unix(), }, }, } @@ -133,7 +133,7 @@ func TestBlacklistTokens(t *testing.T) { Owner: owner, Version: 0, Name: "name1", - IssuedAt: time.Now().UnixNano(), + IssuedAt: time.Now().Unix(), ClientId: "client1", }, { @@ -141,7 +141,7 @@ func TestBlacklistTokens(t *testing.T) { Owner: owner, Version: 0, Name: "name2", - IssuedAt: time.Now().UnixNano(), + IssuedAt: time.Now().Unix(), ClientId: "client1", }, { @@ -149,7 +149,7 @@ func TestBlacklistTokens(t *testing.T) { Owner: owner, Version: 0, Name: "name3", - IssuedAt: time.Now().UnixNano(), + IssuedAt: time.Now().Unix(), ClientId: "client1", }, } @@ -173,11 +173,11 @@ func TestBlacklistTokens(t *testing.T) { Owner: owner, Version: 0, Name: "name1", - IssuedAt: time.Now().UnixNano(), + IssuedAt: time.Now().Unix(), ClientId: "client1", Blacklisted: &pb.Token_BlackListed{ Flag: true, - Timestamp: time.Now().UnixNano(), + Timestamp: time.Now().Unix(), }, }, { @@ -185,11 +185,11 @@ func TestBlacklistTokens(t *testing.T) { Owner: owner, Version: 0, Name: "name2", - IssuedAt: time.Now().UnixNano(), + IssuedAt: time.Now().Unix(), ClientId: "client1", Blacklisted: &pb.Token_BlackListed{ Flag: true, - Timestamp: time.Now().UnixNano(), + Timestamp: time.Now().Unix(), }, }, } @@ -226,12 +226,12 @@ func TestDeleteTokens(t *testing.T) { Owner: owner, Version: 0, Name: "name1", - IssuedAt: time.Now().UnixNano(), + IssuedAt: time.Now().Unix(), ClientId: "client1", - Expiration: time.Now().Add(time.Minute * 10).UnixNano(), + Expiration: time.Now().Add(time.Minute * 10).Unix(), Blacklisted: &pb.Token_BlackListed{ Flag: true, - Timestamp: time.Now().UnixNano(), + Timestamp: time.Now().Unix(), }, }, { @@ -239,12 +239,12 @@ func TestDeleteTokens(t *testing.T) { Owner: owner, Version: 0, Name: "name2", - IssuedAt: time.Now().UnixNano(), + IssuedAt: time.Now().Unix(), ClientId: "client1", - Expiration: time.Now().Add(time.Minute * 10).UnixNano(), + Expiration: time.Now().Add(time.Minute * 10).Unix(), Blacklisted: &pb.Token_BlackListed{ Flag: true, - Timestamp: time.Now().Add(time.Minute).UnixNano(), + Timestamp: time.Now().Add(time.Minute).Unix(), }, }, { @@ -252,7 +252,7 @@ func TestDeleteTokens(t *testing.T) { Owner: owner, Version: 0, Name: "name3", - IssuedAt: time.Now().UnixNano(), + IssuedAt: time.Now().Unix(), ClientId: "client1", }, } @@ -271,7 +271,7 @@ func TestDeleteTokens(t *testing.T) { Owner: owner, Version: 0, Name: "name3", - IssuedAt: time.Now().UnixNano(), + IssuedAt: time.Now().Unix(), ClientId: "client1", }, } diff --git a/m2m-oauth-server/swagger.yaml b/m2m-oauth-server/swagger.yaml index 69460134d..ba4c23b1f 100644 --- a/m2m-oauth-server/swagger.yaml +++ b/m2m-oauth-server/swagger.yaml @@ -34,9 +34,9 @@ paths: token_name: type: string description: "The name of the token which will be used in the name claim." - time_to_live: + expiration: type: integer - description: "The time to live of the token in nano seconds. If not provided, the token will be max allowed by client." + description: "The requested expiration time in seconds from the client. If not provided, the token will be max allowed by client." scope: type: string description: "The scopes that are requested, separated by space. Must be a subset of the allowed scopes for the client." @@ -226,11 +226,11 @@ components: type: object properties: flag: - title: "Blacklisted/revoked enabled flag, if once token has been blacklisted/revoked\ - \ then it can't be unblacklisted/unrevoked" + title: "Blacklisted enabled flag, if once token has been blacklisted then\ + \ it can't be unblacklisted/unrevoked" type: boolean timestamp: - title: Unix timestamp in ns when the token has been blacklisted/revoked + title: Unix timestamp in s when the token has been blacklisted type: string format: int64 pbBlacklistTokensRequest: @@ -250,27 +250,36 @@ components: type: object properties: clientId: + title: Client ID type: string clientSecret: + title: Client Secret type: string audience: + title: Requested token Audience type: array items: type: string scope: + title: Requested token scopes type: array items: type: string - timeToLive: + expiration: + title: Requested token expiration in Unix timestamp seconds type: string format: int64 clientAssertionType: + title: Client assertion type type: string clientAssertion: + title: Client assertion type: string tokenName: + title: Token name type: string grantType: + title: Grant type type: string pbCreateTokenResponse: type: object @@ -304,7 +313,7 @@ components: title: Owner of the token type: string issuedAt: - title: Unix timestamp in ns when the condition has been created/updated + title: Unix timestamp in s when the condition has been created/updated type: string format: int64 audience: @@ -318,7 +327,7 @@ components: items: type: string expiration: - title: Original token expiration + title: Token expiration in Unix timestamp seconds type: string format: int64 clientId: diff --git a/m2m-oauth-server/uri/uri.go b/m2m-oauth-server/uri/uri.go index d6013bcd9..349de5d32 100644 --- a/m2m-oauth-server/uri/uri.go +++ b/m2m-oauth-server/uri/uri.go @@ -8,7 +8,7 @@ const ( TokenNameKey = "token_name" ScopeKey = "scope" GrantTypeKey = "grant_type" - TimeToLiveKey = "time_to_live" + ExpirationKey = "expiration" AudienceKey = jwt.AudienceKey AccessTokenKey = "access_token" ClientAssertionTypeKey = "client_assertion_type" diff --git a/pkg/time/unixnano.go b/pkg/time/unixnano.go index d25d7e356..98018b174 100644 --- a/pkg/time/unixnano.go +++ b/pkg/time/unixnano.go @@ -16,6 +16,14 @@ func UnixNano(t time.Time) int64 { return v } +func UnixSec(t time.Time) int64 { + v := int64(0) + if !t.IsZero() { + v = t.Unix() + } + return v +} + func Unix(sec int64, nsec int64) time.Time { if sec != 0 || nsec != 0 { return time.Unix(sec, nsec) From 8cb1d2ab74edeabdab129d89226b1ab92cbe0683 Mon Sep 17 00:00:00 2001 From: Jozef Kralik Date: Thu, 18 Jul 2024 12:47:27 +0000 Subject: [PATCH 15/31] fix sonarcloud issue --- m2m-oauth-server/pb/token.go | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/m2m-oauth-server/pb/token.go b/m2m-oauth-server/pb/token.go index 49c89ba40..94d9bebda 100644 --- a/m2m-oauth-server/pb/token.go +++ b/m2m-oauth-server/pb/token.go @@ -10,9 +10,11 @@ import ( "google.golang.org/protobuf/encoding/protojson" ) +var errTokenIsNil = errors.New("Token is nil") + func (x *Token) Validate() error { if x == nil { - return errors.New("Token is nil") + return errTokenIsNil } if x.GetId() == "" { return errors.New("Token.Id is empty") @@ -103,7 +105,7 @@ func (x *Token) ToBsonMap() (map[string]interface{}, error) { func (x *Token) FromMap(m map[string]interface{}) error { if x == nil { - return errors.New("Token is nil") + return errTokenIsNil } data, err := json.Marshal(m) if err != nil { @@ -118,7 +120,7 @@ func (x *Token) FromMap(m map[string]interface{}) error { func (x *Token) FromBsonMap(m map[string]interface{}) error { if x == nil { - return errors.New("Token is nil") + return errTokenIsNil } m["id"] = m["_id"] delete(m, "_id") From a4e12b5e467948915c11f058b5bde5fcac8f35c9 Mon Sep 17 00:00:00 2001 From: Jozef Kralik Date: Thu, 18 Jul 2024 19:49:37 +0000 Subject: [PATCH 16/31] add subject --- m2m-oauth-server/pb/README.md | 1 + m2m-oauth-server/pb/doc.html | 7 + m2m-oauth-server/pb/service.pb.go | 210 ++++++++++++----------- m2m-oauth-server/pb/service.proto | 2 + m2m-oauth-server/pb/service.swagger.json | 4 + m2m-oauth-server/service/grpc/server.go | 2 + m2m-oauth-server/service/grpc/token.go | 2 +- 7 files changed, 127 insertions(+), 101 deletions(-) diff --git a/m2m-oauth-server/pb/README.md b/m2m-oauth-server/pb/README.md index ea4e88250..bc8e8b657 100644 --- a/m2m-oauth-server/pb/README.md +++ b/m2m-oauth-server/pb/README.md @@ -134,6 +134,7 @@ driven by resource change event | client_id | [string](#string) | | Client ID | | original_token_claims | [google.protobuf.Value](#google-protobuf-Value) | | Original token claims | | blacklisted | [Token.BlackListed](#m2moauthserver-pb-Token-BlackListed) | | Token black list section | +| subject | [string](#string) | | Subject of the token | diff --git a/m2m-oauth-server/pb/doc.html b/m2m-oauth-server/pb/doc.html index 04e5dc89c..254f85a36 100644 --- a/m2m-oauth-server/pb/doc.html +++ b/m2m-oauth-server/pb/doc.html @@ -526,6 +526,13 @@

    Token

    + + + + + + +
    GetTokensGetTokensRequestToken

    Returns all tokens of the owner

    CreateTokenCreateTokenRequestCreateTokenResponse

    Creates a new token

    GetBlacklistedTokensGetBlacklistedTokensRequestGetBlacklistedTokensResponse

    Returns all blacklisted/revoked not expired tokens

    GetTokensGetTokensRequestToken stream

    Returns all tokens of the owner

    GetTokensGETCreateTokenPOST /m2m-oauth-server/api/v1/tokens*
    GetBlacklistedTokensGetTokens GET/m2m-oauth-server/api/v1/blacklist/m2m-oauth-server/api/v1/tokens
    id string

    Token ID / jti - -@gotags: bson:"_id"

    Token ID / jti

    client_id string

    Client ID

    client_secret string

    Client Secret

    audience string repeated

    Requested token Audience

    scope string repeated

    Requested token scopes

    time_to_liveexpiration int64

    Requested token expiration in Unix timestamp seconds

    client_assertion_type string

    Client assertion type

    client_assertion string

    Client assertion

    token_name string

    Token name

    grant_type string

    Grant type

    issued_at int64

    Unix timestamp in ns when the condition has been created/updated

    Unix timestamp in s when the condition has been created/updated

    expiration int64

    Original token expiration

    Token expiration in Unix timestamp seconds

    flag bool

    Blacklisted/revoked enabled flag, if once token has been blacklisted/revoked then it can't be unblacklisted/unrevoked

    Blacklisted enabled flag, if once token has been blacklisted then it can't be unblacklisted/unrevoked

    timestamp int64

    Unix timestamp in ns when the token has been blacklisted/revoked

    Unix timestamp in s when the token has been blacklisted

    Token black list section

    subjectstring

    Subject of the token

    diff --git a/m2m-oauth-server/pb/service.pb.go b/m2m-oauth-server/pb/service.pb.go index 5c3a0539d..8b756dd44 100644 --- a/m2m-oauth-server/pb/service.pb.go +++ b/m2m-oauth-server/pb/service.pb.go @@ -51,6 +51,8 @@ type Token struct { OriginalTokenClaims *structpb.Value `protobuf:"bytes,10,opt,name=original_token_claims,json=originalTokenClaims,proto3" json:"original_token_claims,omitempty"` // Token black list section Blacklisted *Token_BlackListed `protobuf:"bytes,11,opt,name=blacklisted,proto3" json:"blacklisted,omitempty"` + // Subject of the token + Subject string `protobuf:"bytes,12,opt,name=subject,proto3" json:"subject,omitempty"` } func (x *Token) Reset() { @@ -162,6 +164,13 @@ func (x *Token) GetBlacklisted() *Token_BlackListed { return nil } +func (x *Token) GetSubject() string { + if x != nil { + return x.Subject + } + return "" +} + type GetTokensRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -580,7 +589,7 @@ var file_m2m_oauth_server_pb_service_proto_rawDesc = []byte{ 0x74, 0x6f, 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x2d, 0x67, 0x65, 0x6e, 0x2d, 0x6f, 0x70, 0x65, 0x6e, 0x61, 0x70, 0x69, 0x76, 0x32, 0x2f, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2f, 0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x22, 0xbc, 0x03, 0x0a, 0x05, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x0e, 0x0a, 0x02, + 0x74, 0x6f, 0x22, 0xd6, 0x03, 0x0a, 0x05, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, @@ -604,106 +613,107 @@ var file_m2m_oauth_server_pb_service_proto_rawDesc = []byte{ 0x6d, 0x32, 0x6d, 0x6f, 0x61, 0x75, 0x74, 0x68, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x70, 0x62, 0x2e, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x2e, 0x42, 0x6c, 0x61, 0x63, 0x6b, 0x4c, 0x69, 0x73, 0x74, 0x65, 0x64, 0x52, 0x0b, 0x62, 0x6c, 0x61, 0x63, 0x6b, 0x6c, 0x69, 0x73, 0x74, 0x65, 0x64, - 0x1a, 0x3f, 0x0a, 0x0b, 0x42, 0x6c, 0x61, 0x63, 0x6b, 0x4c, 0x69, 0x73, 0x74, 0x65, 0x64, 0x12, - 0x12, 0x0a, 0x04, 0x66, 0x6c, 0x61, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x04, 0x66, - 0x6c, 0x61, 0x67, 0x12, 0x1c, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, - 0x70, 0x22, 0x89, 0x01, 0x0a, 0x10, 0x47, 0x65, 0x74, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x69, 0x64, 0x5f, 0x66, 0x69, 0x6c, - 0x74, 0x65, 0x72, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x08, 0x69, 0x64, 0x46, 0x69, 0x6c, - 0x74, 0x65, 0x72, 0x12, 0x27, 0x0a, 0x0f, 0x61, 0x75, 0x64, 0x69, 0x65, 0x6e, 0x63, 0x65, 0x5f, - 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0e, 0x61, 0x75, - 0x64, 0x69, 0x65, 0x6e, 0x63, 0x65, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x12, 0x2f, 0x0a, 0x13, - 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x5f, 0x62, 0x6c, 0x61, 0x63, 0x6b, 0x6c, 0x69, 0x73, - 0x74, 0x65, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x12, 0x69, 0x6e, 0x63, 0x6c, 0x75, - 0x64, 0x65, 0x42, 0x6c, 0x61, 0x63, 0x6b, 0x6c, 0x69, 0x73, 0x74, 0x65, 0x64, 0x22, 0x35, 0x0a, - 0x16, 0x42, 0x6c, 0x61, 0x63, 0x6b, 0x6c, 0x69, 0x73, 0x74, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x69, 0x64, 0x5f, 0x66, 0x69, - 0x6c, 0x74, 0x65, 0x72, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x08, 0x69, 0x64, 0x46, 0x69, - 0x6c, 0x74, 0x65, 0x72, 0x22, 0x2f, 0x0a, 0x17, 0x42, 0x6c, 0x61, 0x63, 0x6b, 0x6c, 0x69, 0x73, - 0x74, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x14, 0x0a, 0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, - 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0xc5, 0x02, 0x0a, 0x12, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, - 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1b, 0x0a, 0x09, - 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x08, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x6c, 0x69, - 0x65, 0x6e, 0x74, 0x5f, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x0c, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x12, 0x1a, - 0x0a, 0x08, 0x61, 0x75, 0x64, 0x69, 0x65, 0x6e, 0x63, 0x65, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, - 0x52, 0x08, 0x61, 0x75, 0x64, 0x69, 0x65, 0x6e, 0x63, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x63, - 0x6f, 0x70, 0x65, 0x18, 0x04, 0x20, 0x03, 0x28, 0x09, 0x52, 0x05, 0x73, 0x63, 0x6f, 0x70, 0x65, - 0x12, 0x1e, 0x0a, 0x0a, 0x65, 0x78, 0x70, 0x69, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x05, - 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x65, 0x78, 0x70, 0x69, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x12, 0x32, 0x0a, 0x15, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x61, 0x73, 0x73, 0x65, 0x72, - 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x13, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x41, 0x73, 0x73, 0x65, 0x72, 0x74, 0x69, 0x6f, 0x6e, - 0x54, 0x79, 0x70, 0x65, 0x12, 0x29, 0x0a, 0x10, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x61, - 0x73, 0x73, 0x65, 0x72, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, - 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x41, 0x73, 0x73, 0x65, 0x72, 0x74, 0x69, 0x6f, 0x6e, 0x12, - 0x1d, 0x0a, 0x0a, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x08, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x09, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1d, - 0x0a, 0x0a, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x09, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x09, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x22, 0x8c, 0x01, - 0x0a, 0x13, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x5f, - 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x61, 0x63, 0x63, - 0x65, 0x73, 0x73, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x1d, 0x0a, 0x0a, 0x74, 0x6f, 0x6b, 0x65, - 0x6e, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x74, 0x6f, - 0x6b, 0x65, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x65, 0x78, 0x70, 0x69, 0x72, - 0x65, 0x73, 0x5f, 0x69, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x65, 0x78, 0x70, - 0x69, 0x72, 0x65, 0x73, 0x49, 0x6e, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x18, - 0x04, 0x20, 0x03, 0x28, 0x09, 0x52, 0x05, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x32, 0xcf, 0x03, 0x0a, - 0x0f, 0x4d, 0x32, 0x4d, 0x4f, 0x41, 0x75, 0x74, 0x68, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, - 0x12, 0x93, 0x01, 0x0a, 0x0b, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, - 0x12, 0x25, 0x2e, 0x6d, 0x32, 0x6d, 0x6f, 0x61, 0x75, 0x74, 0x68, 0x73, 0x65, 0x72, 0x76, 0x65, - 0x72, 0x2e, 0x70, 0x62, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x26, 0x2e, 0x6d, 0x32, 0x6d, 0x6f, 0x61, 0x75, - 0x74, 0x68, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x70, 0x62, 0x2e, 0x43, 0x72, 0x65, 0x61, - 0x74, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, - 0x35, 0x92, 0x41, 0x08, 0x0a, 0x06, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x82, 0xd3, 0xe4, 0x93, - 0x02, 0x24, 0x3a, 0x01, 0x2a, 0x22, 0x1f, 0x2f, 0x6d, 0x32, 0x6d, 0x2d, 0x6f, 0x61, 0x75, 0x74, - 0x68, 0x2d, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, - 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x12, 0x80, 0x01, 0x0a, 0x09, 0x47, 0x65, 0x74, 0x54, 0x6f, - 0x6b, 0x65, 0x6e, 0x73, 0x12, 0x23, 0x2e, 0x6d, 0x32, 0x6d, 0x6f, 0x61, 0x75, 0x74, 0x68, 0x73, - 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x70, 0x62, 0x2e, 0x47, 0x65, 0x74, 0x54, 0x6f, 0x6b, 0x65, - 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x6d, 0x32, 0x6d, 0x6f, - 0x61, 0x75, 0x74, 0x68, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x70, 0x62, 0x2e, 0x54, 0x6f, - 0x6b, 0x65, 0x6e, 0x22, 0x32, 0x92, 0x41, 0x08, 0x0a, 0x06, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, - 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x21, 0x12, 0x1f, 0x2f, 0x6d, 0x32, 0x6d, 0x2d, 0x6f, 0x61, 0x75, - 0x74, 0x68, 0x2d, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, - 0x2f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x30, 0x01, 0x12, 0xa2, 0x01, 0x0a, 0x0f, 0x42, 0x6c, - 0x61, 0x63, 0x6b, 0x6c, 0x69, 0x73, 0x74, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x12, 0x29, 0x2e, - 0x6d, 0x32, 0x6d, 0x6f, 0x61, 0x75, 0x74, 0x68, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x70, - 0x62, 0x2e, 0x42, 0x6c, 0x61, 0x63, 0x6b, 0x6c, 0x69, 0x73, 0x74, 0x54, 0x6f, 0x6b, 0x65, 0x6e, - 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x6d, 0x32, 0x6d, 0x6f, 0x61, - 0x75, 0x74, 0x68, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x70, 0x62, 0x2e, 0x42, 0x6c, 0x61, - 0x63, 0x6b, 0x6c, 0x69, 0x73, 0x74, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x38, 0x92, 0x41, 0x08, 0x0a, 0x06, 0x54, 0x6f, 0x6b, 0x65, 0x6e, - 0x73, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x27, 0x3a, 0x01, 0x2a, 0x22, 0x22, 0x2f, 0x6d, 0x32, 0x6d, - 0x2d, 0x6f, 0x61, 0x75, 0x74, 0x68, 0x2d, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2f, 0x61, 0x70, - 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x62, 0x6c, 0x61, 0x63, 0x6b, 0x6c, 0x69, 0x73, 0x74, 0x42, 0xcb, - 0x02, 0x92, 0x41, 0x94, 0x02, 0x12, 0xbc, 0x01, 0x0a, 0x0c, 0x50, 0x4c, 0x47, 0x44, 0x20, 0x4d, - 0x32, 0x4d, 0x20, 0x41, 0x50, 0x49, 0x12, 0x24, 0x41, 0x50, 0x49, 0x20, 0x66, 0x6f, 0x72, 0x20, - 0x74, 0x6f, 0x20, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x20, 0x6d, 0x32, 0x6d, 0x20, 0x74, 0x6f, - 0x6b, 0x65, 0x6e, 0x73, 0x20, 0x69, 0x6e, 0x20, 0x50, 0x4c, 0x47, 0x44, 0x22, 0x3a, 0x0a, 0x08, - 0x70, 0x6c, 0x67, 0x64, 0x2e, 0x64, 0x65, 0x76, 0x12, 0x1f, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, + 0x12, 0x18, 0x0a, 0x07, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x18, 0x0c, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x07, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x1a, 0x3f, 0x0a, 0x0b, 0x42, 0x6c, + 0x61, 0x63, 0x6b, 0x4c, 0x69, 0x73, 0x74, 0x65, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x66, 0x6c, 0x61, + 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x04, 0x66, 0x6c, 0x61, 0x67, 0x12, 0x1c, 0x0a, + 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x22, 0x89, 0x01, 0x0a, 0x10, + 0x47, 0x65, 0x74, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x12, 0x1b, 0x0a, 0x09, 0x69, 0x64, 0x5f, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x18, 0x01, 0x20, + 0x03, 0x28, 0x09, 0x52, 0x08, 0x69, 0x64, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x12, 0x27, 0x0a, + 0x0f, 0x61, 0x75, 0x64, 0x69, 0x65, 0x6e, 0x63, 0x65, 0x5f, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, + 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0e, 0x61, 0x75, 0x64, 0x69, 0x65, 0x6e, 0x63, 0x65, + 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x12, 0x2f, 0x0a, 0x13, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, + 0x65, 0x5f, 0x62, 0x6c, 0x61, 0x63, 0x6b, 0x6c, 0x69, 0x73, 0x74, 0x65, 0x64, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x08, 0x52, 0x12, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x42, 0x6c, 0x61, 0x63, + 0x6b, 0x6c, 0x69, 0x73, 0x74, 0x65, 0x64, 0x22, 0x35, 0x0a, 0x16, 0x42, 0x6c, 0x61, 0x63, 0x6b, + 0x6c, 0x69, 0x73, 0x74, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x69, 0x64, 0x5f, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x18, 0x01, + 0x20, 0x03, 0x28, 0x09, 0x52, 0x08, 0x69, 0x64, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x22, 0x2f, + 0x0a, 0x17, 0x42, 0x6c, 0x61, 0x63, 0x6b, 0x6c, 0x69, 0x73, 0x74, 0x54, 0x6f, 0x6b, 0x65, 0x6e, + 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x63, 0x6f, 0x75, + 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x22, + 0xc5, 0x02, 0x0a, 0x12, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, + 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x63, 0x6c, 0x69, 0x65, 0x6e, + 0x74, 0x49, 0x64, 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x73, 0x65, + 0x63, 0x72, 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x63, 0x6c, 0x69, 0x65, + 0x6e, 0x74, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x61, 0x75, 0x64, 0x69, + 0x65, 0x6e, 0x63, 0x65, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x08, 0x61, 0x75, 0x64, 0x69, + 0x65, 0x6e, 0x63, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x18, 0x04, 0x20, + 0x03, 0x28, 0x09, 0x52, 0x05, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x65, 0x78, + 0x70, 0x69, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, + 0x65, 0x78, 0x70, 0x69, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x32, 0x0a, 0x15, 0x63, 0x6c, + 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x61, 0x73, 0x73, 0x65, 0x72, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x74, + 0x79, 0x70, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x13, 0x63, 0x6c, 0x69, 0x65, 0x6e, + 0x74, 0x41, 0x73, 0x73, 0x65, 0x72, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x12, 0x29, + 0x0a, 0x10, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x61, 0x73, 0x73, 0x65, 0x72, 0x74, 0x69, + 0x6f, 0x6e, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, + 0x41, 0x73, 0x73, 0x65, 0x72, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1d, 0x0a, 0x0a, 0x74, 0x6f, 0x6b, + 0x65, 0x6e, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x74, + 0x6f, 0x6b, 0x65, 0x6e, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x67, 0x72, 0x61, 0x6e, + 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x67, 0x72, + 0x61, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x22, 0x8c, 0x01, 0x0a, 0x13, 0x43, 0x72, 0x65, 0x61, + 0x74, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x21, 0x0a, 0x0c, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x54, 0x6f, 0x6b, + 0x65, 0x6e, 0x12, 0x1d, 0x0a, 0x0a, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x5f, 0x74, 0x79, 0x70, 0x65, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x54, 0x79, 0x70, + 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x73, 0x5f, 0x69, 0x6e, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x73, 0x49, 0x6e, + 0x12, 0x14, 0x0a, 0x05, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x18, 0x04, 0x20, 0x03, 0x28, 0x09, 0x52, + 0x05, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x32, 0xcf, 0x03, 0x0a, 0x0f, 0x4d, 0x32, 0x4d, 0x4f, 0x41, + 0x75, 0x74, 0x68, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x93, 0x01, 0x0a, 0x0b, 0x43, + 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x25, 0x2e, 0x6d, 0x32, 0x6d, + 0x6f, 0x61, 0x75, 0x74, 0x68, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x70, 0x62, 0x2e, 0x43, + 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x26, 0x2e, 0x6d, 0x32, 0x6d, 0x6f, 0x61, 0x75, 0x74, 0x68, 0x73, 0x65, 0x72, 0x76, + 0x65, 0x72, 0x2e, 0x70, 0x62, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x6f, 0x6b, 0x65, + 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x35, 0x92, 0x41, 0x08, 0x0a, 0x06, + 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x24, 0x3a, 0x01, 0x2a, 0x22, + 0x1f, 0x2f, 0x6d, 0x32, 0x6d, 0x2d, 0x6f, 0x61, 0x75, 0x74, 0x68, 0x2d, 0x73, 0x65, 0x72, 0x76, + 0x65, 0x72, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x73, + 0x12, 0x80, 0x01, 0x0a, 0x09, 0x47, 0x65, 0x74, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x12, 0x23, + 0x2e, 0x6d, 0x32, 0x6d, 0x6f, 0x61, 0x75, 0x74, 0x68, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, + 0x70, 0x62, 0x2e, 0x47, 0x65, 0x74, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x6d, 0x32, 0x6d, 0x6f, 0x61, 0x75, 0x74, 0x68, 0x73, 0x65, + 0x72, 0x76, 0x65, 0x72, 0x2e, 0x70, 0x62, 0x2e, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0x32, 0x92, + 0x41, 0x08, 0x0a, 0x06, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x21, + 0x12, 0x1f, 0x2f, 0x6d, 0x32, 0x6d, 0x2d, 0x6f, 0x61, 0x75, 0x74, 0x68, 0x2d, 0x73, 0x65, 0x72, + 0x76, 0x65, 0x72, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, + 0x73, 0x30, 0x01, 0x12, 0xa2, 0x01, 0x0a, 0x0f, 0x42, 0x6c, 0x61, 0x63, 0x6b, 0x6c, 0x69, 0x73, + 0x74, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x12, 0x29, 0x2e, 0x6d, 0x32, 0x6d, 0x6f, 0x61, 0x75, + 0x74, 0x68, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x70, 0x62, 0x2e, 0x42, 0x6c, 0x61, 0x63, + 0x6b, 0x6c, 0x69, 0x73, 0x74, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x6d, 0x32, 0x6d, 0x6f, 0x61, 0x75, 0x74, 0x68, 0x73, 0x65, 0x72, + 0x76, 0x65, 0x72, 0x2e, 0x70, 0x62, 0x2e, 0x42, 0x6c, 0x61, 0x63, 0x6b, 0x6c, 0x69, 0x73, 0x74, + 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x38, + 0x92, 0x41, 0x08, 0x0a, 0x06, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x82, 0xd3, 0xe4, 0x93, 0x02, + 0x27, 0x3a, 0x01, 0x2a, 0x22, 0x22, 0x2f, 0x6d, 0x32, 0x6d, 0x2d, 0x6f, 0x61, 0x75, 0x74, 0x68, + 0x2d, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x62, + 0x6c, 0x61, 0x63, 0x6b, 0x6c, 0x69, 0x73, 0x74, 0x42, 0xcb, 0x02, 0x92, 0x41, 0x94, 0x02, 0x12, + 0xbc, 0x01, 0x0a, 0x0c, 0x50, 0x4c, 0x47, 0x44, 0x20, 0x4d, 0x32, 0x4d, 0x20, 0x41, 0x50, 0x49, + 0x12, 0x24, 0x41, 0x50, 0x49, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x74, 0x6f, 0x20, 0x6d, 0x61, 0x6e, + 0x61, 0x67, 0x65, 0x20, 0x6d, 0x32, 0x6d, 0x20, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x20, 0x69, + 0x6e, 0x20, 0x50, 0x4c, 0x47, 0x44, 0x22, 0x3a, 0x0a, 0x08, 0x70, 0x6c, 0x67, 0x64, 0x2e, 0x64, + 0x65, 0x76, 0x12, 0x1f, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x67, 0x69, 0x74, 0x68, + 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x70, 0x6c, 0x67, 0x64, 0x2d, 0x64, 0x65, 0x76, 0x2f, + 0x68, 0x75, 0x62, 0x1a, 0x0d, 0x69, 0x6e, 0x66, 0x6f, 0x40, 0x70, 0x6c, 0x67, 0x64, 0x2e, 0x64, + 0x65, 0x76, 0x2a, 0x45, 0x0a, 0x12, 0x41, 0x70, 0x61, 0x63, 0x68, 0x65, 0x20, 0x4c, 0x69, 0x63, + 0x65, 0x6e, 0x73, 0x65, 0x20, 0x32, 0x2e, 0x30, 0x12, 0x2f, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x70, 0x6c, 0x67, - 0x64, 0x2d, 0x64, 0x65, 0x76, 0x2f, 0x68, 0x75, 0x62, 0x1a, 0x0d, 0x69, 0x6e, 0x66, 0x6f, 0x40, - 0x70, 0x6c, 0x67, 0x64, 0x2e, 0x64, 0x65, 0x76, 0x2a, 0x45, 0x0a, 0x12, 0x41, 0x70, 0x61, 0x63, - 0x68, 0x65, 0x20, 0x4c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x20, 0x32, 0x2e, 0x30, 0x12, 0x2f, - 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, - 0x6f, 0x6d, 0x2f, 0x70, 0x6c, 0x67, 0x64, 0x2d, 0x64, 0x65, 0x76, 0x2f, 0x68, 0x75, 0x62, 0x2f, - 0x62, 0x6c, 0x6f, 0x62, 0x2f, 0x76, 0x32, 0x2f, 0x4c, 0x49, 0x43, 0x45, 0x4e, 0x53, 0x45, 0x32, - 0x03, 0x31, 0x2e, 0x30, 0x2a, 0x01, 0x02, 0x32, 0x10, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x6a, 0x73, 0x6f, 0x6e, 0x32, 0x15, 0x61, 0x70, 0x70, 0x6c, 0x69, - 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x6a, 0x73, 0x6f, 0x6e, - 0x3a, 0x10, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x6a, 0x73, - 0x6f, 0x6e, 0x3a, 0x15, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, - 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x6a, 0x73, 0x6f, 0x6e, 0x5a, 0x31, 0x67, 0x69, 0x74, 0x68, 0x75, - 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x70, 0x6c, 0x67, 0x64, 0x2d, 0x64, 0x65, 0x76, 0x2f, 0x68, - 0x75, 0x62, 0x2f, 0x76, 0x32, 0x2f, 0x6d, 0x32, 0x6d, 0x2d, 0x6f, 0x61, 0x75, 0x74, 0x68, 0x2d, - 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2f, 0x70, 0x62, 0x3b, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x33, + 0x64, 0x2d, 0x64, 0x65, 0x76, 0x2f, 0x68, 0x75, 0x62, 0x2f, 0x62, 0x6c, 0x6f, 0x62, 0x2f, 0x76, + 0x32, 0x2f, 0x4c, 0x49, 0x43, 0x45, 0x4e, 0x53, 0x45, 0x32, 0x03, 0x31, 0x2e, 0x30, 0x2a, 0x01, + 0x02, 0x32, 0x10, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x6a, + 0x73, 0x6f, 0x6e, 0x32, 0x15, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x6a, 0x73, 0x6f, 0x6e, 0x3a, 0x10, 0x61, 0x70, 0x70, 0x6c, + 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x6a, 0x73, 0x6f, 0x6e, 0x3a, 0x15, 0x61, 0x70, + 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x6a, + 0x73, 0x6f, 0x6e, 0x5a, 0x31, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, + 0x70, 0x6c, 0x67, 0x64, 0x2d, 0x64, 0x65, 0x76, 0x2f, 0x68, 0x75, 0x62, 0x2f, 0x76, 0x32, 0x2f, + 0x6d, 0x32, 0x6d, 0x2d, 0x6f, 0x61, 0x75, 0x74, 0x68, 0x2d, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, + 0x2f, 0x70, 0x62, 0x3b, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/m2m-oauth-server/pb/service.proto b/m2m-oauth-server/pb/service.proto index 345f04f2e..fb74b3e78 100644 --- a/m2m-oauth-server/pb/service.proto +++ b/m2m-oauth-server/pb/service.proto @@ -58,6 +58,8 @@ message Token { // driven by resource change event } // Token black list section BlackListed blacklisted = 11; + // Subject of the token + string subject = 12; } diff --git a/m2m-oauth-server/pb/service.swagger.json b/m2m-oauth-server/pb/service.swagger.json index 4a718640f..8738eba57 100644 --- a/m2m-oauth-server/pb/service.swagger.json +++ b/m2m-oauth-server/pb/service.swagger.json @@ -314,6 +314,10 @@ "blacklisted": { "$ref": "#/definitions/TokenBlackListed", "title": "Token black list section" + }, + "subject": { + "type": "string", + "title": "Subject of the token" } }, "description": "driven by resource change event", diff --git a/m2m-oauth-server/service/grpc/server.go b/m2m-oauth-server/service/grpc/server.go index aad8aa466..16b6d3b18 100644 --- a/m2m-oauth-server/service/grpc/server.go +++ b/m2m-oauth-server/service/grpc/server.go @@ -88,6 +88,7 @@ func (s *M2MOAuthServiceServer) CreateToken(ctx context.Context, req *pb.CreateT tokenReq.ownerClaim = s.signer.Config.OwnerClaim tokenReq.id = uuid.NewString() tokenReq.expiration = getExpirationTime(clientCfg, tokenReq) + tokenReq.subject = getSubject(clientCfg, tokenReq) accessToken, err := s.generateAccessToken( clientCfg, tokenReq) @@ -105,6 +106,7 @@ func (s *M2MOAuthServiceServer) CreateToken(ctx context.Context, req *pb.CreateT Expiration: pkgTime.UnixSec(tokenReq.expiration), ClientId: tokenReq.CreateTokenRequest.GetClientId(), OriginalTokenClaims: originalTokenClaims, + Subject: tokenReq.subject, }) if err != nil { return nil, status.Errorf(getGRPCErrorCode(err), "%v", errCannotCreateConfiguration(err)) diff --git a/m2m-oauth-server/service/grpc/token.go b/m2m-oauth-server/service/grpc/token.go index f56c9c1ba..59f07547e 100644 --- a/m2m-oauth-server/service/grpc/token.go +++ b/m2m-oauth-server/service/grpc/token.go @@ -27,7 +27,7 @@ func makeAccessToken(clientCfg *oauthsigner.Client, tokenReq tokenRequest) (jwt. claims := map[string]interface{}{ jwt.JwtIDKey: tokenReq.id, - jwt.SubjectKey: getSubject(clientCfg, tokenReq), + jwt.SubjectKey: tokenReq.subject, jwt.AudienceKey: tokenReq.host, jwt.IssuedAtKey: tokenReq.issuedAt, uri.ScopeKey: tokenReq.scopes, From a94e0d5c82c4831a66e91b0b9b6caa01f48814a6 Mon Sep 17 00:00:00 2001 From: Jozef Kralik Date: Thu, 18 Jul 2024 19:51:03 +0000 Subject: [PATCH 17/31] add subject --- m2m-oauth-server/swagger.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/m2m-oauth-server/swagger.yaml b/m2m-oauth-server/swagger.yaml index ba4c23b1f..eadf1e51b 100644 --- a/m2m-oauth-server/swagger.yaml +++ b/m2m-oauth-server/swagger.yaml @@ -338,6 +338,9 @@ components: type: object blacklisted: $ref: '#/components/schemas/TokenBlackListed' + subject: + title: Subject of the token + type: string description: driven by resource change event protobufAny: type: object From 7c562da09b7e6461e76bf72eeb3f84d72a5ad386 Mon Sep 17 00:00:00 2001 From: Jozef Kralik Date: Fri, 19 Jul 2024 06:19:45 +0000 Subject: [PATCH 18/31] fix crt --- charts/plgd-hub/templates/m2m-oauth-server/service-crt.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/charts/plgd-hub/templates/m2m-oauth-server/service-crt.yaml b/charts/plgd-hub/templates/m2m-oauth-server/service-crt.yaml index 4eb579984..7255493f4 100644 --- a/charts/plgd-hub/templates/m2m-oauth-server/service-crt.yaml +++ b/charts/plgd-hub/templates/m2m-oauth-server/service-crt.yaml @@ -28,10 +28,10 @@ spec: - {{ printf "%s.%s.svc.%s" $serviceDns .Release.Namespace .Values.cluster.dns | quote }} - {{ $serviceDns | quote }} {{- if .Values.m2moauthserver.service.grpc.crt.extraDnsNames }} - {{- toYaml .Values.m2moauthserver.service.grpc.crt.extraDnsNames | nindent 4}} + {{- toYaml .Values.m2moauthserver.service.grpc.crt.extraDnsNames | nindent 4 }} {{- end }} {{- if .Values.m2moauthserver.service.http.crt.extraDnsNames }} - {{- toYaml .Values.m2moauthserver.service.http.crt.extraDnsNames | nindent 4}} + {{- toYaml .Values.m2moauthserver.service.http.crt.extraDnsNames | nindent 4 }} {{- end }} duration: {{ .Values.certmanager.internal.cert.duration | default .Values.certmanager.default.cert.duration }} renewBefore: {{ .Values.certmanager.internal.cert.renewBefore | default .Values.certmanager.default.cert.renewBefore }} From 249005aa3ae9a1f813dab647b07c5e6ed6e52840 Mon Sep 17 00:00:00 2001 From: Jozef Kralik Date: Fri, 19 Jul 2024 15:11:45 +0000 Subject: [PATCH 19/31] fix for CR --- .../certificate-authority/config.yaml | 6 +++--- .../templates/m2m-oauth-server/config.yaml | 6 +++--- .../templates/snippet-service/config.yaml | 6 +++--- m2m-oauth-server/oauthSigner/oauthSigner.go | 20 +++++++++++++++++-- m2m-oauth-server/pb/tags.go | 4 ++-- m2m-oauth-server/service/grpc/server.go | 12 +++++------ m2m-oauth-server/service/grpc/service.go | 2 +- m2m-oauth-server/service/grpc/token.go | 9 +++------ .../service/http/postToken_test.go | 4 ++-- .../service/http/requestHandler.go | 2 +- m2m-oauth-server/service/service.go | 4 ---- m2m-oauth-server/store/mongodb/tokens_test.go | 6 +++--- m2m-oauth-server/store/store.go | 4 ---- 13 files changed, 45 insertions(+), 40 deletions(-) diff --git a/charts/plgd-hub/templates/certificate-authority/config.yaml b/charts/plgd-hub/templates/certificate-authority/config.yaml index 67bde2980..a72d72326 100644 --- a/charts/plgd-hub/templates/certificate-authority/config.yaml +++ b/charts/plgd-hub/templates/certificate-authority/config.yaml @@ -30,11 +30,11 @@ data: # 0s - means infinity maxConnectionIdle: {{ .apis.grpc.keepAlive.maxConnectionIdle }} # 0s - means infinity - maxConnectionAge: {{ .apis.grpc.keepAlive.maxConnectionIdle }} + maxConnectionAge: {{ .apis.grpc.keepAlive.maxConnectionAge }} # 0s - means infinity maxConnectionAgeGrace: {{ .apis.grpc.keepAlive.maxConnectionAgeGrace }} - time: {{ .apis.grpc.keepAlive.maxConnectionIdle }} - timeout: {{ .apis.grpc.keepAlive.maxConnectionIdle }} + time: {{ .apis.grpc.keepAlive.time }} + timeout: {{ .apis.grpc.keepAlive.timeout }} tls: {{- $tls := .apis.grpc.tls }} {{- include "plgd-hub.internalCertificateConfig" (list $ $tls $cert ) | indent 8 }} diff --git a/charts/plgd-hub/templates/m2m-oauth-server/config.yaml b/charts/plgd-hub/templates/m2m-oauth-server/config.yaml index 36c98c9b8..416b06f2e 100644 --- a/charts/plgd-hub/templates/m2m-oauth-server/config.yaml +++ b/charts/plgd-hub/templates/m2m-oauth-server/config.yaml @@ -28,11 +28,11 @@ data: # 0s - means infinity maxConnectionIdle: {{ .apis.grpc.keepAlive.maxConnectionIdle }} # 0s - means infinity - maxConnectionAge: {{ .apis.grpc.keepAlive.maxConnectionIdle }} + maxConnectionAge: {{ .apis.grpc.keepAlive.maxConnectionAge }} # 0s - means infinity maxConnectionAgeGrace: {{ .apis.grpc.keepAlive.maxConnectionAgeGrace }} - time: {{ .apis.grpc.keepAlive.maxConnectionIdle }} - timeout: {{ .apis.grpc.keepAlive.maxConnectionIdle }} + time: {{ .apis.grpc.keepAlive.time }} + timeout: {{ .apis.grpc.keepAlive.timeout }} tls: {{- $tls := .apis.grpc.tls }} {{- include "plgd-hub.internalCertificateConfig" (list $ $tls $cert ) | indent 8 }} diff --git a/charts/plgd-hub/templates/snippet-service/config.yaml b/charts/plgd-hub/templates/snippet-service/config.yaml index 9ed69d7bf..2c790b21f 100644 --- a/charts/plgd-hub/templates/snippet-service/config.yaml +++ b/charts/plgd-hub/templates/snippet-service/config.yaml @@ -30,11 +30,11 @@ data: # 0s - means infinity maxConnectionIdle: {{ .apis.grpc.keepAlive.maxConnectionIdle }} # 0s - means infinity - maxConnectionAge: {{ .apis.grpc.keepAlive.maxConnectionIdle }} + maxConnectionAge: {{ .apis.grpc.keepAlive.maxConnectionAge }} # 0s - means infinity maxConnectionAgeGrace: {{ .apis.grpc.keepAlive.maxConnectionAgeGrace }} - time: {{ .apis.grpc.keepAlive.maxConnectionIdle }} - timeout: {{ .apis.grpc.keepAlive.maxConnectionIdle }} + time: {{ .apis.grpc.keepAlive.time }} + timeout: {{ .apis.grpc.keepAlive.timeout }} tls: {{- $tls := .apis.grpc.tls }} {{- include "plgd-hub.internalCertificateConfig" (list $ $tls $cert ) | indent 8 }} diff --git a/m2m-oauth-server/oauthSigner/oauthSigner.go b/m2m-oauth-server/oauthSigner/oauthSigner.go index 050fe45c4..7ad465d16 100644 --- a/m2m-oauth-server/oauthSigner/oauthSigner.go +++ b/m2m-oauth-server/oauthSigner/oauthSigner.go @@ -23,7 +23,7 @@ func setKeyError(key string, err error) error { type OAuthSigner struct { privateKeyJWTValidators map[string]*validator.Validator closer fn.FuncList - Config Config + config Config accessTokenKey interface{} accessTokenJwkKey jwk.Key } @@ -55,7 +55,7 @@ func New(ctx context.Context, config Config, getOpenIDConfiguration validator.Ge return &OAuthSigner{ privateKeyJWTValidators: privateKeyJWTValidators, closer: closer, - Config: config, + config: config, accessTokenKey: accessTokenKey, accessTokenJwkKey: accessTokenJwkKey, }, nil @@ -97,3 +97,19 @@ func (s *OAuthSigner) Sign(token jwt.Token) ([]byte, error) { func (s *OAuthSigner) Close() { s.closer.Execute() } + +func (s *OAuthSigner) GetDomain() string { + return s.config.GetDomain() +} + +func (s *OAuthSigner) GetOwnerClaim() string { + return s.config.OwnerClaim +} + +func (s *OAuthSigner) GetDeviceIDClaim() string { + return s.config.DeviceIDClaim +} + +func (s *OAuthSigner) GetClients() OAuthClientsConfig { + return s.config.Clients +} diff --git a/m2m-oauth-server/pb/tags.go b/m2m-oauth-server/pb/tags.go index 7933036c6..54d87687d 100644 --- a/m2m-oauth-server/pb/tags.go +++ b/m2m-oauth-server/pb/tags.go @@ -3,8 +3,8 @@ package pb const ( ExpirationKey = "expiration" OwnerKey = "owner" - BlackListedFlagKey = "blacklisted.flag" - BlackListedTimestampKey = "blacklisted.timestamp" + BlackListedFlagKey = BlackListedKey + ".flag" + BlackListedTimestampKey = BlackListedKey + ".timestamp" BlackListedKey = "blacklisted" TimestampKey = "timestamp" AudienceKey = "audience" diff --git a/m2m-oauth-server/service/grpc/server.go b/m2m-oauth-server/service/grpc/server.go index 16b6d3b18..9133bcf08 100644 --- a/m2m-oauth-server/service/grpc/server.go +++ b/m2m-oauth-server/service/grpc/server.go @@ -38,7 +38,7 @@ func NewM2MOAuthServerServer(store store.Store, signer *oauthsigner.OAuthSigner, } func (s *M2MOAuthServiceServer) getOwner(ctx context.Context) (string, error) { - ownerFromToken, err := pkgGrpc.OwnerFromTokenMD(ctx, s.signer.Config.OwnerClaim) + ownerFromToken, err := pkgGrpc.OwnerFromTokenMD(ctx, s.signer.GetOwnerClaim()) if err != nil { return "", err } @@ -62,12 +62,12 @@ func errCannotCreateToken(err error) error { func (s *M2MOAuthServiceServer) CreateToken(ctx context.Context, req *pb.CreateTokenRequest) (*pb.CreateTokenResponse, error) { tokenReq := tokenRequest{ - host: s.signer.Config.GetDomain(), + host: s.signer.GetDomain(), tokenType: oauthsigner.AccessTokenType_JWT, issuedAt: time.Now(), CreateTokenRequest: req, } - clientCfg := s.signer.Config.Clients.Find(tokenReq.CreateTokenRequest.GetClientId()) + clientCfg := s.signer.GetClients().Find(tokenReq.CreateTokenRequest.GetClientId()) if clientCfg == nil { return nil, status.Errorf(codes.Unauthenticated, "%v", errCannotCreateToken(fmt.Errorf("client(%v) not found", tokenReq.CreateTokenRequest.GetClientId()))) } @@ -84,8 +84,8 @@ func (s *M2MOAuthServiceServer) CreateToken(ctx context.Context, req *pb.CreateT } } tokenReq.scopes = strings.Join(req.GetScope(), " ") - tokenReq.deviceIDClaim = s.signer.Config.DeviceIDClaim - tokenReq.ownerClaim = s.signer.Config.OwnerClaim + tokenReq.deviceIDClaim = s.signer.GetDeviceIDClaim() + tokenReq.ownerClaim = s.signer.GetOwnerClaim() tokenReq.id = uuid.NewString() tokenReq.expiration = getExpirationTime(clientCfg, tokenReq) tokenReq.subject = getSubject(clientCfg, tokenReq) @@ -162,5 +162,5 @@ func (s *M2MOAuthServiceServer) GetJWK() jwk.Key { } func (s *M2MOAuthServiceServer) GetDomain() string { - return s.signer.Config.GetDomain() + return s.signer.GetDomain() } diff --git a/m2m-oauth-server/service/grpc/service.go b/m2m-oauth-server/service/grpc/service.go index c6c5f3b06..dbe0569f1 100644 --- a/m2m-oauth-server/service/grpc/service.go +++ b/m2m-oauth-server/service/grpc/service.go @@ -26,7 +26,7 @@ func New(config Config, m2mOAuthServiceServer *M2MOAuthServiceServer, validator } pb.RegisterM2MOAuthServiceServer(server.Server, m2mOAuthServiceServer) - // SnippetService needs to stop gracefully to ensure that all commands are processed. + // M2MOAuthService needs to stop gracefully to ensure that all commands are processed. server.SetGracefulStop(true) return &Service{ diff --git a/m2m-oauth-server/service/grpc/token.go b/m2m-oauth-server/service/grpc/token.go index 59f07547e..bbb8d2b13 100644 --- a/m2m-oauth-server/service/grpc/token.go +++ b/m2m-oauth-server/service/grpc/token.go @@ -115,9 +115,6 @@ func getExpirationTime(clientCfg *oauthsigner.Client, tokenReq tokenRequest) tim return time.Time{} } if tokenReq.CreateTokenRequest.GetExpiration() == 0 { - if clientCfg.AccessTokenLifetime == 0 { - return time.Time{} - } return tokenReq.issuedAt.Add(clientCfg.AccessTokenLifetime) } wantExpiration := time.Unix(tokenReq.CreateTokenRequest.GetExpiration(), 0) @@ -267,7 +264,7 @@ func (s *M2MOAuthServiceServer) validateClientAssertion(ctx context.Context, tok } tokenReq.originalTokenClaims = token claims := pkgJwt.Claims(token) - owner, err := claims.GetOwner(s.signer.Config.OwnerClaim) + owner, err := claims.GetOwner(s.signer.GetOwnerClaim()) if err != nil { return fmt.Errorf("invalid client assertion - claim owner: %w", err) } @@ -277,10 +274,10 @@ func (s *M2MOAuthServiceServer) validateClientAssertion(ctx context.Context, tok return fmt.Errorf("invalid client assertion - claim sub: %w", err) } tokenReq.subject = sub - if s.signer.Config.DeviceIDClaim == "" { + if s.signer.GetDeviceIDClaim() == "" { return nil } - deviceID, err := claims.GetDeviceID(s.signer.Config.DeviceIDClaim) + deviceID, err := claims.GetDeviceID(s.signer.GetDeviceIDClaim()) if err == nil { tokenReq.deviceID = deviceID } diff --git a/m2m-oauth-server/service/http/postToken_test.go b/m2m-oauth-server/service/http/postToken_test.go index 411b70dc5..adf229d80 100644 --- a/m2m-oauth-server/service/http/postToken_test.go +++ b/m2m-oauth-server/service/http/postToken_test.go @@ -67,7 +67,7 @@ func TestPostToken(t *testing.T) { }, }, { - name: "snippetServiceToken - JWT", + name: "ownerToken - JWT", args: m2mOauthServerTest.AccessTokenOptions{ Ctx: context.Background(), ClientID: m2mOauthServerTest.JWTPrivateKeyOAuthClient.ID, @@ -93,7 +93,7 @@ func TestPostToken(t *testing.T) { wantCode: http.StatusUnauthorized, }, { - name: "snippetServiceToken - invalid JWT", + name: "ownerToken - invalid JWT", args: m2mOauthServerTest.AccessTokenOptions{ Ctx: context.Background(), ClientID: m2mOauthServerTest.JWTPrivateKeyOAuthClient.ID, diff --git a/m2m-oauth-server/service/http/requestHandler.go b/m2m-oauth-server/service/http/requestHandler.go index b264e0230..d4679a0ff 100644 --- a/m2m-oauth-server/service/http/requestHandler.go +++ b/m2m-oauth-server/service/http/requestHandler.go @@ -21,7 +21,7 @@ type RequestHandler struct { mux *runtime.ServeMux } -// NewHTTP returns HTTP handler +// NewRequestHandler returns HTTP handler func NewRequestHandler(config *Config, r *mux.Router, m2mOAuthServiceServer *grpcService.M2MOAuthServiceServer) (*RequestHandler, error) { requestHandler := &RequestHandler{ config: config, diff --git a/m2m-oauth-server/service/service.go b/m2m-oauth-server/service/service.go index e8bb6fb7e..93ea4346d 100644 --- a/m2m-oauth-server/service/service.go +++ b/m2m-oauth-server/service/service.go @@ -153,7 +153,3 @@ func New(ctx context.Context, config Config, fileWatcher *fsnotify.Watcher, logg store: db, }, nil } - -func (s *Service) SnippetServiceStore() store.Store { - return s.store -} diff --git a/m2m-oauth-server/store/mongodb/tokens_test.go b/m2m-oauth-server/store/mongodb/tokens_test.go index 381fd0e12..c5e2f3990 100644 --- a/m2m-oauth-server/store/mongodb/tokens_test.go +++ b/m2m-oauth-server/store/mongodb/tokens_test.go @@ -15,7 +15,7 @@ func TestGetTokens(t *testing.T) { s, cleanUpStore := test.NewMongoStore(t) defer cleanUpStore() - ctx, cancel := context.WithTimeout(context.Background(), config.TEST_TIMEOUT*100) + ctx, cancel := context.WithTimeout(context.Background(), config.TEST_TIMEOUT) defer cancel() // Set the owner and request parameters @@ -123,7 +123,7 @@ func TestBlacklistTokens(t *testing.T) { s, cleanUpStore := test.NewMongoStore(t) defer cleanUpStore() - ctx, cancel := context.WithTimeout(context.Background(), config.TEST_TIMEOUT*100) + ctx, cancel := context.WithTimeout(context.Background(), config.TEST_TIMEOUT) defer cancel() owner := "testOwner" @@ -216,7 +216,7 @@ func TestDeleteTokens(t *testing.T) { s, cleanUpStore := test.NewMongoStore(t) defer cleanUpStore() - ctx, cancel := context.WithTimeout(context.Background(), config.TEST_TIMEOUT*100) + ctx, cancel := context.WithTimeout(context.Background(), config.TEST_TIMEOUT) defer cancel() owner := "testOwner" diff --git a/m2m-oauth-server/store/store.go b/m2m-oauth-server/store/store.go index c46b2974b..e88a01e5f 100644 --- a/m2m-oauth-server/store/store.go +++ b/m2m-oauth-server/store/store.go @@ -27,10 +27,6 @@ var ( ErrPartialDelete = errors.New("some errors occurred while deleting") ) -func IsDuplicateKeyError(err error) bool { - return mongo.IsDuplicateKeyError(err) -} - type BsonMapper interface { FromBsonMap(m map[string]interface{}) error } From b2a1a299d66f569fa84a598c9e07f8c23405dec1 Mon Sep 17 00:00:00 2001 From: Jozef Kralik Date: Fri, 19 Jul 2024 15:15:35 +0000 Subject: [PATCH 20/31] fix sentence --- m2m-oauth-server/swagger.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/m2m-oauth-server/swagger.yaml b/m2m-oauth-server/swagger.yaml index eadf1e51b..b1fbadab5 100644 --- a/m2m-oauth-server/swagger.yaml +++ b/m2m-oauth-server/swagger.yaml @@ -36,7 +36,7 @@ paths: description: "The name of the token which will be used in the name claim." expiration: type: integer - description: "The requested expiration time in seconds from the client. If not provided, the token will be max allowed by client." + description: "The requested expiration time in unit timestamp seconds from the client. If not provided, the token will use the maximum allowed by the client, or if it exceeds the maximum allowed, an error will occur." scope: type: string description: "The scopes that are requested, separated by space. Must be a subset of the allowed scopes for the client." From 43287b4936510f57ce6193280e679fd01a235777 Mon Sep 17 00:00:00 2001 From: Jozef Kralik Date: Fri, 19 Jul 2024 15:17:47 +0000 Subject: [PATCH 21/31] fix for CR 2 --- grpc-gateway/pb/hubConfiguration.pb.go | 2 +- m2m-oauth-server/pb/README.md | 2 +- m2m-oauth-server/pb/doc.html | 2 +- m2m-oauth-server/pb/service.pb.go | 2 +- m2m-oauth-server/pb/service.proto | 2 +- m2m-oauth-server/pb/service.swagger.json | 2 +- m2m-oauth-server/swagger.yaml | 2 +- snippet-service/pb/service.pb.go | 12 ++++++------ 8 files changed, 13 insertions(+), 13 deletions(-) diff --git a/grpc-gateway/pb/hubConfiguration.pb.go b/grpc-gateway/pb/hubConfiguration.pb.go index 497f005cc..716c681e5 100644 --- a/grpc-gateway/pb/hubConfiguration.pb.go +++ b/grpc-gateway/pb/hubConfiguration.pb.go @@ -66,7 +66,7 @@ type OAuthClient struct { ClientId string `protobuf:"bytes,1,opt,name=client_id,json=clientId,proto3" json:"client_id,omitempty" yaml:"clientID"` // @gotags: yaml:"clientID" Audience string `protobuf:"bytes,2,opt,name=audience,proto3" json:"audience,omitempty"` Scopes []string `protobuf:"bytes,3,rep,name=scopes,proto3" json:"scopes,omitempty"` - ProviderName string `protobuf:"bytes,4,opt,name=provider_name,json=providerName,proto3" json:"provider_name,omitempty" yaml:"providerName"` // @gotags: yaml:"providerName" + ProviderName string `protobuf:"bytes,4,opt,name=provider_name,json=providerName,proto3" json:"provider_name,omitempty" yaml:"providerName"` // @gotags: yaml:"providerName" ClientAssertionType string `protobuf:"bytes,5,opt,name=client_assertion_type,json=clientAssertionType,proto3" json:"client_assertion_type,omitempty" yaml:"clientAssertionType"` // @gotags: yaml:"clientAssertionType" Authority string `protobuf:"bytes,6,opt,name=authority,proto3" json:"authority,omitempty"` GrantType string `protobuf:"bytes,7,opt,name=grant_type,json=grantType,proto3" json:"grant_type,omitempty" yaml:"grantType"` // @gotags: yaml:"grantType" diff --git a/m2m-oauth-server/pb/README.md b/m2m-oauth-server/pb/README.md index bc8e8b657..25d133ddb 100644 --- a/m2m-oauth-server/pb/README.md +++ b/m2m-oauth-server/pb/README.md @@ -67,7 +67,7 @@ | client_secret | [string](#string) | | Client Secret | | audience | [string](#string) | repeated | Requested token Audience | | scope | [string](#string) | repeated | Requested token scopes | -| expiration | [int64](#int64) | | Requested token expiration in Unix timestamp seconds | +| expiration | [int64](#int64) | | The requested expiration time in unit timestamp seconds from the client. If not provided, the token will use the maximum allowed by the client, or if it exceeds the maximum allowed, an error will occur. | | client_assertion_type | [string](#string) | | Client assertion type | | client_assertion | [string](#string) | | Client assertion | | token_name | [string](#string) | | Token name | diff --git a/m2m-oauth-server/pb/doc.html b/m2m-oauth-server/pb/doc.html index 254f85a36..43ae551ac 100644 --- a/m2m-oauth-server/pb/doc.html +++ b/m2m-oauth-server/pb/doc.html @@ -318,7 +318,7 @@

    CreateTokenRequest

    expiration int64 -

    Requested token expiration in Unix timestamp seconds

    +

    The requested expiration time in unit timestamp seconds from the client. If not provided, the token will use the maximum allowed by the client, or if it exceeds the maximum allowed, an error will occur.

    diff --git a/m2m-oauth-server/pb/service.pb.go b/m2m-oauth-server/pb/service.pb.go index 8b756dd44..e93bab04f 100644 --- a/m2m-oauth-server/pb/service.pb.go +++ b/m2m-oauth-server/pb/service.pb.go @@ -341,7 +341,7 @@ type CreateTokenRequest struct { Audience []string `protobuf:"bytes,3,rep,name=audience,proto3" json:"audience,omitempty"` // Requested token scopes Scope []string `protobuf:"bytes,4,rep,name=scope,proto3" json:"scope,omitempty"` - // Requested token expiration in Unix timestamp seconds + // The requested expiration time in unit timestamp seconds from the client. If not provided, the token will use the maximum allowed by the client, or if it exceeds the maximum allowed, an error will occur. Expiration int64 `protobuf:"varint,5,opt,name=expiration,proto3" json:"expiration,omitempty"` // Client assertion type ClientAssertionType string `protobuf:"bytes,6,opt,name=client_assertion_type,json=clientAssertionType,proto3" json:"client_assertion_type,omitempty"` diff --git a/m2m-oauth-server/pb/service.proto b/m2m-oauth-server/pb/service.proto index fb74b3e78..3e79988b8 100644 --- a/m2m-oauth-server/pb/service.proto +++ b/m2m-oauth-server/pb/service.proto @@ -87,7 +87,7 @@ message CreateTokenRequest { repeated string audience = 3; // Requested token scopes repeated string scope = 4; - // Requested token expiration in Unix timestamp seconds + // The requested expiration time in unit timestamp seconds from the client. If not provided, the token will use the maximum allowed by the client, or if it exceeds the maximum allowed, an error will occur. int64 expiration = 5; // Client assertion type string client_assertion_type = 6; diff --git a/m2m-oauth-server/pb/service.swagger.json b/m2m-oauth-server/pb/service.swagger.json index 8738eba57..8a6492a2d 100644 --- a/m2m-oauth-server/pb/service.swagger.json +++ b/m2m-oauth-server/pb/service.swagger.json @@ -219,7 +219,7 @@ "expiration": { "type": "string", "format": "int64", - "title": "Requested token expiration in Unix timestamp seconds" + "description": "The requested expiration time in unit timestamp seconds from the client. If not provided, the token will use the maximum allowed by the client, or if it exceeds the maximum allowed, an error will occur." }, "clientAssertionType": { "type": "string", diff --git a/m2m-oauth-server/swagger.yaml b/m2m-oauth-server/swagger.yaml index b1fbadab5..1b8187ef8 100644 --- a/m2m-oauth-server/swagger.yaml +++ b/m2m-oauth-server/swagger.yaml @@ -266,7 +266,7 @@ components: items: type: string expiration: - title: Requested token expiration in Unix timestamp seconds + title: The requested expiration time in unit timestamp seconds from the client. If not provided, the token will use the maximum allowed by the client, or if it exceeds the maximum allowed, an error will occur. type: string format: int64 clientAssertionType: diff --git a/snippet-service/pb/service.pb.go b/snippet-service/pb/service.pb.go index 7f23b6748..ab33dcf66 100644 --- a/snippet-service/pb/service.pb.go +++ b/snippet-service/pb/service.pb.go @@ -765,8 +765,8 @@ type AppliedConfiguration struct { unknownFields protoimpl.UnknownFields Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` - DeviceId string `protobuf:"bytes,2,opt,name=device_id,json=deviceId,proto3" json:"device_id,omitempty" bson:"deviceId"` - ConfigurationId *AppliedConfiguration_LinkedTo `protobuf:"bytes,3,opt,name=configuration_id,json=configurationId,proto3" json:"configuration_id,omitempty" bson:"configurationId"` + DeviceId string `protobuf:"bytes,2,opt,name=device_id,json=deviceId,proto3" json:"device_id,omitempty" bson:"deviceId"` + ConfigurationId *AppliedConfiguration_LinkedTo `protobuf:"bytes,3,opt,name=configuration_id,json=configurationId,proto3" json:"configuration_id,omitempty" bson:"configurationId"` // Types that are assignable to ExecutedBy: // // *AppliedConfiguration_OnDemand @@ -882,7 +882,7 @@ type AppliedConfiguration_OnDemand struct { } type AppliedConfiguration_ConditionId struct { - ConditionId *AppliedConfiguration_LinkedTo `protobuf:"bytes,5,opt,name=condition_id,json=conditionId,proto3,oneof" bson:"conditionId"` + ConditionId *AppliedConfiguration_LinkedTo `protobuf:"bytes,5,opt,name=condition_id,json=conditionId,proto3,oneof" bson:"conditionId"` } func (*AppliedConfiguration_OnDemand) isAppliedConfiguration_ExecutedBy() {} @@ -1269,11 +1269,11 @@ type AppliedConfiguration_Resource struct { Href string `protobuf:"bytes,1,opt,name=href,proto3" json:"href,omitempty"` // Reused from invoke command or generated. Can be used to retrieve corresponding pending command. - CorrelationId string `protobuf:"bytes,2,opt,name=correlation_id,json=correlationId,proto3" json:"correlation_id,omitempty" bson:"correlationId"` + CorrelationId string `protobuf:"bytes,2,opt,name=correlation_id,json=correlationId,proto3" json:"correlation_id,omitempty" bson:"correlationId"` Status AppliedConfiguration_Resource_Status `protobuf:"varint,3,opt,name=status,proto3,enum=snippetservice.pb.AppliedConfiguration_Resource_Status" json:"status,omitempty"` - ResourceUpdated *events.ResourceUpdated `protobuf:"bytes,4,opt,name=resource_updated,json=resourceUpdated,proto3" json:"resource_updated,omitempty" bson:"resourceUpdated,omitempty"` + ResourceUpdated *events.ResourceUpdated `protobuf:"bytes,4,opt,name=resource_updated,json=resourceUpdated,proto3" json:"resource_updated,omitempty" bson:"resourceUpdated,omitempty"` // Unix nanoseconds timestamp for resource in PENDING status, until which the pending update is valid - ValidUntil int64 `protobuf:"varint,5,opt,name=valid_until,json=validUntil,proto3" json:"valid_until,omitempty" bson:"validUntil,omitempty"` + ValidUntil int64 `protobuf:"varint,5,opt,name=valid_until,json=validUntil,proto3" json:"valid_until,omitempty" bson:"validUntil,omitempty"` } func (x *AppliedConfiguration_Resource) Reset() { From 0c765b94321f9b2c0a06187f7698cd0eaab1780c Mon Sep 17 00:00:00 2001 From: Jozef Kralik Date: Mon, 22 Jul 2024 15:28:07 +0000 Subject: [PATCH 22/31] fix for CR3 --- m2m-oauth-server/pb/tags.go | 1 + m2m-oauth-server/pb/token.go | 167 +++--------- .../service/http/postToken_test.go | 29 +++ m2m-oauth-server/service/service.go | 1 + m2m-oauth-server/store/mongodb/tokens.go | 12 +- m2m-oauth-server/store/mongodb/tokens_test.go | 46 +++- m2m-oauth-server/store/store.go | 13 - m2m-oauth-server/test/test.go | 10 + pkg/mongodb/marshal.go | 240 +++++++++++++++--- pkg/mongodb/marshal_test.go | 223 ++++++++++++++++ snippet-service/pb/appliedConfiguration.go | 22 +- snippet-service/store/appliedConfiguration.go | 17 +- 12 files changed, 560 insertions(+), 221 deletions(-) create mode 100644 pkg/mongodb/marshal_test.go diff --git a/m2m-oauth-server/pb/tags.go b/m2m-oauth-server/pb/tags.go index 54d87687d..f77f1d074 100644 --- a/m2m-oauth-server/pb/tags.go +++ b/m2m-oauth-server/pb/tags.go @@ -8,4 +8,5 @@ const ( BlackListedKey = "blacklisted" TimestampKey = "timestamp" AudienceKey = "audience" + IssuedAtKey = "issuedAt" ) diff --git a/m2m-oauth-server/pb/token.go b/m2m-oauth-server/pb/token.go index 94d9bebda..82174ce8c 100644 --- a/m2m-oauth-server/pb/token.go +++ b/m2m-oauth-server/pb/token.go @@ -1,13 +1,10 @@ package pb import ( - "encoding/json" "errors" "fmt" - "strconv" - "github.com/hashicorp/go-multierror" - "google.golang.org/protobuf/encoding/protojson" + pkgMongo "github.com/plgd-dev/hub/v2/pkg/mongodb" ) var errTokenIsNil = errors.New("Token is nil") @@ -31,160 +28,58 @@ func (x *Token) Validate() error { return nil } -func (x *Token) ToMap() (map[string]interface{}, error) { - v := protojson.MarshalOptions{ - AllowPartial: true, - EmitUnpopulated: true, +func (x *Token) jsonToBSONTag(json map[string]interface{}) error { + json["_id"] = x.GetId() + delete(json, "id") + if _, err := pkgMongo.ConvertStringValueToInt64(json, false, "."+IssuedAtKey); err != nil { + return fmt.Errorf("cannot convert issueAt to int64: %w", err) } - data, err := v.Marshal(x) - if err != nil { - return nil, err - } - var m map[string]interface{} - err = json.Unmarshal(data, &m) - if err != nil { - return nil, err - } - return m, nil -} - -func replaceStrToInt64(m map[string]interface{}, keys ...string) error { - var errs *multierror.Error - for _, k := range keys { - exp, ok := m[k] - if ok { - str, ok := exp.(string) - if ok { - i, err := strconv.ParseInt(str, 10, 64) - if err != nil { - errs = multierror.Append(errs, fmt.Errorf("cannot convert key %v to int64, %w", k, err)) - } else { - m[k] = i - } - } - } - } - return errs.ErrorOrNil() -} - -func replaceInt64ToStr(m map[string]interface{}, keys ...string) { - for _, k := range keys { - exp, ok := m[k] - if ok { - i, ok := exp.(int64) - if ok { - m[k] = strconv.FormatInt(i, 10) - } - } - } -} - -func (x *Token) ToBsonMap() (map[string]interface{}, error) { - m, err := x.ToMap() - if err != nil { - return nil, err - } - m["_id"] = x.GetId() - delete(m, "id") - err = replaceStrToInt64(m, ExpirationKey, TimestampKey) - if err != nil { - return nil, err + if _, err := pkgMongo.ConvertStringValueToInt64(json, true, "."+ExpirationKey); err != nil { + return fmt.Errorf("cannot convert expiration to int64: %w", err) } - blackListed, ok := m[BlackListedKey] - if ok { - mapBlacklisted, ok := blackListed.(map[string]interface{}) - if ok { - err = replaceStrToInt64(mapBlacklisted, TimestampKey) - if err != nil { - return nil, err - } - } + if _, err := pkgMongo.ConvertStringValueToInt64(json, true, "."+BlackListedKey+"."+TimestampKey); err != nil { + return fmt.Errorf("cannot convert blacklisted.timestamp to int64: %w", err) } - return m, nil + return nil } -func (x *Token) FromMap(m map[string]interface{}) error { +func (x *Token) MarshalBSON() ([]byte, error) { if x == nil { - return errTokenIsNil + return nil, errTokenIsNil } - data, err := json.Marshal(m) - if err != nil { - return err - } - v := protojson.UnmarshalOptions{ - AllowPartial: true, - DiscardUnknown: true, - } - return v.Unmarshal(data, x) + return pkgMongo.MarshalProtoBSON(x, x.jsonToBSONTag) } -func (x *Token) FromBsonMap(m map[string]interface{}) error { +func (x *Token) UnmarshalBSON(data []byte) error { if x == nil { return errTokenIsNil } - m["id"] = m["_id"] - delete(m, "_id") - - replaceInt64ToStr(m, ExpirationKey, TimestampKey) - blackListed, ok := m[BlackListedKey] - if ok { - mapBlacklisted, ok := blackListed.(map[string]interface{}) + var id string + update := func(json map[string]interface{}) error { + idI, ok := json["_id"] if ok { - replaceInt64ToStr(mapBlacklisted, TimestampKey) + id = idI.(string) } + delete(json, "_id") + return nil } - - return x.FromMap(m) -} - -func (x *Token_BlackListed) ToMap() (map[string]interface{}, error) { - v := protojson.MarshalOptions{ - AllowPartial: true, - EmitUnpopulated: true, - } - data, err := v.Marshal(x) - if err != nil { - return nil, err - } - var m map[string]interface{} - err = json.Unmarshal(data, &m) - if err != nil { - return nil, err - } - return m, nil -} - -func (x *Token_BlackListed) FromMap(m map[string]interface{}) error { - if x == nil { - return errors.New("Token_BlackListed is nil") - } - data, err := json.Marshal(m) + err := pkgMongo.UnmarshalProtoBSON(data, x, update) if err != nil { return err } - v := protojson.UnmarshalOptions{ - AllowPartial: true, - DiscardUnknown: true, + if x.GetId() == "" && id != "" { + x.Id = id } - return v.Unmarshal(data, x) + return nil } -func (x *Token_BlackListed) ToBsonMap() (map[string]interface{}, error) { - m, err := x.ToMap() - if err != nil { - return nil, err +func (x *Token_BlackListed) jsonToBSONTag(json map[string]interface{}) error { + if _, err := pkgMongo.ConvertStringValueToInt64(json, false, "."+TimestampKey); err != nil { + return fmt.Errorf("cannot convert timestamp to int64: %w", err) } - err = replaceStrToInt64(m, TimestampKey) - if err != nil { - return nil, err - } - return m, nil + return nil } -func (x *Token_BlackListed) FromBsonMap(m map[string]interface{}) error { - if x == nil { - return errors.New("Token_BlackListed is nil") - } - replaceInt64ToStr(m, TimestampKey) - return x.FromMap(m) +func (x *Token_BlackListed) MarshalBSON() ([]byte, error) { + return pkgMongo.MarshalProtoBSON(x, x.jsonToBSONTag) } diff --git a/m2m-oauth-server/service/http/postToken_test.go b/m2m-oauth-server/service/http/postToken_test.go index adf229d80..9e005c9bb 100644 --- a/m2m-oauth-server/service/http/postToken_test.go +++ b/m2m-oauth-server/service/http/postToken_test.go @@ -5,6 +5,7 @@ import ( "fmt" "net/http" "testing" + "time" "github.com/golang-jwt/jwt/v5" oauthsigner "github.com/plgd-dev/hub/v2/m2m-oauth-server/oauthSigner" @@ -81,6 +82,22 @@ func TestPostToken(t *testing.T) { existOriginalTokenClaims: true, }, }, + { + name: "ownerToken with expiration- JWT", + args: m2mOauthServerTest.AccessTokenOptions{ + Ctx: context.Background(), + ClientID: m2mOauthServerTest.JWTPrivateKeyOAuthClient.ID, + GrantType: string(oauthsigner.GrantTypeClientCredentials), + Host: config.M2M_OAUTH_SERVER_HTTP_HOST, + JWT: token, + Expiration: time.Now().Add(time.Hour), + }, + wantCode: http.StatusOK, + want: want{ + owner: "1", + existOriginalTokenClaims: true, + }, + }, { name: "invalid client", args: m2mOauthServerTest.AccessTokenOptions{ @@ -103,6 +120,18 @@ func TestPostToken(t *testing.T) { }, wantCode: http.StatusUnauthorized, }, + { + name: "invalid expiration", + args: m2mOauthServerTest.AccessTokenOptions{ + Ctx: context.Background(), + ClientID: m2mOauthServerTest.JWTPrivateKeyOAuthClient.ID, + GrantType: string(oauthsigner.GrantTypeClientCredentials), + Host: config.M2M_OAUTH_SERVER_HTTP_HOST, + JWT: token, + Expiration: time.Now().Add(-time.Hour), + }, + wantCode: http.StatusUnauthorized, + }, } webTearDown := m2mOauthServerTest.SetUp(t) diff --git a/m2m-oauth-server/service/service.go b/m2m-oauth-server/service/service.go index 93ea4346d..139d5dca3 100644 --- a/m2m-oauth-server/service/service.go +++ b/m2m-oauth-server/service/service.go @@ -49,6 +49,7 @@ func createStore(ctx context.Context, config storeConfig.Config, fileWatcher *fs } }) if err2 != nil { + s.Close(ctx) return nil, fmt.Errorf("cannot create scheduler: %w", err2) } s.AddCloseFunc(func() { diff --git a/m2m-oauth-server/store/mongodb/tokens.go b/m2m-oauth-server/store/mongodb/tokens.go index 87eeea0da..08c88344e 100644 --- a/m2m-oauth-server/store/mongodb/tokens.go +++ b/m2m-oauth-server/store/mongodb/tokens.go @@ -28,11 +28,7 @@ func (s *Store) CreateToken(ctx context.Context, owner string, token *pb.Token) if err != nil { return nil, err } - m, err := token.ToBsonMap() - if err != nil { - return nil, err - } - _, err = s.Store.Collection(tokensCol).InsertOne(ctx, m) + _, err = s.Store.Collection(tokensCol).InsertOne(ctx, token) if err != nil { return nil, err } @@ -158,15 +154,11 @@ func (s *Store) BlacklistTokens(ctx context.Context, owner string, req *pb.Black Flag: true, Timestamp: time.Now().Unix(), } - value, err := blacklisted.ToBsonMap() - if err != nil { - return nil, err - } update := bson.D{ { Key: mongodb.Set, Value: bson.M{ - pb.BlackListedKey: value, + pb.BlackListedKey: &blacklisted, }, }, } diff --git a/m2m-oauth-server/store/mongodb/tokens_test.go b/m2m-oauth-server/store/mongodb/tokens_test.go index c5e2f3990..f8ecb88c5 100644 --- a/m2m-oauth-server/store/mongodb/tokens_test.go +++ b/m2m-oauth-server/store/mongodb/tokens_test.go @@ -18,16 +18,19 @@ func TestGetTokens(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), config.TEST_TIMEOUT) defer cancel() + expiration := time.Now().Add(time.Minute * 10).Unix() + // Set the owner and request parameters owner := "testOwner" tokens := []*pb.Token{ { - Id: "token1", - Owner: owner, - Version: 0, - Name: "name1", - IssuedAt: time.Now().Unix(), - ClientId: "client1", + Id: "token1", + Owner: owner, + Version: 0, + Name: "name1", + IssuedAt: time.Now().Unix(), + ClientId: "client1", + Expiration: expiration, }, { Id: "token2", @@ -50,9 +53,9 @@ func TestGetTokens(t *testing.T) { } tests := []struct { - name string - args args - wantLen int + name string + args args + want []*pb.Token }{ { name: "all tokens", @@ -61,7 +64,9 @@ func TestGetTokens(t *testing.T) { owner: owner, req: &pb.GetTokensRequest{}, }, - wantLen: 1, + want: []*pb.Token{ + tokens[0], + }, }, { name: "all tokens including blacklisted", @@ -72,7 +77,7 @@ func TestGetTokens(t *testing.T) { IncludeBlacklisted: true, }, }, - wantLen: 2, + want: tokens, }, { name: "certain token", @@ -84,7 +89,9 @@ func TestGetTokens(t *testing.T) { IncludeBlacklisted: true, }, }, - wantLen: 1, + want: []*pb.Token{ + tokens[1], + }, }, { name: "all tokens another owner", @@ -93,7 +100,7 @@ func TestGetTokens(t *testing.T) { owner: "anotherOwner", req: &pb.GetTokensRequest{}, }, - wantLen: 0, + want: nil, }, } @@ -114,7 +121,18 @@ func TestGetTokens(t *testing.T) { // Call the GetTokens method err := s.GetTokens(tt.args.ctx, tt.args.owner, tt.args.req, process) require.NoError(t, err) - require.Len(t, result, tt.wantLen) + require.Len(t, result, len(tt.want)) + for _, token := range tt.want { + require.Contains(t, result, token.GetId()) + require.Equal(t, token.GetExpiration(), result[token.GetId()].GetExpiration()) + require.Equal(t, token.GetIssuedAt(), result[token.GetId()].GetIssuedAt()) + require.Equal(t, token.GetClientId(), result[token.GetId()].GetClientId()) + require.Equal(t, token.GetOwner(), result[token.GetId()].GetOwner()) + require.Equal(t, token.GetVersion(), result[token.GetId()].GetVersion()) + require.Equal(t, token.GetName(), result[token.GetId()].GetName()) + require.Equal(t, token.GetBlacklisted().GetFlag(), result[token.GetId()].GetBlacklisted().GetFlag()) + require.Equal(t, token.GetBlacklisted().GetTimestamp(), result[token.GetId()].GetBlacklisted().GetTimestamp()) + } }) } } diff --git a/m2m-oauth-server/store/store.go b/m2m-oauth-server/store/store.go index e88a01e5f..60015a071 100644 --- a/m2m-oauth-server/store/store.go +++ b/m2m-oauth-server/store/store.go @@ -27,10 +27,6 @@ var ( ErrPartialDelete = errors.New("some errors occurred while deleting") ) -type BsonMapper interface { - FromBsonMap(m map[string]interface{}) error -} - type MongoIterator[T any] struct { Cursor *mongo.Cursor } @@ -39,15 +35,6 @@ func (i *MongoIterator[T]) Next(ctx context.Context, s *T) bool { if !i.Cursor.Next(ctx) { return false } - var tmp interface{} = s - if tmp, ok := tmp.(BsonMapper); ok { - var mapValue map[string]interface{} - err := i.Cursor.Decode(&mapValue) - if err == nil { - err = tmp.FromBsonMap(mapValue) - } - return err == nil - } err := i.Cursor.Decode(s) return err == nil } diff --git a/m2m-oauth-server/test/test.go b/m2m-oauth-server/test/test.go index a412c8c6e..2cc2b08c1 100644 --- a/m2m-oauth-server/test/test.go +++ b/m2m-oauth-server/test/test.go @@ -142,6 +142,7 @@ type AccessTokenOptions struct { Audience string JWT string PostForm bool + Expiration time.Time Ctx context.Context } @@ -193,6 +194,12 @@ func WithPostFrom(enabled bool) func(opts *AccessTokenOptions) { } } +func WithExpiration(expiration time.Time) func(opts *AccessTokenOptions) { + return func(opts *AccessTokenOptions) { + opts.Expiration = expiration + } +} + func WithContext(ctx context.Context) func(opts *AccessTokenOptions) { return func(opts *AccessTokenOptions) { opts.Ctx = ctx @@ -229,6 +236,9 @@ func GetAccessToken(t *testing.T, expectedCode int, opts ...func(opts *AccessTok reqBody[uri.ClientAssertionKey] = options.JWT reqBody[uri.ClientAssertionTypeKey] = uri.ClientAssertionTypeJWT } + if !options.Expiration.IsZero() { + reqBody[uri.ExpirationKey] = options.Expiration.Unix() + } var data []byte if options.PostForm { data = []byte(mapToURLValues(reqBody).Encode()) diff --git a/pkg/mongodb/marshal.go b/pkg/mongodb/marshal.go index 54dbd8acb..8ae33d21f 100644 --- a/pkg/mongodb/marshal.go +++ b/pkg/mongodb/marshal.go @@ -2,68 +2,230 @@ package mongodb import ( "encoding/json" + "errors" + "fmt" + "regexp" "strconv" "strings" + "github.com/hashicorp/go-multierror" "go.mongodb.org/mongo-driver/bson" "google.golang.org/protobuf/encoding/protojson" "google.golang.org/protobuf/proto" ) -type updateJSON = func(map[string]interface{}) +type updateJSON = func(map[string]any) error -func ConvertStringValueToInt64(json map[string]interface{}, path string) { - pos := strings.Index(path, ".") - if pos == -1 { - valueI, ok := json[path] - if !ok { - return - } - valueStr, ok := valueI.(string) - if !ok { - return +var ErrPathNotFound = errors.New("path not found") + +// ConvertStringValueToInt64 converts string values to int64 in a JSON map based on provided paths. +// It iterates over the specified paths in the JSON map and converts the string values found at those paths to int64 values. +// If permitMissingPaths is set to true, missing paths in the JSON map will be ignored and the modified JSON map will be returned. +// If permitMissingPaths is set to false, an error will be returned if any of the specified paths are not found in the JSON map. +// The function returns the updated JSON map with the converted int64 values. +// If an error occurs during the conversion, the partially modified JSON map is returned along with the error. +func ConvertStringValueToInt64(jsonMap any, permitMissingPaths bool, paths ...string) (any, error) { + for _, path := range paths { + newMap, err := convertPath(jsonMap, permitMissingPaths, path) + if err != nil { + return jsonMap, err } - value, err := strconv.ParseInt(valueStr, 10, 64) + jsonMap = newMap + } + return jsonMap, nil +} + +func handleSlice(slice []any, permitMissingPaths bool, remainingParts []string) ([]any, error) { + var ( + parents []any + errs *multierror.Error + ) + + for _, item := range slice { + p, err := findParents(item, permitMissingPaths, remainingParts) if err != nil { - return + errs = multierror.Append(errs, err) + } else if p != nil { + parents = append(parents, p...) } - json[path] = value - return } - elemPath := path[:pos] - elem, ok := json[elemPath] - if !ok { - return - } - elemArray, ok := elem.([]interface{}) - if ok { - for i, elem := range elemArray { - elemMap, ok2 := elem.(map[string]interface{}) - if !ok2 { - continue + if len(parents) == 0 { + return nil, errs.ErrorOrNil() + } + + return parents, errs.ErrorOrNil() +} + +func findParents(current any, permitMissingPaths bool, parts []string) ([]any, error) { + for idx, part := range parts { + if part == "" { + continue + } + switch curr := current.(type) { + case map[string]any: + if value, exists := curr[part]; exists { + current = value + } else if permitMissingPaths { + return nil, nil + } else { + return nil, fmt.Errorf("path segment %s: %w", part, ErrPathNotFound) + } + case []any: + if part == "" || part == "*" { + return handleSlice(curr, permitMissingPaths, parts[idx+1:]) + } + index, err := strconv.Atoi(part) + if err != nil { + return nil, fmt.Errorf("invalid array index %s", part) + } + if index < 0 || index >= len(curr) { + if permitMissingPaths { + return nil, nil + } + return nil, fmt.Errorf("index out of range %d: %w", index, ErrPathNotFound) } - ConvertStringValueToInt64(elemMap, path[pos+1:]) - elemArray[i] = elemMap + current = curr[index] + default: + return nil, fmt.Errorf("unsupported type %T at path segment %s", current, part) } - json[elemPath] = elemArray - return } - elemMap, ok := elem.(map[string]interface{}) + return []any{current}, nil +} + +var splitPathRE = regexp.MustCompile(`\.\[|\]\.|\.|\[|\]`) + +func splitPath(path string) []string { + parts := splitPathRE.Split(path, -1) + var cleanParts []string + for _, part := range parts { + if part != "" { + cleanParts = append(cleanParts, part) + } + } + return cleanParts +} + +func setMap(data any, permitMissingPaths bool, path string, parent map[string]any, lastPart string) (out any, err error) { + value, exists := parent[lastPart] + if !exists { + if permitMissingPaths { + return data, nil + } + return data, fmt.Errorf("path %s: %w", path, ErrPathNotFound) + } + strVal, ok := value.(string) if !ok { - return + return data, fmt.Errorf("expected string at path %s, but found %T", path, value) + } + intVal, err := strconv.ParseInt(strVal, 10, 64) + if err != nil { + return data, fmt.Errorf("error converting string to int64 at path %s: %w", path, err) } - ConvertStringValueToInt64(elemMap, path[pos+1:]) - json[elemPath] = elemMap + parent[lastPart] = intVal + + return data, nil +} + +func setSliceValue(data any, permitMissingPaths bool, path string, parent []any, index int) (out any, err error) { + if index < 0 || index >= len(parent) { + if permitMissingPaths { + return data, nil + } + return data, fmt.Errorf("index out of range %d", index) + } + if value, ok := parent[index].(string); ok { + intVal, err := strconv.ParseInt(value, 10, 64) + if err != nil { + return data, fmt.Errorf("error converting string to int64 at path %s: %w", path, err) + } + parent[index] = intVal + } else { + return data, fmt.Errorf("expected string at path %s, but found %T", path, parent[index]) + } + return data, nil +} + +func setSlice(data any, permitMissingPaths bool, path string, parent []any, lastPart string) (out any, err error) { + if lastPart == "" || lastPart == "*" { + out = data + for i := range parent { + out, err = setSliceValue(out, permitMissingPaths, path, parent, i) + if err != nil { + return out, err + } + } + return out, err + } + index, err := strconv.Atoi(lastPart) + if err != nil { + return data, fmt.Errorf("invalid array index %s", lastPart) + } + return setSliceValue(data, permitMissingPaths, path, parent, index) +} + +func setDirectValue(data any, path string) (out any, err error) { + intVal, err := strconv.ParseInt(data.(string), 10, 64) + if err != nil { + return data, fmt.Errorf("error converting string to int64 at path %s: %w", path, err) + } + return intVal, nil +} + +func convertPath(data any, permitMissingPaths bool, path string) (out any, err error) { + var parentsRaw []any + var lastPart string + var parts []string + if path == "." { + parentsRaw = []any{data} + } else { + parts = splitPath(path) + if len(parts) == 0 { + return data, errors.New("empty path") + } + + lastPart = parts[len(parts)-1] + parentsRaw, err = findParents(data, permitMissingPaths, parts[:len(parts)-1]) + if err != nil { + return data, fmt.Errorf("error finding parent for path %s: %w", path, err) + } + } + + out = data + var errs *multierror.Error + for _, parentRaw := range parentsRaw { + switch parent := parentRaw.(type) { + case map[string]any: + out, err = setMap(out, permitMissingPaths, path, parent, lastPart) + if err != nil { + errs = multierror.Append(errs, err) + } + case []any: + out, err = setSlice(out, permitMissingPaths, path, parent, lastPart) + if err != nil { + errs = multierror.Append(errs, err) + } + case string: + out, err = setDirectValue(parent, path) + if err != nil { + errs = multierror.Append(errs, err) + } + default: + return data, fmt.Errorf("unsupported type %T at parent path %s", parent, strings.Join(parts[:len(parts)-1], ".")) + } + } + return out, errs.ErrorOrNil() } func UnmarshalProtoBSON(data []byte, m proto.Message, update updateJSON) error { - var obj map[string]interface{} + var obj map[string]any if err := bson.Unmarshal(data, &obj); err != nil { return err } if update != nil { - update(obj) + if err := update(obj); err != nil { + return err + } } jsonData, err := json.Marshal(obj) if err != nil { @@ -77,13 +239,15 @@ func MarshalProtoBSON(m proto.Message, update updateJSON) ([]byte, error) { if err != nil { return nil, err } - var obj map[string]interface{} + var obj map[string]any err = json.Unmarshal(data, &obj) if err != nil { return nil, err } if update != nil { - update(obj) + if err := update(obj); err != nil { + return nil, err + } } return bson.Marshal(obj) } diff --git a/pkg/mongodb/marshal_test.go b/pkg/mongodb/marshal_test.go new file mode 100644 index 000000000..edeb1ad87 --- /dev/null +++ b/pkg/mongodb/marshal_test.go @@ -0,0 +1,223 @@ +package mongodb_test + +import ( + "testing" + + "github.com/plgd-dev/hub/v2/pkg/mongodb" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestConvertStringValueToInt64(t *testing.T) { + type args struct { + data interface{} + paths []string + permitMissingPaths bool + } + + tests := []struct { + name string + args args + want interface{} + wantErr bool + ignoreErr bool + }{ + { + name: "emptyPath", + args: args{ + data: map[string]interface{}{}, + paths: []string{""}, + }, + wantErr: true, + }, + { + name: "invalidPath", + args: args{ + data: map[string]interface{}{}, + paths: []string{"foo"}, + }, + wantErr: true, + }, + { + name: "directValue", + args: args{ + data: "123", + paths: []string{"."}, + }, + want: int64(123), + }, + { + name: "arrayValue", + args: args{ + data: []interface{}{ + "123", + "456", + "789", + }, + paths: []string{".[0]", ".[2]"}, + }, + want: []interface{}{int64(123), "456", int64(789)}, + }, + { + name: "mapValue", + args: args{ + data: map[string]interface{}{ + "foo": "123", + }, + paths: []string{".foo"}, + }, + want: map[string]interface{}{ + "foo": int64(123), + }, + }, + { + name: "mapArrayValue", + args: args{ + data: map[string]interface{}{ + "foo": []interface{}{ + "123", + "456", + "789", + }, + }, + paths: []string{".foo[0]", ".foo[2]"}, + }, + want: map[string]interface{}{ + "foo": []interface{}{int64(123), "456", int64(789)}, + }, + }, + { + name: "nestedMapValue", + args: args{ + data: map[string]interface{}{ + "foo": map[string]interface{}{ + "bar": "123", + }, + }, + paths: []string{".foo.bar"}, + }, + want: map[string]interface{}{ + "foo": map[string]interface{}{ + "bar": int64(123), + }, + }, + }, + { + name: "nestedArrayMapValue", + args: args{ + data: map[string]interface{}{ + "foo": []interface{}{ + map[string]interface{}{ + "bar": "123", + }, + map[string]interface{}{ + "bar": "456", + }, + }, + }, + paths: []string{".foo[0].bar", ".foo[1].bar"}, + }, + want: map[string]interface{}{ + "foo": []interface{}{ + map[string]interface{}{ + "bar": int64(123), + }, + map[string]interface{}{ + "bar": int64(456), + }, + }, + }, + }, + { + name: "nestedArrayMapAllValues", + args: args{ + data: map[string]interface{}{ + "foo": []interface{}{ + map[string]interface{}{ + "bar": "123", + }, + map[string]interface{}{ + "bar": "456", + }, + }, + }, + paths: []string{".foo[*].bar"}, + }, + want: map[string]interface{}{ + "foo": []interface{}{ + map[string]interface{}{ + "bar": int64(123), + }, + map[string]interface{}{ + "bar": int64(456), + }, + }, + }, + }, + { + name: "nestedArrayMapWithMissingPathsAllValues", + args: args{ + data: map[string]interface{}{ + "foo": []interface{}{ + map[string]interface{}{ + "bar": "123", + }, + map[string]interface{}{ + "efg": "456", + }, + map[string]interface{}{ + "bar": "789", + }, + }, + }, + paths: []string{".foo[*].bar"}, + permitMissingPaths: true, + }, + want: map[string]interface{}{ + "foo": []interface{}{ + map[string]interface{}{ + "bar": int64(123), + }, + map[string]interface{}{ + "efg": "456", + }, + map[string]interface{}{ + "bar": int64(789), + }, + }, + }, + }, + { + name: "mapArrayAllValues", + args: args{ + data: map[string]interface{}{ + "foo": []interface{}{ + "123", + "456", + "789", + }, + }, + paths: []string{".foo[*]"}, + }, + want: map[string]interface{}{ + "foo": []interface{}{int64(123), int64(456), int64(789)}, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := mongodb.ConvertStringValueToInt64(tt.args.data, tt.args.permitMissingPaths, tt.args.paths...) + if tt.wantErr { + require.Error(t, err) + if !tt.ignoreErr { + return + } + } + if !tt.ignoreErr { + require.NoError(t, err) + } + assert.Equal(t, tt.want, got) + }) + } +} diff --git a/snippet-service/pb/appliedConfiguration.go b/snippet-service/pb/appliedConfiguration.go index 813be74dd..02ddf05c4 100644 --- a/snippet-service/pb/appliedConfiguration.go +++ b/snippet-service/pb/appliedConfiguration.go @@ -92,8 +92,11 @@ func (r *AppliedConfiguration_Resource) Clone() *AppliedConfiguration_Resource { } } -func (r *AppliedConfiguration_Resource) jsonToBSONTag(json map[string]interface{}) { - pkgMongo.ConvertStringValueToInt64(json, "validUntil") +func (r *AppliedConfiguration_Resource) jsonToBSONTag(json map[string]interface{}) error { + if _, err := pkgMongo.ConvertStringValueToInt64(json, true, "."+ValidUntil); err != nil { + return fmt.Errorf("cannot convert .validUntil to int64: %w", err) + } + return nil } func (r *AppliedConfiguration_Resource) MarshalBSON() ([]byte, error) { @@ -133,10 +136,17 @@ func (c *AppliedConfiguration) Clone() *AppliedConfiguration { } } -func (c *AppliedConfiguration) jsonToBSONTag(json map[string]interface{}) { - pkgMongo.ConvertStringValueToInt64(json, "configurationId.version") - pkgMongo.ConvertStringValueToInt64(json, "conditionId.version") - pkgMongo.ConvertStringValueToInt64(json, "resources.validUntil") +func (c *AppliedConfiguration) jsonToBSONTag(json map[string]interface{}) error { + if _, err := pkgMongo.ConvertStringValueToInt64(json, true, "."+ConfigurationIDKey+"."+VersionKey); err != nil { + return fmt.Errorf("cannot convert configurationId.version to int64: %w", err) + } + if _, err := pkgMongo.ConvertStringValueToInt64(json, true, "."+ConditionIDKey+"."+VersionKey); err != nil { + return fmt.Errorf("cannot convert conditionId.version to int64: %w", err) + } + if _, err := pkgMongo.ConvertStringValueToInt64(json, true, "."+ResourcesKey+".[*]."+ValidUntil); err != nil { + return fmt.Errorf("cannot convert resources.validUntil to int64: %w", err) + } + return nil } func (c *AppliedConfiguration) MarshalBSON() ([]byte, error) { diff --git a/snippet-service/store/appliedConfiguration.go b/snippet-service/store/appliedConfiguration.go index 9664e97e0..a4a87b93f 100644 --- a/snippet-service/store/appliedConfiguration.go +++ b/snippet-service/store/appliedConfiguration.go @@ -44,14 +44,23 @@ func (c *AppliedConfiguration) GetAppliedConfiguration() *pb.AppliedConfiguratio } func (c *AppliedConfiguration) UnmarshalBSON(data []byte) error { - update := func(json map[string]interface{}) { - recordID, ok := json[pb.RecordIDKey] + var recordID string + update := func(json map[string]interface{}) error { + recordIDI, ok := json[pb.RecordIDKey] if ok { - c.RecordID = recordID.(primitive.ObjectID).Hex() + recordID = recordIDI.(primitive.ObjectID).Hex() } delete(json, pb.RecordIDKey) + return nil + } + err := pkgMongo.UnmarshalProtoBSON(data, &c.AppliedConfiguration, update) + if err != nil { + return err } - return pkgMongo.UnmarshalProtoBSON(data, &c.AppliedConfiguration, update) + if c.GetId() == "" && recordID != "" { + c.RecordID = recordID + } + return nil } type UpdateAppliedConfigurationResourceRequest struct { From 915b39a4a702fffdbfa42906080088d514c7eb13 Mon Sep 17 00:00:00 2001 From: Jozef Kralik Date: Tue, 23 Jul 2024 07:20:09 +0000 Subject: [PATCH 23/31] fix smells --- pkg/mongodb/marshal.go | 102 ++++++++++++++++++++++++++--------------- 1 file changed, 65 insertions(+), 37 deletions(-) diff --git a/pkg/mongodb/marshal.go b/pkg/mongodb/marshal.go index 8ae33d21f..a86ff1449 100644 --- a/pkg/mongodb/marshal.go +++ b/pkg/mongodb/marshal.go @@ -57,6 +57,32 @@ func handleSlice(slice []any, permitMissingPaths bool, remainingParts []string) return parents, errs.ErrorOrNil() } +func iterateOverMap(curr map[string]any, permitMissingPaths bool, part string) (any, error) { + var current any + if value, exists := curr[part]; exists { + current = value + } else if permitMissingPaths { + return nil, nil + } else { + return nil, fmt.Errorf("path segment %s: %w", part, ErrPathNotFound) + } + return current, nil +} + +func iterateOverSlice(curr []any, permitMissingPaths bool, part string) (any, error) { + index, err := strconv.Atoi(part) + if err != nil { + return nil, fmt.Errorf("invalid array index %s", part) + } + if index < 0 || index >= len(curr) { + if permitMissingPaths { + return nil, nil + } + return nil, fmt.Errorf("index out of range %d: %w", index, ErrPathNotFound) + } + return curr[index], nil +} + func findParents(current any, permitMissingPaths bool, parts []string) ([]any, error) { for idx, part := range parts { if part == "" { @@ -64,28 +90,26 @@ func findParents(current any, permitMissingPaths bool, parts []string) ([]any, e } switch curr := current.(type) { case map[string]any: - if value, exists := curr[part]; exists { - current = value - } else if permitMissingPaths { + var err error + current, err = iterateOverMap(curr, permitMissingPaths, part) + if err != nil { + return nil, err + } + if current == nil { return nil, nil - } else { - return nil, fmt.Errorf("path segment %s: %w", part, ErrPathNotFound) } case []any: - if part == "" || part == "*" { + if part == "*" { return handleSlice(curr, permitMissingPaths, parts[idx+1:]) } - index, err := strconv.Atoi(part) + var err error + current, err = iterateOverSlice(curr, permitMissingPaths, part) if err != nil { - return nil, fmt.Errorf("invalid array index %s", part) + return nil, err } - if index < 0 || index >= len(curr) { - if permitMissingPaths { - return nil, nil - } - return nil, fmt.Errorf("index out of range %d: %w", index, ErrPathNotFound) + if current == nil { + return nil, nil } - current = curr[index] default: return nil, fmt.Errorf("unsupported type %T at path segment %s", current, part) } @@ -118,9 +142,9 @@ func setMap(data any, permitMissingPaths bool, path string, parent map[string]an if !ok { return data, fmt.Errorf("expected string at path %s, but found %T", path, value) } - intVal, err := strconv.ParseInt(strVal, 10, 64) + intVal, err := setDirectValue(strVal, path) if err != nil { - return data, fmt.Errorf("error converting string to int64 at path %s: %w", path, err) + return data, err } parent[lastPart] = intVal @@ -135,9 +159,9 @@ func setSliceValue(data any, permitMissingPaths bool, path string, parent []any, return data, fmt.Errorf("index out of range %d", index) } if value, ok := parent[index].(string); ok { - intVal, err := strconv.ParseInt(value, 10, 64) + intVal, err := setDirectValue(value, path) if err != nil { - return data, fmt.Errorf("error converting string to int64 at path %s: %w", path, err) + return data, err } parent[index] = intVal } else { @@ -147,7 +171,7 @@ func setSliceValue(data any, permitMissingPaths bool, path string, parent []any, } func setSlice(data any, permitMissingPaths bool, path string, parent []any, lastPart string) (out any, err error) { - if lastPart == "" || lastPart == "*" { + if lastPart == "*" { out = data for i := range parent { out, err = setSliceValue(out, permitMissingPaths, path, parent, i) @@ -164,31 +188,35 @@ func setSlice(data any, permitMissingPaths bool, path string, parent []any, last return setSliceValue(data, permitMissingPaths, path, parent, index) } -func setDirectValue(data any, path string) (out any, err error) { - intVal, err := strconv.ParseInt(data.(string), 10, 64) +func setDirectValue(data string, path string) (out int64, err error) { + intVal, err := strconv.ParseInt(data, 10, 64) if err != nil { - return data, fmt.Errorf("error converting string to int64 at path %s: %w", path, err) + return -1, fmt.Errorf("error converting string to int64 at path %s: %w", path, err) } return intVal, nil } -func convertPath(data any, permitMissingPaths bool, path string) (out any, err error) { - var parentsRaw []any - var lastPart string - var parts []string +func processPath(data any, permitMissingPaths bool, path string) (parentsRaw []any, parts []string, lastPart string, err error) { if path == "." { - parentsRaw = []any{data} - } else { - parts = splitPath(path) - if len(parts) == 0 { - return data, errors.New("empty path") - } + return []any{data}, nil, "", nil + } + parts = splitPath(path) + if len(parts) == 0 { + return nil, nil, "", errors.New("empty path") + } - lastPart = parts[len(parts)-1] - parentsRaw, err = findParents(data, permitMissingPaths, parts[:len(parts)-1]) - if err != nil { - return data, fmt.Errorf("error finding parent for path %s: %w", path, err) - } + lastPart = parts[len(parts)-1] + parentsRaw, err = findParents(data, permitMissingPaths, parts[:len(parts)-1]) + if err != nil { + return nil, nil, "", fmt.Errorf("error finding parent for path %s: %w", path, err) + } + return parentsRaw, parts, lastPart, nil +} + +func convertPath(data any, permitMissingPaths bool, path string) (out any, err error) { + parentsRaw, parts, lastPart, err := processPath(data, permitMissingPaths, path) + if err != nil { + return data, err } out = data From 1a746f62c1d71ec9cdd21b3fd4158acad2da8faf Mon Sep 17 00:00:00 2001 From: Jozef Kralik Date: Tue, 23 Jul 2024 13:49:38 +0000 Subject: [PATCH 24/31] fix after CR 4 --- m2m-oauth-server/pb/README.md | 1 - m2m-oauth-server/pb/doc.html | 7 - m2m-oauth-server/pb/service.pb.go | 196 +++++++++--------- m2m-oauth-server/pb/service.proto | 3 +- m2m-oauth-server/pb/service.swagger.json | 10 - m2m-oauth-server/service/grpc/token.go | 20 +- .../service/grpc/token_internal_test.go | 112 ++++++++++ .../service/http/postToken_test.go | 16 +- m2m-oauth-server/store/mongodb/tokens.go | 4 - m2m-oauth-server/swagger.yaml | 8 - pkg/mongodb/marshal.go | 66 +++--- 11 files changed, 273 insertions(+), 170 deletions(-) create mode 100644 m2m-oauth-server/service/grpc/token_internal_test.go diff --git a/m2m-oauth-server/pb/README.md b/m2m-oauth-server/pb/README.md index 25d133ddb..dfdac8a60 100644 --- a/m2m-oauth-server/pb/README.md +++ b/m2m-oauth-server/pb/README.md @@ -105,7 +105,6 @@ | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | id_filter | [string](#string) | repeated | | -| audience_filter | [string](#string) | repeated | | | include_blacklisted | [bool](#bool) | | | diff --git a/m2m-oauth-server/pb/doc.html b/m2m-oauth-server/pb/doc.html index 43ae551ac..a4437f810 100644 --- a/m2m-oauth-server/pb/doc.html +++ b/m2m-oauth-server/pb/doc.html @@ -418,13 +418,6 @@

    GetTokensRequest

    - - audience_filter - string - repeated -

    - - include_blacklisted bool diff --git a/m2m-oauth-server/pb/service.pb.go b/m2m-oauth-server/pb/service.pb.go index e93bab04f..63f911c3b 100644 --- a/m2m-oauth-server/pb/service.pb.go +++ b/m2m-oauth-server/pb/service.pb.go @@ -177,7 +177,6 @@ type GetTokensRequest struct { unknownFields protoimpl.UnknownFields IdFilter []string `protobuf:"bytes,1,rep,name=id_filter,json=idFilter,proto3" json:"id_filter,omitempty"` - AudienceFilter []string `protobuf:"bytes,2,rep,name=audience_filter,json=audienceFilter,proto3" json:"audience_filter,omitempty"` IncludeBlacklisted bool `protobuf:"varint,3,opt,name=include_blacklisted,json=includeBlacklisted,proto3" json:"include_blacklisted,omitempty"` } @@ -220,13 +219,6 @@ func (x *GetTokensRequest) GetIdFilter() []string { return nil } -func (x *GetTokensRequest) GetAudienceFilter() []string { - if x != nil { - return x.AudienceFilter - } - return nil -} - func (x *GetTokensRequest) GetIncludeBlacklisted() bool { if x != nil { return x.IncludeBlacklisted @@ -618,102 +610,100 @@ var file_m2m_oauth_server_pb_service_proto_rawDesc = []byte{ 0x61, 0x63, 0x6b, 0x4c, 0x69, 0x73, 0x74, 0x65, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x66, 0x6c, 0x61, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x04, 0x66, 0x6c, 0x61, 0x67, 0x12, 0x1c, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, - 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x22, 0x89, 0x01, 0x0a, 0x10, - 0x47, 0x65, 0x74, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x12, 0x1b, 0x0a, 0x09, 0x69, 0x64, 0x5f, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x18, 0x01, 0x20, - 0x03, 0x28, 0x09, 0x52, 0x08, 0x69, 0x64, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x12, 0x27, 0x0a, - 0x0f, 0x61, 0x75, 0x64, 0x69, 0x65, 0x6e, 0x63, 0x65, 0x5f, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, - 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0e, 0x61, 0x75, 0x64, 0x69, 0x65, 0x6e, 0x63, 0x65, - 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x12, 0x2f, 0x0a, 0x13, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, - 0x65, 0x5f, 0x62, 0x6c, 0x61, 0x63, 0x6b, 0x6c, 0x69, 0x73, 0x74, 0x65, 0x64, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x08, 0x52, 0x12, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x42, 0x6c, 0x61, 0x63, - 0x6b, 0x6c, 0x69, 0x73, 0x74, 0x65, 0x64, 0x22, 0x35, 0x0a, 0x16, 0x42, 0x6c, 0x61, 0x63, 0x6b, - 0x6c, 0x69, 0x73, 0x74, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x69, 0x64, 0x5f, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x18, 0x01, - 0x20, 0x03, 0x28, 0x09, 0x52, 0x08, 0x69, 0x64, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x22, 0x2f, - 0x0a, 0x17, 0x42, 0x6c, 0x61, 0x63, 0x6b, 0x6c, 0x69, 0x73, 0x74, 0x54, 0x6f, 0x6b, 0x65, 0x6e, - 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x63, 0x6f, 0x75, - 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x22, - 0xc5, 0x02, 0x0a, 0x12, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, - 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x63, 0x6c, 0x69, 0x65, 0x6e, - 0x74, 0x49, 0x64, 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x73, 0x65, - 0x63, 0x72, 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x63, 0x6c, 0x69, 0x65, - 0x6e, 0x74, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x61, 0x75, 0x64, 0x69, - 0x65, 0x6e, 0x63, 0x65, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x08, 0x61, 0x75, 0x64, 0x69, - 0x65, 0x6e, 0x63, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x18, 0x04, 0x20, - 0x03, 0x28, 0x09, 0x52, 0x05, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x65, 0x78, - 0x70, 0x69, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, - 0x65, 0x78, 0x70, 0x69, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x32, 0x0a, 0x15, 0x63, 0x6c, - 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x61, 0x73, 0x73, 0x65, 0x72, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x74, - 0x79, 0x70, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x13, 0x63, 0x6c, 0x69, 0x65, 0x6e, - 0x74, 0x41, 0x73, 0x73, 0x65, 0x72, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x12, 0x29, - 0x0a, 0x10, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x61, 0x73, 0x73, 0x65, 0x72, 0x74, 0x69, - 0x6f, 0x6e, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, - 0x41, 0x73, 0x73, 0x65, 0x72, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1d, 0x0a, 0x0a, 0x74, 0x6f, 0x6b, - 0x65, 0x6e, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x74, - 0x6f, 0x6b, 0x65, 0x6e, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x67, 0x72, 0x61, 0x6e, - 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x67, 0x72, - 0x61, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x22, 0x8c, 0x01, 0x0a, 0x13, 0x43, 0x72, 0x65, 0x61, - 0x74, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x21, 0x0a, 0x0c, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x54, 0x6f, 0x6b, - 0x65, 0x6e, 0x12, 0x1d, 0x0a, 0x0a, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x5f, 0x74, 0x79, 0x70, 0x65, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x54, 0x79, 0x70, - 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x73, 0x5f, 0x69, 0x6e, 0x18, - 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x73, 0x49, 0x6e, - 0x12, 0x14, 0x0a, 0x05, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x18, 0x04, 0x20, 0x03, 0x28, 0x09, 0x52, - 0x05, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x32, 0xcf, 0x03, 0x0a, 0x0f, 0x4d, 0x32, 0x4d, 0x4f, 0x41, - 0x75, 0x74, 0x68, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x93, 0x01, 0x0a, 0x0b, 0x43, - 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x25, 0x2e, 0x6d, 0x32, 0x6d, - 0x6f, 0x61, 0x75, 0x74, 0x68, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x70, 0x62, 0x2e, 0x43, - 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x26, 0x2e, 0x6d, 0x32, 0x6d, 0x6f, 0x61, 0x75, 0x74, 0x68, 0x73, 0x65, 0x72, 0x76, - 0x65, 0x72, 0x2e, 0x70, 0x62, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x6f, 0x6b, 0x65, - 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x35, 0x92, 0x41, 0x08, 0x0a, 0x06, - 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x24, 0x3a, 0x01, 0x2a, 0x22, - 0x1f, 0x2f, 0x6d, 0x32, 0x6d, 0x2d, 0x6f, 0x61, 0x75, 0x74, 0x68, 0x2d, 0x73, 0x65, 0x72, 0x76, - 0x65, 0x72, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x73, - 0x12, 0x80, 0x01, 0x0a, 0x09, 0x47, 0x65, 0x74, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x12, 0x23, - 0x2e, 0x6d, 0x32, 0x6d, 0x6f, 0x61, 0x75, 0x74, 0x68, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, - 0x70, 0x62, 0x2e, 0x47, 0x65, 0x74, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x6d, 0x32, 0x6d, 0x6f, 0x61, 0x75, 0x74, 0x68, 0x73, 0x65, - 0x72, 0x76, 0x65, 0x72, 0x2e, 0x70, 0x62, 0x2e, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0x32, 0x92, - 0x41, 0x08, 0x0a, 0x06, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x21, - 0x12, 0x1f, 0x2f, 0x6d, 0x32, 0x6d, 0x2d, 0x6f, 0x61, 0x75, 0x74, 0x68, 0x2d, 0x73, 0x65, 0x72, - 0x76, 0x65, 0x72, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, - 0x73, 0x30, 0x01, 0x12, 0xa2, 0x01, 0x0a, 0x0f, 0x42, 0x6c, 0x61, 0x63, 0x6b, 0x6c, 0x69, 0x73, - 0x74, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x12, 0x29, 0x2e, 0x6d, 0x32, 0x6d, 0x6f, 0x61, 0x75, - 0x74, 0x68, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x70, 0x62, 0x2e, 0x42, 0x6c, 0x61, 0x63, - 0x6b, 0x6c, 0x69, 0x73, 0x74, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x6d, 0x32, 0x6d, 0x6f, 0x61, 0x75, 0x74, 0x68, 0x73, 0x65, 0x72, - 0x76, 0x65, 0x72, 0x2e, 0x70, 0x62, 0x2e, 0x42, 0x6c, 0x61, 0x63, 0x6b, 0x6c, 0x69, 0x73, 0x74, - 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x38, - 0x92, 0x41, 0x08, 0x0a, 0x06, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x82, 0xd3, 0xe4, 0x93, 0x02, - 0x27, 0x3a, 0x01, 0x2a, 0x22, 0x22, 0x2f, 0x6d, 0x32, 0x6d, 0x2d, 0x6f, 0x61, 0x75, 0x74, 0x68, - 0x2d, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x62, - 0x6c, 0x61, 0x63, 0x6b, 0x6c, 0x69, 0x73, 0x74, 0x42, 0xcb, 0x02, 0x92, 0x41, 0x94, 0x02, 0x12, - 0xbc, 0x01, 0x0a, 0x0c, 0x50, 0x4c, 0x47, 0x44, 0x20, 0x4d, 0x32, 0x4d, 0x20, 0x41, 0x50, 0x49, - 0x12, 0x24, 0x41, 0x50, 0x49, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x74, 0x6f, 0x20, 0x6d, 0x61, 0x6e, - 0x61, 0x67, 0x65, 0x20, 0x6d, 0x32, 0x6d, 0x20, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x20, 0x69, - 0x6e, 0x20, 0x50, 0x4c, 0x47, 0x44, 0x22, 0x3a, 0x0a, 0x08, 0x70, 0x6c, 0x67, 0x64, 0x2e, 0x64, - 0x65, 0x76, 0x12, 0x1f, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x67, 0x69, 0x74, 0x68, - 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x70, 0x6c, 0x67, 0x64, 0x2d, 0x64, 0x65, 0x76, 0x2f, - 0x68, 0x75, 0x62, 0x1a, 0x0d, 0x69, 0x6e, 0x66, 0x6f, 0x40, 0x70, 0x6c, 0x67, 0x64, 0x2e, 0x64, - 0x65, 0x76, 0x2a, 0x45, 0x0a, 0x12, 0x41, 0x70, 0x61, 0x63, 0x68, 0x65, 0x20, 0x4c, 0x69, 0x63, - 0x65, 0x6e, 0x73, 0x65, 0x20, 0x32, 0x2e, 0x30, 0x12, 0x2f, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, + 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x22, 0x60, 0x0a, 0x10, 0x47, + 0x65, 0x74, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, + 0x1b, 0x0a, 0x09, 0x69, 0x64, 0x5f, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x18, 0x01, 0x20, 0x03, + 0x28, 0x09, 0x52, 0x08, 0x69, 0x64, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x12, 0x2f, 0x0a, 0x13, + 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x5f, 0x62, 0x6c, 0x61, 0x63, 0x6b, 0x6c, 0x69, 0x73, + 0x74, 0x65, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x12, 0x69, 0x6e, 0x63, 0x6c, 0x75, + 0x64, 0x65, 0x42, 0x6c, 0x61, 0x63, 0x6b, 0x6c, 0x69, 0x73, 0x74, 0x65, 0x64, 0x22, 0x35, 0x0a, + 0x16, 0x42, 0x6c, 0x61, 0x63, 0x6b, 0x6c, 0x69, 0x73, 0x74, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x69, 0x64, 0x5f, 0x66, 0x69, + 0x6c, 0x74, 0x65, 0x72, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x08, 0x69, 0x64, 0x46, 0x69, + 0x6c, 0x74, 0x65, 0x72, 0x22, 0x2f, 0x0a, 0x17, 0x42, 0x6c, 0x61, 0x63, 0x6b, 0x6c, 0x69, 0x73, + 0x74, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x14, 0x0a, 0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, + 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0xc5, 0x02, 0x0a, 0x12, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, + 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1b, 0x0a, 0x09, + 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x08, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x6c, 0x69, + 0x65, 0x6e, 0x74, 0x5f, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x0c, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x12, 0x1a, + 0x0a, 0x08, 0x61, 0x75, 0x64, 0x69, 0x65, 0x6e, 0x63, 0x65, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, + 0x52, 0x08, 0x61, 0x75, 0x64, 0x69, 0x65, 0x6e, 0x63, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x63, + 0x6f, 0x70, 0x65, 0x18, 0x04, 0x20, 0x03, 0x28, 0x09, 0x52, 0x05, 0x73, 0x63, 0x6f, 0x70, 0x65, + 0x12, 0x1e, 0x0a, 0x0a, 0x65, 0x78, 0x70, 0x69, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x05, + 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x65, 0x78, 0x70, 0x69, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x12, 0x32, 0x0a, 0x15, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x61, 0x73, 0x73, 0x65, 0x72, + 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x13, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x41, 0x73, 0x73, 0x65, 0x72, 0x74, 0x69, 0x6f, 0x6e, + 0x54, 0x79, 0x70, 0x65, 0x12, 0x29, 0x0a, 0x10, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x61, + 0x73, 0x73, 0x65, 0x72, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, + 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x41, 0x73, 0x73, 0x65, 0x72, 0x74, 0x69, 0x6f, 0x6e, 0x12, + 0x1d, 0x0a, 0x0a, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x08, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x09, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1d, + 0x0a, 0x0a, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x09, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x09, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x22, 0x8c, 0x01, + 0x0a, 0x13, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x5f, + 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x61, 0x63, 0x63, + 0x65, 0x73, 0x73, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x1d, 0x0a, 0x0a, 0x74, 0x6f, 0x6b, 0x65, + 0x6e, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x74, 0x6f, + 0x6b, 0x65, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x65, 0x78, 0x70, 0x69, 0x72, + 0x65, 0x73, 0x5f, 0x69, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x65, 0x78, 0x70, + 0x69, 0x72, 0x65, 0x73, 0x49, 0x6e, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x18, + 0x04, 0x20, 0x03, 0x28, 0x09, 0x52, 0x05, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x32, 0xcf, 0x03, 0x0a, + 0x0f, 0x4d, 0x32, 0x4d, 0x4f, 0x41, 0x75, 0x74, 0x68, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, + 0x12, 0x93, 0x01, 0x0a, 0x0b, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, + 0x12, 0x25, 0x2e, 0x6d, 0x32, 0x6d, 0x6f, 0x61, 0x75, 0x74, 0x68, 0x73, 0x65, 0x72, 0x76, 0x65, + 0x72, 0x2e, 0x70, 0x62, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x26, 0x2e, 0x6d, 0x32, 0x6d, 0x6f, 0x61, 0x75, + 0x74, 0x68, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x70, 0x62, 0x2e, 0x43, 0x72, 0x65, 0x61, + 0x74, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, + 0x35, 0x92, 0x41, 0x08, 0x0a, 0x06, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x82, 0xd3, 0xe4, 0x93, + 0x02, 0x24, 0x3a, 0x01, 0x2a, 0x22, 0x1f, 0x2f, 0x6d, 0x32, 0x6d, 0x2d, 0x6f, 0x61, 0x75, 0x74, + 0x68, 0x2d, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, + 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x12, 0x80, 0x01, 0x0a, 0x09, 0x47, 0x65, 0x74, 0x54, 0x6f, + 0x6b, 0x65, 0x6e, 0x73, 0x12, 0x23, 0x2e, 0x6d, 0x32, 0x6d, 0x6f, 0x61, 0x75, 0x74, 0x68, 0x73, + 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x70, 0x62, 0x2e, 0x47, 0x65, 0x74, 0x54, 0x6f, 0x6b, 0x65, + 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x6d, 0x32, 0x6d, 0x6f, + 0x61, 0x75, 0x74, 0x68, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x70, 0x62, 0x2e, 0x54, 0x6f, + 0x6b, 0x65, 0x6e, 0x22, 0x32, 0x92, 0x41, 0x08, 0x0a, 0x06, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, + 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x21, 0x12, 0x1f, 0x2f, 0x6d, 0x32, 0x6d, 0x2d, 0x6f, 0x61, 0x75, + 0x74, 0x68, 0x2d, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, + 0x2f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x30, 0x01, 0x12, 0xa2, 0x01, 0x0a, 0x0f, 0x42, 0x6c, + 0x61, 0x63, 0x6b, 0x6c, 0x69, 0x73, 0x74, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x12, 0x29, 0x2e, + 0x6d, 0x32, 0x6d, 0x6f, 0x61, 0x75, 0x74, 0x68, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x70, + 0x62, 0x2e, 0x42, 0x6c, 0x61, 0x63, 0x6b, 0x6c, 0x69, 0x73, 0x74, 0x54, 0x6f, 0x6b, 0x65, 0x6e, + 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x6d, 0x32, 0x6d, 0x6f, 0x61, + 0x75, 0x74, 0x68, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x70, 0x62, 0x2e, 0x42, 0x6c, 0x61, + 0x63, 0x6b, 0x6c, 0x69, 0x73, 0x74, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x38, 0x92, 0x41, 0x08, 0x0a, 0x06, 0x54, 0x6f, 0x6b, 0x65, 0x6e, + 0x73, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x27, 0x3a, 0x01, 0x2a, 0x22, 0x22, 0x2f, 0x6d, 0x32, 0x6d, + 0x2d, 0x6f, 0x61, 0x75, 0x74, 0x68, 0x2d, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2f, 0x61, 0x70, + 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x62, 0x6c, 0x61, 0x63, 0x6b, 0x6c, 0x69, 0x73, 0x74, 0x42, 0xcb, + 0x02, 0x92, 0x41, 0x94, 0x02, 0x12, 0xbc, 0x01, 0x0a, 0x0c, 0x50, 0x4c, 0x47, 0x44, 0x20, 0x4d, + 0x32, 0x4d, 0x20, 0x41, 0x50, 0x49, 0x12, 0x24, 0x41, 0x50, 0x49, 0x20, 0x66, 0x6f, 0x72, 0x20, + 0x74, 0x6f, 0x20, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x20, 0x6d, 0x32, 0x6d, 0x20, 0x74, 0x6f, + 0x6b, 0x65, 0x6e, 0x73, 0x20, 0x69, 0x6e, 0x20, 0x50, 0x4c, 0x47, 0x44, 0x22, 0x3a, 0x0a, 0x08, + 0x70, 0x6c, 0x67, 0x64, 0x2e, 0x64, 0x65, 0x76, 0x12, 0x1f, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x70, 0x6c, 0x67, - 0x64, 0x2d, 0x64, 0x65, 0x76, 0x2f, 0x68, 0x75, 0x62, 0x2f, 0x62, 0x6c, 0x6f, 0x62, 0x2f, 0x76, - 0x32, 0x2f, 0x4c, 0x49, 0x43, 0x45, 0x4e, 0x53, 0x45, 0x32, 0x03, 0x31, 0x2e, 0x30, 0x2a, 0x01, - 0x02, 0x32, 0x10, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x6a, - 0x73, 0x6f, 0x6e, 0x32, 0x15, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x6a, 0x73, 0x6f, 0x6e, 0x3a, 0x10, 0x61, 0x70, 0x70, 0x6c, - 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x6a, 0x73, 0x6f, 0x6e, 0x3a, 0x15, 0x61, 0x70, - 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x6a, - 0x73, 0x6f, 0x6e, 0x5a, 0x31, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, - 0x70, 0x6c, 0x67, 0x64, 0x2d, 0x64, 0x65, 0x76, 0x2f, 0x68, 0x75, 0x62, 0x2f, 0x76, 0x32, 0x2f, - 0x6d, 0x32, 0x6d, 0x2d, 0x6f, 0x61, 0x75, 0x74, 0x68, 0x2d, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, - 0x2f, 0x70, 0x62, 0x3b, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x64, 0x2d, 0x64, 0x65, 0x76, 0x2f, 0x68, 0x75, 0x62, 0x1a, 0x0d, 0x69, 0x6e, 0x66, 0x6f, 0x40, + 0x70, 0x6c, 0x67, 0x64, 0x2e, 0x64, 0x65, 0x76, 0x2a, 0x45, 0x0a, 0x12, 0x41, 0x70, 0x61, 0x63, + 0x68, 0x65, 0x20, 0x4c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x20, 0x32, 0x2e, 0x30, 0x12, 0x2f, + 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, + 0x6f, 0x6d, 0x2f, 0x70, 0x6c, 0x67, 0x64, 0x2d, 0x64, 0x65, 0x76, 0x2f, 0x68, 0x75, 0x62, 0x2f, + 0x62, 0x6c, 0x6f, 0x62, 0x2f, 0x76, 0x32, 0x2f, 0x4c, 0x49, 0x43, 0x45, 0x4e, 0x53, 0x45, 0x32, + 0x03, 0x31, 0x2e, 0x30, 0x2a, 0x01, 0x02, 0x32, 0x10, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x6a, 0x73, 0x6f, 0x6e, 0x32, 0x15, 0x61, 0x70, 0x70, 0x6c, 0x69, + 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x6a, 0x73, 0x6f, 0x6e, + 0x3a, 0x10, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x6a, 0x73, + 0x6f, 0x6e, 0x3a, 0x15, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x6a, 0x73, 0x6f, 0x6e, 0x5a, 0x31, 0x67, 0x69, 0x74, 0x68, 0x75, + 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x70, 0x6c, 0x67, 0x64, 0x2d, 0x64, 0x65, 0x76, 0x2f, 0x68, + 0x75, 0x62, 0x2f, 0x76, 0x32, 0x2f, 0x6d, 0x32, 0x6d, 0x2d, 0x6f, 0x61, 0x75, 0x74, 0x68, 0x2d, + 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2f, 0x70, 0x62, 0x3b, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/m2m-oauth-server/pb/service.proto b/m2m-oauth-server/pb/service.proto index 3e79988b8..a55f56b4a 100644 --- a/m2m-oauth-server/pb/service.proto +++ b/m2m-oauth-server/pb/service.proto @@ -66,8 +66,7 @@ message Token { // driven by resource change event message GetTokensRequest { repeated string id_filter = 1; - repeated string audience_filter = 2; - bool include_blacklisted = 3; + bool include_blacklisted = 2; } message BlacklistTokensRequest { diff --git a/m2m-oauth-server/pb/service.swagger.json b/m2m-oauth-server/pb/service.swagger.json index 8a6492a2d..46abc4c85 100644 --- a/m2m-oauth-server/pb/service.swagger.json +++ b/m2m-oauth-server/pb/service.swagger.json @@ -102,16 +102,6 @@ }, "collectionFormat": "multi" }, - { - "name": "audienceFilter", - "in": "query", - "required": false, - "type": "array", - "items": { - "type": "string" - }, - "collectionFormat": "multi" - }, { "name": "includeBlacklisted", "in": "query", diff --git a/m2m-oauth-server/service/grpc/token.go b/m2m-oauth-server/service/grpc/token.go index bbb8d2b13..3730ebd62 100644 --- a/m2m-oauth-server/service/grpc/token.go +++ b/m2m-oauth-server/service/grpc/token.go @@ -108,19 +108,25 @@ func setOriginTokenClaims(token jwt.Token, tokenReq tokenRequest) error { } func getExpirationTime(clientCfg *oauthsigner.Client, tokenReq tokenRequest) time.Time { + var wantExpiration time.Time + if tokenReq.CreateTokenRequest.GetExpiration() > 0 { + wantExpiration = time.Unix(tokenReq.CreateTokenRequest.GetExpiration(), 0) + } + if !wantExpiration.IsZero() && tokenReq.issuedAt.After(wantExpiration) { + return time.Time{} + } if clientCfg.AccessTokenLifetime == 0 { - if tokenReq.CreateTokenRequest.GetExpiration() > 0 { - return time.Unix(tokenReq.CreateTokenRequest.GetExpiration(), 0) + if !wantExpiration.IsZero() { + return wantExpiration } return time.Time{} } - if tokenReq.CreateTokenRequest.GetExpiration() == 0 { + if wantExpiration.IsZero() { return tokenReq.issuedAt.Add(clientCfg.AccessTokenLifetime) } - wantExpiration := time.Unix(tokenReq.CreateTokenRequest.GetExpiration(), 0) - clientWantExpiration := tokenReq.issuedAt.Add(clientCfg.AccessTokenLifetime) - if clientWantExpiration.Before(wantExpiration) { - return clientWantExpiration + clientExpiration := tokenReq.issuedAt.Add(clientCfg.AccessTokenLifetime) + if clientExpiration.Before(wantExpiration) { + return clientExpiration } return wantExpiration } diff --git a/m2m-oauth-server/service/grpc/token_internal_test.go b/m2m-oauth-server/service/grpc/token_internal_test.go new file mode 100644 index 000000000..4ded85f52 --- /dev/null +++ b/m2m-oauth-server/service/grpc/token_internal_test.go @@ -0,0 +1,112 @@ +package grpc + +import ( + "testing" + "time" + + oauthsigner "github.com/plgd-dev/hub/v2/m2m-oauth-server/oauthSigner" + "github.com/plgd-dev/hub/v2/m2m-oauth-server/pb" + "github.com/stretchr/testify/require" +) + +func TestGetExpirationTime(t *testing.T) { + now := time.Now() + type args struct { + clientCfg *oauthsigner.Client + tokenReq tokenRequest + } + tests := []struct { + name string + args args + want time.Time + }{ + { + name: "client access token lifetime = 0h, token expiration = now + 1h", + args: args{ + clientCfg: &oauthsigner.Client{ + AccessTokenLifetime: 0, + }, + tokenReq: tokenRequest{ + CreateTokenRequest: &pb.CreateTokenRequest{ + Expiration: now.Unix() + int64(time.Hour.Seconds()), + }, + issuedAt: now, + }, + }, + want: now.Add(time.Hour), + }, + { + name: "client access token lifetime = 1h, token expiration = 0", + args: args{ + clientCfg: &oauthsigner.Client{ + AccessTokenLifetime: time.Hour, + }, + tokenReq: tokenRequest{ + CreateTokenRequest: &pb.CreateTokenRequest{}, + issuedAt: now, + }, + }, + want: now.Add(time.Hour), + }, + { + name: "client access token lifetime = 1h, token expiration = now + 1h", + args: args{ + clientCfg: &oauthsigner.Client{ + AccessTokenLifetime: time.Hour, + }, + tokenReq: tokenRequest{ + CreateTokenRequest: &pb.CreateTokenRequest{ + Expiration: now.Unix() + int64(time.Hour.Seconds()), + }, + issuedAt: now, + }, + }, + want: now.Add(time.Hour), + }, + { + name: "client access token lifetime = 1h, token expiration = now + 2h", + args: args{ + clientCfg: &oauthsigner.Client{ + AccessTokenLifetime: time.Hour, + }, + tokenReq: tokenRequest{ + CreateTokenRequest: &pb.CreateTokenRequest{ + Expiration: now.Unix() + int64(time.Hour.Seconds()*2), + }, + issuedAt: now, + }, + }, + want: now.Add(time.Hour), + }, + { + name: "client access token lifetime = 0h, token expiration = now - 2h", + args: args{ + clientCfg: &oauthsigner.Client{}, + tokenReq: tokenRequest{ + CreateTokenRequest: &pb.CreateTokenRequest{ + Expiration: now.Unix() - int64(time.Hour.Seconds()*2), + }, + issuedAt: now, + }, + }, + want: time.Time{}, + }, + { + name: "client access token lifetime = 0h, token expiration = 0", + args: args{ + clientCfg: &oauthsigner.Client{}, + tokenReq: tokenRequest{ + CreateTokenRequest: &pb.CreateTokenRequest{}, + issuedAt: now, + }, + }, + want: time.Time{}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := getExpirationTime(tt.args.clientCfg, tt.args.tokenReq) + require.Equal(t, tt.want.Unix(), got.Unix()) + }) + } +} diff --git a/m2m-oauth-server/service/http/postToken_test.go b/m2m-oauth-server/service/http/postToken_test.go index 9e005c9bb..eb453d0bc 100644 --- a/m2m-oauth-server/service/http/postToken_test.go +++ b/m2m-oauth-server/service/http/postToken_test.go @@ -98,6 +98,18 @@ func TestPostToken(t *testing.T) { existOriginalTokenClaims: true, }, }, + { + name: "ownerToken with over time expiration- JWT", + args: m2mOauthServerTest.AccessTokenOptions{ + Ctx: context.Background(), + ClientID: m2mOauthServerTest.JWTPrivateKeyOAuthClient.ID, + GrantType: string(oauthsigner.GrantTypeClientCredentials), + Host: config.M2M_OAUTH_SERVER_HTTP_HOST, + JWT: token, + Expiration: time.Now().Add(time.Hour * 24 * 365), + }, + wantCode: http.StatusUnauthorized, + }, { name: "invalid client", args: m2mOauthServerTest.AccessTokenOptions{ @@ -134,7 +146,9 @@ func TestPostToken(t *testing.T) { }, } - webTearDown := m2mOauthServerTest.SetUp(t) + cfg := m2mOauthServerTest.MakeConfig(t) + cfg.OAuthSigner.Clients.Find(m2mOauthServerTest.JWTPrivateKeyOAuthClient.ID).AccessTokenLifetime = time.Hour * 24 + webTearDown := m2mOauthServerTest.New(t, cfg) defer webTearDown() for _, tt := range tests { diff --git a/m2m-oauth-server/store/mongodb/tokens.go b/m2m-oauth-server/store/mongodb/tokens.go index 08c88344e..c4c0f3eb1 100644 --- a/m2m-oauth-server/store/mongodb/tokens.go +++ b/m2m-oauth-server/store/mongodb/tokens.go @@ -47,10 +47,6 @@ func toFilter(owner string, req *pb.GetTokensRequest) (filter bson.D, hint inter } else { setIdOwnerHint = false } - if len(req.GetAudienceFilter()) > 0 { - filter = append(filter, bson.E{Key: pb.AudienceKey, Value: bson.M{mongodb.In: req.GetAudienceFilter()}}) - setIdOwnerHint = false - } if !req.GetIncludeBlacklisted() { setIdOwnerHint = false filter = append(filter, diff --git a/m2m-oauth-server/swagger.yaml b/m2m-oauth-server/swagger.yaml index 1b8187ef8..7b60de173 100644 --- a/m2m-oauth-server/swagger.yaml +++ b/m2m-oauth-server/swagger.yaml @@ -143,14 +143,6 @@ paths: type: array items: type: string - - name: audienceFilter - in: query - style: form - explode: true - schema: - type: array - items: - type: string - name: includeBlacklisted in: query schema: diff --git a/pkg/mongodb/marshal.go b/pkg/mongodb/marshal.go index a86ff1449..1bf4a811d 100644 --- a/pkg/mongodb/marshal.go +++ b/pkg/mongodb/marshal.go @@ -83,36 +83,48 @@ func iterateOverSlice(curr []any, permitMissingPaths bool, part string) (any, er return curr[index], nil } +func nextCurrent(current any, permitMissingPaths bool, parts []string, idx int) ([]any, any, error) { + part := parts[idx] + if part == "" { + return nil, current, nil + } + var err error + switch curr := current.(type) { + case map[string]any: + current, err = iterateOverMap(curr, permitMissingPaths, part) + case []any: + if part == "*" { + val, err2 := handleSlice(curr, permitMissingPaths, parts[idx+1:]) + return val, nil, err2 + } + current, err = iterateOverSlice(curr, permitMissingPaths, part) + default: + return nil, nil, fmt.Errorf("unsupported type %T at path segment %s", current, part) + } + if err != nil { + return nil, nil, err + } + if current == nil { + return nil, nil, nil + } + return nil, current, nil +} + func findParents(current any, permitMissingPaths bool, parts []string) ([]any, error) { - for idx, part := range parts { - if part == "" { - continue + for idx := range parts { + var result []any + var err error + result, next, err := nextCurrent(current, permitMissingPaths, parts, idx) + if err != nil { + return nil, err } - switch curr := current.(type) { - case map[string]any: - var err error - current, err = iterateOverMap(curr, permitMissingPaths, part) - if err != nil { - return nil, err - } - if current == nil { - return nil, nil - } - case []any: - if part == "*" { - return handleSlice(curr, permitMissingPaths, parts[idx+1:]) - } - var err error - current, err = iterateOverSlice(curr, permitMissingPaths, part) - if err != nil { - return nil, err - } - if current == nil { - return nil, nil - } - default: - return nil, fmt.Errorf("unsupported type %T at path segment %s", current, part) + if result != nil { + return result, nil + } + if next == nil { + return nil, nil } + current = next } return []any{current}, nil } From b44161c7303fbd859c8e1ca34454ec85c202cd39 Mon Sep 17 00:00:00 2001 From: Daniel Adam Date: Tue, 23 Jul 2024 11:45:44 +0200 Subject: [PATCH 25/31] Refactor for m2m token validation --- .../service/deviceSubscription.go | 6 +- .../service/publishResource.go | 4 +- cloud2cloud-connector/service/pull.go | 4 +- .../service/requestHandler.go | 9 +-- .../service/resourceSubscription.go | 4 +- cloud2cloud-connector/service/service.go | 13 ++-- .../service/subscriptions.go | 6 +- .../service/updateResource.go | 4 +- cloud2cloud-gateway/service/httpApi.go | 14 ++-- cloud2cloud-gateway/service/service.go | 9 +-- .../service/subscribeToResource.go | 2 +- http-gateway/serverMux/router.go | 13 ++-- http-gateway/service/requestHandler.go | 3 +- http-gateway/service/service.go | 9 +-- m2m-oauth-server/service/http/service.go | 3 +- m2m-oauth-server/test/test.go | 2 +- pkg/net/http/auth.go | 64 +++---------------- pkg/net/http/header.go | 8 ++- .../http/{interceptor.go => jwt/claims.go} | 30 +-------- pkg/net/http/jwt/interceptor.go | 56 ++++++++++++++++ pkg/net/http/jwt/token.go | 37 +++++++++++ .../http/{auth_test.go => jwt/token_test.go} | 8 +-- pkg/net/http/service/config.go | 6 +- pkg/net/http/service/service.go | 9 +-- pkg/net/http/toUrlString.go | 13 ---- pkg/net/http/toUrlString_test.go | 45 ------------- pkg/net/http/{ => uri}/canonicalHref.go | 20 ++++-- pkg/net/http/{ => uri}/canonicalHref_test.go | 10 ++- pkg/security/jwt/claims.go | 21 ++++++ pkg/security/jwt/multiJwk.go | 27 +------- pkg/security/jwt/scopeClaims.go | 4 ++ pkg/security/jwt/validator.go | 12 ++-- pkg/security/jwt/validator/validator.go | 16 +++-- test/oauth-server/service/httpApi.go | 4 +- test/oauth-server/test/test.go | 2 +- 35 files changed, 246 insertions(+), 251 deletions(-) rename pkg/net/http/{interceptor.go => jwt/claims.go} (50%) create mode 100644 pkg/net/http/jwt/interceptor.go create mode 100644 pkg/net/http/jwt/token.go rename pkg/net/http/{auth_test.go => jwt/token_test.go} (62%) delete mode 100644 pkg/net/http/toUrlString.go delete mode 100644 pkg/net/http/toUrlString_test.go rename pkg/net/http/{ => uri}/canonicalHref.go (51%) rename pkg/net/http/{ => uri}/canonicalHref_test.go (78%) diff --git a/cloud2cloud-connector/service/deviceSubscription.go b/cloud2cloud-connector/service/deviceSubscription.go index a739fbe5d..f80f8647c 100644 --- a/cloud2cloud-connector/service/deviceSubscription.go +++ b/cloud2cloud-connector/service/deviceSubscription.go @@ -12,7 +12,7 @@ import ( "github.com/plgd-dev/hub/v2/cloud2cloud-connector/events" "github.com/plgd-dev/hub/v2/cloud2cloud-connector/store" "github.com/plgd-dev/hub/v2/pkg/log" - kitHttp "github.com/plgd-dev/hub/v2/pkg/net/http" + pkgHttpUri "github.com/plgd-dev/hub/v2/pkg/net/http/uri" "github.com/plgd-dev/hub/v2/resource-aggregate/commands" "go.opentelemetry.io/otel/trace" ) @@ -111,7 +111,7 @@ func (s *SubscriptionManager) handleResourcesPublished(ctx context.Context, d Su Priority: int64(endpoint.Priority), }) } - href := kitHttp.CanonicalHref(trimDeviceIDFromHref(link.DeviceID, link.Href)) + href := pkgHttpUri.CanonicalHref(trimDeviceIDFromHref(link.DeviceID, link.Href)) _, err := s.raClient.PublishResourceLinks(ctx, &commands.PublishResourceLinksRequest{ DeviceId: link.DeviceID, Resources: []*commands.Resource{{ @@ -153,7 +153,7 @@ func (s *SubscriptionManager) handleResourcesUnpublished(ctx context.Context, d var errors *multierror.Error for _, link := range links { link.DeviceID = d.subscription.DeviceID - href := kitHttp.CanonicalHref(trimDeviceIDFromHref(link.DeviceID, link.Href)) + href := pkgHttpUri.CanonicalHref(trimDeviceIDFromHref(link.DeviceID, link.Href)) _, err := s.raClient.UnpublishResourceLinks(ctx, &commands.UnpublishResourceLinksRequest{ DeviceId: link.GetDeviceID(), Hrefs: []string{href}, diff --git a/cloud2cloud-connector/service/publishResource.go b/cloud2cloud-connector/service/publishResource.go index c48677c4a..8646814fe 100644 --- a/cloud2cloud-connector/service/publishResource.go +++ b/cloud2cloud-connector/service/publishResource.go @@ -4,7 +4,7 @@ import ( "context" "github.com/plgd-dev/device/v2/schema" - kitHttp "github.com/plgd-dev/hub/v2/pkg/net/http" + pkgHttpUri "github.com/plgd-dev/hub/v2/pkg/net/http/uri" "github.com/plgd-dev/hub/v2/resource-aggregate/commands" raService "github.com/plgd-dev/hub/v2/resource-aggregate/service" ) @@ -17,7 +17,7 @@ func publishResource(ctx context.Context, raClient raService.ResourceAggregateCl Priority: int64(endpoint.Priority), }) } - href := kitHttp.CanonicalHref(trimDeviceIDFromHref(link.DeviceID, link.Href)) + href := pkgHttpUri.CanonicalHref(trimDeviceIDFromHref(link.DeviceID, link.Href)) _, err := raClient.PublishResourceLinks(ctx, &commands.PublishResourceLinksRequest{ DeviceId: link.DeviceID, Resources: []*commands.Resource{{ diff --git a/cloud2cloud-connector/service/pull.go b/cloud2cloud-connector/service/pull.go index ca97e02e2..336de558d 100644 --- a/cloud2cloud-connector/service/pull.go +++ b/cloud2cloud-connector/service/pull.go @@ -18,7 +18,7 @@ import ( pbIS "github.com/plgd-dev/hub/v2/identity-store/pb" "github.com/plgd-dev/hub/v2/pkg/log" kitNetGrpc "github.com/plgd-dev/hub/v2/pkg/net/grpc" - kitHttp "github.com/plgd-dev/hub/v2/pkg/net/http" + pkgHttpUri "github.com/plgd-dev/hub/v2/pkg/net/http/uri" "github.com/plgd-dev/hub/v2/pkg/security/oauth2" "github.com/plgd-dev/hub/v2/resource-aggregate/commands" raService "github.com/plgd-dev/hub/v2/resource-aggregate/service" @@ -300,7 +300,7 @@ func (p *pullDevicesHandler) notifyResourceChanged(ctx context.Context, linkedAc } _, err = p.raClient.NotifyResourceChanged(ctx, &commands.NotifyResourceChangedRequest{ - ResourceId: commands.NewResourceID(deviceID, kitHttp.CanonicalHref(link.Href)), + ResourceId: commands.NewResourceID(deviceID, pkgHttpUri.CanonicalHref(link.Href)), CommandMetadata: &commands.CommandMetadata{ ConnectionId: linkedAccount.ID, Sequence: uint64(time.Now().UnixNano()), diff --git a/cloud2cloud-connector/service/requestHandler.go b/cloud2cloud-connector/service/requestHandler.go index 265cfe217..a94b77d27 100644 --- a/cloud2cloud-connector/service/requestHandler.go +++ b/cloud2cloud-connector/service/requestHandler.go @@ -13,7 +13,8 @@ import ( "github.com/plgd-dev/hub/v2/cloud2cloud-connector/store" "github.com/plgd-dev/hub/v2/cloud2cloud-connector/uri" "github.com/plgd-dev/hub/v2/pkg/log" - kitNetHttp "github.com/plgd-dev/hub/v2/pkg/net/http" + pkgHttp "github.com/plgd-dev/hub/v2/pkg/net/http" + pkgHttpJwt "github.com/plgd-dev/hub/v2/pkg/net/http/jwt" pkgOAuth2 "github.com/plgd-dev/hub/v2/pkg/security/oauth2" "go.opentelemetry.io/otel/trace" ) @@ -78,11 +79,11 @@ func healthCheck(w http.ResponseWriter, _ *http.Request) { } // NewHTTP returns HTTP handler -func NewHTTP(requestHandler *RequestHandler, authInterceptor kitNetHttp.Interceptor, logger log.Logger) (http.Handler, error) { +func NewHTTP(requestHandler *RequestHandler, authInterceptor pkgHttpJwt.Interceptor, logger log.Logger) (http.Handler, error) { r := router.NewRouter() r.StrictSlash(true) - r.Use(kitNetHttp.CreateLoggingMiddleware(kitNetHttp.WithLogger(logger))) - r.Use(kitNetHttp.CreateAuthMiddleware(authInterceptor, func(_ context.Context, w http.ResponseWriter, r *http.Request, err error) { + r.Use(pkgHttp.CreateLoggingMiddleware(pkgHttp.WithLogger(logger))) + r.Use(pkgHttp.CreateAuthMiddleware(authInterceptor, func(_ context.Context, w http.ResponseWriter, r *http.Request, err error) { logAndWriteErrorResponse(fmt.Errorf("cannot process request on %v: %w", r.RequestURI, err), http.StatusUnauthorized, w) })) diff --git a/cloud2cloud-connector/service/resourceSubscription.go b/cloud2cloud-connector/service/resourceSubscription.go index 732a9c51f..976ccd8e8 100644 --- a/cloud2cloud-connector/service/resourceSubscription.go +++ b/cloud2cloud-connector/service/resourceSubscription.go @@ -10,7 +10,7 @@ import ( "github.com/plgd-dev/go-coap/v3/pkg/cache" "github.com/plgd-dev/hub/v2/cloud2cloud-connector/events" "github.com/plgd-dev/hub/v2/cloud2cloud-connector/store" - kitHttp "github.com/plgd-dev/hub/v2/pkg/net/http" + pkgHttpUri "github.com/plgd-dev/hub/v2/pkg/net/http/uri" "github.com/plgd-dev/hub/v2/resource-aggregate/commands" "go.opentelemetry.io/otel/trace" ) @@ -88,7 +88,7 @@ func cancelResourceSubscription(ctx context.Context, traceProvider trace.TracerP func (s *SubscriptionManager) handleResourceChangedEvent(ctx context.Context, subscriptionData SubscriptionData, header events.EventHeader, body []byte) error { coapContentFormat := stringToSupportedMediaType(header.ContentType) _, err := s.raClient.NotifyResourceChanged(ctx, &commands.NotifyResourceChangedRequest{ - ResourceId: commands.NewResourceID(subscriptionData.subscription.DeviceID, kitHttp.CanonicalHref(subscriptionData.subscription.Href)), + ResourceId: commands.NewResourceID(subscriptionData.subscription.DeviceID, pkgHttpUri.CanonicalHref(subscriptionData.subscription.Href)), CommandMetadata: &commands.CommandMetadata{ ConnectionId: subscriptionData.linkedAccount.ID + "." + subscriptionData.subscription.ID, Sequence: header.SequenceNumber, diff --git a/cloud2cloud-connector/service/service.go b/cloud2cloud-connector/service/service.go index ee1cf1e4c..b7c1d5078 100644 --- a/cloud2cloud-connector/service/service.go +++ b/cloud2cloud-connector/service/service.go @@ -18,7 +18,8 @@ import ( pkgMongo "github.com/plgd-dev/hub/v2/pkg/mongodb" kitNetGrpc "github.com/plgd-dev/hub/v2/pkg/net/grpc" grpcClient "github.com/plgd-dev/hub/v2/pkg/net/grpc/client" - kitNetHttp "github.com/plgd-dev/hub/v2/pkg/net/http" + pkgHttp "github.com/plgd-dev/hub/v2/pkg/net/http" + pkgHttpJwt "github.com/plgd-dev/hub/v2/pkg/net/http/jwt" "github.com/plgd-dev/hub/v2/pkg/net/listener" otelClient "github.com/plgd-dev/hub/v2/pkg/opentelemetry/collector/client" cmClient "github.com/plgd-dev/hub/v2/pkg/security/certManager/client" @@ -53,10 +54,10 @@ func toValidator(c oauth2.Config) validator.Config { const serviceName = "cloud2cloud-connector" -func newAuthInterceptor(validator *validator.Validator) kitNetHttp.Interceptor { - authRules := kitNetHttp.NewDefaultAuthorizationRules(uri.API) +func newAuthInterceptor(validator *validator.Validator) pkgHttpJwt.Interceptor { + authRules := pkgHttp.NewDefaultAuthorizationRules(uri.API) - whiteList := []kitNetHttp.RequestMatcher{ + whiteList := []pkgHttpJwt.RequestMatcher{ { Method: http.MethodGet, URI: regexp.MustCompile(regexp.QuoteMeta(uri.OAuthCallback)), @@ -70,7 +71,7 @@ func newAuthInterceptor(validator *validator.Validator) kitNetHttp.Interceptor { URI: regexp.MustCompile(regexp.QuoteMeta(uri.Events)), }, } - auth := kitNetHttp.NewInterceptorWithValidator(validator, authRules, whiteList...) + auth := pkgHttpJwt.NewInterceptorWithValidator(validator, authRules, whiteList...) return auth } @@ -267,7 +268,7 @@ func New(ctx context.Context, config Config, fileWatcher *fsnotify.Watcher, logg } httpServer := http.Server{ - Handler: kitNetHttp.OpenTelemetryNewHandler(httpHandler, serviceName, tracerProvider), + Handler: pkgHttp.OpenTelemetryNewHandler(httpHandler, serviceName, tracerProvider), ReadTimeout: config.APIs.HTTP.Server.ReadTimeout, ReadHeaderTimeout: config.APIs.HTTP.Server.ReadHeaderTimeout, WriteTimeout: config.APIs.HTTP.Server.WriteTimeout, diff --git a/cloud2cloud-connector/service/subscriptions.go b/cloud2cloud-connector/service/subscriptions.go index d95a30792..f7ad167d2 100644 --- a/cloud2cloud-connector/service/subscriptions.go +++ b/cloud2cloud-connector/service/subscriptions.go @@ -14,7 +14,7 @@ import ( pbIS "github.com/plgd-dev/hub/v2/identity-store/pb" "github.com/plgd-dev/hub/v2/pkg/log" kitNetGrpc "github.com/plgd-dev/hub/v2/pkg/net/grpc" - kitHttp "github.com/plgd-dev/hub/v2/pkg/net/http" + pkgHttpUri "github.com/plgd-dev/hub/v2/pkg/net/http/uri" "github.com/plgd-dev/hub/v2/pkg/security/oauth2" raService "github.com/plgd-dev/hub/v2/resource-aggregate/service" "github.com/plgd-dev/kit/v2/codec/json" @@ -94,7 +94,7 @@ func subscribe(ctx context.Context, tracerProvider trace.TracerProvider, href, c r, w := io.Pipe() - req, err := http.NewRequestWithContext(ctx, http.MethodPost, linkedCloud.Endpoint.URL+kitHttp.CanonicalHref(href), r) + req, err := http.NewRequestWithContext(ctx, http.MethodPost, linkedCloud.Endpoint.URL+pkgHttpUri.CanonicalHref(href), r) if err != nil { return resp, fmt.Errorf("cannot create post request: %w", err) } @@ -138,7 +138,7 @@ func subscribe(ctx context.Context, tracerProvider trace.TracerProvider, href, c func cancelSubscription(ctx context.Context, tracerProvider trace.TracerProvider, href string, linkedAccount store.LinkedAccount, linkedCloud store.LinkedCloud) error { client := linkedCloud.GetHTTPClient(tracerProvider) defer client.CloseIdleConnections() - req, err := http.NewRequestWithContext(ctx, http.MethodDelete, linkedCloud.Endpoint.URL+kitHttp.CanonicalHref(href), nil) + req, err := http.NewRequestWithContext(ctx, http.MethodDelete, linkedCloud.Endpoint.URL+pkgHttpUri.CanonicalHref(href), nil) if err != nil { return fmt.Errorf("cannot create delete request: %w", err) } diff --git a/cloud2cloud-connector/service/updateResource.go b/cloud2cloud-connector/service/updateResource.go index fb53a7e78..4f6aa010f 100644 --- a/cloud2cloud-connector/service/updateResource.go +++ b/cloud2cloud-connector/service/updateResource.go @@ -12,7 +12,7 @@ import ( "github.com/plgd-dev/hub/v2/cloud2cloud-connector/store" "github.com/plgd-dev/hub/v2/pkg/log" kitNetGrpc "github.com/plgd-dev/hub/v2/pkg/net/grpc" - kitHttp "github.com/plgd-dev/hub/v2/pkg/net/http" + pkgHttpUri "github.com/plgd-dev/hub/v2/pkg/net/http/uri" "github.com/plgd-dev/hub/v2/resource-aggregate/commands" raEvents "github.com/plgd-dev/hub/v2/resource-aggregate/events" raService "github.com/plgd-dev/hub/v2/resource-aggregate/service" @@ -20,7 +20,7 @@ import ( ) func makeHTTPEndpoint(url, deviceID, href string) string { - return url + kitHttp.CanonicalHref("devices/"+deviceID+"/"+href) + return url + pkgHttpUri.CanonicalHref("devices/"+deviceID+"/"+href) } func updateDeviceResource(ctx context.Context, tracerProvider trace.TracerProvider, deviceID, href, contentType string, content []byte, linkedAccount store.LinkedAccount, linkedCloud store.LinkedCloud) (string, []byte, commands.Status, error) { diff --git a/cloud2cloud-gateway/service/httpApi.go b/cloud2cloud-gateway/service/httpApi.go index 9da7ef2e7..4ffe5b209 100644 --- a/cloud2cloud-gateway/service/httpApi.go +++ b/cloud2cloud-gateway/service/httpApi.go @@ -13,7 +13,9 @@ import ( "github.com/plgd-dev/hub/v2/cloud2cloud-gateway/uri" pbGRPC "github.com/plgd-dev/hub/v2/grpc-gateway/pb" "github.com/plgd-dev/hub/v2/pkg/log" - kitNetHttp "github.com/plgd-dev/hub/v2/pkg/net/http" + pkgHttp "github.com/plgd-dev/hub/v2/pkg/net/http" + pkgHttpJwt "github.com/plgd-dev/hub/v2/pkg/net/http/jwt" + pkgHttpUri "github.com/plgd-dev/hub/v2/pkg/net/http/uri" raClient "github.com/plgd-dev/hub/v2/resource-aggregate/client" "github.com/plgd-dev/kit/v2/codec/cbor" "github.com/plgd-dev/kit/v2/codec/json" @@ -45,7 +47,7 @@ type RequestHandler struct { func logAndWriteErrorResponse(err error, statusCode int, w http.ResponseWriter) { log.Errorf("%v", err) w.Header().Set(events.ContentTypeKey, "text/plain") - w.WriteHeader(kitNetHttp.ErrToStatusWithDef(err, statusCode)) + w.WriteHeader(pkgHttp.ErrToStatusWithDef(err, statusCode)) if _, err2 := w.Write([]byte(err.Error())); err2 != nil { log.Errorf("failed to write error response body: %w", err2) } @@ -128,7 +130,7 @@ func makeHref(path []string) string { } func splitDevicePath(requestURI string) []string { - p := kitNetHttp.CanonicalHref(requestURI) + p := pkgHttpUri.CanonicalHref(requestURI) p = strings.TrimPrefix(p, uri.Devices) // remove core prefix p = strings.TrimLeft(p, "/") return strings.Split(p, "/") @@ -168,11 +170,11 @@ func resourceMatcher(r *http.Request, rm *router.RouteMatch) bool { } // NewHTTP returns HTTP handler -func NewHTTP(requestHandler *RequestHandler, authInterceptor kitNetHttp.Interceptor, logger log.Logger) http.Handler { +func NewHTTP(requestHandler *RequestHandler, authInterceptor pkgHttpJwt.Interceptor, logger log.Logger) http.Handler { r := router.NewRouter() r.StrictSlash(true) - r.Use(kitNetHttp.CreateLoggingMiddleware(kitNetHttp.WithLogger(logger))) - r.Use(kitNetHttp.CreateAuthMiddleware(authInterceptor, func(_ context.Context, w http.ResponseWriter, r *http.Request, err error) { + r.Use(pkgHttp.CreateLoggingMiddleware(pkgHttp.WithLogger(logger))) + r.Use(pkgHttp.CreateAuthMiddleware(authInterceptor, func(_ context.Context, w http.ResponseWriter, r *http.Request, err error) { logAndWriteErrorResponse(fmt.Errorf("cannot process request on %v: %w", r.RequestURI, err), http.StatusUnauthorized, w) })) diff --git a/cloud2cloud-gateway/service/service.go b/cloud2cloud-gateway/service/service.go index 536f7d899..44f38897b 100644 --- a/cloud2cloud-gateway/service/service.go +++ b/cloud2cloud-gateway/service/service.go @@ -14,7 +14,8 @@ import ( "github.com/plgd-dev/hub/v2/pkg/log" kitNetGrpc "github.com/plgd-dev/hub/v2/pkg/net/grpc" grpcClient "github.com/plgd-dev/hub/v2/pkg/net/grpc/client" - kitNetHttp "github.com/plgd-dev/hub/v2/pkg/net/http" + pkgHttp "github.com/plgd-dev/hub/v2/pkg/net/http" + pkgHttpJwt "github.com/plgd-dev/hub/v2/pkg/net/http/jwt" "github.com/plgd-dev/hub/v2/pkg/net/listener" otelClient "github.com/plgd-dev/hub/v2/pkg/opentelemetry/collector/client" cmClient "github.com/plgd-dev/hub/v2/pkg/security/certManager/client" @@ -38,7 +39,7 @@ type Server struct { } // https://openconnectivity.org/draftspecs/Gaborone/OCF_Cloud_API_for_Cloud_Services.pdf -var authRules = map[string][]kitNetHttp.AuthArgs{ +var authRules = map[string][]pkgHttpJwt.AuthArgs{ http.MethodGet: { { URI: regexp.MustCompile(`[\/]+api[\/]+v1[\/]+devices[\/]*$`), @@ -249,7 +250,7 @@ func New(ctx context.Context, config Config, fileWatcher *fsnotify.Watcher, logg return nil, fmt.Errorf("cannot create validator: %w", err) } listener.AddCloseFunc(validator.Close) - auth := kitNetHttp.NewInterceptorWithValidator(validator, authRules) + auth := pkgHttpJwt.NewInterceptorWithValidator(validator, authRules) gwClient, closeGwClient, err := newGrpcGatewayClient(config.Clients.GrpcGateway, fileWatcher, logger, tracerProvider) if err != nil { @@ -298,7 +299,7 @@ func New(ctx context.Context, config Config, fileWatcher *fsnotify.Watcher, logg requestHandler := NewRequestHandler(gwClient, raClient, subMgr, emitEvent) httpServer := http.Server{ - Handler: kitNetHttp.OpenTelemetryNewHandler(NewHTTP(requestHandler, auth, logger), serviceName, tracerProvider), + Handler: pkgHttp.OpenTelemetryNewHandler(NewHTTP(requestHandler, auth, logger), serviceName, tracerProvider), ReadTimeout: config.APIs.HTTP.Server.ReadTimeout, ReadHeaderTimeout: config.APIs.HTTP.Server.ReadHeaderTimeout, WriteTimeout: config.APIs.HTTP.Server.WriteTimeout, diff --git a/cloud2cloud-gateway/service/subscribeToResource.go b/cloud2cloud-gateway/service/subscribeToResource.go index d91050545..526ce6a8f 100644 --- a/cloud2cloud-gateway/service/subscribeToResource.go +++ b/cloud2cloud-gateway/service/subscribeToResource.go @@ -28,7 +28,7 @@ func (rh *RequestHandler) makeSubscription(r *http.Request, typ store.Type, vali return res, http.StatusBadRequest, fmt.Errorf("invalid eventsurl(%w)", err) } - token, err := pkgHttp.ParseToken(r.Header.Get("Authorization")) + token, err := pkgHttp.GetToken(r.Header.Get("Authorization")) if err != nil { return res, http.StatusUnauthorized, fmt.Errorf("invalid accessToken(%w)", err) } diff --git a/http-gateway/serverMux/router.go b/http-gateway/serverMux/router.go index 58651a566..4983120dc 100644 --- a/http-gateway/serverMux/router.go +++ b/http-gateway/serverMux/router.go @@ -6,18 +6,19 @@ import ( router "github.com/gorilla/mux" "github.com/plgd-dev/hub/v2/pkg/net/grpc" - pktHttp "github.com/plgd-dev/hub/v2/pkg/net/http" + pkgHttp "github.com/plgd-dev/hub/v2/pkg/net/http" + pkgHttpJwt "github.com/plgd-dev/hub/v2/pkg/net/http/jwt" "google.golang.org/grpc/codes" ) // NewRouter creates router with default middlewares -func NewRouter(queryCaseInsensitive map[string]string, authInterceptor pktHttp.Interceptor, opts ...pktHttp.LogOpt) *router.Router { +func NewRouter(queryCaseInsensitive map[string]string, authInterceptor pkgHttpJwt.Interceptor, opts ...pkgHttp.LogOpt) *router.Router { r := router.NewRouter() - r.Use(pktHttp.CreateLoggingMiddleware(opts...)) - r.Use(pktHttp.CreateAuthMiddleware(authInterceptor, func(_ context.Context, w http.ResponseWriter, r *http.Request, err error) { + r.Use(pkgHttp.CreateLoggingMiddleware(opts...)) + r.Use(pkgHttp.CreateAuthMiddleware(authInterceptor, func(_ context.Context, w http.ResponseWriter, r *http.Request, err error) { WriteError(w, grpc.ForwardErrorf(codes.Unauthenticated, "cannot access to %v: %w", r.RequestURI, err)) })) - r.Use(pktHttp.CreateMakeQueryCaseInsensitiveMiddleware(queryCaseInsensitive, opts...)) - r.Use(pktHttp.CreateTrailSlashSuffixMiddleware(opts...)) + r.Use(pkgHttp.CreateMakeQueryCaseInsensitiveMiddleware(queryCaseInsensitive, opts...)) + r.Use(pkgHttp.CreateTrailSlashSuffixMiddleware(opts...)) return r } diff --git a/http-gateway/service/requestHandler.go b/http-gateway/service/requestHandler.go index dd53adccb..affab337a 100644 --- a/http-gateway/service/requestHandler.go +++ b/http-gateway/service/requestHandler.go @@ -16,6 +16,7 @@ import ( "github.com/plgd-dev/hub/v2/http-gateway/uri" "github.com/plgd-dev/hub/v2/pkg/log" pkgHttp "github.com/plgd-dev/hub/v2/pkg/net/http" + pkgHttpUri "github.com/plgd-dev/hub/v2/pkg/net/http/uri" "github.com/plgd-dev/hub/v2/pkg/security/openid" pkgStrings "github.com/plgd-dev/hub/v2/pkg/strings" ) @@ -33,7 +34,7 @@ func matchPrefixAndSplitURIPath(requestURI, prefix string) []string { if len(requestURI) == 0 { return nil } - v := pkgHttp.CanonicalHref(requestURI) + v := pkgHttpUri.CanonicalHref(requestURI) p := strings.TrimPrefix(v, prefix) // remove core prefix if p == v { return nil diff --git a/http-gateway/service/service.go b/http-gateway/service/service.go index bc96556e7..7959e14f6 100644 --- a/http-gateway/service/service.go +++ b/http-gateway/service/service.go @@ -13,7 +13,8 @@ import ( "github.com/plgd-dev/hub/v2/pkg/fsnotify" "github.com/plgd-dev/hub/v2/pkg/log" grpcClient "github.com/plgd-dev/hub/v2/pkg/net/grpc/client" - kitNetHttp "github.com/plgd-dev/hub/v2/pkg/net/http" + pkgHttp "github.com/plgd-dev/hub/v2/pkg/net/http" + pkgHttpJwt "github.com/plgd-dev/hub/v2/pkg/net/http/jwt" httpService "github.com/plgd-dev/hub/v2/pkg/net/http/service" otelClient "github.com/plgd-dev/hub/v2/pkg/opentelemetry/collector/client" "github.com/plgd-dev/hub/v2/pkg/security/jwt/validator" @@ -38,7 +39,7 @@ func New(ctx context.Context, config Config, fileWatcher *fsnotify.Watcher, logg return nil, fmt.Errorf("cannot create validator: %w", err) } - whiteList := []kitNetHttp.RequestMatcher{ + whiteList := []pkgHttpJwt.RequestMatcher{ { Method: http.MethodGet, URI: regexp.MustCompile(regexp.QuoteMeta(uri.APIWS) + `.*`), @@ -49,7 +50,7 @@ func New(ctx context.Context, config Config, fileWatcher *fsnotify.Watcher, logg }, } if config.UI.Enabled { - whiteList = append(whiteList, kitNetHttp.RequestMatcher{ + whiteList = append(whiteList, pkgHttpJwt.RequestMatcher{ Method: http.MethodGet, URI: regexp.MustCompile(AuthorizationWhiteListedEndpointsRegexp), }) @@ -58,7 +59,7 @@ func New(ctx context.Context, config Config, fileWatcher *fsnotify.Watcher, logg HTTPConnection: config.APIs.HTTP.Connection, HTTPServer: config.APIs.HTTP.Server, ServiceName: serviceName, - AuthRules: kitNetHttp.NewDefaultAuthorizationRules(uri.API), + AuthRules: pkgHttp.NewDefaultAuthorizationRules(uri.API), WhiteEndpointList: whiteList, FileWatcher: fileWatcher, Logger: logger, diff --git a/m2m-oauth-server/service/http/service.go b/m2m-oauth-server/service/http/service.go index a95b449fc..7b599f998 100644 --- a/m2m-oauth-server/service/http/service.go +++ b/m2m-oauth-server/service/http/service.go @@ -10,6 +10,7 @@ import ( "github.com/plgd-dev/hub/v2/pkg/fsnotify" "github.com/plgd-dev/hub/v2/pkg/log" pkgHttp "github.com/plgd-dev/hub/v2/pkg/net/http" + pkgHttpJwt "github.com/plgd-dev/hub/v2/pkg/net/http/jwt" httpService "github.com/plgd-dev/hub/v2/pkg/net/http/service" "github.com/plgd-dev/hub/v2/pkg/security/jwt/validator" "go.opentelemetry.io/otel/trace" @@ -23,7 +24,7 @@ type Service struct { // New parses configuration and creates new Server with provided store and bus func New(serviceName string, config Config, m2mOAuthServiceServer *grpcService.M2MOAuthServiceServer, validator *validator.Validator, fileWatcher *fsnotify.Watcher, logger log.Logger, tracerProvider trace.TracerProvider) (*Service, error) { - whiteList := []pkgHttp.RequestMatcher{ + whiteList := []pkgHttpJwt.RequestMatcher{ { Method: http.MethodGet, URI: regexp.MustCompile(regexp.QuoteMeta(uri.JWKs)), diff --git a/m2m-oauth-server/test/test.go b/m2m-oauth-server/test/test.go index 2cc2b08c1..41173f1b3 100644 --- a/m2m-oauth-server/test/test.go +++ b/m2m-oauth-server/test/test.go @@ -285,5 +285,5 @@ func GetJWTValidator(jwkURL string) *jwt.Validator { Transport: t, Timeout: time.Second * 10, } - return jwt.NewValidator(jwt.NewKeyCache(jwkURL, &client)) + return jwt.NewValidator(jwt.NewKeyCache(jwkURL, &client), log.Get()) } diff --git a/pkg/net/http/auth.go b/pkg/net/http/auth.go index 4dc64be5b..e4cd006a7 100644 --- a/pkg/net/http/auth.go +++ b/pkg/net/http/auth.go @@ -2,31 +2,24 @@ package http import ( "context" - "fmt" "net/http" "regexp" "strings" - "github.com/golang-jwt/jwt/v5" "github.com/plgd-dev/hub/v2/pkg/net/grpc" + pkgHttpJwt "github.com/plgd-dev/hub/v2/pkg/net/http/jwt" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" ) -type ( - ClaimsFunc = func(ctx context.Context, method, uri string) jwt.ClaimsValidator - OnUnauthorizedAccessFunc = func(ctx context.Context, w http.ResponseWriter, r *http.Request, err error) - Validator interface { - ParseWithClaims(token string, claims jwt.Claims) error - } -) +type OnUnauthorizedAccessFunc = func(ctx context.Context, w http.ResponseWriter, r *http.Request, err error) // NewDefaultAuthorizationRules returns a map of HTTP methods to a slice of AuthArgs. // The AuthArgs contain a URI field that is a regular expression matching the given apiPath // with any path suffix. This function is used to create default authorization rules for // HTTP methods GET, POST, DELETE, and PUT. -func NewDefaultAuthorizationRules(apiPath string) map[string][]AuthArgs { - return map[string][]AuthArgs{ +func NewDefaultAuthorizationRules(apiPath string) map[string][]pkgHttpJwt.AuthArgs { + return map[string][]pkgHttpJwt.AuthArgs{ http.MethodGet: { { URI: regexp.MustCompile(regexp.QuoteMeta(apiPath) + AnyPathSuffixRegex), @@ -52,58 +45,17 @@ func NewDefaultAuthorizationRules(apiPath string) map[string][]AuthArgs { const ( AnyPathSuffixRegex = `\/.*` - - bearerKey = "bearer" -) - -type key int - -const ( - authorizationKey key = 0 ) -func ctxWithToken(ctx context.Context, token string) context.Context { - if !strings.HasPrefix(strings.ToLower(token), bearerKey+" ") { - token = fmt.Sprintf("%s %s", bearerKey, token) - } - return context.WithValue(ctx, authorizationKey, token) -} - -func tokenFromCtx(ctx context.Context) (string, error) { - val := ctx.Value(authorizationKey) - if bearer, ok := val.(string); ok && strings.HasPrefix(strings.ToLower(bearer), bearerKey+" ") { - token := bearer[7:] - if token == "" { - return "", status.Errorf(codes.Unauthenticated, "invalid token") - } - return token, nil - } - return "", status.Errorf(codes.Unauthenticated, "token not found") -} - -func ParseToken(auth string) (string, error) { +func GetToken(auth string) (string, error) { if strings.HasPrefix(strings.ToLower(auth), "bearer ") { return auth[7:], nil } return "", status.Errorf(codes.Unauthenticated, "cannot parse bearer: prefix 'Bearer ' not found") } -func validateJWTWithValidator(validator Validator, claims ClaimsFunc) Interceptor { - return func(ctx context.Context, method, uri string) (context.Context, error) { - token, err := tokenFromCtx(ctx) - if err != nil { - return nil, err - } - err = validator.ParseWithClaims(token, claims(ctx, method, uri)) - if err != nil { - return nil, fmt.Errorf("invalid token: %w", err) - } - return ctx, nil - } -} - // CreateAuthMiddleware creates middleware for authorization -func CreateAuthMiddleware(authInterceptor Interceptor, onUnauthorizedAccessFunc OnUnauthorizedAccessFunc) func(next http.Handler) http.Handler { +func CreateAuthMiddleware(authInterceptor pkgHttpJwt.Interceptor, onUnauthorizedAccessFunc OnUnauthorizedAccessFunc) func(next http.Handler) http.Handler { return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { switch r.RequestURI { @@ -111,14 +63,14 @@ func CreateAuthMiddleware(authInterceptor Interceptor, onUnauthorizedAccessFunc next.ServeHTTP(w, r) default: token := r.Header.Get("Authorization") - ctx := ctxWithToken(r.Context(), token) + ctx := pkgHttpJwt.CtxWithToken(r.Context(), token) _, err := authInterceptor(ctx, r.Method, r.RequestURI) if err != nil { onUnauthorizedAccessFunc(ctx, w, r, err) return } - if rawToken, err := ParseToken(token); err == nil { + if rawToken, err := GetToken(token); err == nil { r = r.WithContext(grpc.CtxWithToken(r.Context(), rawToken)) } next.ServeHTTP(w, r) diff --git a/pkg/net/http/header.go b/pkg/net/http/header.go index a882347c2..f278f851c 100644 --- a/pkg/net/http/header.go +++ b/pkg/net/http/header.go @@ -3,10 +3,14 @@ package http const ( ApplicationProtoJsonContentType = "application/protojson" - CorrelationIDHeaderKey = "Correlation-Id" + AcceptHeaderKey = "Accept" + AuthorizationHeaderKey = "Authorization" + ConnectionHeaderKey = "Connection" ContentLengthHeaderKey = "Content-Length" ContentTypeHeaderKey = "Content-Type" ContentTypeOptionsHeaderKey = "X-Content-Type-Options" - AcceptHeaderKey = "Accept" + CorrelationIDHeaderKey = "Correlation-Id" ETagHeaderKey = "ETag" + + AuthorizationBearerPrefix = "Bearer " ) diff --git a/pkg/net/http/interceptor.go b/pkg/net/http/jwt/claims.go similarity index 50% rename from pkg/net/http/interceptor.go rename to pkg/net/http/jwt/claims.go index 08f6e425c..b35a59db1 100644 --- a/pkg/net/http/interceptor.go +++ b/pkg/net/http/jwt/claims.go @@ -1,41 +1,13 @@ -package http +package jwt import ( "context" "fmt" - "regexp" - "strings" "github.com/golang-jwt/jwt/v5" pkgJwt "github.com/plgd-dev/hub/v2/pkg/security/jwt" ) -type Interceptor = func(ctx context.Context, method, uri string) (context.Context, error) - -type AuthArgs struct { - URI *regexp.Regexp - Scopes []*regexp.Regexp -} - -// RequestMatcher allows request without token validation. -type RequestMatcher struct { - Method string - URI *regexp.Regexp -} - -// NewInterceptor authorizes HTTP request with validator. -func NewInterceptorWithValidator(validator Validator, auths map[string][]AuthArgs, whiteList ...RequestMatcher) Interceptor { - validateJWT := validateJWTWithValidator(validator, MakeClaimsFunc(auths)) - return func(ctx context.Context, method, uri string) (context.Context, error) { - for _, wa := range whiteList { - if strings.EqualFold(method, wa.Method) && wa.URI.MatchString(uri) { - return ctx, nil - } - } - return validateJWT(ctx, method, uri) - } -} - type DeniedClaimsError struct { jwt.MapClaims Err error diff --git a/pkg/net/http/jwt/interceptor.go b/pkg/net/http/jwt/interceptor.go new file mode 100644 index 000000000..6e501a486 --- /dev/null +++ b/pkg/net/http/jwt/interceptor.go @@ -0,0 +1,56 @@ +package jwt + +import ( + "context" + "fmt" + "regexp" + "strings" + + "github.com/golang-jwt/jwt/v5" +) + +type ( + ClaimsFunc = func(ctx context.Context, method, uri string) jwt.ClaimsValidator + Validator interface { + ParseWithClaims(token string, claims jwt.Claims) error + } + Interceptor = func(ctx context.Context, method, uri string) (context.Context, error) +) + +type AuthArgs struct { + URI *regexp.Regexp + Scopes []*regexp.Regexp +} + +// RequestMatcher allows request without token validation. +type RequestMatcher struct { + Method string + URI *regexp.Regexp +} + +func validateJWTWithValidator(validator Validator, claims ClaimsFunc) Interceptor { + return func(ctx context.Context, method, uri string) (context.Context, error) { + token, err := tokenFromCtx(ctx) + if err != nil { + return nil, err + } + err = validator.ParseWithClaims(token, claims(ctx, method, uri)) + if err != nil { + return nil, fmt.Errorf("invalid token: %w", err) + } + return ctx, nil + } +} + +// NewInterceptor authorizes HTTP request with validator. +func NewInterceptorWithValidator(validator Validator, auths map[string][]AuthArgs, whiteList ...RequestMatcher) Interceptor { + validateJWT := validateJWTWithValidator(validator, MakeClaimsFunc(auths)) + return func(ctx context.Context, method, uri string) (context.Context, error) { + for _, wa := range whiteList { + if strings.EqualFold(method, wa.Method) && wa.URI.MatchString(uri) { + return ctx, nil + } + } + return validateJWT(ctx, method, uri) + } +} diff --git a/pkg/net/http/jwt/token.go b/pkg/net/http/jwt/token.go new file mode 100644 index 000000000..a6ff38606 --- /dev/null +++ b/pkg/net/http/jwt/token.go @@ -0,0 +1,37 @@ +package jwt + +import ( + "context" + "fmt" + "strings" + + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" +) + +type key int + +const ( + authorizationKey key = 0 + + bearerKey = "bearer" +) + +func CtxWithToken(ctx context.Context, token string) context.Context { + if !strings.HasPrefix(strings.ToLower(token), bearerKey+" ") { + token = fmt.Sprintf("%s %s", bearerKey, token) + } + return context.WithValue(ctx, authorizationKey, token) +} + +func tokenFromCtx(ctx context.Context) (string, error) { + val := ctx.Value(authorizationKey) + if bearer, ok := val.(string); ok && strings.HasPrefix(strings.ToLower(bearer), bearerKey+" ") { + token := bearer[7:] + if token == "" { + return "", status.Errorf(codes.Unauthenticated, "invalid token") + } + return token, nil + } + return "", status.Errorf(codes.Unauthenticated, "token not found") +} diff --git a/pkg/net/http/auth_test.go b/pkg/net/http/jwt/token_test.go similarity index 62% rename from pkg/net/http/auth_test.go rename to pkg/net/http/jwt/token_test.go index 7ab8f571d..ac72b6027 100644 --- a/pkg/net/http/auth_test.go +++ b/pkg/net/http/jwt/token_test.go @@ -1,4 +1,4 @@ -package http +package jwt import ( "context" @@ -9,15 +9,15 @@ import ( func TestCtxWithToken(t *testing.T) { ctx := context.Background() - token, err := tokenFromCtx(ctxWithToken(ctx, "a")) + token, err := tokenFromCtx(CtxWithToken(ctx, "a")) require.NoError(t, err) require.Equal(t, "a", token) - token, err = tokenFromCtx(ctxWithToken(ctx, bearerKey+" b")) + token, err = tokenFromCtx(CtxWithToken(ctx, bearerKey+" b")) require.NoError(t, err) require.Equal(t, "b", token) - token, err = tokenFromCtx(ctxWithToken(ctx, "Bearer c")) + token, err = tokenFromCtx(CtxWithToken(ctx, "Bearer c")) require.NoError(t, err) require.Equal(t, "c", token) } diff --git a/pkg/net/http/service/config.go b/pkg/net/http/service/config.go index cdde59691..5ee7214b5 100644 --- a/pkg/net/http/service/config.go +++ b/pkg/net/http/service/config.go @@ -7,7 +7,7 @@ import ( "github.com/plgd-dev/hub/v2/pkg/config" "github.com/plgd-dev/hub/v2/pkg/fsnotify" "github.com/plgd-dev/hub/v2/pkg/log" - kitNetHttp "github.com/plgd-dev/hub/v2/pkg/net/http" + pkgHttpJwt "github.com/plgd-dev/hub/v2/pkg/net/http/jwt" "github.com/plgd-dev/hub/v2/pkg/net/http/server" "github.com/plgd-dev/hub/v2/pkg/net/listener" "github.com/plgd-dev/hub/v2/pkg/security/jwt/validator" @@ -21,8 +21,8 @@ type Config struct { ServiceName string FileWatcher *fsnotify.Watcher Logger log.Logger - WhiteEndpointList []kitNetHttp.RequestMatcher - AuthRules map[string][]kitNetHttp.AuthArgs + WhiteEndpointList []pkgHttpJwt.RequestMatcher + AuthRules map[string][]pkgHttpJwt.AuthArgs TraceProvider trace.TracerProvider Validator *validator.Validator QueryCaseInsensitive map[string]string diff --git a/pkg/net/http/service/service.go b/pkg/net/http/service/service.go index d23257265..a2aef07c3 100644 --- a/pkg/net/http/service/service.go +++ b/pkg/net/http/service/service.go @@ -9,7 +9,8 @@ import ( "github.com/gorilla/mux" "github.com/plgd-dev/hub/v2/http-gateway/serverMux" - kitNetHttp "github.com/plgd-dev/hub/v2/pkg/net/http" + pkgHttp "github.com/plgd-dev/hub/v2/pkg/net/http" + pkgHttpJwt "github.com/plgd-dev/hub/v2/pkg/net/http/jwt" "github.com/plgd-dev/hub/v2/pkg/net/listener" ) @@ -30,12 +31,12 @@ func New(config Config) (*Service, error) { } router := mux.NewRouter() - auth := kitNetHttp.NewInterceptorWithValidator(config.Validator, config.AuthRules, config.WhiteEndpointList...) - r0 := serverMux.NewRouter(config.QueryCaseInsensitive, auth, kitNetHttp.WithLogger(config.Logger)) + auth := pkgHttpJwt.NewInterceptorWithValidator(config.Validator, config.AuthRules, config.WhiteEndpointList...) + r0 := serverMux.NewRouter(config.QueryCaseInsensitive, auth, pkgHttp.WithLogger(config.Logger)) r0.PathPrefix("/").Handler(router) httpServer := http.Server{ - Handler: kitNetHttp.OpenTelemetryNewHandler(r0, config.ServiceName, config.TraceProvider), + Handler: pkgHttp.OpenTelemetryNewHandler(r0, config.ServiceName, config.TraceProvider), ReadTimeout: config.HTTPServer.ReadTimeout, ReadHeaderTimeout: config.HTTPServer.ReadHeaderTimeout, WriteTimeout: config.HTTPServer.WriteTimeout, diff --git a/pkg/net/http/toUrlString.go b/pkg/net/http/toUrlString.go deleted file mode 100644 index c35145ba2..000000000 --- a/pkg/net/http/toUrlString.go +++ /dev/null @@ -1,13 +0,0 @@ -package http - -import "net/url" - -// ToURLString convert scheme, host, path to escaped url. -func ToURLString(scheme string, host string, path string) string { - url := url.URL{ - Scheme: scheme, - Host: host, - Path: path, - } - return url.String() -} diff --git a/pkg/net/http/toUrlString_test.go b/pkg/net/http/toUrlString_test.go deleted file mode 100644 index ee7f74e9f..000000000 --- a/pkg/net/http/toUrlString_test.go +++ /dev/null @@ -1,45 +0,0 @@ -package http - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestToURLString(t *testing.T) { - type args struct { - scheme string - host string - path string - } - tests := []struct { - name string - args args - want string - }{ - { - name: "a://b/c", - args: args{ - scheme: "a", - host: "b", - path: "c", - }, - want: "a://b/c", - }, - { - name: "a://b/%", - args: args{ - scheme: "a", - host: "b", - path: "%", - }, - want: "a://b/%25", - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got := ToURLString(tt.args.scheme, tt.args.host, tt.args.path) - assert.Equal(t, tt.want, got) - }) - } -} diff --git a/pkg/net/http/canonicalHref.go b/pkg/net/http/uri/canonicalHref.go similarity index 51% rename from pkg/net/http/canonicalHref.go rename to pkg/net/http/uri/canonicalHref.go index c32bc8e62..ea1cf849b 100644 --- a/pkg/net/http/canonicalHref.go +++ b/pkg/net/http/uri/canonicalHref.go @@ -1,4 +1,4 @@ -package http +package uri import ( "regexp" @@ -7,10 +7,22 @@ import ( // CanonicalHref always lead by "/" func CanonicalHref(href string) string { + p := CanonicalURI(href) + p = strings.TrimLeft(p, "/") + return "/" + p +} + +func CanonicalURI(uri string) string { + var schema string + href := uri + components := strings.SplitN(uri, "://", 2) + if len(components) > 1 { + schema = components[0] + "://" + href = components[1] + } + backslash := regexp.MustCompile(`\/+`) p := backslash.ReplaceAllString(href, "/") - p = strings.TrimLeft(p, "/") p = strings.TrimRight(p, "/") - - return "/" + p + return schema + p } diff --git a/pkg/net/http/canonicalHref_test.go b/pkg/net/http/uri/canonicalHref_test.go similarity index 78% rename from pkg/net/http/canonicalHref_test.go rename to pkg/net/http/uri/canonicalHref_test.go index a58bcdea7..ebcb06a77 100644 --- a/pkg/net/http/canonicalHref_test.go +++ b/pkg/net/http/uri/canonicalHref_test.go @@ -1,6 +1,10 @@ -package http +package uri_test -import "testing" +import ( + "testing" + + "github.com/plgd-dev/hub/v2/pkg/net/http/uri" +) func TestCanonicalHref(t *testing.T) { type args struct { @@ -35,7 +39,7 @@ func TestCanonicalHref(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if got := CanonicalHref(tt.args.href); got != tt.want { + if got := uri.CanonicalHref(tt.args.href); got != tt.want { t.Errorf("CanonicalHref() = %v, want %v", got, tt.want) } }) diff --git a/pkg/security/jwt/claims.go b/pkg/security/jwt/claims.go index 2e8aa9300..f198d744f 100644 --- a/pkg/security/jwt/claims.go +++ b/pkg/security/jwt/claims.go @@ -6,6 +6,7 @@ import ( "strings" "github.com/golang-jwt/jwt/v5" + pkgHttpUri "github.com/plgd-dev/hub/v2/pkg/net/http/uri" pkgStrings "github.com/plgd-dev/hub/v2/pkg/strings" ) @@ -158,3 +159,23 @@ func ParseToken(token string) (Claims, error) { } return claims, nil } + +func getIssuer(token *jwt.Token) (string, error) { + if token == nil { + return "", ErrMissingToken + } + if token.Claims == nil { + return "", ErrMissingClaims + } + + switch claims := token.Claims.(type) { + case interface{ GetIssuer() (string, error) }: + issuer, err := claims.GetIssuer() + if err != nil { + return "", ErrMissingIssuer + } + return pkgHttpUri.CanonicalURI(issuer), nil + default: + return "", fmt.Errorf("unsupported type %T", token.Claims) + } +} diff --git a/pkg/security/jwt/multiJwk.go b/pkg/security/jwt/multiJwk.go index ba2f88311..ba0780509 100644 --- a/pkg/security/jwt/multiJwk.go +++ b/pkg/security/jwt/multiJwk.go @@ -13,6 +13,7 @@ import ( var ( ErrMissingClaims = errors.New("missing claims") ErrMissingIssuer = errors.New("missing issuer") + ErrMissingID = errors.New("missing jti") ) type MultiKeyCache struct { @@ -33,32 +34,6 @@ func (c *MultiKeyCache) GetOrFetchKey(token *jwt.Token) (interface{}, error) { return c.GetOrFetchKeyWithContext(context.Background(), token) } -func getIssuer(token *jwt.Token) (string, error) { - if token == nil { - return "", ErrMissingToken - } - if token.Claims == nil { - return "", ErrMissingClaims - } - - switch claims := token.Claims.(type) { - case jwt.MapClaims: - issuer, ok := claims["iss"].(string) - if !ok { - return "", ErrMissingIssuer - } - return strings.TrimSuffix(issuer, "/"), nil - case interface{ GetIssuer() (string, error) }: - issuer, err := claims.GetIssuer() - if err != nil { - return "", ErrMissingIssuer - } - return strings.TrimSuffix(issuer, "/"), nil - default: - return "", fmt.Errorf("unsupported type %T", token.Claims) - } -} - func checkForError(token *jwt.Token) error { if claims, ok := token.Claims.(interface { Error() string diff --git a/pkg/security/jwt/scopeClaims.go b/pkg/security/jwt/scopeClaims.go index c149c0cd5..e97f27b3a 100644 --- a/pkg/security/jwt/scopeClaims.go +++ b/pkg/security/jwt/scopeClaims.go @@ -52,6 +52,10 @@ func (c *ScopeClaims) GetAudience() (jwt.ClaimStrings, error) { return Claims(*c).GetAudience() } +func (c *ScopeClaims) GetID() (string, error) { + return Claims(*c).GetID() +} + func (c *ScopeClaims) Validate() error { v := Claims(*c) rs, ok := v[PlgdRequiredScope] diff --git a/pkg/security/jwt/validator.go b/pkg/security/jwt/validator.go index 2eb887af2..f7edc4601 100644 --- a/pkg/security/jwt/validator.go +++ b/pkg/security/jwt/validator.go @@ -6,6 +6,7 @@ import ( "fmt" "github.com/golang-jwt/jwt/v5" + "github.com/plgd-dev/hub/v2/pkg/log" ) type KeyCacheI interface { @@ -14,7 +15,8 @@ type KeyCacheI interface { } type Validator struct { - keys KeyCacheI + keys KeyCacheI + logger log.Logger } var ( @@ -22,8 +24,11 @@ var ( ErrCannotParseToken = errors.New("could not parse token") ) -func NewValidator(keyCache KeyCacheI) *Validator { - return &Validator{keys: keyCache} +func NewValidator(keyCache KeyCacheI, logger log.Logger) *Validator { + return &Validator{ + keys: keyCache, + logger: logger, + } } func errParseToken(err error) error { @@ -55,7 +60,6 @@ func (v *Validator) ParseWithContext(ctx context.Context, token string) (jwt.Map if !ok { return nil, errParseTokenInvalidClaimsType(t) } - return c, nil } diff --git a/pkg/security/jwt/validator/validator.go b/pkg/security/jwt/validator/validator.go index 87305d9c9..a4fd93afc 100644 --- a/pkg/security/jwt/validator/validator.go +++ b/pkg/security/jwt/validator/validator.go @@ -11,6 +11,7 @@ import ( "github.com/plgd-dev/hub/v2/pkg/fsnotify" "github.com/plgd-dev/hub/v2/pkg/log" "github.com/plgd-dev/hub/v2/pkg/net/http/client" + pkgHttpUri "github.com/plgd-dev/hub/v2/pkg/net/http/uri" jwtValidator "github.com/plgd-dev/hub/v2/pkg/security/jwt" "github.com/plgd-dev/hub/v2/pkg/security/openid" "go.opentelemetry.io/otel/trace" @@ -43,18 +44,18 @@ func (v *Validator) GetParser() *jwtValidator.Validator { type GetOpenIDConfigurationFunc func(ctx context.Context, c *http.Client, authority string) (openid.Config, error) type Options struct { - GetOpenIDConfiguration GetOpenIDConfigurationFunc + getOpenIDConfiguration GetOpenIDConfigurationFunc } func WithGetOpenIDConfiguration(f GetOpenIDConfigurationFunc) func(o *Options) { return func(o *Options) { - o.GetOpenIDConfiguration = f + o.getOpenIDConfiguration = f } } func New(ctx context.Context, config Config, fileWatcher *fsnotify.Watcher, logger log.Logger, tracerProvider trace.TracerProvider, opts ...func(o *Options)) (*Validator, error) { options := Options{ - GetOpenIDConfiguration: openid.GetConfiguration, + getOpenIDConfiguration: openid.GetConfiguration, } for _, o := range opts { o(&options) @@ -72,24 +73,25 @@ func New(ctx context.Context, config Config, fileWatcher *fsnotify.Watcher, logg ctx2, cancel := context.WithTimeout(ctx, authority.HTTP.Timeout) defer cancel() - if options.GetOpenIDConfiguration == nil { + if options.getOpenIDConfiguration == nil { return nil, errors.New("GetOpenIDConfiguration is nil") } - openIDCfg, err := options.GetOpenIDConfiguration(ctx2, httpClient.HTTP(), authority.Authority) + openIDCfg, err := options.getOpenIDConfiguration(ctx2, httpClient.HTTP(), authority.Authority) if err != nil { onClose.Execute() httpClient.Close() return nil, fmt.Errorf("cannot get openId configuration: %w", err) } onClose.AddFunc(httpClient.Close) - keys.Add(openIDCfg.Issuer, openIDCfg.JWKSURL, httpClient.HTTP()) + issuer := pkgHttpUri.CanonicalURI(openIDCfg.Issuer) + keys.Add(issuer, openIDCfg.JWKSURL, httpClient.HTTP()) openIDConfigurations = append(openIDConfigurations, openIDCfg) } return &Validator{ openIDConfigurations: openIDConfigurations, - validator: jwtValidator.NewValidator(keys), + validator: jwtValidator.NewValidator(keys, logger), audience: config.Audience, }, nil } diff --git a/test/oauth-server/service/httpApi.go b/test/oauth-server/service/httpApi.go index 22c0b0956..11e299b89 100644 --- a/test/oauth-server/service/httpApi.go +++ b/test/oauth-server/service/httpApi.go @@ -12,7 +12,7 @@ import ( "github.com/plgd-dev/go-coap/v3/pkg/cache" "github.com/plgd-dev/go-coap/v3/pkg/runner/periodic" "github.com/plgd-dev/hub/v2/pkg/log" - kitHttp "github.com/plgd-dev/hub/v2/pkg/net/http" + pkgHttp "github.com/plgd-dev/hub/v2/pkg/net/http" pkgJwt "github.com/plgd-dev/hub/v2/pkg/security/jwt" "github.com/plgd-dev/hub/v2/test/oauth-server/uri" ) @@ -65,7 +65,7 @@ func NewRequestHandler(ctx context.Context, config *Config, idTokenKey *rsa.Priv // NewHTTP returns HTTP handler func NewHTTP(requestHandler *RequestHandler, logger log.Logger) http.Handler { r := router.NewRouter() - r.Use(kitHttp.CreateLoggingMiddleware(kitHttp.WithLogger(logger))) + r.Use(pkgHttp.CreateLoggingMiddleware(pkgHttp.WithLogger(logger))) r.StrictSlash(true) // get JWKs diff --git a/test/oauth-server/test/test.go b/test/oauth-server/test/test.go index e91be0e8c..4e172037b 100644 --- a/test/oauth-server/test/test.go +++ b/test/oauth-server/test/test.go @@ -246,7 +246,7 @@ func GetJWTValidator(jwkURL string) *jwt.Validator { Transport: t, Timeout: time.Second * 10, } - return jwt.NewValidator(jwt.NewKeyCache(jwkURL, &client)) + return jwt.NewValidator(jwt.NewKeyCache(jwkURL, &client), log.Get()) } func GetAuthorizationCode(t require.TestingT, authServerHost, clientID, deviceID, scopes string) string { From 5a9998dc6a58341dfc6df0cbaa441d5176164257 Mon Sep 17 00:00:00 2001 From: Daniel Adam Date: Tue, 23 Jul 2024 19:17:58 +0200 Subject: [PATCH 26/31] security: check whether token is trusted after validation --- .../service/cleanDatabase_test.go | 2 +- .../service/subscribeToResource_test.go | 2 +- .../service/devicesStatusUpdater_test.go | 4 +- coap-gateway/service/mem_test.go | 2 +- .../observation/deviceObserver_test.go | 2 +- .../service/observation/observation_test.go | 2 +- coap-gateway/service/reconnect_test.go | 19 +-- coap-gateway/service/refreshToken_test.go | 3 +- coap-gateway/service/service_test.go | 4 +- coap-gateway/service/utils_test.go | 3 +- grpc-gateway/pb/devices.go | 8 + grpc-gateway/service/getResources_test.go | 96 +++++++++-- .../service/subscribeToEvents_test.go | 31 ++-- .../service/cancelPendingCommands_test.go | 4 +- http-gateway/service/createResource_test.go | 3 +- http-gateway/service/deleteDevices_test.go | 4 +- http-gateway/service/deleteResource_test.go | 6 +- .../service/getDevicePendingCommands_test.go | 37 ++-- .../service/getDeviceResourceLinks_test.go | 4 +- .../service/getDeviceResources_test.go | 4 +- http-gateway/service/getDevice_test.go | 4 +- .../service/getDevicesMetadata_test.go | 4 +- http-gateway/service/getDevices_test.go | 4 +- http-gateway/service/getEvents_test.go | 4 +- .../service/getHubConfiguration_test.go | 6 +- .../service/getPendingCommands_test.go | 37 ++-- .../service/getPendingMetadataUpdates_test.go | 36 ++-- http-gateway/service/getResourceLinks_test.go | 4 +- .../getResourcePendingCommands_test.go | 36 ++-- http-gateway/service/getResource_test.go | 4 +- http-gateway/service/getResources_test.go | 4 +- http-gateway/service/getThings.go | 13 +- http-gateway/service/getThings_test.go | 31 +++- .../service/subscribeToEvents_test.go | 4 +- .../service/updateDeviceMetadata_test.go | 5 +- http-gateway/service/updateResource_test.go | 3 +- identity-store/client/ownerCache_test.go | 8 +- .../service/http/createToken_test.go | 3 +- .../service/http/getTokens_test.go | 5 +- m2m-oauth-server/service/service.go | 4 +- m2m-oauth-server/test/http.go | 38 +++++ m2m-oauth-server/test/service.go | 5 - pkg/net/grpc/auth.go | 4 +- pkg/net/http/jwt/interceptor.go | 4 +- pkg/net/http/jwt/token.go | 13 ++ pkg/net/http/loggingMiddleware.go | 11 +- pkg/net/http/pb/protojson.go | 83 +++++++++ pkg/security/jwt/claims.go | 19 +++ pkg/security/jwt/validator.go | 160 +++++++++++++++++- pkg/security/jwt/validator/validator.go | 24 ++- pkg/security/jwt/validator_test.go | 8 +- resource-aggregate/events/resourceChanged.go | 4 + resource-aggregate/service/service_test.go | 21 +-- .../service/getPendingCommands_test.go | 32 ++-- .../service/http/createCondition_test.go | 5 +- .../service/http/createConfiguration_test.go | 5 +- .../http/deleteAppliedConfigurations_test.go | 5 +- .../service/http/deleteConditions_test.go | 5 +- .../service/http/deleteConfigurations_test.go | 5 +- .../http/getAppliedConfigurations_test.go | 5 +- .../service/http/getConditions_test.go | 5 +- .../service/http/getConfigurations_test.go | 5 +- .../service/http/invokeConfiguration_test.go | 3 +- .../service/http/updateCondition_test.go | 5 +- .../service/http/updateConfiguration_test.go | 5 +- snippet-service/service/service_test.go | 3 +- test/config/config.go | 5 + test/http/unmarshal.go | 66 +------- test/iotivity-lite/service/offboard_test.go | 12 +- test/iotivity-lite/service/republish_test.go | 2 +- test/pb/pendingCommand.go | 39 ++--- test/service/service.go | 66 +++++--- 72 files changed, 750 insertions(+), 381 deletions(-) create mode 100644 m2m-oauth-server/test/http.go create mode 100644 pkg/net/http/pb/protojson.go diff --git a/certificate-authority/service/cleanDatabase_test.go b/certificate-authority/service/cleanDatabase_test.go index 29ba16dbb..afe8d2c87 100644 --- a/certificate-authority/service/cleanDatabase_test.go +++ b/certificate-authority/service/cleanDatabase_test.go @@ -29,7 +29,7 @@ func TestCertificateAuthorityServerCleanUpSigningRecords(t *testing.T) { cfg.Clients.Storage.CleanUpRecords = "*/1 * * * * *" fmt.Printf("%v\n\n", test.MakeConfig(t)) - shutDown := testService.SetUpServices(context.Background(), t, testService.SetUpServicesCertificateAuthority|testService.SetUpServicesOAuth, testService.WithCAConfig(cfg)) + shutDown := testService.SetUpServices(context.Background(), t, testService.SetUpServicesCertificateAuthority|testService.SetUpServicesOAuth|testService.SetUpServicesMachine2MachineOAuth, testService.WithCAConfig(cfg)) defer shutDown() storeDB, closeStore := test.NewStore(t) diff --git a/cloud2cloud-gateway/service/subscribeToResource_test.go b/cloud2cloud-gateway/service/subscribeToResource_test.go index b4cf32957..119b00754 100644 --- a/cloud2cloud-gateway/service/subscribeToResource_test.go +++ b/cloud2cloud-gateway/service/subscribeToResource_test.go @@ -71,7 +71,7 @@ func TestRequestHandlerSubscribeToResourceTokenTimeout(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), time.Minute) defer cancel() - services := service.SetUpServicesOAuth | service.SetUpServicesId | service.SetUpServicesCertificateAuthority | + services := service.SetUpServicesOAuth | service.SetUpServicesMachine2MachineOAuth | service.SetUpServicesId | service.SetUpServicesCertificateAuthority | service.SetUpServicesResourceAggregate | service.SetUpServicesResourceDirectory | service.SetUpServicesGrpcGateway | service.SetUpServicesCoapGateway tearDown := service.SetUpServices(ctx, t, services) diff --git a/coap-gateway/service/devicesStatusUpdater_test.go b/coap-gateway/service/devicesStatusUpdater_test.go index 02eba2127..3559eadab 100644 --- a/coap-gateway/service/devicesStatusUpdater_test.go +++ b/coap-gateway/service/devicesStatusUpdater_test.go @@ -21,6 +21,7 @@ import ( "github.com/plgd-dev/hub/v2/test/device" oauthService "github.com/plgd-dev/hub/v2/test/oauth-server/service" oauthTest "github.com/plgd-dev/hub/v2/test/oauth-server/test" + "github.com/plgd-dev/hub/v2/test/service" testService "github.com/plgd-dev/hub/v2/test/service" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -30,7 +31,8 @@ import ( func onboardDeviceAndGetDevice(ctx context.Context, t *testing.T, device device.Device, oauthCfg oauthService.Config, coapCfg coapService.Config, wait time.Duration) (*pb.Device, time.Time /*startOnboard*/, time.Duration /*delta*/) { oauthShutdown := oauthTest.New(t, oauthCfg) - servicesTeardown := testService.SetUpServices(context.Background(), t, testService.SetUpServicesCertificateAuthority|testService.SetUpServicesId|testService.SetUpServicesResourceAggregate|testService.SetUpServicesResourceDirectory|testService.SetUpServicesCoapGateway|testService.SetUpServicesGrpcGateway, testService.WithCOAPGWConfig(coapCfg)) + servicesTeardown := testService.SetUpServices(context.Background(), t, service.SetUpServicesMachine2MachineOAuth|testService.SetUpServicesCertificateAuthority|testService.SetUpServicesId|testService.SetUpServicesResourceAggregate| + testService.SetUpServicesResourceDirectory|testService.SetUpServicesCoapGateway|testService.SetUpServicesGrpcGateway, testService.WithCOAPGWConfig(coapCfg)) ctx = kitNetGrpc.CtxWithToken(ctx, oauthTest.GetDefaultAccessToken(t)) conn, err := grpc.NewClient(config.GRPC_GW_HOST, grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{ diff --git a/coap-gateway/service/mem_test.go b/coap-gateway/service/mem_test.go index 94a502d2b..6b667001a 100644 --- a/coap-gateway/service/mem_test.go +++ b/coap-gateway/service/mem_test.go @@ -179,7 +179,7 @@ func testDevices(t *testing.T, numDevices, numResources, expRSSInMB int, resourc require.NoError(t, err) rdConfig := rdTest.MakeConfig(&testingT{}) rdConfig.Clients.Eventstore.ProjectionCacheExpiration = time.Second * 2 - const services = service.SetUpServicesOAuth | service.SetUpServicesId | service.SetUpServicesResourceDirectory | + const services = service.SetUpServicesOAuth | service.SetUpServicesMachine2MachineOAuth | service.SetUpServicesId | service.SetUpServicesResourceDirectory | service.SetUpServicesGrpcGateway | service.SetUpServicesResourceAggregate rdCfgFile, err := os.CreateTemp("", "rd") require.NoError(t, err) diff --git a/coap-gateway/service/observation/deviceObserver_test.go b/coap-gateway/service/observation/deviceObserver_test.go index 54fd8882f..45563cc49 100644 --- a/coap-gateway/service/observation/deviceObserver_test.go +++ b/coap-gateway/service/observation/deviceObserver_test.go @@ -376,7 +376,7 @@ type ( func runTestDeviceObserverRegister(ctx context.Context, t *testing.T, deviceID string, expectedObserved, expectedRetrieved strings.Set, verifyHandler verifyHandlerFn, prepareHub, postHub actioneHubFn, requireBatchObserveEnabled bool) { // TODO: add test with expectedRetrieved - const services = service.SetUpServicesOAuth | service.SetUpServicesId | service.SetUpServicesResourceDirectory | + const services = service.SetUpServicesMachine2MachineOAuth | service.SetUpServicesOAuth | service.SetUpServicesId | service.SetUpServicesResourceDirectory | service.SetUpServicesGrpcGateway | service.SetUpServicesResourceAggregate isConfig := isTest.MakeConfig(t) diff --git a/coap-gateway/service/observation/observation_test.go b/coap-gateway/service/observation/observation_test.go index f8f88c16d..b32936596 100644 --- a/coap-gateway/service/observation/observation_test.go +++ b/coap-gateway/service/observation/observation_test.go @@ -44,7 +44,7 @@ func TestIsResourceObservableWithInterface(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), config.TEST_TIMEOUT) defer cancel() - const services = service.SetUpServicesOAuth | service.SetUpServicesId | service.SetUpServicesResourceDirectory | + const services = service.SetUpServicesMachine2MachineOAuth | service.SetUpServicesOAuth | service.SetUpServicesId | service.SetUpServicesResourceDirectory | service.SetUpServicesGrpcGateway tearDown := service.SetUpServices(ctx, t, services) defer tearDown() diff --git a/coap-gateway/service/reconnect_test.go b/coap-gateway/service/reconnect_test.go index da224abe4..9e03ac48f 100644 --- a/coap-gateway/service/reconnect_test.go +++ b/coap-gateway/service/reconnect_test.go @@ -15,11 +15,9 @@ import ( coapCodes "github.com/plgd-dev/go-coap/v3/message/codes" coapgwTest "github.com/plgd-dev/hub/v2/coap-gateway/test" "github.com/plgd-dev/hub/v2/coap-gateway/uri" - idTest "github.com/plgd-dev/hub/v2/identity-store/test" - raTest "github.com/plgd-dev/hub/v2/resource-aggregate/test" rdTest "github.com/plgd-dev/hub/v2/resource-directory/test" test "github.com/plgd-dev/hub/v2/test" - oauthTest "github.com/plgd-dev/hub/v2/test/oauth-server/test" + "github.com/plgd-dev/hub/v2/test/service" testService "github.com/plgd-dev/hub/v2/test/service" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -59,19 +57,12 @@ func TestReconnectNATSAndGrpcGateway(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), TestExchangeTimeout) defer cancel() testService.ClearDB(ctx, t) - oauthShutdown := oauthTest.SetUp(t) - auShutdown := idTest.SetUp(t) - raShutdown := raTest.SetUp(t) - rdShutdown := rdTest.SetUp(t) coapgwCfg := coapgwTest.MakeConfig(t) coapgwCfg.Log.DumpBody = true - gwShutdown := coapgwTest.New(t, coapgwCfg) - defer func() { - gwShutdown() - raShutdown() - auShutdown() - oauthShutdown() - }() + teardown := testService.SetUpServices(ctx, t, testService.SetUpServicesOAuth|service.SetUpServicesMachine2MachineOAuth|service.SetUpServicesMachine2MachineOAuth|testService.SetUpServicesCertificateAuthority|testService.SetUpServicesId|testService.SetUpServicesResourceAggregate| + testService.SetUpServicesCoapGateway, testService.WithCOAPGWConfig(coapgwCfg)) + defer teardown() + rdShutdown := rdTest.SetUp(t) co := testCoapDial(t, "", true, true, time.Now().Add(time.Minute)) if co == nil { diff --git a/coap-gateway/service/refreshToken_test.go b/coap-gateway/service/refreshToken_test.go index 351e60d1d..4af76770b 100644 --- a/coap-gateway/service/refreshToken_test.go +++ b/coap-gateway/service/refreshToken_test.go @@ -16,6 +16,7 @@ import ( "github.com/plgd-dev/hub/v2/pkg/net/listener" "github.com/plgd-dev/hub/v2/test/config" oauthTest "github.com/plgd-dev/hub/v2/test/oauth-server/test" + "github.com/plgd-dev/hub/v2/test/service" testService "github.com/plgd-dev/hub/v2/test/service" "github.com/stretchr/testify/require" ) @@ -96,7 +97,7 @@ func TestRefreshTokenWithOAuthNotWorking(t *testing.T) { oauthShutdown := oauthTest.New(t, cfg) coapgwCfg := coapgwTest.MakeConfig(t) coapgwCfg.APIs.COAP.Authorization.Providers[0].HTTP.Timeout = time.Second - shutdown := testService.SetUpServices(ctx, t, testService.SetUpServicesId|testService.SetUpServicesCoapGateway|testService.SetUpServicesResourceAggregate|testService.SetUpServicesResourceDirectory, testService.WithCOAPGWConfig(coapgwCfg)) + shutdown := testService.SetUpServices(ctx, t, service.SetUpServicesMachine2MachineOAuth|testService.SetUpServicesId|testService.SetUpServicesCoapGateway|testService.SetUpServicesResourceAggregate|testService.SetUpServicesResourceDirectory, testService.WithCOAPGWConfig(coapgwCfg)) defer shutdown() co := testCoapDial(t, "", true, true, time.Now().Add(time.Minute)) diff --git a/coap-gateway/service/service_test.go b/coap-gateway/service/service_test.go index c705725fa..42cc309ec 100644 --- a/coap-gateway/service/service_test.go +++ b/coap-gateway/service/service_test.go @@ -73,7 +73,7 @@ func TestServiceConfigWithDataScheme(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), config.TEST_TIMEOUT) defer cancel() - const services = service.SetUpServicesOAuth | service.SetUpServicesId | service.SetUpServicesResourceDirectory | + const services = service.SetUpServicesOAuth | service.SetUpServicesMachine2MachineOAuth | service.SetUpServicesId | service.SetUpServicesResourceDirectory | service.SetUpServicesGrpcGateway | service.SetUpServicesResourceAggregate tearDown := service.SetUpServices(ctx, t, services) defer tearDown() @@ -87,7 +87,7 @@ func TestShutdownServiceWithDeviceIssue627(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), config.TEST_TIMEOUT) defer cancel() - const services = service.SetUpServicesOAuth | service.SetUpServicesId | service.SetUpServicesResourceDirectory | + const services = service.SetUpServicesOAuth | service.SetUpServicesMachine2MachineOAuth | service.SetUpServicesId | service.SetUpServicesResourceDirectory | service.SetUpServicesGrpcGateway | service.SetUpServicesResourceAggregate tearDown := service.SetUpServices(ctx, t, services) defer tearDown() diff --git a/coap-gateway/service/utils_test.go b/coap-gateway/service/utils_test.go index eb7aad785..2a11aa712 100644 --- a/coap-gateway/service/utils_test.go +++ b/coap-gateway/service/utils_test.go @@ -479,7 +479,8 @@ func setUp(t *testing.T, coapgwCfgs ...service.Config) func() { if len(coapgwCfgs) > 0 { coapgwCfg = coapgwCfgs[0] } - return testService.SetUpServices(context.Background(), t, testService.SetUpServicesCertificateAuthority|testService.SetUpServicesOAuth|testService.SetUpServicesId|testService.SetUpServicesResourceAggregate|testService.SetUpServicesResourceDirectory|testService.SetUpServicesCoapGateway|testService.SetUpServicesGrpcGateway, testService.WithCOAPGWConfig(coapgwCfg)) + return testService.SetUpServices(context.Background(), t, testService.SetUpServicesMachine2MachineOAuth|testService.SetUpServicesCertificateAuthority|testService.SetUpServicesOAuth| + testService.SetUpServicesId|testService.SetUpServicesResourceAggregate|testService.SetUpServicesResourceDirectory|testService.SetUpServicesCoapGateway|testService.SetUpServicesGrpcGateway, testService.WithCOAPGWConfig(coapgwCfg)) } var ( diff --git a/grpc-gateway/pb/devices.go b/grpc-gateway/pb/devices.go index 586063f1a..478b1f603 100644 --- a/grpc-gateway/pb/devices.go +++ b/grpc-gateway/pb/devices.go @@ -2,6 +2,7 @@ package pb import ( "encoding/base64" + "slices" "strings" commands "github.com/plgd-dev/hub/v2/resource-aggregate/commands" @@ -94,3 +95,10 @@ func (f *ResourceIdFilter) ToString() string { } return sb.String() } + +func (r *Resource) Clone() *Resource { + return &Resource{ + Types: slices.Clone(r.GetTypes()), + Data: r.GetData().Clone(), + } +} diff --git a/grpc-gateway/service/getResources_test.go b/grpc-gateway/service/getResources_test.go index e16e45e11..e5debed6c 100644 --- a/grpc-gateway/service/getResources_test.go +++ b/grpc-gateway/service/getResources_test.go @@ -10,7 +10,9 @@ import ( "github.com/plgd-dev/device/v2/test/resource/types" "github.com/plgd-dev/hub/v2/grpc-gateway/pb" - kitNetGrpc "github.com/plgd-dev/hub/v2/pkg/net/grpc" + m2mOauthTest "github.com/plgd-dev/hub/v2/m2m-oauth-server/test" + pkgGrpc "github.com/plgd-dev/hub/v2/pkg/net/grpc" + pkgJwt "github.com/plgd-dev/hub/v2/pkg/security/jwt" "github.com/plgd-dev/hub/v2/resource-aggregate/commands" "github.com/plgd-dev/hub/v2/resource-aggregate/events" "github.com/plgd-dev/hub/v2/test" @@ -24,6 +26,25 @@ import ( "google.golang.org/grpc/credentials" ) +func getResources(ctx context.Context, c pb.GrpcGatewayClient, req *pb.GetResourcesRequest) ([]*pb.Resource, error) { + client, err := c.GetResources(ctx, req) + if err != nil { + return nil, err + } + values := make([]*pb.Resource, 0, 1) + for { + value, err := client.Recv() + if errors.Is(err, io.EOF) { + break + } + if err != nil { + return nil, err + } + values = append(values, value) + } + return values, nil +} + func TestRequestHandlerGetResources(t *testing.T) { deviceID := test.MustFindDeviceByName(test.TestDeviceName) @@ -32,7 +53,7 @@ func TestRequestHandlerGetResources(t *testing.T) { tearDown := service.SetUp(ctx, t) defer tearDown() - ctx = kitNetGrpc.CtxWithToken(ctx, oauthTest.GetDefaultAccessToken(t)) + ctx = pkgGrpc.CtxWithToken(ctx, oauthTest.GetDefaultAccessToken(t)) conn, err := grpc.NewClient(config.GRPC_GW_HOST, grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{ RootCAs: test.GetRootCertificatePool(t), @@ -180,17 +201,8 @@ func TestRequestHandlerGetResources(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - client, err := c.GetResources(ctx, tt.args.req) + values, err := getResources(ctx, c, tt.args.req) require.NoError(t, err) - values := make([]*pb.Resource, 0, 1) - for { - value, err := client.Recv() - if errors.Is(err, io.EOF) { - break - } - require.NoError(t, err) - values = append(values, value) - } if tt.cmpFn != nil { tt.cmpFn(t, tt.want, values) return @@ -199,3 +211,63 @@ func TestRequestHandlerGetResources(t *testing.T) { }) } } + +func TestRequestHandlerGetResourcesWithBlacklistedToken(t *testing.T) { + deviceID := test.MustFindDeviceByName(test.TestDeviceName) + + ctx, cancel := context.WithTimeout(context.Background(), config.TEST_TIMEOUT*100) + defer cancel() + + tearDown := service.SetUp(ctx, t) + defer tearDown() + validTokenStr := oauthTest.GetDefaultAccessToken(t) + ctxWithToken := pkgGrpc.CtxWithToken(ctx, validTokenStr) + + conn, err := grpc.NewClient(config.GRPC_GW_HOST, grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{ + RootCAs: test.GetRootCertificatePool(t), + }))) + require.NoError(t, err) + defer func() { + _ = conn.Close() + }() + c := pb.NewGrpcGatewayClient(conn) + + _, shutdownDevSim := test.OnboardDevSim(ctxWithToken, t, c, deviceID, config.ACTIVE_COAP_SCHEME+"://"+config.COAP_GW_HOST, test.GetAllBackendResourceLinks()) + defer shutdownDevSim() + + req := &pb.GetResourcesRequest{ResourceIdFilter: []*pb.ResourceIdFilter{{ResourceId: commands.NewResourceID(deviceID, test.TestResourceLightInstanceHref("1"))}}} + exp := &pb.Resource{ + Types: []string{types.CORE_LIGHT}, + Data: pbTest.MakeResourceChanged(t, deviceID, test.TestResourceLightInstanceHref("1"), test.TestResourceLightInstanceResourceTypes, "", + map[string]interface{}{ + "state": false, + "power": uint64(0), + "name": "Light", + }), + } + + tokenStr := m2mOauthTest.GetDefaultAccessToken(t) + values, err := getResources(pkgGrpc.CtxWithToken(ctx, tokenStr), c, req) + require.NoError(t, err) + require.NotEmpty(t, values) + pbTest.CmpResourceValues(t, []*pb.Resource{exp.Clone()}, values) + + // invalid token + _, err = getResources(pkgGrpc.CtxWithToken(ctx, "invalid"), c, req) + require.Error(t, err) + + // blacklist the token + token, err := pkgJwt.ParseToken(tokenStr) + require.NoError(t, err) + tokenID, err := token.GetID() + require.NoError(t, err) + m2mOauthTest.BlacklistTokens(ctx, t, []string{tokenID}, validTokenStr) + _, err = getResources(pkgGrpc.CtxWithToken(ctx, tokenStr), c, req) + require.ErrorContains(t, err, pkgJwt.ErrBlackListedToken.Error()) + + // non-blacklisted tokens should still work + values, err = getResources(pkgGrpc.CtxWithToken(ctx, validTokenStr), c, req) + require.NoError(t, err) + require.NotEmpty(t, values) + pbTest.CmpResourceValues(t, []*pb.Resource{exp.Clone()}, values) +} diff --git a/grpc-gateway/service/subscribeToEvents_test.go b/grpc-gateway/service/subscribeToEvents_test.go index 4c006c914..8233b93fc 100644 --- a/grpc-gateway/service/subscribeToEvents_test.go +++ b/grpc-gateway/service/subscribeToEvents_test.go @@ -13,14 +13,12 @@ import ( "github.com/plgd-dev/device/v2/schema/device" "github.com/plgd-dev/device/v2/schema/platform" "github.com/plgd-dev/go-coap/v3/message" - caService "github.com/plgd-dev/hub/v2/certificate-authority/test" coapgwTest "github.com/plgd-dev/hub/v2/coap-gateway/test" "github.com/plgd-dev/hub/v2/grpc-gateway/client" "github.com/plgd-dev/hub/v2/grpc-gateway/pb" "github.com/plgd-dev/hub/v2/grpc-gateway/service" grpcgwService "github.com/plgd-dev/hub/v2/grpc-gateway/test" isEvents "github.com/plgd-dev/hub/v2/identity-store/events" - idService "github.com/plgd-dev/hub/v2/identity-store/test" "github.com/plgd-dev/hub/v2/pkg/fsnotify" "github.com/plgd-dev/hub/v2/pkg/log" kitNetGrpc "github.com/plgd-dev/hub/v2/pkg/net/grpc" @@ -656,20 +654,19 @@ func TestRequestHandlerSubscribeForPendingCommands(t *testing.T) { defer cancel() hubTestService.ClearDB(ctx, t) - oauthShutdown := oauthTest.SetUp(t) - authShutdown := idService.SetUp(t) - raShutdown := raTest.SetUp(t) - rdShutdown := rdTest.SetUp(t) - grpcShutdown := grpcgwService.SetUp(t) - caShutdown := caService.SetUp(t) - secureGWShutdown := coapgwTest.SetUp(t) - defer caShutdown() - defer grpcShutdown() - defer rdShutdown() - defer raShutdown() - defer authShutdown() - defer oauthShutdown() + const services = hubTestService.SetUpServicesMachine2MachineOAuth | hubTestService.SetUpServicesOAuth | hubTestService.SetUpServicesId | hubTestService.SetUpServicesResourceAggregate | + hubTestService.SetUpServicesResourceDirectory | hubTestService.SetUpServicesCertificateAuthority | hubTestService.SetUpServicesGrpcGateway + tearDown := hubTestService.SetUpServices(ctx, t, services) + defer tearDown() + + deferedSecureGWShutdown := true + secureGWShutdown := coapgwTest.SetUp(t) + defer func() { + if deferedSecureGWShutdown { + secureGWShutdown() + } + }() ctx = kitNetGrpc.CtxWithToken(ctx, oauthTest.GetDefaultAccessToken(t)) @@ -688,6 +685,7 @@ func TestRequestHandlerSubscribeForPendingCommands(t *testing.T) { deviceID, shutdownDevSim := test.OnboardDevSim(ctx, t, c, deviceID, config.ACTIVE_COAP_SCHEME+"://"+config.COAP_GW_HOST, test.GetAllBackendResourceLinks()) defer shutdownDevSim() + deferedSecureGWShutdown = false secureGWShutdown() createFn := func(timeToLive time.Duration) { @@ -1003,7 +1001,8 @@ func TestCoAPGatewayServiceHeartbeat(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), time.Minute*3) defer cancel() - tearDown := hubTestService.SetUpServices(ctx, t, hubTestService.SetUpServicesCertificateAuthority|hubTestService.SetUpServicesGrpcGateway|hubTestService.SetUpServicesId|hubTestService.SetUpServicesResourceDirectory|hubTestService.SetUpServicesOAuth) + tearDown := hubTestService.SetUpServices(ctx, t, hubTestService.SetUpServicesMachine2MachineOAuth|hubTestService.SetUpServicesCertificateAuthority|hubTestService.SetUpServicesGrpcGateway| + hubTestService.SetUpServicesId|hubTestService.SetUpServicesResourceDirectory|hubTestService.SetUpServicesOAuth) defer tearDown() racfg := raTest.MakeConfig(t) diff --git a/http-gateway/service/cancelPendingCommands_test.go b/http-gateway/service/cancelPendingCommands_test.go index f6aa07fec..c7f850820 100644 --- a/http-gateway/service/cancelPendingCommands_test.go +++ b/http-gateway/service/cancelPendingCommands_test.go @@ -9,8 +9,8 @@ import ( httpgwTest "github.com/plgd-dev/hub/v2/http-gateway/test" "github.com/plgd-dev/hub/v2/http-gateway/uri" pkgHttp "github.com/plgd-dev/hub/v2/pkg/net/http" + pkgHttpPb "github.com/plgd-dev/hub/v2/pkg/net/http/pb" "github.com/plgd-dev/hub/v2/test/config" - httpTest "github.com/plgd-dev/hub/v2/test/http" oauthTest "github.com/plgd-dev/hub/v2/test/oauth-server/test" pbTest "github.com/plgd-dev/hub/v2/test/pb" "github.com/stretchr/testify/assert" @@ -23,7 +23,7 @@ func doPendingCommand(t *testing.T, request *http.Request) (*pb.CancelPendingCom _ = resp.Body.Close() }() var v pb.CancelPendingCommandsResponse - err := httpTest.Unmarshal(resp.StatusCode, resp.Body, &v) + err := pkgHttpPb.Unmarshal(resp.StatusCode, resp.Body, &v) return &v, resp.StatusCode, err } diff --git a/http-gateway/service/createResource_test.go b/http-gateway/service/createResource_test.go index be2f188e2..637f15831 100644 --- a/http-gateway/service/createResource_test.go +++ b/http-gateway/service/createResource_test.go @@ -17,6 +17,7 @@ import ( "github.com/plgd-dev/hub/v2/http-gateway/uri" kitNetGrpc "github.com/plgd-dev/hub/v2/pkg/net/grpc" pkgHttp "github.com/plgd-dev/hub/v2/pkg/net/http" + pkgHttpPb "github.com/plgd-dev/hub/v2/pkg/net/http/pb" "github.com/plgd-dev/hub/v2/test" "github.com/plgd-dev/hub/v2/test/config" httpTest "github.com/plgd-dev/hub/v2/test/http" @@ -214,7 +215,7 @@ func TestRequestHandler_CreateResource(t *testing.T) { assert.Equal(t, tt.wantHTTPCode, resp.StatusCode) var got pb.CreateResourceResponse - err = httpTest.Unmarshal(resp.StatusCode, resp.Body, &got) + err = pkgHttpPb.Unmarshal(resp.StatusCode, resp.Body, &got) if tt.wantErr { require.Error(t, err) assert.Equal(t, tt.wantErrCode.String(), status.Convert(err).Code().String()) diff --git a/http-gateway/service/deleteDevices_test.go b/http-gateway/service/deleteDevices_test.go index 0ac875b87..6dcdf9542 100644 --- a/http-gateway/service/deleteDevices_test.go +++ b/http-gateway/service/deleteDevices_test.go @@ -10,9 +10,9 @@ import ( httpgwTest "github.com/plgd-dev/hub/v2/http-gateway/test" "github.com/plgd-dev/hub/v2/http-gateway/uri" kitNetGrpc "github.com/plgd-dev/hub/v2/pkg/net/grpc" + pkgHttpPb "github.com/plgd-dev/hub/v2/pkg/net/http/pb" "github.com/plgd-dev/hub/v2/test" "github.com/plgd-dev/hub/v2/test/config" - httpTest "github.com/plgd-dev/hub/v2/test/http" oauthTest "github.com/plgd-dev/hub/v2/test/oauth-server/test" "github.com/plgd-dev/hub/v2/test/service" "github.com/stretchr/testify/assert" @@ -92,7 +92,7 @@ func TestRequestHandlerDeleteDevices(t *testing.T) { assert.Equal(t, tt.wantHTTPCode, resp.StatusCode) var got pb.DeleteDevicesResponse - err = httpTest.Unmarshal(resp.StatusCode, resp.Body, &got) + err = pkgHttpPb.Unmarshal(resp.StatusCode, resp.Body, &got) require.NoError(t, err) require.Equal(t, tt.want.GetDeviceIds(), got.GetDeviceIds()) }) diff --git a/http-gateway/service/deleteResource_test.go b/http-gateway/service/deleteResource_test.go index 5ca97d743..5a3bc73be 100644 --- a/http-gateway/service/deleteResource_test.go +++ b/http-gateway/service/deleteResource_test.go @@ -17,11 +17,11 @@ import ( "github.com/plgd-dev/hub/v2/http-gateway/uri" kitNetGrpc "github.com/plgd-dev/hub/v2/pkg/net/grpc" pkgHttp "github.com/plgd-dev/hub/v2/pkg/net/http" + pkgHttpPb "github.com/plgd-dev/hub/v2/pkg/net/http/pb" "github.com/plgd-dev/hub/v2/resource-aggregate/commands" "github.com/plgd-dev/hub/v2/resource-aggregate/events" "github.com/plgd-dev/hub/v2/test" "github.com/plgd-dev/hub/v2/test/config" - httpTest "github.com/plgd-dev/hub/v2/test/http" oauthTest "github.com/plgd-dev/hub/v2/test/oauth-server/test" pbTest "github.com/plgd-dev/hub/v2/test/pb" "github.com/plgd-dev/hub/v2/test/service" @@ -144,7 +144,7 @@ func TestRequestHandlerDeleteResource(t *testing.T) { assert.Equal(t, tt.wantHTTPCode, resp.StatusCode) var got pb.DeleteResourceResponse - err = httpTest.Unmarshal(resp.StatusCode, resp.Body, &got) + err = pkgHttpPb.Unmarshal(resp.StatusCode, resp.Body, &got) if tt.wantErr { require.Error(t, err) assert.Equal(t, tt.wantErrCode.String(), exCodes.Code(status.Convert(err).Code()).String()) @@ -254,7 +254,7 @@ func TestRequestHandlerBatchDeleteResource(t *testing.T) { assert.Equal(t, tt.wantHTTPCode, resp.StatusCode) var got pb.DeleteResourceResponse - err = httpTest.Unmarshal(resp.StatusCode, resp.Body, &got) + err = pkgHttpPb.Unmarshal(resp.StatusCode, resp.Body, &got) if tt.wantErr { require.Error(t, err) assert.Equal(t, tt.wantErrCode.String(), exCodes.Code(status.Convert(err).Code()).String()) diff --git a/http-gateway/service/getDevicePendingCommands_test.go b/http-gateway/service/getDevicePendingCommands_test.go index 28c906605..40715d1f8 100644 --- a/http-gateway/service/getDevicePendingCommands_test.go +++ b/http-gateway/service/getDevicePendingCommands_test.go @@ -12,22 +12,18 @@ import ( "github.com/plgd-dev/device/v2/schema/device" "github.com/plgd-dev/device/v2/schema/platform" "github.com/plgd-dev/go-coap/v3/message" - caService "github.com/plgd-dev/hub/v2/certificate-authority/test" coapgwTest "github.com/plgd-dev/hub/v2/coap-gateway/test" "github.com/plgd-dev/hub/v2/grpc-gateway/pb" - grpcgwService "github.com/plgd-dev/hub/v2/grpc-gateway/test" httpgwTest "github.com/plgd-dev/hub/v2/http-gateway/test" "github.com/plgd-dev/hub/v2/http-gateway/uri" - idService "github.com/plgd-dev/hub/v2/identity-store/test" + "github.com/plgd-dev/hub/v2/pkg/fn" kitNetGrpc "github.com/plgd-dev/hub/v2/pkg/net/grpc" pkgHttp "github.com/plgd-dev/hub/v2/pkg/net/http" + pkgHttpPb "github.com/plgd-dev/hub/v2/pkg/net/http/pb" "github.com/plgd-dev/hub/v2/resource-aggregate/commands" "github.com/plgd-dev/hub/v2/resource-aggregate/events" - raService "github.com/plgd-dev/hub/v2/resource-aggregate/test" - rdService "github.com/plgd-dev/hub/v2/resource-directory/test" "github.com/plgd-dev/hub/v2/test" "github.com/plgd-dev/hub/v2/test/config" - httpTest "github.com/plgd-dev/hub/v2/test/http" "github.com/plgd-dev/hub/v2/test/oauth-server/service" oauthTest "github.com/plgd-dev/hub/v2/test/oauth-server/test" pbTest "github.com/plgd-dev/hub/v2/test/pb" @@ -217,20 +213,20 @@ func TestRequestHandlerGetDevicePendingCommands(t *testing.T) { defer cancel() testService.ClearDB(ctx, t) - oauthShutdown := oauthTest.SetUp(t) - idShutdown := idService.SetUp(t) - raShutdown := raService.SetUp(t) - rdShutdown := rdService.SetUp(t) - grpcShutdown := grpcgwService.SetUp(t) - caShutdown := caService.SetUp(t) - secureGWShutdown := coapgwTest.SetUp(t) - defer caShutdown() - defer grpcShutdown() - defer rdShutdown() - defer raShutdown() - defer idShutdown() - defer oauthShutdown() + var closeFunc fn.FuncList + defer closeFunc.Execute() + tearDown := testService.SetUpServices(ctx, t, testService.SetUpServicesOAuth|testService.SetUpServicesMachine2MachineOAuth|testService.SetUpServicesId|testService.SetUpServicesResourceAggregate| + testService.SetUpServicesResourceDirectory|testService.SetUpServicesCertificateAuthority|testService.SetUpServicesGrpcGateway) + closeFunc.AddFunc(tearDown) + + deferedSecureGWShutdown := true + secureGWShutdown := coapgwTest.SetUp(t) + defer func() { + if deferedSecureGWShutdown { + secureGWShutdown() + } + }() shutdownHttp := httpgwTest.SetUp(t) defer shutdownHttp() @@ -250,6 +246,7 @@ func TestRequestHandlerGetDevicePendingCommands(t *testing.T) { deviceID, shutdownDevSim := test.OnboardDevSim(ctx, t, c, deviceID, config.ACTIVE_COAP_SCHEME+"://"+config.COAP_GW_HOST, test.GetAllBackendResourceLinks()) defer shutdownDevSim() + deferedSecureGWShutdown = false secureGWShutdown() createFn := func() { @@ -320,7 +317,7 @@ func TestRequestHandlerGetDevicePendingCommands(t *testing.T) { var values []*pb.PendingCommand for { var v pb.PendingCommand - err = httpTest.Unmarshal(resp.StatusCode, resp.Body, &v) + err = pkgHttpPb.Unmarshal(resp.StatusCode, resp.Body, &v) if errors.Is(err, io.EOF) { break } diff --git a/http-gateway/service/getDeviceResourceLinks_test.go b/http-gateway/service/getDeviceResourceLinks_test.go index 13a8a650c..474b9cd4e 100644 --- a/http-gateway/service/getDeviceResourceLinks_test.go +++ b/http-gateway/service/getDeviceResourceLinks_test.go @@ -17,11 +17,11 @@ import ( httpgwTest "github.com/plgd-dev/hub/v2/http-gateway/test" "github.com/plgd-dev/hub/v2/http-gateway/uri" kitNetGrpc "github.com/plgd-dev/hub/v2/pkg/net/grpc" + pkgHttpPb "github.com/plgd-dev/hub/v2/pkg/net/http/pb" "github.com/plgd-dev/hub/v2/resource-aggregate/commands" "github.com/plgd-dev/hub/v2/resource-aggregate/events" test "github.com/plgd-dev/hub/v2/test" "github.com/plgd-dev/hub/v2/test/config" - httpTest "github.com/plgd-dev/hub/v2/test/http" oauthService "github.com/plgd-dev/hub/v2/test/oauth-server/service" oauthTest "github.com/plgd-dev/hub/v2/test/oauth-server/test" pbTest "github.com/plgd-dev/hub/v2/test/pb" @@ -120,7 +120,7 @@ func TestRequestHandlerGetDeviceResourceLinks(t *testing.T) { var links []*events.ResourceLinksPublished for { var v events.ResourceLinksPublished - err = httpTest.Unmarshal(resp.StatusCode, resp.Body, &v) + err = pkgHttpPb.Unmarshal(resp.StatusCode, resp.Body, &v) if errors.Is(err, io.EOF) { break } diff --git a/http-gateway/service/getDeviceResources_test.go b/http-gateway/service/getDeviceResources_test.go index 9156ee151..35b2c95f9 100644 --- a/http-gateway/service/getDeviceResources_test.go +++ b/http-gateway/service/getDeviceResources_test.go @@ -19,11 +19,11 @@ import ( "github.com/plgd-dev/hub/v2/http-gateway/uri" kitNetGrpc "github.com/plgd-dev/hub/v2/pkg/net/grpc" pkgHttp "github.com/plgd-dev/hub/v2/pkg/net/http" + pkgHttpPb "github.com/plgd-dev/hub/v2/pkg/net/http/pb" "github.com/plgd-dev/hub/v2/resource-aggregate/commands" "github.com/plgd-dev/hub/v2/resource-aggregate/events" "github.com/plgd-dev/hub/v2/test" "github.com/plgd-dev/hub/v2/test/config" - httpTest "github.com/plgd-dev/hub/v2/test/http" oauthTest "github.com/plgd-dev/hub/v2/test/oauth-server/test" pbTest "github.com/plgd-dev/hub/v2/test/pb" "github.com/plgd-dev/hub/v2/test/service" @@ -182,7 +182,7 @@ func TestRequestHandlerGetDeviceResources(t *testing.T) { values := make([]*pb.Resource, 0, 1) for { var value pb.Resource - err = httpTest.Unmarshal(resp.StatusCode, resp.Body, &value) + err = pkgHttpPb.Unmarshal(resp.StatusCode, resp.Body, &value) if errors.Is(err, io.EOF) { break } diff --git a/http-gateway/service/getDevice_test.go b/http-gateway/service/getDevice_test.go index 3584b6062..a118f9df5 100644 --- a/http-gateway/service/getDevice_test.go +++ b/http-gateway/service/getDevice_test.go @@ -13,9 +13,9 @@ import ( httpgwTest "github.com/plgd-dev/hub/v2/http-gateway/test" "github.com/plgd-dev/hub/v2/http-gateway/uri" kitNetGrpc "github.com/plgd-dev/hub/v2/pkg/net/grpc" + pkgHttpPb "github.com/plgd-dev/hub/v2/pkg/net/http/pb" "github.com/plgd-dev/hub/v2/test" "github.com/plgd-dev/hub/v2/test/config" - httpTest "github.com/plgd-dev/hub/v2/test/http" oauthTest "github.com/plgd-dev/hub/v2/test/oauth-server/test" pbTest "github.com/plgd-dev/hub/v2/test/pb" "github.com/plgd-dev/hub/v2/test/service" @@ -93,7 +93,7 @@ func TestRequestHandlerGetDevice(t *testing.T) { devices := make([]*pb.Device, 0, 1) for { var dev pb.Device - err = httpTest.Unmarshal(resp.StatusCode, resp.Body, &dev) + err = pkgHttpPb.Unmarshal(resp.StatusCode, resp.Body, &dev) if errors.Is(err, io.EOF) { break } diff --git a/http-gateway/service/getDevicesMetadata_test.go b/http-gateway/service/getDevicesMetadata_test.go index d6dde4980..28d319add 100644 --- a/http-gateway/service/getDevicesMetadata_test.go +++ b/http-gateway/service/getDevicesMetadata_test.go @@ -13,11 +13,11 @@ import ( httpgwTest "github.com/plgd-dev/hub/v2/http-gateway/test" "github.com/plgd-dev/hub/v2/http-gateway/uri" kitNetGrpc "github.com/plgd-dev/hub/v2/pkg/net/grpc" + pkgHttpPb "github.com/plgd-dev/hub/v2/pkg/net/http/pb" "github.com/plgd-dev/hub/v2/resource-aggregate/commands" "github.com/plgd-dev/hub/v2/resource-aggregate/events" "github.com/plgd-dev/hub/v2/test" "github.com/plgd-dev/hub/v2/test/config" - httpTest "github.com/plgd-dev/hub/v2/test/http" oauthService "github.com/plgd-dev/hub/v2/test/oauth-server/service" oauthTest "github.com/plgd-dev/hub/v2/test/oauth-server/test" pbTest "github.com/plgd-dev/hub/v2/test/pb" @@ -146,7 +146,7 @@ func TestRequestHandlerGetDevicesMetadata(t *testing.T) { var values []*events.DeviceMetadataUpdated for { var value events.DeviceMetadataUpdated - err = httpTest.Unmarshal(resp.StatusCode, resp.Body, &value) + err = pkgHttpPb.Unmarshal(resp.StatusCode, resp.Body, &value) if errors.Is(err, io.EOF) { break } diff --git a/http-gateway/service/getDevices_test.go b/http-gateway/service/getDevices_test.go index f2c3fd248..67cd41d16 100644 --- a/http-gateway/service/getDevices_test.go +++ b/http-gateway/service/getDevices_test.go @@ -18,10 +18,10 @@ import ( "github.com/plgd-dev/hub/v2/http-gateway/uri" pkgGrpc "github.com/plgd-dev/hub/v2/pkg/net/grpc" pkgHttp "github.com/plgd-dev/hub/v2/pkg/net/http" + pkgHttpPb "github.com/plgd-dev/hub/v2/pkg/net/http/pb" "github.com/plgd-dev/hub/v2/resource-aggregate/commands" "github.com/plgd-dev/hub/v2/test" "github.com/plgd-dev/hub/v2/test/config" - httpTest "github.com/plgd-dev/hub/v2/test/http" oauthTest "github.com/plgd-dev/hub/v2/test/oauth-server/test" pbTest "github.com/plgd-dev/hub/v2/test/pb" "github.com/plgd-dev/hub/v2/test/service" @@ -155,7 +155,7 @@ func TestRequestHandlerGetDevices(t *testing.T) { var devices []*pb.Device for { var dev pb.Device - err = httpTest.Unmarshal(resp.StatusCode, resp.Body, &dev) + err = pkgHttpPb.Unmarshal(resp.StatusCode, resp.Body, &dev) if errors.Is(err, io.EOF) { break } diff --git a/http-gateway/service/getEvents_test.go b/http-gateway/service/getEvents_test.go index e2497c9e6..e29db1129 100644 --- a/http-gateway/service/getEvents_test.go +++ b/http-gateway/service/getEvents_test.go @@ -14,9 +14,9 @@ import ( "github.com/plgd-dev/hub/v2/http-gateway/uri" pkgGrpc "github.com/plgd-dev/hub/v2/pkg/net/grpc" pkgHttp "github.com/plgd-dev/hub/v2/pkg/net/http" + pkgHttpPb "github.com/plgd-dev/hub/v2/pkg/net/http/pb" "github.com/plgd-dev/hub/v2/test" "github.com/plgd-dev/hub/v2/test/config" - httpTest "github.com/plgd-dev/hub/v2/test/http" oauthTest "github.com/plgd-dev/hub/v2/test/oauth-server/test" pbTest "github.com/plgd-dev/hub/v2/test/pb" "github.com/plgd-dev/hub/v2/test/service" @@ -174,7 +174,7 @@ func TestRequestHandlerGetEvents(t *testing.T) { values := make([]*pb.GetEventsResponse, 0, 1) for { var value pb.GetEventsResponse - err = httpTest.Unmarshal(resp.StatusCode, resp.Body, &value) + err = pkgHttpPb.Unmarshal(resp.StatusCode, resp.Body, &value) if errors.Is(err, io.EOF) { break } diff --git a/http-gateway/service/getHubConfiguration_test.go b/http-gateway/service/getHubConfiguration_test.go index fba747935..41b9d40ed 100644 --- a/http-gateway/service/getHubConfiguration_test.go +++ b/http-gateway/service/getHubConfiguration_test.go @@ -9,9 +9,9 @@ import ( httpgwTest "github.com/plgd-dev/hub/v2/http-gateway/test" "github.com/plgd-dev/hub/v2/http-gateway/uri" pkgHttp "github.com/plgd-dev/hub/v2/pkg/net/http" + pkgHttpPb "github.com/plgd-dev/hub/v2/pkg/net/http/pb" rdTest "github.com/plgd-dev/hub/v2/resource-directory/test" "github.com/plgd-dev/hub/v2/test/config" - httpTest "github.com/plgd-dev/hub/v2/test/http" pbTest "github.com/plgd-dev/hub/v2/test/pb" "github.com/plgd-dev/hub/v2/test/service" "github.com/stretchr/testify/require" @@ -65,7 +65,7 @@ func TestRequestHandlerGetHubConfiguration(t *testing.T) { _ = resp.Body.Close() }() var got pb.HubConfigurationResponse - err := httpTest.Unmarshal(resp.StatusCode, resp.Body, &got) + err := pkgHttpPb.Unmarshal(resp.StatusCode, resp.Body, &got) require.NoError(t, err) pbTest.CmpHubConfigurationResponse(t, tt.want, &got) }) @@ -121,7 +121,7 @@ func TestRequestHandlerGetHubConfigurationWithoutM2MOAuthClient(t *testing.T) { _ = resp.Body.Close() }() var got pb.HubConfigurationResponse - err := httpTest.Unmarshal(resp.StatusCode, resp.Body, &got) + err := pkgHttpPb.Unmarshal(resp.StatusCode, resp.Body, &got) require.NoError(t, err) pbTest.CmpHubConfigurationResponse(t, tt.want, &got) }) diff --git a/http-gateway/service/getPendingCommands_test.go b/http-gateway/service/getPendingCommands_test.go index a39ca1c2f..f1ff93b8f 100644 --- a/http-gateway/service/getPendingCommands_test.go +++ b/http-gateway/service/getPendingCommands_test.go @@ -12,22 +12,18 @@ import ( "github.com/plgd-dev/device/v2/schema/device" "github.com/plgd-dev/device/v2/schema/platform" "github.com/plgd-dev/go-coap/v3/message" - caService "github.com/plgd-dev/hub/v2/certificate-authority/test" coapgwTest "github.com/plgd-dev/hub/v2/coap-gateway/test" "github.com/plgd-dev/hub/v2/grpc-gateway/pb" - grpcgwService "github.com/plgd-dev/hub/v2/grpc-gateway/test" httpgwTest "github.com/plgd-dev/hub/v2/http-gateway/test" "github.com/plgd-dev/hub/v2/http-gateway/uri" - idService "github.com/plgd-dev/hub/v2/identity-store/test" + "github.com/plgd-dev/hub/v2/pkg/fn" pkgGrpc "github.com/plgd-dev/hub/v2/pkg/net/grpc" pkgHttp "github.com/plgd-dev/hub/v2/pkg/net/http" + pkgHttpPb "github.com/plgd-dev/hub/v2/pkg/net/http/pb" "github.com/plgd-dev/hub/v2/resource-aggregate/commands" "github.com/plgd-dev/hub/v2/resource-aggregate/events" - raService "github.com/plgd-dev/hub/v2/resource-aggregate/test" - rdService "github.com/plgd-dev/hub/v2/resource-directory/test" "github.com/plgd-dev/hub/v2/test" "github.com/plgd-dev/hub/v2/test/config" - httpTest "github.com/plgd-dev/hub/v2/test/http" "github.com/plgd-dev/hub/v2/test/oauth-server/service" oauthTest "github.com/plgd-dev/hub/v2/test/oauth-server/test" pbTest "github.com/plgd-dev/hub/v2/test/pb" @@ -264,20 +260,20 @@ func TestRequestHandlerGetPendingCommands(t *testing.T) { defer cancel() testService.ClearDB(ctx, t) - oauthShutdown := oauthTest.SetUp(t) - idShutdown := idService.SetUp(t) - raShutdown := raService.SetUp(t) - rdShutdown := rdService.SetUp(t) - grpcShutdown := grpcgwService.SetUp(t) - caShutdown := caService.SetUp(t) - secureGWShutdown := coapgwTest.SetUp(t) - defer caShutdown() - defer grpcShutdown() - defer rdShutdown() - defer raShutdown() - defer idShutdown() - defer oauthShutdown() + var closeFunc fn.FuncList + defer closeFunc.Execute() + tearDown := testService.SetUpServices(ctx, t, testService.SetUpServicesOAuth|testService.SetUpServicesMachine2MachineOAuth|testService.SetUpServicesId|testService.SetUpServicesResourceAggregate| + testService.SetUpServicesResourceDirectory|testService.SetUpServicesCertificateAuthority|testService.SetUpServicesGrpcGateway) + closeFunc.AddFunc(tearDown) + + deferedSecureGWShutdown := true + secureGWShutdown := coapgwTest.SetUp(t) + defer func() { + if deferedSecureGWShutdown { + secureGWShutdown() + } + }() shutdownHttp := httpgwTest.SetUp(t) defer shutdownHttp() @@ -297,6 +293,7 @@ func TestRequestHandlerGetPendingCommands(t *testing.T) { deviceID, shutdownDevSim := test.OnboardDevSim(ctx, t, c, deviceID, config.ACTIVE_COAP_SCHEME+"://"+config.COAP_GW_HOST, test.GetAllBackendResourceLinks()) defer shutdownDevSim() + deferedSecureGWShutdown = false secureGWShutdown() createFn := func() { @@ -368,7 +365,7 @@ func TestRequestHandlerGetPendingCommands(t *testing.T) { var values []*pb.PendingCommand for { var v pb.PendingCommand - err = httpTest.Unmarshal(resp.StatusCode, resp.Body, &v) + err = pkgHttpPb.Unmarshal(resp.StatusCode, resp.Body, &v) if errors.Is(err, io.EOF) { break } diff --git a/http-gateway/service/getPendingMetadataUpdates_test.go b/http-gateway/service/getPendingMetadataUpdates_test.go index a53297447..ab1eb4ead 100644 --- a/http-gateway/service/getPendingMetadataUpdates_test.go +++ b/http-gateway/service/getPendingMetadataUpdates_test.go @@ -12,22 +12,18 @@ import ( "github.com/plgd-dev/device/v2/schema/device" "github.com/plgd-dev/device/v2/schema/platform" "github.com/plgd-dev/go-coap/v3/message" - caService "github.com/plgd-dev/hub/v2/certificate-authority/test" coapgwTest "github.com/plgd-dev/hub/v2/coap-gateway/test" "github.com/plgd-dev/hub/v2/grpc-gateway/pb" - grpcgwService "github.com/plgd-dev/hub/v2/grpc-gateway/test" httpgwTest "github.com/plgd-dev/hub/v2/http-gateway/test" "github.com/plgd-dev/hub/v2/http-gateway/uri" - idService "github.com/plgd-dev/hub/v2/identity-store/test" + "github.com/plgd-dev/hub/v2/pkg/fn" pkgGrpc "github.com/plgd-dev/hub/v2/pkg/net/grpc" pkgHttp "github.com/plgd-dev/hub/v2/pkg/net/http" + pkgHttpPb "github.com/plgd-dev/hub/v2/pkg/net/http/pb" "github.com/plgd-dev/hub/v2/resource-aggregate/commands" "github.com/plgd-dev/hub/v2/resource-aggregate/events" - raService "github.com/plgd-dev/hub/v2/resource-aggregate/test" - rdService "github.com/plgd-dev/hub/v2/resource-directory/test" "github.com/plgd-dev/hub/v2/test" "github.com/plgd-dev/hub/v2/test/config" - httpTest "github.com/plgd-dev/hub/v2/test/http" "github.com/plgd-dev/hub/v2/test/oauth-server/service" oauthTest "github.com/plgd-dev/hub/v2/test/oauth-server/test" pbTest "github.com/plgd-dev/hub/v2/test/pb" @@ -74,20 +70,19 @@ func TestRequestHandlerGetPendingMetadataUpdates(t *testing.T) { defer cancel() testService.ClearDB(ctx, t) - oauthShutdown := oauthTest.SetUp(t) - idShutdown := idService.SetUp(t) - raShutdown := raService.SetUp(t) - rdShutdown := rdService.SetUp(t) - grpcShutdown := grpcgwService.SetUp(t) - caShutdown := caService.SetUp(t) - secureGWShutdown := coapgwTest.SetUp(t) + var closeFunc fn.FuncList + defer closeFunc.Execute() + tearDown := testService.SetUpServices(ctx, t, testService.SetUpServicesOAuth|testService.SetUpServicesMachine2MachineOAuth|testService.SetUpServicesId|testService.SetUpServicesResourceAggregate| + testService.SetUpServicesResourceDirectory|testService.SetUpServicesCertificateAuthority|testService.SetUpServicesGrpcGateway) + closeFunc.AddFunc(tearDown) - defer caShutdown() - defer grpcShutdown() - defer rdShutdown() - defer raShutdown() - defer idShutdown() - defer oauthShutdown() + deferedSecureGWShutdown := true + secureGWShutdown := coapgwTest.SetUp(t) + defer func() { + if deferedSecureGWShutdown { + secureGWShutdown() + } + }() shutdownHttp := httpgwTest.SetUp(t) defer shutdownHttp() @@ -107,6 +102,7 @@ func TestRequestHandlerGetPendingMetadataUpdates(t *testing.T) { deviceID, shutdownDevSim := test.OnboardDevSim(ctx, t, c, deviceID, config.ACTIVE_COAP_SCHEME+"://"+config.COAP_GW_HOST, test.GetAllBackendResourceLinks()) defer shutdownDevSim() + deferedSecureGWShutdown = false secureGWShutdown() createFn := func() { @@ -181,7 +177,7 @@ func TestRequestHandlerGetPendingMetadataUpdates(t *testing.T) { for { var v pb.PendingCommand - err = httpTest.Unmarshal(resp.StatusCode, resp.Body, &v) + err = pkgHttpPb.Unmarshal(resp.StatusCode, resp.Body, &v) if errors.Is(err, io.EOF) { break } diff --git a/http-gateway/service/getResourceLinks_test.go b/http-gateway/service/getResourceLinks_test.go index 0d62c3734..afecab1cc 100644 --- a/http-gateway/service/getResourceLinks_test.go +++ b/http-gateway/service/getResourceLinks_test.go @@ -16,11 +16,11 @@ import ( httpgwTest "github.com/plgd-dev/hub/v2/http-gateway/test" "github.com/plgd-dev/hub/v2/http-gateway/uri" kitNetGrpc "github.com/plgd-dev/hub/v2/pkg/net/grpc" + pkgHttpPb "github.com/plgd-dev/hub/v2/pkg/net/http/pb" "github.com/plgd-dev/hub/v2/resource-aggregate/commands" "github.com/plgd-dev/hub/v2/resource-aggregate/events" test "github.com/plgd-dev/hub/v2/test" "github.com/plgd-dev/hub/v2/test/config" - httpTest "github.com/plgd-dev/hub/v2/test/http" oauthService "github.com/plgd-dev/hub/v2/test/oauth-server/service" oauthTest "github.com/plgd-dev/hub/v2/test/oauth-server/test" pbTest "github.com/plgd-dev/hub/v2/test/pb" @@ -112,7 +112,7 @@ func TestRequestHandlerGetResourceLinks(t *testing.T) { var links []*events.ResourceLinksPublished for { var v events.ResourceLinksPublished - err = httpTest.Unmarshal(resp.StatusCode, resp.Body, &v) + err = pkgHttpPb.Unmarshal(resp.StatusCode, resp.Body, &v) if errors.Is(err, io.EOF) { break } diff --git a/http-gateway/service/getResourcePendingCommands_test.go b/http-gateway/service/getResourcePendingCommands_test.go index d75438626..e02772e3e 100644 --- a/http-gateway/service/getResourcePendingCommands_test.go +++ b/http-gateway/service/getResourcePendingCommands_test.go @@ -12,22 +12,18 @@ import ( "github.com/plgd-dev/device/v2/schema/device" "github.com/plgd-dev/device/v2/schema/platform" "github.com/plgd-dev/go-coap/v3/message" - caService "github.com/plgd-dev/hub/v2/certificate-authority/test" coapgwTest "github.com/plgd-dev/hub/v2/coap-gateway/test" "github.com/plgd-dev/hub/v2/grpc-gateway/pb" - grpcgwService "github.com/plgd-dev/hub/v2/grpc-gateway/test" httpgwTest "github.com/plgd-dev/hub/v2/http-gateway/test" "github.com/plgd-dev/hub/v2/http-gateway/uri" - idService "github.com/plgd-dev/hub/v2/identity-store/test" + "github.com/plgd-dev/hub/v2/pkg/fn" pkgGrpc "github.com/plgd-dev/hub/v2/pkg/net/grpc" pkgHttp "github.com/plgd-dev/hub/v2/pkg/net/http" + pkgHttpPb "github.com/plgd-dev/hub/v2/pkg/net/http/pb" "github.com/plgd-dev/hub/v2/resource-aggregate/commands" "github.com/plgd-dev/hub/v2/resource-aggregate/events" - raService "github.com/plgd-dev/hub/v2/resource-aggregate/test" - rdService "github.com/plgd-dev/hub/v2/resource-directory/test" "github.com/plgd-dev/hub/v2/test" "github.com/plgd-dev/hub/v2/test/config" - httpTest "github.com/plgd-dev/hub/v2/test/http" "github.com/plgd-dev/hub/v2/test/oauth-server/service" oauthTest "github.com/plgd-dev/hub/v2/test/oauth-server/test" pbTest "github.com/plgd-dev/hub/v2/test/pb" @@ -129,20 +125,19 @@ func TestRequestHandlerGetResourcePendingCommands(t *testing.T) { defer cancel() testService.ClearDB(ctx, t) - oauthShutdown := oauthTest.SetUp(t) - idShutdown := idService.SetUp(t) - raShutdown := raService.SetUp(t) - rdShutdown := rdService.SetUp(t) - grpcShutdown := grpcgwService.SetUp(t) - caShutdown := caService.SetUp(t) - secureGWShutdown := coapgwTest.SetUp(t) + var closeFunc fn.FuncList + defer closeFunc.Execute() + tearDown := testService.SetUpServices(ctx, t, testService.SetUpServicesOAuth|testService.SetUpServicesMachine2MachineOAuth|testService.SetUpServicesId|testService.SetUpServicesResourceAggregate| + testService.SetUpServicesResourceDirectory|testService.SetUpServicesCertificateAuthority|testService.SetUpServicesGrpcGateway) + closeFunc.AddFunc(tearDown) - defer caShutdown() - defer grpcShutdown() - defer rdShutdown() - defer raShutdown() - defer idShutdown() - defer oauthShutdown() + deferedSecureGWShutdown := true + secureGWShutdown := coapgwTest.SetUp(t) + defer func() { + if deferedSecureGWShutdown { + secureGWShutdown() + } + }() shutdownHttp := httpgwTest.SetUp(t) defer shutdownHttp() @@ -162,6 +157,7 @@ func TestRequestHandlerGetResourcePendingCommands(t *testing.T) { deviceID, shutdownDevSim := test.OnboardDevSim(ctx, t, c, deviceID, config.ACTIVE_COAP_SCHEME+"://"+config.COAP_GW_HOST, test.GetAllBackendResourceLinks()) defer shutdownDevSim() + deferedSecureGWShutdown = false secureGWShutdown() createFn := func() { @@ -232,7 +228,7 @@ func TestRequestHandlerGetResourcePendingCommands(t *testing.T) { var values []*pb.PendingCommand for { var v pb.PendingCommand - err = httpTest.Unmarshal(resp.StatusCode, resp.Body, &v) + err = pkgHttpPb.Unmarshal(resp.StatusCode, resp.Body, &v) if errors.Is(err, io.EOF) { break } diff --git a/http-gateway/service/getResource_test.go b/http-gateway/service/getResource_test.go index 55343c283..e50c4fd2d 100644 --- a/http-gateway/service/getResource_test.go +++ b/http-gateway/service/getResource_test.go @@ -17,11 +17,11 @@ import ( "github.com/plgd-dev/hub/v2/http-gateway/uri" pkgGrpc "github.com/plgd-dev/hub/v2/pkg/net/grpc" pkgHttp "github.com/plgd-dev/hub/v2/pkg/net/http" + pkgHttpPb "github.com/plgd-dev/hub/v2/pkg/net/http/pb" "github.com/plgd-dev/hub/v2/resource-aggregate/commands" "github.com/plgd-dev/hub/v2/resource-aggregate/events" "github.com/plgd-dev/hub/v2/test" "github.com/plgd-dev/hub/v2/test/config" - httpTest "github.com/plgd-dev/hub/v2/test/http" oauthService "github.com/plgd-dev/hub/v2/test/oauth-server/service" oauthTest "github.com/plgd-dev/hub/v2/test/oauth-server/test" pbTest "github.com/plgd-dev/hub/v2/test/pb" @@ -201,7 +201,7 @@ func TestRequestHandlerGetResource(t *testing.T) { values := make([]*events.ResourceRetrieved, 0, 1) for { var value pb.GetResourceFromDeviceResponse - err = httpTest.Unmarshal(resp.StatusCode, resp.Body, &value) + err = pkgHttpPb.Unmarshal(resp.StatusCode, resp.Body, &value) if errors.Is(err, io.EOF) { break } diff --git a/http-gateway/service/getResources_test.go b/http-gateway/service/getResources_test.go index e531ec823..6d576500c 100644 --- a/http-gateway/service/getResources_test.go +++ b/http-gateway/service/getResources_test.go @@ -17,11 +17,11 @@ import ( "github.com/plgd-dev/hub/v2/http-gateway/uri" pkgGrpc "github.com/plgd-dev/hub/v2/pkg/net/grpc" pkgHttp "github.com/plgd-dev/hub/v2/pkg/net/http" + pkgHttpPb "github.com/plgd-dev/hub/v2/pkg/net/http/pb" "github.com/plgd-dev/hub/v2/resource-aggregate/commands" "github.com/plgd-dev/hub/v2/resource-aggregate/events" "github.com/plgd-dev/hub/v2/test" "github.com/plgd-dev/hub/v2/test/config" - httpTest "github.com/plgd-dev/hub/v2/test/http" oauthService "github.com/plgd-dev/hub/v2/test/oauth-server/service" oauthTest "github.com/plgd-dev/hub/v2/test/oauth-server/test" pbTest "github.com/plgd-dev/hub/v2/test/pb" @@ -229,7 +229,7 @@ func TestRequestHandlerGetResources(t *testing.T) { values := make([]*pb.Resource, 0, 1) for { var value pb.Resource - err = httpTest.Unmarshal(resp.StatusCode, resp.Body, &value) + err = pkgHttpPb.Unmarshal(resp.StatusCode, resp.Body, &value) if errors.Is(err, io.EOF) { break } diff --git a/http-gateway/service/getThings.go b/http-gateway/service/getThings.go index 59012a046..311417e4d 100644 --- a/http-gateway/service/getThings.go +++ b/http-gateway/service/getThings.go @@ -329,12 +329,15 @@ func ThingSetSecurity(td *wotTD.ThingDescription, openIDConfigs []openid.Config) td.Security = &wotTD.TypeDeclaration{} td.SecurityDefinitions = make(map[string]wotTD.SecurityScheme) for idx := range openIDConfigs { - td.SecurityDefinitions[toSecurityName(idx)] = wotTD.SecurityScheme{ - Scheme: "oauth2", - Flow: bridgeDeviceTD.StringToPtr("code"), - Authorization: &(openIDConfigs[idx].AuthURL), - Token: &(openIDConfigs[idx].TokenURL), + ss := wotTD.SecurityScheme{ + Scheme: "oauth2", + Flow: bridgeDeviceTD.StringToPtr("code"), + Token: &(openIDConfigs[idx].TokenURL), } + if openIDConfigs[idx].AuthURL != "" { + ss.Authorization = &(openIDConfigs[idx].AuthURL) + } + td.SecurityDefinitions[toSecurityName(idx)] = ss td.Security.StringArray = append(td.Security.StringArray, toSecurityName(idx)) } } diff --git a/http-gateway/service/getThings_test.go b/http-gateway/service/getThings_test.go index 333a05f82..e829e538a 100644 --- a/http-gateway/service/getThings_test.go +++ b/http-gateway/service/getThings_test.go @@ -9,6 +9,7 @@ import ( "net/http" "net/url" "strconv" + "strings" "testing" "github.com/google/uuid" @@ -37,6 +38,7 @@ import ( "github.com/plgd-dev/hub/v2/test/device/bridge" httpTest "github.com/plgd-dev/hub/v2/test/http" oauthTest "github.com/plgd-dev/hub/v2/test/oauth-server/test" + oauthUri "github.com/plgd-dev/hub/v2/test/oauth-server/uri" "github.com/plgd-dev/hub/v2/test/service" vd "github.com/plgd-dev/hub/v2/test/virtual-device" "github.com/stretchr/testify/require" @@ -102,7 +104,7 @@ func TestRequestHandlerGetThings(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), config.TEST_TIMEOUT) defer cancel() - const services = service.SetUpServicesOAuth | service.SetUpServicesId | service.SetUpServicesResourceDirectory | + const services = service.SetUpServicesOAuth | service.SetUpServicesMachine2MachineOAuth | service.SetUpServicesId | service.SetUpServicesResourceDirectory | service.SetUpServicesGrpcGateway | service.SetUpServicesResourceAggregate isConfig := isTest.MakeConfig(t) isConfig.APIs.GRPC.TLS.ClientCertificateRequired = false @@ -200,6 +202,28 @@ func TestBridgeDeviceGetThings(t *testing.T) { } } +func patchSecurity(td *wotTD.ThingDescription) { + vCfg := config.MakeValidatorConfig() + openCfgs := make([]openid.Config, 0, len(vCfg.Endpoints)) + for _, ep := range vCfg.Endpoints { + host := ep.Authority + if strings.Contains(host, config.OAUTH_SERVER_HOST) { + openCfgs = append(openCfgs, openid.Config{ + TokenURL: ep.Authority + oauthUri.Token, + AuthURL: ep.Authority + oauthUri.Authorize, + }) + continue + } + if strings.Contains(host, config.M2M_OAUTH_SERVER_HTTP_HOST) { + openCfgs = append(openCfgs, openid.Config{ + TokenURL: ep.Authority + oauthUri.Token, + }) + continue + } + } + httpgwService.ThingSetSecurity(td, openCfgs) +} + func getPatchedTD(t *testing.T, deviceCfg bridgeDevice.Config, deviceID string, links []wotTD.IconLinkElement, validateDevices map[string]struct{}, title, host string) *wotTD.ThingDescription { td, err := bridgeDevice.GetThingDescription(deviceCfg.ThingDescription.File, deviceCfg.NumResourcesPerDevice) require.NoError(t, err) @@ -226,10 +250,7 @@ func getPatchedTD(t *testing.T, deviceCfg bridgeDevice.Config, deviceID string, } td.Properties[schemaDevice.ResourceURI] = dev - httpgwService.ThingSetSecurity(&td, []openid.Config{{ - TokenURL: "https://localhost:20009/oauth/token", - AuthURL: "https://localhost:20009/authorize", - }}) + patchSecurity(&td) mnt, ok := bridgeResourcesTD.GetOCFResourcePropertyElement(schemaMaintenance.ResourceURI) require.True(t, ok) diff --git a/http-gateway/service/subscribeToEvents_test.go b/http-gateway/service/subscribeToEvents_test.go index 24b5d2a2d..235d5a626 100644 --- a/http-gateway/service/subscribeToEvents_test.go +++ b/http-gateway/service/subscribeToEvents_test.go @@ -19,6 +19,7 @@ import ( isEvents "github.com/plgd-dev/hub/v2/identity-store/events" pkgGrpc "github.com/plgd-dev/hub/v2/pkg/net/grpc" pkgHttp "github.com/plgd-dev/hub/v2/pkg/net/http" + pkgHttpPb "github.com/plgd-dev/hub/v2/pkg/net/http/pb" "github.com/plgd-dev/hub/v2/resource-aggregate/commands" natsClient "github.com/plgd-dev/hub/v2/resource-aggregate/cqrs/eventbus/nats/client" "github.com/plgd-dev/hub/v2/resource-aggregate/cqrs/eventbus/nats/publisher" @@ -27,7 +28,6 @@ import ( rdTest "github.com/plgd-dev/hub/v2/resource-directory/test" "github.com/plgd-dev/hub/v2/test" "github.com/plgd-dev/hub/v2/test/config" - httpTest "github.com/plgd-dev/hub/v2/test/http" oauthTest "github.com/plgd-dev/hub/v2/test/oauth-server/test" pbTest "github.com/plgd-dev/hub/v2/test/pb" "github.com/plgd-dev/hub/v2/test/service" @@ -212,7 +212,7 @@ func testRequestHandlerSubscribeToEvents(t *testing.T, deviceID string, resource return nil, errM } var event pb.Event - errM = httpTest.Unmarshal(http.StatusOK, reader, &event) + errM = pkgHttpPb.Unmarshal(http.StatusOK, reader, &event) return &event, errM } diff --git a/http-gateway/service/updateDeviceMetadata_test.go b/http-gateway/service/updateDeviceMetadata_test.go index 1e93fd028..473cc1636 100644 --- a/http-gateway/service/updateDeviceMetadata_test.go +++ b/http-gateway/service/updateDeviceMetadata_test.go @@ -18,6 +18,7 @@ import ( "github.com/plgd-dev/hub/v2/pkg/log" pkgGrpc "github.com/plgd-dev/hub/v2/pkg/net/grpc" pkgHttp "github.com/plgd-dev/hub/v2/pkg/net/http" + pkgHttpPb "github.com/plgd-dev/hub/v2/pkg/net/http/pb" "github.com/plgd-dev/hub/v2/resource-aggregate/commands" "github.com/plgd-dev/hub/v2/resource-aggregate/cqrs/eventbus" "github.com/plgd-dev/hub/v2/resource-aggregate/cqrs/eventbus/nats/subscriber" @@ -107,7 +108,7 @@ func updateResource(t *testing.T, req *pb.UpdateResourceRequest, token string) e }() var got pb.UpdateResourceResponse - err = httpTest.Unmarshal(resp.StatusCode, resp.Body, &got) + err = pkgHttpPb.Unmarshal(resp.StatusCode, resp.Body, &got) if err != nil { return err } @@ -175,7 +176,7 @@ func TestRequestHandlerUpdateDeviceMetadata(t *testing.T) { }(resp) var got pb.UpdateDeviceMetadataResponse - errM = httpTest.Unmarshal(resp.StatusCode, resp.Body, &got) + errM = pkgHttpPb.Unmarshal(resp.StatusCode, resp.Body, &got) return errM } diff --git a/http-gateway/service/updateResource_test.go b/http-gateway/service/updateResource_test.go index f39a9f6ce..fb8e080bc 100644 --- a/http-gateway/service/updateResource_test.go +++ b/http-gateway/service/updateResource_test.go @@ -17,6 +17,7 @@ import ( "github.com/plgd-dev/hub/v2/http-gateway/uri" kitNetGrpc "github.com/plgd-dev/hub/v2/pkg/net/grpc" pkgHttp "github.com/plgd-dev/hub/v2/pkg/net/http" + pkgHttpPb "github.com/plgd-dev/hub/v2/pkg/net/http/pb" "github.com/plgd-dev/hub/v2/resource-aggregate/commands" "github.com/plgd-dev/hub/v2/resource-aggregate/events" "github.com/plgd-dev/hub/v2/test" @@ -231,7 +232,7 @@ func TestRequestHandlerUpdateResourcesValues(t *testing.T) { assert.Equal(t, tt.wantHTTPCode, resp.StatusCode) var got pb.UpdateResourceResponse - err = httpTest.Unmarshal(resp.StatusCode, resp.Body, &got) + err = pkgHttpPb.Unmarshal(resp.StatusCode, resp.Body, &got) if tt.wantErr { require.Error(t, err) return diff --git a/identity-store/client/ownerCache_test.go b/identity-store/client/ownerCache_test.go index 6ab95b03b..77f8313a1 100644 --- a/identity-store/client/ownerCache_test.go +++ b/identity-store/client/ownerCache_test.go @@ -36,12 +36,8 @@ func TestOwnerCacheSubscribe(t *testing.T) { devices := []string{test.GenerateDeviceIDbyIdx(1), test.GenerateDeviceIDbyIdx(2), test.GenerateDeviceIDbyIdx(3)} cfg := idService.MakeConfig(t) cfg.APIs.GRPC.Addr = "localhost:1234" - - oauthShutdown := oauthService.SetUp(t) - defer oauthShutdown() - - shutdown := idService.New(t, cfg) - defer shutdown() + tearDown := service.SetUpServices(ctx, t, service.SetUpServicesOAuth|service.SetUpServicesMachine2MachineOAuth|service.SetUpServicesId, service.WithISConfig(cfg)) + defer tearDown() token := oauthService.GetDefaultAccessToken(t) diff --git a/m2m-oauth-server/service/http/createToken_test.go b/m2m-oauth-server/service/http/createToken_test.go index ce19727fb..ced03a017 100644 --- a/m2m-oauth-server/service/http/createToken_test.go +++ b/m2m-oauth-server/service/http/createToken_test.go @@ -12,6 +12,7 @@ import ( "github.com/plgd-dev/hub/v2/m2m-oauth-server/pb" m2mOauthServerTest "github.com/plgd-dev/hub/v2/m2m-oauth-server/test" "github.com/plgd-dev/hub/v2/m2m-oauth-server/uri" + pkgHttpPb "github.com/plgd-dev/hub/v2/pkg/net/http/pb" "github.com/plgd-dev/hub/v2/test" "github.com/plgd-dev/hub/v2/test/config" testHttp "github.com/plgd-dev/hub/v2/test/http" @@ -68,7 +69,7 @@ func TestCreateToken(t *testing.T) { require.Equal(t, tt.wantHTTPCode, resp.StatusCode) var got pb.CreateTokenResponse - err = testHttp.Unmarshal(resp.StatusCode, resp.Body, &got) + err = pkgHttpPb.Unmarshal(resp.StatusCode, resp.Body, &got) if tt.wantErr { require.Error(t, err) return diff --git a/m2m-oauth-server/service/http/getTokens_test.go b/m2m-oauth-server/service/http/getTokens_test.go index d5eec0bf2..dd7654b8c 100644 --- a/m2m-oauth-server/service/http/getTokens_test.go +++ b/m2m-oauth-server/service/http/getTokens_test.go @@ -14,6 +14,7 @@ import ( "github.com/plgd-dev/hub/v2/m2m-oauth-server/pb" m2mOauthServerTest "github.com/plgd-dev/hub/v2/m2m-oauth-server/test" "github.com/plgd-dev/hub/v2/m2m-oauth-server/uri" + pkgHttpPb "github.com/plgd-dev/hub/v2/pkg/net/http/pb" "github.com/plgd-dev/hub/v2/pkg/security/jwt" "github.com/plgd-dev/hub/v2/test" "github.com/plgd-dev/hub/v2/test/config" @@ -40,7 +41,7 @@ func createTokens(ctx context.Context, t *testing.T, createTokens []*pb.CreateTo }() require.Equal(t, http.StatusOK, resp.StatusCode) var got pb.CreateTokenResponse - err = testHttp.Unmarshal(resp.StatusCode, resp.Body, &got) + err = pkgHttpPb.Unmarshal(resp.StatusCode, resp.Body, &got) require.NoError(t, err) claims, err := jwt.ParseToken(got.GetAccessToken()) require.NoError(t, err) @@ -255,7 +256,7 @@ func TestGetTokens(t *testing.T) { var got []*pb.Token for { var gotToken pb.Token - err := testHttp.Unmarshal(resp.StatusCode, resp.Body, &gotToken) + err := pkgHttpPb.Unmarshal(resp.StatusCode, resp.Body, &gotToken) if errors.Is(err, io.EOF) { break } diff --git a/m2m-oauth-server/service/service.go b/m2m-oauth-server/service/service.go index 139d5dca3..15543f098 100644 --- a/m2m-oauth-server/service/service.go +++ b/m2m-oauth-server/service/service.go @@ -63,7 +63,7 @@ func createStore(ctx context.Context, config storeConfig.Config, fileWatcher *fs } func newHttpService(ctx context.Context, config HTTPConfig, validatorConfig validator.Config, getOpenIDConfiguration validator.GetOpenIDConfigurationFunc, tlsConfig certManagerServer.Config, ss *grpcService.M2MOAuthServiceServer, fileWatcher *fsnotify.Watcher, logger log.Logger, tracerProvider trace.TracerProvider) (*httpService.Service, func(), error) { - httpValidator, err := validator.New(ctx, validatorConfig, fileWatcher, logger, tracerProvider, validator.WithGetOpenIDConfiguration(getOpenIDConfiguration)) + httpValidator, err := validator.New(ctx, validatorConfig, fileWatcher, logger, tracerProvider, validator.WithGetOpenIDConfiguration(getOpenIDConfiguration), validator.WithTrustVerificationDisabled()) if err != nil { return nil, nil, fmt.Errorf("cannot create http validator: %w", err) } @@ -83,7 +83,7 @@ func newHttpService(ctx context.Context, config HTTPConfig, validatorConfig vali } func newGrpcService(ctx context.Context, config grpcService.Config, getOpenIDConfiguration validator.GetOpenIDConfigurationFunc, ss *grpcService.M2MOAuthServiceServer, fileWatcher *fsnotify.Watcher, logger log.Logger, tracerProvider trace.TracerProvider) (*grpcService.Service, func(), error) { - grpcValidator, err := validator.New(ctx, config.Authorization.Config, fileWatcher, logger, tracerProvider, validator.WithGetOpenIDConfiguration(getOpenIDConfiguration)) + grpcValidator, err := validator.New(ctx, config.Authorization.Config, fileWatcher, logger, tracerProvider, validator.WithGetOpenIDConfiguration(getOpenIDConfiguration), validator.WithTrustVerificationDisabled()) if err != nil { return nil, nil, fmt.Errorf("cannot create grpc validator: %w", err) } diff --git a/m2m-oauth-server/test/http.go b/m2m-oauth-server/test/http.go new file mode 100644 index 000000000..0bec01350 --- /dev/null +++ b/m2m-oauth-server/test/http.go @@ -0,0 +1,38 @@ +package test + +import ( + "bytes" + "context" + "net/http" + "testing" + + "github.com/plgd-dev/go-coap/v3/message" + grpcPb "github.com/plgd-dev/hub/v2/grpc-gateway/pb" + "github.com/plgd-dev/hub/v2/m2m-oauth-server/pb" + "github.com/plgd-dev/hub/v2/m2m-oauth-server/uri" + "github.com/plgd-dev/hub/v2/test" + "github.com/plgd-dev/hub/v2/test/config" + testHttp "github.com/plgd-dev/hub/v2/test/http" + "github.com/stretchr/testify/require" +) + +func HTTPURI(uri string) string { + return testHttp.HTTPS_SCHEME + config.M2M_OAUTH_SERVER_HTTP_HOST + uri +} + +func BlacklistTokens(ctx context.Context, t *testing.T, tokenIDs []string, token string) { + data, err := testHttp.GetContentData(&grpcPb.Content{ + ContentType: message.AppOcfCbor.String(), + Data: test.EncodeToCbor(t, &pb.BlacklistTokensRequest{ + IdFilter: tokenIDs, + }), + }, message.AppJSON.String()) + require.NoError(t, err) + rb := testHttp.NewRequest(http.MethodPost, HTTPURI(uri.BlacklistTokens), bytes.NewReader(data)).AuthToken(token) + rb = rb.ContentType(message.AppOcfCbor.String()) + resp := testHttp.Do(t, rb.Build(ctx, t)) + defer func() { + _ = resp.Body.Close() + }() + require.Equal(t, http.StatusOK, resp.StatusCode) +} diff --git a/m2m-oauth-server/test/service.go b/m2m-oauth-server/test/service.go index aeef80772..02db193dc 100644 --- a/m2m-oauth-server/test/service.go +++ b/m2m-oauth-server/test/service.go @@ -12,7 +12,6 @@ import ( "github.com/plgd-dev/hub/v2/pkg/log" "github.com/plgd-dev/hub/v2/pkg/mongodb" "github.com/plgd-dev/hub/v2/test/config" - testHttp "github.com/plgd-dev/hub/v2/test/http" "github.com/stretchr/testify/require" "go.opentelemetry.io/otel/trace/noop" ) @@ -64,7 +63,3 @@ func NewMongoStore(t require.TestingT) (*storeMongo.Store, func()) { return store, cleanUp } - -func HTTPURI(uri string) string { - return testHttp.HTTPS_SCHEME + config.M2M_OAUTH_SERVER_HTTP_HOST + uri -} diff --git a/pkg/net/grpc/auth.go b/pkg/net/grpc/auth.go index 6b2bea660..4bf60c16b 100644 --- a/pkg/net/grpc/auth.go +++ b/pkg/net/grpc/auth.go @@ -43,7 +43,7 @@ func (f AuthInterceptors) Stream() grpc.StreamServerInterceptor { type ( ClaimsFunc = func(ctx context.Context, method string) jwt.ClaimsValidator Validator interface { - ParseWithClaims(token string, claims jwt.Claims) error + ParseWithClaims(ctx context.Context, token string, claims jwt.Claims) error } ) @@ -53,7 +53,7 @@ func ValidateJWTWithValidator(validator Validator, claims ClaimsFunc) Intercepto if err != nil { return nil, err } - err = validator.ParseWithClaims(token, claims(ctx, method)) + err = validator.ParseWithClaims(ctx, token, claims(ctx, method)) if err != nil { return nil, status.Errorf(codes.Unauthenticated, "invalid token: %v", err) } diff --git a/pkg/net/http/jwt/interceptor.go b/pkg/net/http/jwt/interceptor.go index 6e501a486..1ca2a5a94 100644 --- a/pkg/net/http/jwt/interceptor.go +++ b/pkg/net/http/jwt/interceptor.go @@ -12,7 +12,7 @@ import ( type ( ClaimsFunc = func(ctx context.Context, method, uri string) jwt.ClaimsValidator Validator interface { - ParseWithClaims(token string, claims jwt.Claims) error + ParseWithClaims(ctx context.Context, token string, claims jwt.Claims) error } Interceptor = func(ctx context.Context, method, uri string) (context.Context, error) ) @@ -34,7 +34,7 @@ func validateJWTWithValidator(validator Validator, claims ClaimsFunc) Intercepto if err != nil { return nil, err } - err = validator.ParseWithClaims(token, claims(ctx, method, uri)) + err = validator.ParseWithClaims(ctx, token, claims(ctx, method, uri)) if err != nil { return nil, fmt.Errorf("invalid token: %w", err) } diff --git a/pkg/net/http/jwt/token.go b/pkg/net/http/jwt/token.go index a6ff38606..b3fc8d2f4 100644 --- a/pkg/net/http/jwt/token.go +++ b/pkg/net/http/jwt/token.go @@ -5,6 +5,7 @@ import ( "fmt" "strings" + pkgJwt "github.com/plgd-dev/hub/v2/pkg/security/jwt" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" ) @@ -35,3 +36,15 @@ func tokenFromCtx(ctx context.Context) (string, error) { } return "", status.Errorf(codes.Unauthenticated, "token not found") } + +func SubjectFromToken(token string) (string, bool) { + claims, err := pkgJwt.ParseToken(token) + if err != nil { + return "", false + } + subject, err := claims.GetSubject() + if err != nil { + return "", false + } + return subject, true +} diff --git a/pkg/net/http/loggingMiddleware.go b/pkg/net/http/loggingMiddleware.go index e49731803..b16455fa3 100644 --- a/pkg/net/http/loggingMiddleware.go +++ b/pkg/net/http/loggingMiddleware.go @@ -11,7 +11,7 @@ import ( "github.com/plgd-dev/go-coap/v3/message" "github.com/plgd-dev/hub/v2/pkg/log" - pkgJwt "github.com/plgd-dev/hub/v2/pkg/security/jwt" + pkgHttpJwt "github.com/plgd-dev/hub/v2/pkg/net/http/jwt" "go.opentelemetry.io/otel/trace" rpcStatus "google.golang.org/genproto/googleapis/rpc/status" "google.golang.org/grpc/status" @@ -229,14 +229,11 @@ func createLogRequest(r *http.Request) *request { } token := strings.SplitN(bearer, " ", 2) if len(token) == 2 && strings.ToLower(token[0]) == "bearer" { - claims, err := pkgJwt.ParseToken(token[1]) - if err != nil { - return &req - } - subject, err := claims.GetSubject() - if err != nil { + subject, ok := pkgHttpJwt.SubjectFromToken(token[1]) + if !ok { return &req } + req.JWT = &jwtMember{ Sub: subject, } diff --git a/pkg/net/http/pb/protojson.go b/pkg/net/http/pb/protojson.go new file mode 100644 index 000000000..619a5cbfe --- /dev/null +++ b/pkg/net/http/pb/protojson.go @@ -0,0 +1,83 @@ +package pb + +import ( + "encoding/json" + "io" + "net/http" + + jsoniter "github.com/json-iterator/go" + "github.com/plgd-dev/hub/v2/pkg/log" + "google.golang.org/genproto/googleapis/rpc/status" + grpcStatus "google.golang.org/grpc/status" + "google.golang.org/protobuf/encoding/protojson" + "google.golang.org/protobuf/reflect/protoreflect" +) + +func UnmarshalError(data []byte) error { + var s status.Status + err := protojson.Unmarshal(data, &s) + if err != nil { + return err + } + return grpcStatus.ErrorProto(&s) +} + +type Decoder struct { + logger log.Logger +} + +func NewDecoder(logger log.Logger) *Decoder { + return &Decoder{ + logger: logger, + } +} + +func Unmarshal(code int, input io.Reader, v protoreflect.ProtoMessage) error { + d := NewDecoder(log.Get()) + return d.Unmarshal(code, input, v) +} + +func (d *Decoder) Unmarshal(code int, input io.Reader, v protoreflect.ProtoMessage) error { + var data json.RawMessage + err := json.NewDecoder(input).Decode(&data) + if err != nil { + return err + } + + d.logger.Debugf("data: %s\n", data) + + if code != http.StatusOK { + return UnmarshalError(data) + } + + var item struct { + Result json.RawMessage `json:"result"` + Error json.RawMessage `json:"error"` + } + + err = jsoniter.Unmarshal(data, &item) + if err != nil { + return err + } + if len(item.Result) == 0 && len(item.Error) == 0 { + u := protojson.UnmarshalOptions{ + DiscardUnknown: true, + } + err = u.Unmarshal(data, v) + if err != nil { + return err + } + return nil + } + if len(item.Error) > 0 { + return UnmarshalError(item.Error) + } + u := protojson.UnmarshalOptions{ + DiscardUnknown: true, + } + err = u.Unmarshal(item.Result, v) + if err != nil { + return err + } + return nil +} diff --git a/pkg/security/jwt/claims.go b/pkg/security/jwt/claims.go index f198d744f..92fdb5201 100644 --- a/pkg/security/jwt/claims.go +++ b/pkg/security/jwt/claims.go @@ -179,3 +179,22 @@ func getIssuer(token *jwt.Token) (string, error) { return "", fmt.Errorf("unsupported type %T", token.Claims) } } + +func getID(claims jwt.Claims) (string, error) { + switch c := claims.(type) { + case interface{ GetID() (string, error) }: + id, err := c.GetID() + if err != nil { + return "", ErrMissingID + } + return id, nil + case jwt.MapClaims: + id, ok := c[ClaimID].(string) + if !ok { + return "", ErrMissingID + } + return id, nil + default: + return "", fmt.Errorf("unsupported type %T", claims) + } +} diff --git a/pkg/security/jwt/validator.go b/pkg/security/jwt/validator.go index f7edc4601..5fef17050 100644 --- a/pkg/security/jwt/validator.go +++ b/pkg/security/jwt/validator.go @@ -4,9 +4,15 @@ import ( "context" "errors" "fmt" + "net/http" + "net/url" "github.com/golang-jwt/jwt/v5" + grpc_auth "github.com/grpc-ecosystem/go-grpc-middleware/auth" + "github.com/plgd-dev/hub/v2/m2m-oauth-server/pb" "github.com/plgd-dev/hub/v2/pkg/log" + pkgHttpPb "github.com/plgd-dev/hub/v2/pkg/net/http/pb" + pkgHttpUri "github.com/plgd-dev/hub/v2/pkg/net/http/uri" ) type KeyCacheI interface { @@ -14,20 +20,66 @@ type KeyCacheI interface { GetOrFetchKeyWithContext(ctx context.Context, token *jwt.Token) (interface{}, error) } +type Client struct { + *http.Client + tokenEndpoint string +} + +func NewClient(client *http.Client, tokenEndpoint string) *Client { + return &Client{ + Client: client, + tokenEndpoint: tokenEndpoint, + } +} + type Validator struct { - keys KeyCacheI - logger log.Logger + keys KeyCacheI + logger log.Logger + clients map[string]*Client + verifyTrust bool + // tokenCache TokenCache } var ( - ErrMissingToken = errors.New("missing token") - ErrCannotParseToken = errors.New("could not parse token") + ErrMissingToken = errors.New("missing token") + ErrCannotParseToken = errors.New("could not parse token") + ErrCannotVerifyTrust = errors.New("could not verify token trust") + ErrBlackListedToken = errors.New("token is blacklisted") ) -func NewValidator(keyCache KeyCacheI, logger log.Logger) *Validator { +type config struct { + clients map[string]*Client + verifyTrust bool +} + +type Option interface { + apply(*config) +} + +type optionFunc func(*config) + +func (o optionFunc) apply(c *config) { + o(c) +} + +func WithTrustVerification(clients map[string]*Client) Option { + return optionFunc(func(c *config) { + c.verifyTrust = true + c.clients = clients + }) +} + +func NewValidator(keyCache KeyCacheI, logger log.Logger, opts ...Option) *Validator { + c := &config{} + for _, opt := range opts { + opt.apply(c) + } return &Validator{ keys: keyCache, logger: logger, + // tokenCache: make(TokenCache), + clients: c.clients, + verifyTrust: c.verifyTrust, } } @@ -39,6 +91,91 @@ func errParseTokenInvalidClaimsType(t *jwt.Token) error { return fmt.Errorf("%w: unsupported type %T", ErrCannotParseToken, t.Claims) } +func (v *Validator) checkTrust(ctx context.Context, claims jwt.Claims) error { + issuer, err := claims.GetIssuer() + if err != nil { + return err + } + issuer = pkgHttpUri.CanonicalURI(issuer) + + client, ok := v.clients[issuer] + if !ok { + // TODO: set to debug + v.logger.Infof("client not set for issuer %v, trust verification skipped", issuer) + return nil + } + + tokenID, err := getID(claims) + if err != nil { + return err + } + + // tr, ok := v.tokenCache.Get(tokenID, issuer) + // if ok { + // // TODO: check expiration + // if tr.Blacklisted { + // return ErrBlackListedToken + // } + // return nil + // } + + uri, err := url.Parse(client.tokenEndpoint) + if err != nil { + return fmt.Errorf("cannot parse tokenEndpoint %v: %w", client.tokenEndpoint, err) + } + query := uri.Query() + query.Add("idFilter", tokenID) + query.Add("includeBlacklisted", "true") + uri.RawQuery = query.Encode() + + v.logger.Infof("checking trust for issuer %v for token(id=%s)", issuer, tokenID) + req, err := http.NewRequestWithContext(ctx, http.MethodGet, uri.String(), nil) + if err != nil { + return fmt.Errorf("cannot create request for GET %v: %w", uri.String(), err) + } + + token, err := grpc_auth.AuthFromMD(ctx, "bearer") + if err != nil { + return fmt.Errorf("cannot get token from context: %w", err) + } + + req.Header.Set("Accept", "application/protojson") + req.Header.Set("Authorization", "bearer "+token) + resp, err := client.Do(req) + if err != nil { + return fmt.Errorf("cannot send request for GET %v: %w", client.tokenEndpoint, err) + } + defer func() { + if errC := resp.Body.Close(); errC != nil { + v.logger.Errorf("cannot close response body: %w", errC) + } + }() + + if resp.StatusCode != http.StatusOK { + return fmt.Errorf("unexpected statusCode %v", resp.StatusCode) + } + + var gotToken pb.Token + err = pkgHttpPb.Unmarshal(resp.StatusCode, resp.Body, &gotToken) + if err != nil { + return err + } + + // tr = TokenRecord{ + // Expiration: gotToken.GetExpiration(), + // Blacklisted: gotToken.GetBlacklisted().GetFlag(), + // } + // v.tokenCache.Add(tokenID, issuer, tr) + // if tr.Blacklisted { + // return ErrBlackListedToken + // } + if gotToken.GetBlacklisted().GetFlag() { + return ErrBlackListedToken + } + + return nil +} + func (v *Validator) Parse(token string) (jwt.MapClaims, error) { return v.ParseWithContext(context.Background(), token) } @@ -60,10 +197,15 @@ func (v *Validator) ParseWithContext(ctx context.Context, token string) (jwt.Map if !ok { return nil, errParseTokenInvalidClaimsType(t) } + if v.verifyTrust { + if err = v.checkTrust(ctx, c); err != nil { + return nil, fmt.Errorf("%w: %w", ErrCannotVerifyTrust, err) + } + } return c, nil } -func (v *Validator) ParseWithClaims(token string, claims jwt.Claims) error { +func (v *Validator) ParseWithClaims(ctx context.Context, token string, claims jwt.Claims) error { if token == "" { return ErrMissingToken } @@ -72,5 +214,11 @@ func (v *Validator) ParseWithClaims(token string, claims jwt.Claims) error { if err != nil { return errParseToken(err) } + if v.verifyTrust { + if err = v.checkTrust(ctx, claims); err != nil { + return fmt.Errorf("%w: %w", ErrCannotVerifyTrust, err) + } + } + return nil } diff --git a/pkg/security/jwt/validator/validator.go b/pkg/security/jwt/validator/validator.go index a4fd93afc..c33096165 100644 --- a/pkg/security/jwt/validator/validator.go +++ b/pkg/security/jwt/validator/validator.go @@ -44,7 +44,8 @@ func (v *Validator) GetParser() *jwtValidator.Validator { type GetOpenIDConfigurationFunc func(ctx context.Context, c *http.Client, authority string) (openid.Config, error) type Options struct { - getOpenIDConfiguration GetOpenIDConfigurationFunc + getOpenIDConfiguration GetOpenIDConfigurationFunc + trustVerificationDisabled bool } func WithGetOpenIDConfiguration(f GetOpenIDConfigurationFunc) func(o *Options) { @@ -53,6 +54,12 @@ func WithGetOpenIDConfiguration(f GetOpenIDConfigurationFunc) func(o *Options) { } } +func WithTrustVerificationDisabled() func(o *Options) { + return func(o *Options) { + o.trustVerificationDisabled = true + } +} + func New(ctx context.Context, config Config, fileWatcher *fsnotify.Watcher, logger log.Logger, tracerProvider trace.TracerProvider, opts ...func(o *Options)) (*Validator, error) { options := Options{ getOpenIDConfiguration: openid.GetConfiguration, @@ -64,6 +71,7 @@ func New(ctx context.Context, config Config, fileWatcher *fsnotify.Watcher, logg keys := jwtValidator.NewMultiKeyCache() var onClose fn.FuncList openIDConfigurations := make([]openid.Config, 0, len(config.Endpoints)) + clients := make(map[string]*jwtValidator.Client, len(config.Endpoints)) for _, authority := range config.Endpoints { httpClient, err := client.New(authority.HTTP, fileWatcher, logger, tracerProvider) if err != nil { @@ -87,11 +95,19 @@ func New(ctx context.Context, config Config, fileWatcher *fsnotify.Watcher, logg issuer := pkgHttpUri.CanonicalURI(openIDCfg.Issuer) keys.Add(issuer, openIDCfg.JWKSURL, httpClient.HTTP()) openIDConfigurations = append(openIDConfigurations, openIDCfg) + if !options.trustVerificationDisabled && openIDCfg.PlgdTokensEndpoint != "" { + clients[issuer] = jwtValidator.NewClient(httpClient.HTTP(), openIDCfg.PlgdTokensEndpoint) + } + } + + var vopts []jwtValidator.Option + if len(clients) > 0 { + vopts = append(vopts, jwtValidator.WithTrustVerification(clients)) } return &Validator{ openIDConfigurations: openIDConfigurations, - validator: jwtValidator.NewValidator(keys, logger), + validator: jwtValidator.NewValidator(keys, logger, vopts...), audience: config.Audience, }, nil } @@ -104,6 +120,6 @@ func (v *Validator) OpenIDConfiguration() []openid.Config { return v.openIDConfigurations } -func (v *Validator) ParseWithClaims(token string, claims jwt.Claims) error { - return v.validator.ParseWithClaims(token, claims) +func (v *Validator) ParseWithClaims(ctx context.Context, token string, claims jwt.Claims) error { + return v.validator.ParseWithClaims(ctx, token, claims) } diff --git a/pkg/security/jwt/validator_test.go b/pkg/security/jwt/validator_test.go index baef741e4..aed381e0b 100644 --- a/pkg/security/jwt/validator_test.go +++ b/pkg/security/jwt/validator_test.go @@ -49,7 +49,7 @@ func TestValidator(t *testing.T) { v := test.GetJWTValidator(jwks.URL()) var c testClaims - err := v.ParseWithClaims(testToken(t), &c) + err := v.ParseWithClaims(context.Background(), testToken(t), &c) require.NoError(t, err) assert.Equal(t, "test.client.id", c.ClientID) @@ -63,7 +63,7 @@ func TestClaims(t *testing.T) { v := test.GetJWTValidator(jwks.URL()) var c pkgJwt.Claims - err := v.ParseWithClaims(testToken(t), &c) + err := v.ParseWithClaims(context.Background(), testToken(t), &c) require.ErrorIs(t, err, jwt.ErrTokenExpired) clientID, err := c.GetClientID() @@ -133,7 +133,7 @@ func TestEmptyToken(t *testing.T) { require.ErrorIs(t, err, pkgJwt.ErrMissingToken) var c pkgJwt.Claims - err = v.ParseWithClaims("", &c) + err = v.ParseWithClaims(context.Background(), "", &c) require.ErrorIs(t, err, pkgJwt.ErrMissingToken) _, err = v.ParseWithContext(context.Background(), "") @@ -151,6 +151,6 @@ func TestInvalidToken(t *testing.T) { require.ErrorIs(t, err, pkgJwt.ErrCannotParseToken) var c pkgJwt.Claims - err = v.ParseWithClaims("invalid", &c) + err = v.ParseWithClaims(context.Background(), "invalid", &c) require.ErrorIs(t, err, pkgJwt.ErrCannotParseToken) } diff --git a/resource-aggregate/events/resourceChanged.go b/resource-aggregate/events/resourceChanged.go index 91cb5d649..2ea7a4437 100644 --- a/resource-aggregate/events/resourceChanged.go +++ b/resource-aggregate/events/resourceChanged.go @@ -74,6 +74,10 @@ func (rc *ResourceChanged) CopyData(event *ResourceChanged) { rc.ResourceTypes = event.GetResourceTypes() } +func (rc *ResourceChanged) Clone() *ResourceChanged { + return proto.Clone(rc).(*ResourceChanged) +} + func (rc *ResourceChanged) CheckInitialized() bool { return rc.GetResourceId() != nil && rc.GetContent() != nil && diff --git a/resource-aggregate/service/service_test.go b/resource-aggregate/service/service_test.go index 598364b79..81419e721 100644 --- a/resource-aggregate/service/service_test.go +++ b/resource-aggregate/service/service_test.go @@ -7,7 +7,6 @@ import ( "github.com/plgd-dev/device/v2/schema/platform" pbIS "github.com/plgd-dev/hub/v2/identity-store/pb" - idService "github.com/plgd-dev/hub/v2/identity-store/test" "github.com/plgd-dev/hub/v2/pkg/fsnotify" "github.com/plgd-dev/hub/v2/pkg/log" kitNetGrpc "github.com/plgd-dev/hub/v2/pkg/net/grpc" @@ -17,6 +16,7 @@ import ( "github.com/plgd-dev/hub/v2/test" "github.com/plgd-dev/hub/v2/test/config" oauthTest "github.com/plgd-dev/hub/v2/test/oauth-server/test" + hubTestService "github.com/plgd-dev/hub/v2/test/service" "github.com/stretchr/testify/require" "go.opentelemetry.io/otel/trace/noop" ) @@ -27,18 +27,13 @@ func TestPublishUnpublish(t *testing.T) { fmt.Println("cfg: ", cfg) - oauthShutdown := oauthTest.SetUp(t) - defer oauthShutdown() + ctx, cancel := context.WithTimeout(context.Background(), config.TEST_TIMEOUT) + defer cancel() + const services = hubTestService.SetUpServicesMachine2MachineOAuth | hubTestService.SetUpServicesOAuth | hubTestService.SetUpServicesId | hubTestService.SetUpServicesResourceAggregate + tearDown := hubTestService.SetUpServices(ctx, t, services, hubTestService.WithRAConfig(cfg)) + defer tearDown() - idShutdown := idService.SetUp(t) - defer idShutdown() - logCfg := log.MakeDefaultConfig() - logCfg.Level = log.DebugLevel - log.Setup(logCfg) - raShutdown := raTest.New(t, cfg) - defer raShutdown() - - ctx := kitNetGrpc.CtxWithToken(context.Background(), oauthTest.GetDefaultAccessToken(t)) + ctx = kitNetGrpc.CtxWithToken(ctx, oauthTest.GetDefaultAccessToken(t)) fileWatcher, err := fsnotify.NewWatcher(log.Get()) require.NoError(t, err) @@ -74,8 +69,6 @@ func TestPublishUnpublish(t *testing.T) { require.NoError(t, err) }() - logCfg.Level = log.DebugLevel - log.Setup(logCfg) pubReq := testMakePublishResourceRequest(deviceID, []string{href}) _, err = raClient.PublishResourceLinks(ctx, pubReq) require.NoError(t, err) diff --git a/resource-directory/service/getPendingCommands_test.go b/resource-directory/service/getPendingCommands_test.go index 16228fefe..6ba80b681 100644 --- a/resource-directory/service/getPendingCommands_test.go +++ b/resource-directory/service/getPendingCommands_test.go @@ -11,16 +11,12 @@ import ( "github.com/plgd-dev/device/v2/schema/device" "github.com/plgd-dev/device/v2/schema/platform" "github.com/plgd-dev/go-coap/v3/message" - caService "github.com/plgd-dev/hub/v2/certificate-authority/test" coapgwTest "github.com/plgd-dev/hub/v2/coap-gateway/test" "github.com/plgd-dev/hub/v2/grpc-gateway/pb" - grpcgwService "github.com/plgd-dev/hub/v2/grpc-gateway/test" - idService "github.com/plgd-dev/hub/v2/identity-store/test" + "github.com/plgd-dev/hub/v2/pkg/fn" kitNetGrpc "github.com/plgd-dev/hub/v2/pkg/net/grpc" "github.com/plgd-dev/hub/v2/resource-aggregate/commands" "github.com/plgd-dev/hub/v2/resource-aggregate/events" - raService "github.com/plgd-dev/hub/v2/resource-aggregate/test" - rdService "github.com/plgd-dev/hub/v2/resource-directory/test" "github.com/plgd-dev/hub/v2/test" "github.com/plgd-dev/hub/v2/test/config" "github.com/plgd-dev/hub/v2/test/oauth-server/service" @@ -329,20 +325,19 @@ func TestRequestHandlerGetPendingCommands(t *testing.T) { defer cancel() testService.ClearDB(ctx, t) - oauthShutdown := oauthTest.SetUp(t) - authShutdown := idService.SetUp(t) - raShutdown := raService.SetUp(t) - rdShutdown := rdService.SetUp(t) - grpcShutdown := grpcgwService.SetUp(t) - caShutdown := caService.SetUp(t) - secureGWShutdown := coapgwTest.SetUp(t) + var closeFunc fn.FuncList + defer closeFunc.Execute() + tearDown := testService.SetUpServices(ctx, t, testService.SetUpServicesOAuth|testService.SetUpServicesMachine2MachineOAuth|testService.SetUpServicesId|testService.SetUpServicesResourceAggregate| + testService.SetUpServicesResourceDirectory|testService.SetUpServicesCertificateAuthority|testService.SetUpServicesGrpcGateway) + closeFunc.AddFunc(tearDown) - defer caShutdown() - defer grpcShutdown() - defer rdShutdown() - defer raShutdown() - defer authShutdown() - defer oauthShutdown() + deferedSecureGWShutdown := true + secureGWShutdown := coapgwTest.SetUp(t) + defer func() { + if deferedSecureGWShutdown { + secureGWShutdown() + } + }() ctx = kitNetGrpc.CtxWithToken(ctx, oauthTest.GetDefaultAccessToken(t)) @@ -358,6 +353,7 @@ func TestRequestHandlerGetPendingCommands(t *testing.T) { deviceID, shutdownDevSim := test.OnboardDevSim(ctx, t, c, deviceID, config.ACTIVE_COAP_SCHEME+"://"+config.COAP_GW_HOST, test.GetAllBackendResourceLinks()) defer shutdownDevSim() + deferedSecureGWShutdown = false secureGWShutdown() createFn := func(timeToLive time.Duration) { diff --git a/snippet-service/service/http/createCondition_test.go b/snippet-service/service/http/createCondition_test.go index 4d85fb4ba..44f0baf03 100644 --- a/snippet-service/service/http/createCondition_test.go +++ b/snippet-service/service/http/createCondition_test.go @@ -9,6 +9,7 @@ import ( "github.com/google/uuid" "github.com/plgd-dev/go-coap/v3/message" "github.com/plgd-dev/hub/v2/grpc-gateway/pb" + pkgHttpPb "github.com/plgd-dev/hub/v2/pkg/net/http/pb" snippetPb "github.com/plgd-dev/hub/v2/snippet-service/pb" snippetHttp "github.com/plgd-dev/hub/v2/snippet-service/service/http" snippetTest "github.com/plgd-dev/hub/v2/snippet-service/test" @@ -40,7 +41,7 @@ func TestRequestHandlerCreateCondition(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), config.TEST_TIMEOUT) defer cancel() - shutDown := service.SetUpServices(ctx, t, service.SetUpServicesOAuth) + shutDown := service.SetUpServices(ctx, t, service.SetUpServicesOAuth|service.SetUpServicesMachine2MachineOAuth) defer shutDown() snippetCfg := snippetTest.MakeConfig(t) @@ -136,7 +137,7 @@ func TestRequestHandlerCreateCondition(t *testing.T) { require.Equal(t, tt.wantHTTPCode, resp.StatusCode) var got snippetPb.Condition - err = httpTest.Unmarshal(resp.StatusCode, resp.Body, &got) + err = pkgHttpPb.Unmarshal(resp.StatusCode, resp.Body, &got) if tt.wantErr { require.Error(t, err) return diff --git a/snippet-service/service/http/createConfiguration_test.go b/snippet-service/service/http/createConfiguration_test.go index c54472cb2..79a75a365 100644 --- a/snippet-service/service/http/createConfiguration_test.go +++ b/snippet-service/service/http/createConfiguration_test.go @@ -10,6 +10,7 @@ import ( "github.com/plgd-dev/go-coap/v3/message" "github.com/plgd-dev/hub/v2/grpc-gateway/pb" pkgHttp "github.com/plgd-dev/hub/v2/pkg/net/http" + pkgHttpPb "github.com/plgd-dev/hub/v2/pkg/net/http/pb" "github.com/plgd-dev/hub/v2/resource-aggregate/commands" snippetPb "github.com/plgd-dev/hub/v2/snippet-service/pb" snippetHttp "github.com/plgd-dev/hub/v2/snippet-service/service/http" @@ -39,7 +40,7 @@ func TestRequestHandlerCreateConfiguration(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), config.TEST_TIMEOUT) defer cancel() - shutDown := service.SetUpServices(ctx, t, service.SetUpServicesOAuth) + shutDown := service.SetUpServices(ctx, t, service.SetUpServicesOAuth|service.SetUpServicesMachine2MachineOAuth) defer shutDown() snippetCfg := snippetTest.MakeConfig(t) @@ -188,7 +189,7 @@ func TestRequestHandlerCreateConfiguration(t *testing.T) { require.Equal(t, tt.wantHTTPCode, resp.StatusCode) var got snippetPb.Configuration - err = httpTest.Unmarshal(resp.StatusCode, resp.Body, &got) + err = pkgHttpPb.Unmarshal(resp.StatusCode, resp.Body, &got) if tt.wantErr { require.Error(t, err) return diff --git a/snippet-service/service/http/deleteAppliedConfigurations_test.go b/snippet-service/service/http/deleteAppliedConfigurations_test.go index 745c12420..32f52c5e8 100644 --- a/snippet-service/service/http/deleteAppliedConfigurations_test.go +++ b/snippet-service/service/http/deleteAppliedConfigurations_test.go @@ -11,6 +11,7 @@ import ( "github.com/plgd-dev/go-coap/v3/message" pkgGrpc "github.com/plgd-dev/hub/v2/pkg/net/grpc" pkgHttp "github.com/plgd-dev/hub/v2/pkg/net/http" + pkgHttpPb "github.com/plgd-dev/hub/v2/pkg/net/http/pb" "github.com/plgd-dev/hub/v2/snippet-service/pb" snippetHttp "github.com/plgd-dev/hub/v2/snippet-service/service/http" "github.com/plgd-dev/hub/v2/snippet-service/test" @@ -28,7 +29,7 @@ func TestRequestHandlerDeleteAppliedConfigurations(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), config.TEST_TIMEOUT) defer cancel() - shutDown := service.SetUpServices(ctx, t, service.SetUpServicesOAuth) + shutDown := service.SetUpServices(ctx, t, service.SetUpServicesOAuth|service.SetUpServicesMachine2MachineOAuth) defer shutDown() snippetCfg := test.MakeConfig(t) @@ -141,7 +142,7 @@ func TestRequestHandlerDeleteAppliedConfigurations(t *testing.T) { require.Equal(t, tt.wantHTTPCode, resp.StatusCode) var deleteResp pb.DeleteAppliedConfigurationsResponse - err := httpTest.Unmarshal(resp.StatusCode, resp.Body, &deleteResp) + err := pkgHttpPb.Unmarshal(resp.StatusCode, resp.Body, &deleteResp) if tt.wantErr { require.Error(t, err) return diff --git a/snippet-service/service/http/deleteConditions_test.go b/snippet-service/service/http/deleteConditions_test.go index 40863d348..760cf8a5b 100644 --- a/snippet-service/service/http/deleteConditions_test.go +++ b/snippet-service/service/http/deleteConditions_test.go @@ -10,6 +10,7 @@ import ( "github.com/plgd-dev/go-coap/v3/message" pkgHttp "github.com/plgd-dev/hub/v2/pkg/net/http" + pkgHttpPb "github.com/plgd-dev/hub/v2/pkg/net/http/pb" "github.com/plgd-dev/hub/v2/snippet-service/pb" snippetHttp "github.com/plgd-dev/hub/v2/snippet-service/service/http" "github.com/plgd-dev/hub/v2/snippet-service/test" @@ -28,7 +29,7 @@ func TestRequestHandlerDeleteConditions(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), config.TEST_TIMEOUT) defer cancel() - shutDown := service.SetUpServices(ctx, t, service.SetUpServicesOAuth) + shutDown := service.SetUpServices(ctx, t, service.SetUpServicesOAuth|service.SetUpServicesMachine2MachineOAuth) defer shutDown() snippetCfg := test.MakeConfig(t) @@ -136,7 +137,7 @@ func TestRequestHandlerDeleteConditions(t *testing.T) { require.Equal(t, tt.wantHTTPCode, resp.StatusCode) var deleteResp pb.DeleteConditionsResponse - err = httpTest.Unmarshal(resp.StatusCode, resp.Body, &deleteResp) + err = pkgHttpPb.Unmarshal(resp.StatusCode, resp.Body, &deleteResp) if tt.wantErr { require.Error(t, err) return diff --git a/snippet-service/service/http/deleteConfigurations_test.go b/snippet-service/service/http/deleteConfigurations_test.go index 0417087f9..4ebd0b1e2 100644 --- a/snippet-service/service/http/deleteConfigurations_test.go +++ b/snippet-service/service/http/deleteConfigurations_test.go @@ -10,6 +10,7 @@ import ( "github.com/plgd-dev/go-coap/v3/message" pkgHttp "github.com/plgd-dev/hub/v2/pkg/net/http" + pkgHttpPb "github.com/plgd-dev/hub/v2/pkg/net/http/pb" "github.com/plgd-dev/hub/v2/snippet-service/pb" snippetHttp "github.com/plgd-dev/hub/v2/snippet-service/service/http" "github.com/plgd-dev/hub/v2/snippet-service/test" @@ -28,7 +29,7 @@ func TestRequestHandlerDeleteConfigurations(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), config.TEST_TIMEOUT) defer cancel() - shutDown := service.SetUpServices(ctx, t, service.SetUpServicesOAuth) + shutDown := service.SetUpServices(ctx, t, service.SetUpServicesOAuth|service.SetUpServicesMachine2MachineOAuth) defer shutDown() snippetCfg := test.MakeConfig(t) @@ -136,7 +137,7 @@ func TestRequestHandlerDeleteConfigurations(t *testing.T) { require.Equal(t, tt.wantHTTPCode, resp.StatusCode) var deleteResp pb.DeleteConfigurationsResponse - err = httpTest.Unmarshal(resp.StatusCode, resp.Body, &deleteResp) + err = pkgHttpPb.Unmarshal(resp.StatusCode, resp.Body, &deleteResp) if tt.wantErr { require.Error(t, err) return diff --git a/snippet-service/service/http/getAppliedConfigurations_test.go b/snippet-service/service/http/getAppliedConfigurations_test.go index de140f162..20d96f469 100644 --- a/snippet-service/service/http/getAppliedConfigurations_test.go +++ b/snippet-service/service/http/getAppliedConfigurations_test.go @@ -9,6 +9,7 @@ import ( "github.com/plgd-dev/go-coap/v3/message" pkgHttp "github.com/plgd-dev/hub/v2/pkg/net/http" + pkgHttpPb "github.com/plgd-dev/hub/v2/pkg/net/http/pb" "github.com/plgd-dev/hub/v2/snippet-service/pb" snippetHttp "github.com/plgd-dev/hub/v2/snippet-service/service/http" "github.com/plgd-dev/hub/v2/snippet-service/test" @@ -24,7 +25,7 @@ func TestRequestHandlerGetAppliedConfigurations(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), config.TEST_TIMEOUT) defer cancel() - shutDown := service.SetUpServices(ctx, t, service.SetUpServicesOAuth) + shutDown := service.SetUpServices(ctx, t, service.SetUpServicesOAuth|service.SetUpServicesMachine2MachineOAuth) defer shutDown() snippetCfg := test.MakeConfig(t) @@ -151,7 +152,7 @@ func TestRequestHandlerGetAppliedConfigurations(t *testing.T) { receivedConfs := make(map[string]*pb.AppliedConfiguration) for { var value pb.AppliedConfiguration - err := httpTest.Unmarshal(resp.StatusCode, resp.Body, &value) + err := pkgHttpPb.Unmarshal(resp.StatusCode, resp.Body, &value) if errors.Is(err, io.EOF) { break } diff --git a/snippet-service/service/http/getConditions_test.go b/snippet-service/service/http/getConditions_test.go index ad6b07ea3..425781a74 100644 --- a/snippet-service/service/http/getConditions_test.go +++ b/snippet-service/service/http/getConditions_test.go @@ -10,6 +10,7 @@ import ( "github.com/plgd-dev/go-coap/v3/message" pkgHttp "github.com/plgd-dev/hub/v2/pkg/net/http" + pkgHttpPb "github.com/plgd-dev/hub/v2/pkg/net/http/pb" "github.com/plgd-dev/hub/v2/snippet-service/pb" snippetHttp "github.com/plgd-dev/hub/v2/snippet-service/service/http" "github.com/plgd-dev/hub/v2/snippet-service/test" @@ -28,7 +29,7 @@ func TestRequestHandlerGetConditions(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), config.TEST_TIMEOUT) defer cancel() - shutDown := service.SetUpServices(ctx, t, service.SetUpServicesOAuth) + shutDown := service.SetUpServices(ctx, t, service.SetUpServicesOAuth|service.SetUpServicesMachine2MachineOAuth) defer shutDown() snippetCfg := test.MakeConfig(t) @@ -141,7 +142,7 @@ func TestRequestHandlerGetConditions(t *testing.T) { values := make([]*pb.Condition, 0, 1) for { var value pb.Condition - err = httpTest.Unmarshal(resp.StatusCode, resp.Body, &value) + err = pkgHttpPb.Unmarshal(resp.StatusCode, resp.Body, &value) if errors.Is(err, io.EOF) { break } diff --git a/snippet-service/service/http/getConfigurations_test.go b/snippet-service/service/http/getConfigurations_test.go index 6e819cb1c..5663dd416 100644 --- a/snippet-service/service/http/getConfigurations_test.go +++ b/snippet-service/service/http/getConfigurations_test.go @@ -10,6 +10,7 @@ import ( "github.com/plgd-dev/go-coap/v3/message" pkgHttp "github.com/plgd-dev/hub/v2/pkg/net/http" + pkgHttpPb "github.com/plgd-dev/hub/v2/pkg/net/http/pb" "github.com/plgd-dev/hub/v2/snippet-service/pb" snippetHttp "github.com/plgd-dev/hub/v2/snippet-service/service/http" "github.com/plgd-dev/hub/v2/snippet-service/test" @@ -28,7 +29,7 @@ func TestRequestHandlerGetConfigurations(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), config.TEST_TIMEOUT) defer cancel() - shutDown := service.SetUpServices(ctx, t, service.SetUpServicesOAuth) + shutDown := service.SetUpServices(ctx, t, service.SetUpServicesOAuth|service.SetUpServicesMachine2MachineOAuth) defer shutDown() snippetCfg := test.MakeConfig(t) @@ -146,7 +147,7 @@ func TestRequestHandlerGetConfigurations(t *testing.T) { values := make([]*pb.Configuration, 0, 1) for { var value pb.Configuration - err = httpTest.Unmarshal(resp.StatusCode, resp.Body, &value) + err = pkgHttpPb.Unmarshal(resp.StatusCode, resp.Body, &value) if errors.Is(err, io.EOF) { break } diff --git a/snippet-service/service/http/invokeConfiguration_test.go b/snippet-service/service/http/invokeConfiguration_test.go index b477dc445..0a2f0a4f5 100644 --- a/snippet-service/service/http/invokeConfiguration_test.go +++ b/snippet-service/service/http/invokeConfiguration_test.go @@ -19,6 +19,7 @@ import ( "github.com/plgd-dev/hub/v2/pkg/log" pkgGrpc "github.com/plgd-dev/hub/v2/pkg/net/grpc" pkgHttp "github.com/plgd-dev/hub/v2/pkg/net/http" + pkgHttpPb "github.com/plgd-dev/hub/v2/pkg/net/http/pb" "github.com/plgd-dev/hub/v2/resource-aggregate/commands" "github.com/plgd-dev/hub/v2/snippet-service/pb" snippetHttp "github.com/plgd-dev/hub/v2/snippet-service/service/http" @@ -53,7 +54,7 @@ func invokeConfiguration(ctx context.Context, t *testing.T, id, token string, re }() var got pb.InvokeConfigurationResponse - err = httpTest.Unmarshal(resp.StatusCode, resp.Body, &got) + err = pkgHttpPb.Unmarshal(resp.StatusCode, resp.Body, &got) return &got, resp.StatusCode, err } diff --git a/snippet-service/service/http/updateCondition_test.go b/snippet-service/service/http/updateCondition_test.go index 2febb603f..951c76edb 100644 --- a/snippet-service/service/http/updateCondition_test.go +++ b/snippet-service/service/http/updateCondition_test.go @@ -12,6 +12,7 @@ import ( "github.com/plgd-dev/hub/v2/grpc-gateway/pb" pkgGrpc "github.com/plgd-dev/hub/v2/pkg/net/grpc" pkgHttp "github.com/plgd-dev/hub/v2/pkg/net/http" + pkgHttpPb "github.com/plgd-dev/hub/v2/pkg/net/http/pb" snippetPb "github.com/plgd-dev/hub/v2/snippet-service/pb" snippetHttp "github.com/plgd-dev/hub/v2/snippet-service/service/http" snippetTest "github.com/plgd-dev/hub/v2/snippet-service/test" @@ -46,7 +47,7 @@ func TestRequestHandlerUpdateCondition(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), config.TEST_TIMEOUT) defer cancel() - shutDown := service.SetUpServices(ctx, t, service.SetUpServicesOAuth) + shutDown := service.SetUpServices(ctx, t, service.SetUpServicesOAuth|service.SetUpServicesMachine2MachineOAuth) defer shutDown() _, shutdownHttp := snippetTest.SetUp(t) @@ -177,7 +178,7 @@ func TestRequestHandlerUpdateCondition(t *testing.T) { require.Equal(t, tt.wantHTTPCode, resp.StatusCode) var got snippetPb.Condition - err = httpTest.Unmarshal(resp.StatusCode, resp.Body, &got) + err = pkgHttpPb.Unmarshal(resp.StatusCode, resp.Body, &got) if tt.wantErr { require.Error(t, err) return diff --git a/snippet-service/service/http/updateConfiguration_test.go b/snippet-service/service/http/updateConfiguration_test.go index b44d5a84e..5d6e37683 100644 --- a/snippet-service/service/http/updateConfiguration_test.go +++ b/snippet-service/service/http/updateConfiguration_test.go @@ -12,6 +12,7 @@ import ( "github.com/plgd-dev/hub/v2/grpc-gateway/pb" pkgGrpc "github.com/plgd-dev/hub/v2/pkg/net/grpc" pkgHttp "github.com/plgd-dev/hub/v2/pkg/net/http" + pkgHttpPb "github.com/plgd-dev/hub/v2/pkg/net/http/pb" snippetPb "github.com/plgd-dev/hub/v2/snippet-service/pb" snippetHttp "github.com/plgd-dev/hub/v2/snippet-service/service/http" snippetTest "github.com/plgd-dev/hub/v2/snippet-service/test" @@ -30,7 +31,7 @@ func TestRequestHandlerUpdateConfiguration(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), config.TEST_TIMEOUT) defer cancel() - shutDown := service.SetUpServices(ctx, t, service.SetUpServicesOAuth) + shutDown := service.SetUpServices(ctx, t, service.SetUpServicesOAuth|service.SetUpServicesMachine2MachineOAuth) defer shutDown() _, shutdownHttp := snippetTest.SetUp(t) @@ -178,7 +179,7 @@ func TestRequestHandlerUpdateConfiguration(t *testing.T) { require.Equal(t, tt.wantHTTPCode, resp.StatusCode) var got snippetPb.Configuration - err = httpTest.Unmarshal(resp.StatusCode, resp.Body, &got) + err = pkgHttpPb.Unmarshal(resp.StatusCode, resp.Body, &got) if tt.wantErr { require.Error(t, err) return diff --git a/snippet-service/service/service_test.go b/snippet-service/service/service_test.go index be6e30b0f..f10f2b011 100644 --- a/snippet-service/service/service_test.go +++ b/snippet-service/service/service_test.go @@ -49,8 +49,7 @@ func TestServiceNew(t *testing.T) { fileWatcher, err := fsnotify.NewWatcher(logger) require.NoError(t, err) - const services = hubTestService.SetUpServicesOAuth - tearDown := hubTestService.SetUpServices(ctx, t, services) + tearDown := hubTestService.SetUpServices(ctx, t, hubTestService.SetUpServicesOAuth|hubTestService.SetUpServicesMachine2MachineOAuth) defer tearDown() tests := []struct { diff --git a/test/config/config.go b/test/config/config.go index 97e0df347..8b2cd5475 100644 --- a/test/config/config.go +++ b/test/config/config.go @@ -11,6 +11,7 @@ import ( "github.com/golang-jwt/jwt/v5" "github.com/plgd-dev/device/v2/schema" c2curi "github.com/plgd-dev/hub/v2/cloud2cloud-connector/uri" + m2mOauthUri "github.com/plgd-dev/hub/v2/m2m-oauth-server/uri" "github.com/plgd-dev/hub/v2/pkg/config/database" "github.com/plgd-dev/hub/v2/pkg/config/property/urischeme" pkgCqldb "github.com/plgd-dev/hub/v2/pkg/cqldb" @@ -299,6 +300,10 @@ func MakeValidatorConfig() validator.Config { Authority: http.HTTPS_SCHEME + OAUTH_SERVER_HOST, HTTP: MakeHttpClientConfig(), }, + { + Authority: http.HTTPS_SCHEME + M2M_OAUTH_SERVER_HTTP_HOST + m2mOauthUri.Base, + HTTP: MakeHttpClientConfig(), + }, }, } } diff --git a/test/http/unmarshal.go b/test/http/unmarshal.go index c0ba092b9..103a1a8f8 100644 --- a/test/http/unmarshal.go +++ b/test/http/unmarshal.go @@ -2,74 +2,12 @@ package http import ( "encoding/json" - "fmt" "io" "net/http" - jsoniter "github.com/json-iterator/go" - "google.golang.org/genproto/googleapis/rpc/status" - grpcStatus "google.golang.org/grpc/status" - "google.golang.org/protobuf/encoding/protojson" - "google.golang.org/protobuf/reflect/protoreflect" + pkgHttpPb "github.com/plgd-dev/hub/v2/pkg/net/http/pb" ) -type Decoder = interface { - Decode(interface{}) error -} - -func UnmarshalError(data []byte) error { - var s status.Status - err := protojson.Unmarshal(data, &s) - if err != nil { - return err - } - return grpcStatus.ErrorProto(&s) -} - -func Unmarshal(code int, input io.Reader, v protoreflect.ProtoMessage) error { - var data json.RawMessage - err := json.NewDecoder(input).Decode(&data) - if err != nil { - return err - } - fmt.Printf("data: %s\n", data) - - if code != http.StatusOK { - return UnmarshalError(data) - } - - var item struct { - Result json.RawMessage `json:"result"` - Error json.RawMessage `json:"error"` - } - - err = jsoniter.Unmarshal(data, &item) - if err != nil { - return err - } - if len(item.Result) == 0 && len(item.Error) == 0 { - u := protojson.UnmarshalOptions{ - DiscardUnknown: true, - } - err = u.Unmarshal(data, v) - if err != nil { - return err - } - return nil - } - if len(item.Error) > 0 { - return UnmarshalError(item.Error) - } - u := protojson.UnmarshalOptions{ - DiscardUnknown: true, - } - err = u.Unmarshal(item.Result, v) - if err != nil { - return err - } - return nil -} - func UnmarshalJson(code int, input io.Reader, v any) error { var data json.RawMessage err := json.NewDecoder(input).Decode(&data) @@ -77,7 +15,7 @@ func UnmarshalJson(code int, input io.Reader, v any) error { return err } if code != http.StatusOK { - return UnmarshalError(data) + return pkgHttpPb.UnmarshalError(data) } err = json.Unmarshal(data, v) return err diff --git a/test/iotivity-lite/service/offboard_test.go b/test/iotivity-lite/service/offboard_test.go index f8c09c2df..b93c654b2 100644 --- a/test/iotivity-lite/service/offboard_test.go +++ b/test/iotivity-lite/service/offboard_test.go @@ -35,7 +35,7 @@ func TestOffboard(t *testing.T) { ctx, cancel := context.WithDeadline(context.Background(), deadline) defer cancel() - const services = service.SetUpServicesOAuth | service.SetUpServicesGrpcGateway + const services = service.SetUpServicesOAuth | service.SetUpServicesMachine2MachineOAuth | service.SetUpServicesGrpcGateway tearDown := service.SetUpServices(ctx, t, services) defer tearDown() @@ -188,7 +188,7 @@ func TestOffboardWithoutSignIn(t *testing.T) { ctx, cancel := context.WithDeadline(context.Background(), deadline) defer cancel() - const services = service.SetUpServicesOAuth | service.SetUpServicesGrpcGateway + const services = service.SetUpServicesOAuth | service.SetUpServicesMachine2MachineOAuth | service.SetUpServicesGrpcGateway tearDown := service.SetUpServices(ctx, t, services) defer tearDown() @@ -251,7 +251,7 @@ func TestOffboardWithSignIn(t *testing.T) { ctx, cancel := context.WithDeadline(context.Background(), deadline) defer cancel() - const services = service.SetUpServicesOAuth | service.SetUpServicesGrpcGateway + const services = service.SetUpServicesOAuth | service.SetUpServicesMachine2MachineOAuth | service.SetUpServicesGrpcGateway tearDown := service.SetUpServices(ctx, t, services) defer tearDown() @@ -316,7 +316,7 @@ func TestOffboardWithSignInByRefreshToken(t *testing.T) { ctx, cancel := context.WithDeadline(context.Background(), deadline) defer cancel() - const services = service.SetUpServicesOAuth | service.SetUpServicesGrpcGateway + const services = service.SetUpServicesOAuth | service.SetUpServicesMachine2MachineOAuth | service.SetUpServicesGrpcGateway tearDown := service.SetUpServices(ctx, t, services) defer tearDown() @@ -393,7 +393,7 @@ func TestOffboardWithRepeat(t *testing.T) { ctx, cancel := context.WithDeadline(context.Background(), deadline) defer cancel() - const services = service.SetUpServicesOAuth | service.SetUpServicesGrpcGateway + const services = service.SetUpServicesOAuth | service.SetUpServicesMachine2MachineOAuth | service.SetUpServicesGrpcGateway tearDown := service.SetUpServices(ctx, t, services) defer tearDown() @@ -455,7 +455,7 @@ func TestOffboardInterrupt(t *testing.T) { ctx, cancel := context.WithDeadline(context.Background(), deadline) defer cancel() - const services = service.SetUpServicesOAuth | service.SetUpServicesGrpcGateway + const services = service.SetUpServicesOAuth | service.SetUpServicesMachine2MachineOAuth | service.SetUpServicesGrpcGateway tearDown := service.SetUpServices(ctx, t, services) defer tearDown() diff --git a/test/iotivity-lite/service/republish_test.go b/test/iotivity-lite/service/republish_test.go index a6367aae6..343a1ac14 100644 --- a/test/iotivity-lite/service/republish_test.go +++ b/test/iotivity-lite/service/republish_test.go @@ -30,7 +30,7 @@ func TestRepublishAfterRefresh(t *testing.T) { ctx, cancel := context.WithDeadline(context.Background(), deadline) defer cancel() - const services = service.SetUpServicesOAuth | service.SetUpServicesGrpcGateway + const services = service.SetUpServicesOAuth | service.SetUpServicesMachine2MachineOAuth | service.SetUpServicesGrpcGateway tearDown := service.SetUpServices(ctx, t, services) defer tearDown() diff --git a/test/pb/pendingCommand.go b/test/pb/pendingCommand.go index 2a7865b5f..80ad16043 100644 --- a/test/pb/pendingCommand.go +++ b/test/pb/pendingCommand.go @@ -10,18 +10,13 @@ import ( "time" "github.com/plgd-dev/go-coap/v3/message" - caService "github.com/plgd-dev/hub/v2/certificate-authority/test" coapgwTest "github.com/plgd-dev/hub/v2/coap-gateway/test" "github.com/plgd-dev/hub/v2/grpc-gateway/pb" - grpcgwService "github.com/plgd-dev/hub/v2/grpc-gateway/test" httpgwTest "github.com/plgd-dev/hub/v2/http-gateway/test" - idService "github.com/plgd-dev/hub/v2/identity-store/test" "github.com/plgd-dev/hub/v2/pkg/fn" kitNetGrpc "github.com/plgd-dev/hub/v2/pkg/net/grpc" "github.com/plgd-dev/hub/v2/resource-aggregate/commands" "github.com/plgd-dev/hub/v2/resource-aggregate/events" - raService "github.com/plgd-dev/hub/v2/resource-aggregate/test" - rdService "github.com/plgd-dev/hub/v2/resource-directory/test" "github.com/plgd-dev/hub/v2/test" "github.com/plgd-dev/hub/v2/test/config" oauthService "github.com/plgd-dev/hub/v2/test/oauth-server/service" @@ -71,21 +66,25 @@ func InitPendingEvents(ctx context.Context, t *testing.T) (pb.GrpcGatewayClient, service.ClearDB(ctx, t) - oauthShutdown := oauthTest.SetUp(t) - idShutdown := idService.SetUp(t) - raShutdown := raService.SetUp(t) - rdShutdown := rdService.SetUp(t) - grpcShutdown := grpcgwService.SetUp(t) - caShutdown := caService.SetUp(t) - secureGWShutdown := coapgwTest.SetUp(t) - var closeFunc fn.FuncList - closeFunc.AddFunc(caShutdown) - closeFunc.AddFunc(grpcShutdown) - closeFunc.AddFunc(rdShutdown) - closeFunc.AddFunc(raShutdown) - closeFunc.AddFunc(idShutdown) - closeFunc.AddFunc(oauthShutdown) + deferedClose := true + defer func() { + if deferedClose { + closeFunc.Execute() + } + }() + + tearDown := service.SetUpServices(ctx, t, service.SetUpServicesOAuth|service.SetUpServicesMachine2MachineOAuth|service.SetUpServicesId|service.SetUpServicesResourceAggregate| + service.SetUpServicesResourceDirectory|service.SetUpServicesCertificateAuthority|service.SetUpServicesGrpcGateway) + closeFunc.AddFunc(tearDown) + + deferedsecureGWShutdown := true + secureGWShutdown := coapgwTest.SetUp(t) + closeFunc.AddFunc(func() { + if deferedsecureGWShutdown { + secureGWShutdown() + } + }) shutdownHttp := httpgwTest.SetUp(t) closeFunc.AddFunc(shutdownHttp) @@ -105,6 +104,7 @@ func InitPendingEvents(ctx context.Context, t *testing.T) (pb.GrpcGatewayClient, deviceID, shutdownDevSim := test.OnboardDevSim(ctx, t, c, deviceID, config.ACTIVE_COAP_SCHEME+"://"+config.COAP_GW_HOST, test.GetAllBackendResourceLinks()) closeFunc.AddFunc(shutdownDevSim) + deferedsecureGWShutdown = false secureGWShutdown() createFn := func() { @@ -208,6 +208,7 @@ func InitPendingEvents(ctx context.Context, t *testing.T) (pb.GrpcGatewayClient, } } + deferedClose = false return c, resourcePendings, devicePendings, closeFunc.ToFunction() } diff --git a/test/service/service.go b/test/service/service.go index c3665a753..2dee6e893 100644 --- a/test/service/service.go +++ b/test/service/service.go @@ -161,7 +161,7 @@ func WithM2MOAuthConfig(oauth m2mOauthService.Config) SetUpOption { type SetUpOption = func(cfg *Config) -func SetUp(ctx context.Context, t require.TestingT, opts ...SetUpOption) (tearDown func()) { +func SetUp(ctx context.Context, t require.TestingT, opts ...SetUpOption) func() { config := Config{ COAPGW: coapgwTest.MakeConfig(t), RD: rdTest.MakeConfig(t), @@ -177,30 +177,40 @@ func SetUp(ctx context.Context, t require.TestingT, opts ...SetUpOption) (tearDo o(&config) } + var tearDown fn.FuncList + deferedTearDown := true + defer func() { + if deferedTearDown { + tearDown.Execute() + } + }() + ClearDB(ctx, t) oauthShutdown := oauthTest.New(t, config.OAUTH) + tearDown.AddFunc(oauthShutdown) m2mTearDown := m2mOauthTest.New(t, config.M2MOAUTH) + tearDown.AddFunc(m2mTearDown) isShutdown := isTest.New(t, config.IS) + tearDown.AddFunc(isShutdown) raShutdown := raTest.New(t, config.RA) + tearDown.AddFunc(raShutdown) rdShutdown := rdTest.New(t, config.RD) + tearDown.AddFunc(rdShutdown) grpcShutdown := grpcgwTest.New(t, config.GRPCGW) + tearDown.AddFunc(grpcShutdown) c2cgwShutdown := c2cgwService.SetUp(t) + tearDown.AddFunc(c2cgwShutdown) caShutdown := caService.New(t, config.CA) + tearDown.AddFunc(caShutdown) secureGWShutdown := coapgwTest.New(t, config.COAPGW) + tearDown.AddFunc(secureGWShutdown) // wait for all services to start time.Sleep(time.Second) + deferedTearDown = false return func() { - caShutdown() - c2cgwShutdown() - grpcShutdown() - secureGWShutdown() - rdShutdown() - raShutdown() - isShutdown() - m2mTearDown() - oauthShutdown() + tearDown.Execute() // wait for all services to be closed time.Sleep(time.Second) @@ -211,6 +221,7 @@ type SetUpServicesConfig uint16 const ( SetUpServicesOAuth SetUpServicesConfig = 1 << iota + SetUpServicesMachine2MachineOAuth SetUpServicesId SetUpServicesResourceAggregate SetUpServicesResourceDirectory @@ -224,14 +235,29 @@ const ( var setupServicesMap = map[SetUpServicesConfig]func(t require.TestingT, tearDown *fn.FuncList, opts ...SetUpOption){ SetUpServicesOAuth: func(t require.TestingT, tearDown *fn.FuncList, opts ...SetUpOption) { - // to fix `opts` is unused - config := Config{} + config := Config{ + OAUTH: oauthTest.MakeConfig(t), + } for _, o := range opts { o(&config) } - oauthShutdown := oauthTest.SetUp(t) + err := config.OAUTH.Validate() + require.NoError(t, err) + oauthShutdown := oauthTest.New(t, config.OAUTH) tearDown.AddFunc(oauthShutdown) }, + SetUpServicesMachine2MachineOAuth: func(t require.TestingT, tearDown *fn.FuncList, opts ...SetUpOption) { + config := Config{ + M2MOAUTH: m2mOauthTest.MakeConfig(t), + } + for _, o := range opts { + o(&config) + } + err := config.M2MOAUTH.Validate() + require.NoError(t, err) + m2mTearDown := m2mOauthTest.New(t, config.M2MOAUTH) + tearDown.AddFunc(m2mTearDown) + }, SetUpServicesId: func(t require.TestingT, tearDown *fn.FuncList, opts ...SetUpOption) { config := Config{ IS: isTest.MakeConfig(t), @@ -280,12 +306,7 @@ var setupServicesMap = map[SetUpServicesConfig]func(t require.TestingT, tearDown grpcShutdown := grpcgwTest.New(t, config.GRPCGW) tearDown.AddFunc(grpcShutdown) }, - SetUpServicesCloud2CloudGateway: func(t require.TestingT, tearDown *fn.FuncList, opts ...SetUpOption) { - // to fix `opts` is unused - config := Config{} - for _, o := range opts { - o(&config) - } + SetUpServicesCloud2CloudGateway: func(t require.TestingT, tearDown *fn.FuncList, _ ...SetUpOption) { c2cgwShutdown := c2cgwService.SetUp(t) tearDown.AddFunc(c2cgwShutdown) }, @@ -317,6 +338,12 @@ var setupServicesMap = map[SetUpServicesConfig]func(t require.TestingT, tearDown func SetUpServices(ctx context.Context, t require.TestingT, servicesConfig SetUpServicesConfig, opts ...SetUpOption) func() { var tearDown fn.FuncList + deferedTearDown := true + defer func() { + if deferedTearDown { + tearDown.Execute() + } + }() ClearDB(ctx, t) for i := SetUpServicesConfig(1); i < SetUpServicesMax; i <<= 1 { @@ -326,5 +353,6 @@ func SetUpServices(ctx context.Context, t require.TestingT, servicesConfig SetUp } } } + deferedTearDown = false return tearDown.ToFunction() } From 18a1ee84abb8556d53d634aedad2df4dca61345a Mon Sep 17 00:00:00 2001 From: Daniel Adam Date: Thu, 25 Jul 2024 09:24:30 +0200 Subject: [PATCH 27/31] Add token cache --- grpc-gateway/service/getResources_test.go | 3 + pkg/security/jwt/cache.go | 70 +++++++++++++++++++++++ pkg/security/jwt/validator.go | 61 ++++++++------------ 3 files changed, 98 insertions(+), 36 deletions(-) create mode 100644 pkg/security/jwt/cache.go diff --git a/grpc-gateway/service/getResources_test.go b/grpc-gateway/service/getResources_test.go index e5debed6c..05cbf2731 100644 --- a/grpc-gateway/service/getResources_test.go +++ b/grpc-gateway/service/getResources_test.go @@ -256,6 +256,9 @@ func TestRequestHandlerGetResourcesWithBlacklistedToken(t *testing.T) { _, err = getResources(pkgGrpc.CtxWithToken(ctx, "invalid"), c, req) require.Error(t, err) + // whitelisted tokens expire after 10 seconds + time.Sleep(time.Second * 10) + // blacklist the token token, err := pkgJwt.ParseToken(tokenStr) require.NoError(t, err) diff --git a/pkg/security/jwt/cache.go b/pkg/security/jwt/cache.go new file mode 100644 index 000000000..5d9f5ef26 --- /dev/null +++ b/pkg/security/jwt/cache.go @@ -0,0 +1,70 @@ +package jwt + +import ( + "time" + + "github.com/plgd-dev/go-coap/v3/pkg/cache" + pkgTime "github.com/plgd-dev/hub/v2/pkg/time" +) + +type TokenRecord struct { + Blacklisted bool +} + +type TokenCache struct { + cache *cache.Cache[string, TokenRecord] +} + +func NewTokenCache() *TokenCache { + return &TokenCache{ + cache: cache.NewCache[string, TokenRecord](), + } +} + +func getCacheKey(tokenID, issuer string) string { + return tokenID + "." + issuer +} + +func (t *TokenCache) Load(tokenID, issuer string) (TokenRecord, bool) { + key := getCacheKey(tokenID, issuer) + value := t.cache.Load(key) + if value == nil { + return TokenRecord{}, false + } + return value.Data(), true +} + +func (t *TokenCache) AddBlacklisted(tokenID, issuer string, expiresAt int64) { + key := getCacheKey(tokenID, issuer) + // use Replace + //// if not in cache -> add + //// if in cache -> + //// - previously whitelisted -> replace + //// - previously blacklisted -> replace is not necessary, but it doesn't break anything + t.cache.Replace(key, cache.NewElement(TokenRecord{Blacklisted: true}, pkgTime.Unix(expiresAt, 0), nil)) +} + +func (t *TokenCache) AddWhitelisted(tokenID, issuer string) bool { + key := getCacheKey(tokenID, issuer) + // use ReplaceWithFunc + //// if not in cache -> add + //// if in cache -> + //// - previously whitelisted -> replace the record to extend the expiration time + //// - previously blacklisted -> cannot replace, blacklisted tokens stay blacklisted until expiration + //// TODO: configure expiration interval -> for now use 10s + newTr := cache.NewElement(TokenRecord{Blacklisted: false}, time.Now().Add(10*time.Second), nil) + var actual *cache.Element[TokenRecord] + now := time.Now() + t.cache.ReplaceWithFunc(key, func(oldValue *cache.Element[TokenRecord], oldLoaded bool) (*cache.Element[TokenRecord], bool) { + if oldLoaded && oldValue.Data().Blacklisted { + // blacklisted tokens stay blacklisted until expiration + if !oldValue.IsExpired(now) { + actual = oldValue + return oldValue, false + } + } + actual = newTr + return newTr, false + }) + return actual == newTr +} diff --git a/pkg/security/jwt/validator.go b/pkg/security/jwt/validator.go index 5fef17050..2df5d6e64 100644 --- a/pkg/security/jwt/validator.go +++ b/pkg/security/jwt/validator.go @@ -8,7 +8,6 @@ import ( "net/url" "github.com/golang-jwt/jwt/v5" - grpc_auth "github.com/grpc-ecosystem/go-grpc-middleware/auth" "github.com/plgd-dev/hub/v2/m2m-oauth-server/pb" "github.com/plgd-dev/hub/v2/pkg/log" pkgHttpPb "github.com/plgd-dev/hub/v2/pkg/net/http/pb" @@ -37,7 +36,7 @@ type Validator struct { logger log.Logger clients map[string]*Client verifyTrust bool - // tokenCache TokenCache + tokenCache *TokenCache } var ( @@ -75,9 +74,9 @@ func NewValidator(keyCache KeyCacheI, logger log.Logger, opts ...Option) *Valida opt.apply(c) } return &Validator{ - keys: keyCache, - logger: logger, - // tokenCache: make(TokenCache), + keys: keyCache, + logger: logger, + tokenCache: NewTokenCache(), clients: c.clients, verifyTrust: c.verifyTrust, } @@ -91,7 +90,7 @@ func errParseTokenInvalidClaimsType(t *jwt.Token) error { return fmt.Errorf("%w: unsupported type %T", ErrCannotParseToken, t.Claims) } -func (v *Validator) checkTrust(ctx context.Context, claims jwt.Claims) error { +func (v *Validator) checkTrust(ctx context.Context, token string, claims jwt.Claims) error { issuer, err := claims.GetIssuer() if err != nil { return err @@ -100,8 +99,7 @@ func (v *Validator) checkTrust(ctx context.Context, claims jwt.Claims) error { client, ok := v.clients[issuer] if !ok { - // TODO: set to debug - v.logger.Infof("client not set for issuer %v, trust verification skipped", issuer) + v.logger.Debugf("client not set for issuer %v, trust verification skipped", issuer) return nil } @@ -110,14 +108,13 @@ func (v *Validator) checkTrust(ctx context.Context, claims jwt.Claims) error { return err } - // tr, ok := v.tokenCache.Get(tokenID, issuer) - // if ok { - // // TODO: check expiration - // if tr.Blacklisted { - // return ErrBlackListedToken - // } - // return nil - // } + tr, ok := v.tokenCache.Load(tokenID, issuer) + if ok { + if tr.Blacklisted { + return ErrBlackListedToken + } + return nil + } uri, err := url.Parse(client.tokenEndpoint) if err != nil { @@ -134,11 +131,6 @@ func (v *Validator) checkTrust(ctx context.Context, claims jwt.Claims) error { return fmt.Errorf("cannot create request for GET %v: %w", uri.String(), err) } - token, err := grpc_auth.AuthFromMD(ctx, "bearer") - if err != nil { - return fmt.Errorf("cannot get token from context: %w", err) - } - req.Header.Set("Accept", "application/protojson") req.Header.Set("Authorization", "bearer "+token) resp, err := client.Do(req) @@ -161,18 +153,16 @@ func (v *Validator) checkTrust(ctx context.Context, claims jwt.Claims) error { return err } - // tr = TokenRecord{ - // Expiration: gotToken.GetExpiration(), - // Blacklisted: gotToken.GetBlacklisted().GetFlag(), - // } - // v.tokenCache.Add(tokenID, issuer, tr) - // if tr.Blacklisted { - // return ErrBlackListedToken - // } - if gotToken.GetBlacklisted().GetFlag() { + tr = TokenRecord{ + Blacklisted: gotToken.GetBlacklisted().GetFlag(), + } + if tr.Blacklisted { + v.tokenCache.AddBlacklisted(tokenID, issuer, gotToken.GetExpiration()) + return ErrBlackListedToken + } + if !v.tokenCache.AddWhitelisted(tokenID, issuer) { return ErrBlackListedToken } - return nil } @@ -193,16 +183,16 @@ func (v *Validator) ParseWithContext(ctx context.Context, token string) (jwt.Map if err != nil { return nil, errParseToken(err) } - c, ok := t.Claims.(jwt.MapClaims) + claims, ok := t.Claims.(jwt.MapClaims) if !ok { return nil, errParseTokenInvalidClaimsType(t) } if v.verifyTrust { - if err = v.checkTrust(ctx, c); err != nil { + if err = v.checkTrust(ctx, token, claims); err != nil { return nil, fmt.Errorf("%w: %w", ErrCannotVerifyTrust, err) } } - return c, nil + return claims, nil } func (v *Validator) ParseWithClaims(ctx context.Context, token string, claims jwt.Claims) error { @@ -215,10 +205,9 @@ func (v *Validator) ParseWithClaims(ctx context.Context, token string, claims jw return errParseToken(err) } if v.verifyTrust { - if err = v.checkTrust(ctx, claims); err != nil { + if err = v.checkTrust(ctx, token, claims); err != nil { return fmt.Errorf("%w: %w", ErrCannotVerifyTrust, err) } } - return nil } From e6acb127ec951511a30a108794aa11c41774d73f Mon Sep 17 00:00:00 2001 From: Daniel Adam Date: Thu, 25 Jul 2024 16:31:17 +0200 Subject: [PATCH 28/31] validator: add configuration and helm charts --- certificate-authority/config.yaml | 5 ++- charts/plgd-hub/templates/_helpers.tpl | 9 +++++ .../templates/m2m-oauth-server/config.yaml | 2 + charts/plgd-hub/values.yaml | 3 ++ cloud2cloud-gateway/config.yaml | 3 ++ coap-gateway/config.yaml | 3 ++ grpc-gateway/config.yaml | 3 ++ grpc-gateway/service/getResources_test.go | 9 +++-- http-gateway/config.yaml | 37 ++++++++++--------- identity-store/config.yaml | 3 ++ m2m-oauth-server/config.yaml | 2 + m2m-oauth-server/service/service.go | 4 +- m2m-oauth-server/test/test.go | 13 ++++++- pkg/security/jwt/{cache.go => tokenCache.go} | 36 ++++++++++-------- pkg/security/jwt/validator.go | 32 ++++++++++------ pkg/security/jwt/validator/config.go | 24 +++++++++--- pkg/security/jwt/validator/validator.go | 13 ++----- resource-aggregate/config.yaml | 3 ++ resource-directory/config.yaml | 3 ++ snippet-service/config.yaml | 3 ++ test/config/config.go | 5 +++ 21 files changed, 148 insertions(+), 67 deletions(-) rename pkg/security/jwt/{cache.go => tokenCache.go} (53%) diff --git a/certificate-authority/config.yaml b/certificate-authority/config.yaml index 6460ad5cb..cc21c1e61 100644 --- a/certificate-authority/config.yaml +++ b/certificate-authority/config.yaml @@ -44,6 +44,9 @@ apis: keyFile: "/secrets/private/cert.key" certFile: "/secrets/public/cert.crt" useSystemCAPool: false + tokenTrustVerification: + enabled: true + cacheExpiration: 30s http: address: "0.0.0.0:9101" readTimeout: 8s @@ -112,5 +115,3 @@ signer: certFile: "/secrets/public/intermediateca.crt" validFrom: "now-1h" expiresIn: "87600h" - - diff --git a/charts/plgd-hub/templates/_helpers.tpl b/charts/plgd-hub/templates/_helpers.tpl index 29317eea4..5472fbc0d 100644 --- a/charts/plgd-hub/templates/_helpers.tpl +++ b/charts/plgd-hub/templates/_helpers.tpl @@ -220,6 +220,15 @@ tls: {{- include "plgd-hub.httpConfig" (list $ .http $certPath ) | indent 8 }} {{- end }} {{- end }} + tokenTrustVerification: + {{- $tokenTrustVerification := $authorization.tokenTrustVerification }} + {{- if not $tokenTrustVerification }} + {{- $tokenTrustVerification = $.Values.global.authorization.tokenTrustVerification }} + {{- end }} + enabled: {{ $tokenTrustVerification.enabled | default false }} + {{- if $tokenTrustVerification.enabled }} + cacheExpiration: {{ $tokenTrustVerification.cacheExpiration }} + {{- end }} {{- end }} {{- define "plgd-hub.authorizationConfig" }} diff --git a/charts/plgd-hub/templates/m2m-oauth-server/config.yaml b/charts/plgd-hub/templates/m2m-oauth-server/config.yaml index 416b06f2e..128c24d56 100644 --- a/charts/plgd-hub/templates/m2m-oauth-server/config.yaml +++ b/charts/plgd-hub/templates/m2m-oauth-server/config.yaml @@ -113,6 +113,8 @@ data: http: {{- .http | toYaml | nindent 20 }} {{- end }} {{- end }} + tokenTrustVerification: + enabled: false {{- end }} {{- end }} {{- end }} diff --git a/charts/plgd-hub/values.yaml b/charts/plgd-hub/values.yaml index c35b802b7..309d79121 100644 --- a/charts/plgd-hub/values.yaml +++ b/charts/plgd-hub/values.yaml @@ -51,6 +51,9 @@ global: keyFile: certFile: useSystemCAPool: true + tokenTrustVerification: + enabled: true + cacheExpiration: 30s # -- Enable *.{{ global.domain }} for all external domain enableWildCartCert: true # -- Sets cloud to standby mode diff --git a/cloud2cloud-gateway/config.yaml b/cloud2cloud-gateway/config.yaml index ad202cd39..c95ad7d26 100644 --- a/cloud2cloud-gateway/config.yaml +++ b/cloud2cloud-gateway/config.yaml @@ -32,6 +32,9 @@ apis: keyFile: "/secrets/private/cert.key" certFile: "/secrets/public/cert.crt" useSystemCAPool: false + tokenTrustVerification: + enabled: true + cacheExpiration: 30s clients: eventBus: nats: diff --git a/coap-gateway/config.yaml b/coap-gateway/config.yaml index 14b2c7b76..c0da0ed20 100644 --- a/coap-gateway/config.yaml +++ b/coap-gateway/config.yaml @@ -74,6 +74,9 @@ apis: keyFile: "/secrets/private/cert.key" certFile: "/secrets/public/cert.crt" useSystemCAPool: false + tokenTrustVerification: + enabled: true + cacheExpiration: 30s clients: eventBus: nats: diff --git a/grpc-gateway/config.yaml b/grpc-gateway/config.yaml index cfa79e2dc..4dc98e5be 100644 --- a/grpc-gateway/config.yaml +++ b/grpc-gateway/config.yaml @@ -45,6 +45,9 @@ apis: keyFile: "/secrets/private/cert.key" certFile: "/secrets/public/cert.crt" useSystemCAPool: false + tokenTrustVerification: + enabled: true + cacheExpiration: 30s clients: identityStore: grpc: diff --git a/grpc-gateway/service/getResources_test.go b/grpc-gateway/service/getResources_test.go index 05cbf2731..99e66358f 100644 --- a/grpc-gateway/service/getResources_test.go +++ b/grpc-gateway/service/getResources_test.go @@ -10,6 +10,7 @@ import ( "github.com/plgd-dev/device/v2/test/resource/types" "github.com/plgd-dev/hub/v2/grpc-gateway/pb" + grpcgwTest "github.com/plgd-dev/hub/v2/grpc-gateway/test" m2mOauthTest "github.com/plgd-dev/hub/v2/m2m-oauth-server/test" pkgGrpc "github.com/plgd-dev/hub/v2/pkg/net/grpc" pkgJwt "github.com/plgd-dev/hub/v2/pkg/security/jwt" @@ -218,7 +219,9 @@ func TestRequestHandlerGetResourcesWithBlacklistedToken(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), config.TEST_TIMEOUT*100) defer cancel() - tearDown := service.SetUp(ctx, t) + grpcCfg := grpcgwTest.MakeConfig(t) + grpcCfg.APIs.GRPC.Authorization.TokenVerification.CacheExpiration = time.Second * 2 + tearDown := service.SetUp(ctx, t, service.WithGRPCGWConfig(grpcCfg)) defer tearDown() validTokenStr := oauthTest.GetDefaultAccessToken(t) ctxWithToken := pkgGrpc.CtxWithToken(ctx, validTokenStr) @@ -256,8 +259,8 @@ func TestRequestHandlerGetResourcesWithBlacklistedToken(t *testing.T) { _, err = getResources(pkgGrpc.CtxWithToken(ctx, "invalid"), c, req) require.Error(t, err) - // whitelisted tokens expire after 10 seconds - time.Sleep(time.Second * 10) + // whitelisted tokens expire + time.Sleep(grpcCfg.APIs.GRPC.Authorization.TokenVerification.CacheExpiration + time.Second) // blacklist the token token, err := pkgJwt.ParseToken(tokenStr) diff --git a/http-gateway/config.yaml b/http-gateway/config.yaml index b2bf6276f..fe109f644 100644 --- a/http-gateway/config.yaml +++ b/http-gateway/config.yaml @@ -35,6 +35,9 @@ apis: keyFile: "/secrets/private/cert.key" certFile: "/secrets/public/cert.crt" useSystemCAPool: false + tokenTrustVerification: + enabled: true + cacheExpiration: 30s clients: grpcGateway: grpc: @@ -77,36 +80,36 @@ ui: clientID: "" audience: "" scopes: [] - grantTypes: [ "authorization_code" ] + grantTypes: ["authorization_code"] deviceOAuthClient: authority: "" clientID: "" audience: "" scopes: [] providerName: "" - grantTypes: [ "authorization_code" ] + grantTypes: ["authorization_code"] m2mOAuthClient: authority: "" clientID: "" audience: "" scopes: [] providerName: "" - grantTypes: [ "client_credentials" ] + grantTypes: ["client_credentials"] useJWTPrivateKey: true visibility: mainSidebar: - certificates : true - chatRoom : true - configuration : true - deviceProvisioning : true - devices : true - docs : true - pendingCommands : true - remoteClients : true - dashboard : false - integrations : false - deviceFirmwareUpdate : false - deviceLogs : false - apiTokens : false - schemaHub : false + certificates: true + chatRoom: true + configuration: true + deviceProvisioning: true + devices: true + docs: true + pendingCommands: true + remoteClients: true + dashboard: false + integrations: false + deviceFirmwareUpdate: false + deviceLogs: false + apiTokens: false + schemaHub: false snippetService: true diff --git a/identity-store/config.yaml b/identity-store/config.yaml index 1c1baa99d..528d54cb9 100644 --- a/identity-store/config.yaml +++ b/identity-store/config.yaml @@ -44,6 +44,9 @@ apis: keyFile: "/secrets/private/cert.key" certFile: "/secrets/public/cert.crt" useSystemCAPool: false + tokenTrustVerification: + enabled: true + cacheExpiration: 30s clients: eventBus: nats: diff --git a/m2m-oauth-server/config.yaml b/m2m-oauth-server/config.yaml index 9264ee7b1..f05c7b7bf 100644 --- a/m2m-oauth-server/config.yaml +++ b/m2m-oauth-server/config.yaml @@ -43,6 +43,8 @@ apis: keyFile: "/secrets/private/cert.key" certFile: "/secrets/public/cert.crt" useSystemCAPool: false + tokenTrustVerification: + enabled: false clients: storage: cleanUpDeletedTokens: 0 * * * * diff --git a/m2m-oauth-server/service/service.go b/m2m-oauth-server/service/service.go index 15543f098..139d5dca3 100644 --- a/m2m-oauth-server/service/service.go +++ b/m2m-oauth-server/service/service.go @@ -63,7 +63,7 @@ func createStore(ctx context.Context, config storeConfig.Config, fileWatcher *fs } func newHttpService(ctx context.Context, config HTTPConfig, validatorConfig validator.Config, getOpenIDConfiguration validator.GetOpenIDConfigurationFunc, tlsConfig certManagerServer.Config, ss *grpcService.M2MOAuthServiceServer, fileWatcher *fsnotify.Watcher, logger log.Logger, tracerProvider trace.TracerProvider) (*httpService.Service, func(), error) { - httpValidator, err := validator.New(ctx, validatorConfig, fileWatcher, logger, tracerProvider, validator.WithGetOpenIDConfiguration(getOpenIDConfiguration), validator.WithTrustVerificationDisabled()) + httpValidator, err := validator.New(ctx, validatorConfig, fileWatcher, logger, tracerProvider, validator.WithGetOpenIDConfiguration(getOpenIDConfiguration)) if err != nil { return nil, nil, fmt.Errorf("cannot create http validator: %w", err) } @@ -83,7 +83,7 @@ func newHttpService(ctx context.Context, config HTTPConfig, validatorConfig vali } func newGrpcService(ctx context.Context, config grpcService.Config, getOpenIDConfiguration validator.GetOpenIDConfigurationFunc, ss *grpcService.M2MOAuthServiceServer, fileWatcher *fsnotify.Watcher, logger log.Logger, tracerProvider trace.TracerProvider) (*grpcService.Service, func(), error) { - grpcValidator, err := validator.New(ctx, config.Authorization.Config, fileWatcher, logger, tracerProvider, validator.WithGetOpenIDConfiguration(getOpenIDConfiguration), validator.WithTrustVerificationDisabled()) + grpcValidator, err := validator.New(ctx, config.Authorization.Config, fileWatcher, logger, tracerProvider, validator.WithGetOpenIDConfiguration(getOpenIDConfiguration)) if err != nil { return nil, nil, fmt.Errorf("cannot create grpc validator: %w", err) } diff --git a/m2m-oauth-server/test/test.go b/m2m-oauth-server/test/test.go index 41173f1b3..b57b582a1 100644 --- a/m2m-oauth-server/test/test.go +++ b/m2m-oauth-server/test/test.go @@ -54,7 +54,7 @@ var JWTPrivateKeyOAuthClient = oauthsigner.Client{ AllowedScopes: nil, JWTPrivateKey: oauthsigner.PrivateKeyJWTConfig{ Enabled: true, - Authorization: config.MakeValidatorConfig(), + Authorization: MakeValidatorConfig(), }, } @@ -63,6 +63,16 @@ var OAuthClients = oauthsigner.OAuthClientsConfig{ &JWTPrivateKeyOAuthClient, } +func MakeValidatorConfig() validator.Config { + c := config.MakeValidatorConfig() + // tokens are verified by the m2m-oauth-server, so we want to disable the verification here to avoid infinite loop + // of token verification + c.TokenVerification = validator.TokenTrustVerificationConfig{ + Enabled: false, + } + return c +} + func MakeConfig(t require.TestingT) service.Config { var cfg service.Config @@ -81,6 +91,7 @@ func MakeConfig(t require.TestingT) service.Config { HTTP: config.MakeHttpClientConfig(), }, ) + cfg.APIs.GRPC.Authorization.Config = MakeValidatorConfig() cfg.Clients.Storage = MakeStoreConfig() cfg.OAuthSigner.PrivateKeyFile = urischeme.URIScheme(os.Getenv("M2M_OAUTH_SERVER_PRIVATE_KEY")) diff --git a/pkg/security/jwt/cache.go b/pkg/security/jwt/tokenCache.go similarity index 53% rename from pkg/security/jwt/cache.go rename to pkg/security/jwt/tokenCache.go index 5d9f5ef26..86cdc0d01 100644 --- a/pkg/security/jwt/cache.go +++ b/pkg/security/jwt/tokenCache.go @@ -3,8 +3,8 @@ package jwt import ( "time" + "github.com/google/uuid" "github.com/plgd-dev/go-coap/v3/pkg/cache" - pkgTime "github.com/plgd-dev/hub/v2/pkg/time" ) type TokenRecord struct { @@ -12,50 +12,56 @@ type TokenRecord struct { } type TokenCache struct { - cache *cache.Cache[string, TokenRecord] + cache *cache.Cache[uuid.UUID, TokenRecord] + expiration time.Duration } -func NewTokenCache() *TokenCache { +func NewTokenCache(expiration time.Duration) *TokenCache { return &TokenCache{ - cache: cache.NewCache[string, TokenRecord](), + cache: cache.NewCache[uuid.UUID, TokenRecord](), + expiration: expiration, } } -func getCacheKey(tokenID, issuer string) string { - return tokenID + "." + issuer +func getCacheKeyID(tokenID, issuer string) uuid.UUID { + return uuid.NewSHA1(uuid.NameSpaceURL, []byte(tokenID+"."+issuer)) } func (t *TokenCache) Load(tokenID, issuer string) (TokenRecord, bool) { - key := getCacheKey(tokenID, issuer) - value := t.cache.Load(key) + keyID := getCacheKeyID(tokenID, issuer) + value := t.cache.Load(keyID) if value == nil { return TokenRecord{}, false } return value.Data(), true } -func (t *TokenCache) AddBlacklisted(tokenID, issuer string, expiresAt int64) { - key := getCacheKey(tokenID, issuer) +func (t *TokenCache) AddBlacklisted(tokenID, issuer string, validUntil time.Time) { + // pkgTime.Unix(expiresAt, 0) + keyID := getCacheKeyID(tokenID, issuer) // use Replace //// if not in cache -> add //// if in cache -> //// - previously whitelisted -> replace //// - previously blacklisted -> replace is not necessary, but it doesn't break anything - t.cache.Replace(key, cache.NewElement(TokenRecord{Blacklisted: true}, pkgTime.Unix(expiresAt, 0), nil)) + t.cache.Replace(keyID, cache.NewElement(TokenRecord{Blacklisted: true}, validUntil, nil)) } func (t *TokenCache) AddWhitelisted(tokenID, issuer string) bool { - key := getCacheKey(tokenID, issuer) + keyID := getCacheKeyID(tokenID, issuer) + var validUntil time.Time + if t.expiration > 0 { + validUntil = time.Now().Add(t.expiration) + } // use ReplaceWithFunc //// if not in cache -> add //// if in cache -> //// - previously whitelisted -> replace the record to extend the expiration time //// - previously blacklisted -> cannot replace, blacklisted tokens stay blacklisted until expiration - //// TODO: configure expiration interval -> for now use 10s - newTr := cache.NewElement(TokenRecord{Blacklisted: false}, time.Now().Add(10*time.Second), nil) + newTr := cache.NewElement(TokenRecord{Blacklisted: false}, validUntil, nil) var actual *cache.Element[TokenRecord] now := time.Now() - t.cache.ReplaceWithFunc(key, func(oldValue *cache.Element[TokenRecord], oldLoaded bool) (*cache.Element[TokenRecord], bool) { + t.cache.ReplaceWithFunc(keyID, func(oldValue *cache.Element[TokenRecord], oldLoaded bool) (*cache.Element[TokenRecord], bool) { if oldLoaded && oldValue.Data().Blacklisted { // blacklisted tokens stay blacklisted until expiration if !oldValue.IsExpired(now) { diff --git a/pkg/security/jwt/validator.go b/pkg/security/jwt/validator.go index 2df5d6e64..841794402 100644 --- a/pkg/security/jwt/validator.go +++ b/pkg/security/jwt/validator.go @@ -6,12 +6,14 @@ import ( "fmt" "net/http" "net/url" + "time" "github.com/golang-jwt/jwt/v5" "github.com/plgd-dev/hub/v2/m2m-oauth-server/pb" "github.com/plgd-dev/hub/v2/pkg/log" pkgHttpPb "github.com/plgd-dev/hub/v2/pkg/net/http/pb" pkgHttpUri "github.com/plgd-dev/hub/v2/pkg/net/http/uri" + pkgTime "github.com/plgd-dev/hub/v2/pkg/time" ) type KeyCacheI interface { @@ -47,8 +49,9 @@ var ( ) type config struct { - clients map[string]*Client - verifyTrust bool + verifyTrust bool + clients map[string]*Client + cacheExpiration time.Duration } type Option interface { @@ -61,10 +64,11 @@ func (o optionFunc) apply(c *config) { o(c) } -func WithTrustVerification(clients map[string]*Client) Option { +func WithTrustVerification(clients map[string]*Client, cacheExpiration time.Duration) Option { return optionFunc(func(c *config) { c.verifyTrust = true c.clients = clients + c.cacheExpiration = cacheExpiration }) } @@ -73,13 +77,16 @@ func NewValidator(keyCache KeyCacheI, logger log.Logger, opts ...Option) *Valida for _, opt := range opts { opt.apply(c) } - return &Validator{ + v := &Validator{ keys: keyCache, logger: logger, - tokenCache: NewTokenCache(), clients: c.clients, verifyTrust: c.verifyTrust, } + if c.verifyTrust { + v.tokenCache = NewTokenCache(c.cacheExpiration) + } + return v } func errParseToken(err error) error { @@ -143,10 +150,6 @@ func (v *Validator) checkTrust(ctx context.Context, token string, claims jwt.Cla } }() - if resp.StatusCode != http.StatusOK { - return fmt.Errorf("unexpected statusCode %v", resp.StatusCode) - } - var gotToken pb.Token err = pkgHttpPb.Unmarshal(resp.StatusCode, resp.Body, &gotToken) if err != nil { @@ -157,9 +160,10 @@ func (v *Validator) checkTrust(ctx context.Context, token string, claims jwt.Cla Blacklisted: gotToken.GetBlacklisted().GetFlag(), } if tr.Blacklisted { - v.tokenCache.AddBlacklisted(tokenID, issuer, gotToken.GetExpiration()) + v.tokenCache.AddBlacklisted(tokenID, issuer, pkgTime.Unix(gotToken.GetExpiration(), 0)) return ErrBlackListedToken } + //// TODO: configure expiration interval -> for now use 10s if !v.tokenCache.AddWhitelisted(tokenID, issuer) { return ErrBlackListedToken } @@ -170,6 +174,10 @@ func (v *Validator) Parse(token string) (jwt.MapClaims, error) { return v.ParseWithContext(context.Background(), token) } +func errVerifyTrust(err error) error { + return fmt.Errorf("%w: %w", ErrCannotVerifyTrust, err) +} + func (v *Validator) ParseWithContext(ctx context.Context, token string) (jwt.MapClaims, error) { if token == "" { return nil, ErrMissingToken @@ -189,7 +197,7 @@ func (v *Validator) ParseWithContext(ctx context.Context, token string) (jwt.Map } if v.verifyTrust { if err = v.checkTrust(ctx, token, claims); err != nil { - return nil, fmt.Errorf("%w: %w", ErrCannotVerifyTrust, err) + return nil, errVerifyTrust(err) } } return claims, nil @@ -206,7 +214,7 @@ func (v *Validator) ParseWithClaims(ctx context.Context, token string, claims jw } if v.verifyTrust { if err = v.checkTrust(ctx, token, claims); err != nil { - return fmt.Errorf("%w: %w", ErrCannotVerifyTrust, err) + return errVerifyTrust(err) } } return nil diff --git a/pkg/security/jwt/validator/config.go b/pkg/security/jwt/validator/config.go index 6bad3555a..9a971f1c5 100644 --- a/pkg/security/jwt/validator/config.go +++ b/pkg/security/jwt/validator/config.go @@ -2,6 +2,7 @@ package validator import ( "fmt" + "time" "github.com/plgd-dev/hub/v2/pkg/net/http/client" ) @@ -21,11 +22,24 @@ func (c *AuthorityConfig) Validate() error { return nil } +type TokenTrustVerificationConfig struct { + Enabled bool `yaml:"enabled,omitempty" json:"enabled,omitempty"` + CacheExpiration time.Duration `yaml:"cacheExpiration,omitempty" json:"cacheExpiration,omitempty"` +} + +func (c *TokenTrustVerificationConfig) Validate() error { + if c.Enabled && c.CacheExpiration == 0 { + return fmt.Errorf("cacheExpiration('%v')", c.CacheExpiration) + } + return nil +} + type Config struct { - Audience string `yaml:"audience" json:"audience"` - Endpoints []AuthorityConfig `yaml:"endpoints" json:"endpoints"` - Authority *string `yaml:"authority,omitempty" json:"authority,omitempty"` // deprecated - HTTP *client.Config `yaml:"http,omitempty" json:"http,omitempty"` // deprecated + Audience string `yaml:"audience" json:"audience"` + Endpoints []AuthorityConfig `yaml:"endpoints" json:"endpoints"` + TokenVerification TokenTrustVerificationConfig `yaml:"tokenTrustVerification,omitempty" json:"tokenTrustVerification,omitempty"` + Authority *string `yaml:"authority,omitempty" json:"authority,omitempty"` // deprecated + HTTP *client.Config `yaml:"http,omitempty" json:"http,omitempty"` // deprecated } func (c *Config) Validate() error { @@ -45,5 +59,5 @@ func (c *Config) Validate() error { return fmt.Errorf("endpoints[%v].%w", i, err) } } - return nil + return c.TokenVerification.Validate() } diff --git a/pkg/security/jwt/validator/validator.go b/pkg/security/jwt/validator/validator.go index c33096165..a9c40709a 100644 --- a/pkg/security/jwt/validator/validator.go +++ b/pkg/security/jwt/validator/validator.go @@ -44,8 +44,7 @@ func (v *Validator) GetParser() *jwtValidator.Validator { type GetOpenIDConfigurationFunc func(ctx context.Context, c *http.Client, authority string) (openid.Config, error) type Options struct { - getOpenIDConfiguration GetOpenIDConfigurationFunc - trustVerificationDisabled bool + getOpenIDConfiguration GetOpenIDConfigurationFunc } func WithGetOpenIDConfiguration(f GetOpenIDConfigurationFunc) func(o *Options) { @@ -54,12 +53,6 @@ func WithGetOpenIDConfiguration(f GetOpenIDConfigurationFunc) func(o *Options) { } } -func WithTrustVerificationDisabled() func(o *Options) { - return func(o *Options) { - o.trustVerificationDisabled = true - } -} - func New(ctx context.Context, config Config, fileWatcher *fsnotify.Watcher, logger log.Logger, tracerProvider trace.TracerProvider, opts ...func(o *Options)) (*Validator, error) { options := Options{ getOpenIDConfiguration: openid.GetConfiguration, @@ -95,14 +88,14 @@ func New(ctx context.Context, config Config, fileWatcher *fsnotify.Watcher, logg issuer := pkgHttpUri.CanonicalURI(openIDCfg.Issuer) keys.Add(issuer, openIDCfg.JWKSURL, httpClient.HTTP()) openIDConfigurations = append(openIDConfigurations, openIDCfg) - if !options.trustVerificationDisabled && openIDCfg.PlgdTokensEndpoint != "" { + if config.TokenVerification.Enabled && openIDCfg.PlgdTokensEndpoint != "" { clients[issuer] = jwtValidator.NewClient(httpClient.HTTP(), openIDCfg.PlgdTokensEndpoint) } } var vopts []jwtValidator.Option if len(clients) > 0 { - vopts = append(vopts, jwtValidator.WithTrustVerification(clients)) + vopts = append(vopts, jwtValidator.WithTrustVerification(clients, config.TokenVerification.CacheExpiration)) } return &Validator{ diff --git a/resource-aggregate/config.yaml b/resource-aggregate/config.yaml index 9a33922b2..fd1185ee6 100644 --- a/resource-aggregate/config.yaml +++ b/resource-aggregate/config.yaml @@ -45,6 +45,9 @@ apis: keyFile: "/secrets/private/cert.key" certFile: "/secrets/public/cert.crt" useSystemCAPool: false + tokenTrustVerification: + enabled: true + cacheExpiration: 30s clients: eventBus: nats: diff --git a/resource-directory/config.yaml b/resource-directory/config.yaml index 502ccd8c3..0f68f9d03 100644 --- a/resource-directory/config.yaml +++ b/resource-directory/config.yaml @@ -45,6 +45,9 @@ apis: keyFile: "/secrets/private/cert.key" certFile: "/secrets/public/cert.crt" useSystemCAPool: false + tokenTrustVerification: + enabled: true + cacheExpiration: 30s clients: eventBus: # number of routines to process events in projection diff --git a/snippet-service/config.yaml b/snippet-service/config.yaml index 32976d9a2..177c0890e 100644 --- a/snippet-service/config.yaml +++ b/snippet-service/config.yaml @@ -44,6 +44,9 @@ apis: keyFile: "/secrets/private/cert.key" certFile: "/secrets/public/cert.crt" useSystemCAPool: false + tokenTrustVerification: + enabled: true + cacheExpiration: 30s http: address: "0.0.0.0:9101" readTimeout: 8s diff --git a/test/config/config.go b/test/config/config.go index 8b2cd5475..32d8a82ee 100644 --- a/test/config/config.go +++ b/test/config/config.go @@ -63,6 +63,7 @@ const ( OPENTELEMETRY_COLLECTOR_HOST = "localhost:55690" TRUE_STRING = "true" M2M_OAUTH_PRIVATE_KEY_CLIENT_ID = "JWTPrivateKeyClient" + VALIDATOR_CACHE_EXPIRATION = time.Second * 10 ) var ( @@ -305,6 +306,10 @@ func MakeValidatorConfig() validator.Config { HTTP: MakeHttpClientConfig(), }, }, + TokenVerification: validator.TokenTrustVerificationConfig{ + Enabled: true, + CacheExpiration: VALIDATOR_CACHE_EXPIRATION, + }, } } From 433b803354167b7d95e7d8bf38241a8fae0337d1 Mon Sep 17 00:00:00 2001 From: Daniel Adam Date: Thu, 25 Jul 2024 20:16:19 +0200 Subject: [PATCH 29/31] Ensure that for parallel token verification only a single requests is sent --- grpc-gateway/service/getResources_test.go | 25 +- pkg/security/jwt/tokenCache.go | 286 +++++++++++++++++----- pkg/security/jwt/validator.go | 89 +------ pkg/sync/task/future/future_test.go | 11 +- 4 files changed, 262 insertions(+), 149 deletions(-) diff --git a/grpc-gateway/service/getResources_test.go b/grpc-gateway/service/getResources_test.go index 99e66358f..65dc6d074 100644 --- a/grpc-gateway/service/getResources_test.go +++ b/grpc-gateway/service/getResources_test.go @@ -5,6 +5,7 @@ import ( "crypto/tls" "errors" "io" + "sync" "testing" "time" @@ -12,6 +13,7 @@ import ( "github.com/plgd-dev/hub/v2/grpc-gateway/pb" grpcgwTest "github.com/plgd-dev/hub/v2/grpc-gateway/test" m2mOauthTest "github.com/plgd-dev/hub/v2/m2m-oauth-server/test" + "github.com/plgd-dev/hub/v2/pkg/log" pkgGrpc "github.com/plgd-dev/hub/v2/pkg/net/grpc" pkgJwt "github.com/plgd-dev/hub/v2/pkg/security/jwt" "github.com/plgd-dev/hub/v2/resource-aggregate/commands" @@ -22,6 +24,7 @@ import ( oauthTest "github.com/plgd-dev/hub/v2/test/oauth-server/test" pbTest "github.com/plgd-dev/hub/v2/test/pb" "github.com/plgd-dev/hub/v2/test/service" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "google.golang.org/grpc" "google.golang.org/grpc/credentials" @@ -213,7 +216,7 @@ func TestRequestHandlerGetResources(t *testing.T) { } } -func TestRequestHandlerGetResourcesWithBlacklistedToken(t *testing.T) { +func TestRequestHandlerGetResourcesWithM2MTokenVerification(t *testing.T) { deviceID := test.MustFindDeviceByName(test.TestDeviceName) ctx, cancel := context.WithTimeout(context.Background(), config.TEST_TIMEOUT*100) @@ -221,6 +224,7 @@ func TestRequestHandlerGetResourcesWithBlacklistedToken(t *testing.T) { grpcCfg := grpcgwTest.MakeConfig(t) grpcCfg.APIs.GRPC.Authorization.TokenVerification.CacheExpiration = time.Second * 2 + grpcCfg.Log.Level = log.DebugLevel tearDown := service.SetUp(ctx, t, service.WithGRPCGWConfig(grpcCfg)) defer tearDown() validTokenStr := oauthTest.GetDefaultAccessToken(t) @@ -271,9 +275,28 @@ func TestRequestHandlerGetResourcesWithBlacklistedToken(t *testing.T) { _, err = getResources(pkgGrpc.CtxWithToken(ctx, tokenStr), c, req) require.ErrorContains(t, err, pkgJwt.ErrBlackListedToken.Error()) + // repeated requests should still fail, but use the cache + _, err = getResources(pkgGrpc.CtxWithToken(ctx, tokenStr), c, req) + require.ErrorContains(t, err, pkgJwt.ErrBlackListedToken.Error()) + // non-blacklisted tokens should still work values, err = getResources(pkgGrpc.CtxWithToken(ctx, validTokenStr), c, req) require.NoError(t, err) require.NotEmpty(t, values) pbTest.CmpResourceValues(t, []*pb.Resource{exp.Clone()}, values) + + // parallel requests -> cache should be used, only a single request should be made + var wg sync.WaitGroup + newValidTokenStr := m2mOauthTest.GetDefaultAccessToken(t) + for range 5 { + wg.Add(1) + go func() { + defer wg.Done() + values, err := getResources(pkgGrpc.CtxWithToken(ctx, newValidTokenStr), c, req) + assert.NoError(t, err) + assert.NotEmpty(t, values) + pbTest.CmpResourceValues(t, []*pb.Resource{exp.Clone()}, values) + }() + } + wg.Wait() } diff --git a/pkg/security/jwt/tokenCache.go b/pkg/security/jwt/tokenCache.go index 86cdc0d01..e8b274583 100644 --- a/pkg/security/jwt/tokenCache.go +++ b/pkg/security/jwt/tokenCache.go @@ -1,76 +1,250 @@ package jwt import ( + "context" + "fmt" + "net/http" + "net/url" + "sync" "time" + "github.com/golang-jwt/jwt/v5" "github.com/google/uuid" - "github.com/plgd-dev/go-coap/v3/pkg/cache" + "github.com/plgd-dev/hub/v2/m2m-oauth-server/pb" + "github.com/plgd-dev/hub/v2/pkg/log" + pkgHttpPb "github.com/plgd-dev/hub/v2/pkg/net/http/pb" + "github.com/plgd-dev/hub/v2/pkg/sync/task/future" + "go.uber.org/atomic" ) -type TokenRecord struct { - Blacklisted bool +type Client struct { + *http.Client + tokenEndpoint string +} + +func NewClient(client *http.Client, tokenEndpoint string) *Client { + return &Client{ + Client: client, + tokenEndpoint: tokenEndpoint, + } +} + +type tokenRecord struct { + blacklisted bool + validUntil atomic.Time + onExpire func(uuid.UUID) +} + +func newTokenRecord(blacklisted bool, validUntil time.Time, onExpire func(uuid.UUID)) *tokenRecord { + t := tokenRecord{ + blacklisted: blacklisted, + onExpire: onExpire, + } + t.validUntil.Store(validUntil) + return &t +} + +func (t *tokenRecord) IsExpired(now time.Time) bool { + value := t.validUntil.Load() + if value.IsZero() { + return false + } + return now.After(value) +} + +type tokenOrFuture struct { + tokenOrFuture interface{} +} + +func makeTokenOrFuture(token *tokenRecord, tokenFuture *future.Future) tokenOrFuture { + if token != nil { + return tokenOrFuture{token} + } + return tokenOrFuture{tokenFuture} +} + +func (tf *tokenOrFuture) Get(ctx context.Context) (*tokenRecord, error) { + if tr, ok := tf.tokenOrFuture.(*tokenRecord); ok { + return tr, nil + } + tv, err := tf.tokenOrFuture.(*future.Future).Get(ctx) + if err != nil { + return nil, err + } + return tv.(*tokenRecord), nil +} + +type tokenIssuerCache struct { + client *http.Client + tokenEndpoint string + tokens map[uuid.UUID]tokenOrFuture + mutex sync.Mutex +} + +func newTokenIssuerCache(client *Client) *tokenIssuerCache { + return &tokenIssuerCache{ + client: client.Client, + tokenEndpoint: client.tokenEndpoint, + tokens: make(map[uuid.UUID]tokenOrFuture), + } +} + +func (tc *tokenIssuerCache) getValidTokenRecordOrFuture(tokenID uuid.UUID) (tokenOrFuture, future.SetFunc) { + tc.mutex.Lock() + defer tc.mutex.Unlock() + + tf, ok := tc.tokens[tokenID] + if !ok { + f, set := future.New() + newTf := makeTokenOrFuture(nil, f) + tc.tokens[tokenID] = newTf + return newTf, set + } + + if tr, ok := tf.tokenOrFuture.(*tokenRecord); ok && tr.IsExpired(time.Now()) { + if tr.onExpire != nil { + tr.onExpire(tokenID) + } + f, set := future.New() + newTr := makeTokenOrFuture(nil, f) + tc.tokens[tokenID] = newTr + return newTr, set + } + return tf, nil +} + +func (tc *tokenIssuerCache) removeToken(tokenID uuid.UUID) { + delete(tc.tokens, tokenID) +} + +func (tc *tokenIssuerCache) setTokenRecord(tokenID uuid.UUID, tr *tokenRecord) { + tf := makeTokenOrFuture(tr, nil) + tc.mutex.Lock() + defer tc.mutex.Unlock() + tc.tokens[tokenID] = tf } type TokenCache struct { - cache *cache.Cache[uuid.UUID, TokenRecord] expiration time.Duration + cache map[string]*tokenIssuerCache + logger log.Logger } -func NewTokenCache(expiration time.Duration) *TokenCache { - return &TokenCache{ - cache: cache.NewCache[uuid.UUID, TokenRecord](), +func NewTokenCache(clients map[string]*Client, expiration time.Duration, logger log.Logger) *TokenCache { + tc := &TokenCache{ expiration: expiration, + logger: logger, + } + if len(clients) > 0 { + tc.cache = make(map[string]*tokenIssuerCache) + for issuer, client := range clients { + tc.cache[issuer] = newTokenIssuerCache(client) + } + } + return tc +} + +func (t *TokenCache) getValidUntil(token *pb.Token) time.Time { + blacklisted := token.GetBlacklisted().GetFlag() + if blacklisted { + expiration := token.GetExpiration() + if expiration == 0 { + return time.Time{} + } + return time.Unix(expiration, 0) + } + + if t.expiration == 0 { + return time.Time{} } + return time.Now().Add(t.expiration) } -func getCacheKeyID(tokenID, issuer string) uuid.UUID { - return uuid.NewSHA1(uuid.NameSpaceURL, []byte(tokenID+"."+issuer)) -} - -func (t *TokenCache) Load(tokenID, issuer string) (TokenRecord, bool) { - keyID := getCacheKeyID(tokenID, issuer) - value := t.cache.Load(keyID) - if value == nil { - return TokenRecord{}, false - } - return value.Data(), true -} - -func (t *TokenCache) AddBlacklisted(tokenID, issuer string, validUntil time.Time) { - // pkgTime.Unix(expiresAt, 0) - keyID := getCacheKeyID(tokenID, issuer) - // use Replace - //// if not in cache -> add - //// if in cache -> - //// - previously whitelisted -> replace - //// - previously blacklisted -> replace is not necessary, but it doesn't break anything - t.cache.Replace(keyID, cache.NewElement(TokenRecord{Blacklisted: true}, validUntil, nil)) -} - -func (t *TokenCache) AddWhitelisted(tokenID, issuer string) bool { - keyID := getCacheKeyID(tokenID, issuer) - var validUntil time.Time - if t.expiration > 0 { - validUntil = time.Now().Add(t.expiration) - } - // use ReplaceWithFunc - //// if not in cache -> add - //// if in cache -> - //// - previously whitelisted -> replace the record to extend the expiration time - //// - previously blacklisted -> cannot replace, blacklisted tokens stay blacklisted until expiration - newTr := cache.NewElement(TokenRecord{Blacklisted: false}, validUntil, nil) - var actual *cache.Element[TokenRecord] - now := time.Now() - t.cache.ReplaceWithFunc(keyID, func(oldValue *cache.Element[TokenRecord], oldLoaded bool) (*cache.Element[TokenRecord], bool) { - if oldLoaded && oldValue.Data().Blacklisted { - // blacklisted tokens stay blacklisted until expiration - if !oldValue.IsExpired(now) { - actual = oldValue - return oldValue, false - } +func (t *TokenCache) VerifyTrust(ctx context.Context, issuer, token string, tokenClaims jwt.Claims) error { + ic, ok := t.cache[issuer] + if !ok { + t.logger.Debugf("client not set for issuer %v, trust verification skipped", issuer) + return nil + } + tokenID, err := getID(tokenClaims) + if err != nil { + return err + } + tokenUUID, err := uuid.Parse(tokenID) + if err != nil { + return err + } + t.logger.Debugf("checking trust for issuer(%v) for token(id=%s)", issuer, tokenID) + tf, set := ic.getValidTokenRecordOrFuture(tokenUUID) + if set == nil { + tv, errG := tf.Get(ctx) + if errG != nil { + return errG + } + t.logger.Debugf("token(id=%s) found in cache (blacklisted=%v, validUntil=%v)", tokenID, tv.blacklisted, tv.validUntil.Load()) + if tv.blacklisted { + return ErrBlackListedToken + } + return nil + } + + uri, err := url.Parse(ic.tokenEndpoint) + if err != nil { + ic.removeToken(tokenUUID) + set(nil, err) + return fmt.Errorf("cannot parse tokenEndpoint %v: %w", ic.tokenEndpoint, err) + } + query := uri.Query() + query.Add("idFilter", tokenID) + query.Add("includeBlacklisted", "true") + uri.RawQuery = query.Encode() + + t.logger.Debugf("requesting token(id=%s) verification by m2m", tokenID) + req, err := http.NewRequestWithContext(ctx, http.MethodGet, uri.String(), nil) + if err != nil { + ic.removeToken(tokenUUID) + set(nil, err) + return fmt.Errorf("cannot create request for GET %v: %w", uri.String(), err) + } + + req.Header.Set("Accept", "application/protojson") + req.Header.Set("Authorization", "bearer "+token) + resp, err := ic.client.Do(req) + if err != nil { + ic.removeToken(tokenUUID) + set(nil, err) + return fmt.Errorf("cannot send request for GET %v: %w", ic.tokenEndpoint, err) + } + defer func() { + if errC := resp.Body.Close(); errC != nil { + t.logger.Errorf("cannot close response body: %w", errC) + } + }() + + var gotToken pb.Token + err = pkgHttpPb.Unmarshal(resp.StatusCode, resp.Body, &gotToken) + if err != nil { + ic.removeToken(tokenUUID) + set(nil, err) + return err + } + + var onExpire func(uuid.UUID) + if t.logger.Check(log.DebugLevel) { + onExpire = func(tid uuid.UUID) { + t.logger.Debugf("token(id=%s) expired", tid.String()) } - actual = newTr - return newTr, false - }) - return actual == newTr + } + + blacklisted := gotToken.GetBlacklisted().GetFlag() + validUntil := t.getValidUntil(&gotToken) + tr := newTokenRecord(blacklisted, validUntil, onExpire) + t.logger.Debugf("token(id=%s) set (blacklisted=%v, validUntil=%v)", tokenID, blacklisted, validUntil) + ic.setTokenRecord(tokenUUID, tr) + set(tr, nil) + + if blacklisted { + return ErrBlackListedToken + } + return nil } diff --git a/pkg/security/jwt/validator.go b/pkg/security/jwt/validator.go index 841794402..77cfb6498 100644 --- a/pkg/security/jwt/validator.go +++ b/pkg/security/jwt/validator.go @@ -4,16 +4,11 @@ import ( "context" "errors" "fmt" - "net/http" - "net/url" "time" "github.com/golang-jwt/jwt/v5" - "github.com/plgd-dev/hub/v2/m2m-oauth-server/pb" "github.com/plgd-dev/hub/v2/pkg/log" - pkgHttpPb "github.com/plgd-dev/hub/v2/pkg/net/http/pb" pkgHttpUri "github.com/plgd-dev/hub/v2/pkg/net/http/uri" - pkgTime "github.com/plgd-dev/hub/v2/pkg/time" ) type KeyCacheI interface { @@ -21,22 +16,8 @@ type KeyCacheI interface { GetOrFetchKeyWithContext(ctx context.Context, token *jwt.Token) (interface{}, error) } -type Client struct { - *http.Client - tokenEndpoint string -} - -func NewClient(client *http.Client, tokenEndpoint string) *Client { - return &Client{ - Client: client, - tokenEndpoint: tokenEndpoint, - } -} - type Validator struct { keys KeyCacheI - logger log.Logger - clients map[string]*Client verifyTrust bool tokenCache *TokenCache } @@ -79,12 +60,10 @@ func NewValidator(keyCache KeyCacheI, logger log.Logger, opts ...Option) *Valida } v := &Validator{ keys: keyCache, - logger: logger, - clients: c.clients, verifyTrust: c.verifyTrust, } if c.verifyTrust { - v.tokenCache = NewTokenCache(c.cacheExpiration) + v.tokenCache = NewTokenCache(c.clients, c.cacheExpiration, logger) } return v } @@ -103,71 +82,7 @@ func (v *Validator) checkTrust(ctx context.Context, token string, claims jwt.Cla return err } issuer = pkgHttpUri.CanonicalURI(issuer) - - client, ok := v.clients[issuer] - if !ok { - v.logger.Debugf("client not set for issuer %v, trust verification skipped", issuer) - return nil - } - - tokenID, err := getID(claims) - if err != nil { - return err - } - - tr, ok := v.tokenCache.Load(tokenID, issuer) - if ok { - if tr.Blacklisted { - return ErrBlackListedToken - } - return nil - } - - uri, err := url.Parse(client.tokenEndpoint) - if err != nil { - return fmt.Errorf("cannot parse tokenEndpoint %v: %w", client.tokenEndpoint, err) - } - query := uri.Query() - query.Add("idFilter", tokenID) - query.Add("includeBlacklisted", "true") - uri.RawQuery = query.Encode() - - v.logger.Infof("checking trust for issuer %v for token(id=%s)", issuer, tokenID) - req, err := http.NewRequestWithContext(ctx, http.MethodGet, uri.String(), nil) - if err != nil { - return fmt.Errorf("cannot create request for GET %v: %w", uri.String(), err) - } - - req.Header.Set("Accept", "application/protojson") - req.Header.Set("Authorization", "bearer "+token) - resp, err := client.Do(req) - if err != nil { - return fmt.Errorf("cannot send request for GET %v: %w", client.tokenEndpoint, err) - } - defer func() { - if errC := resp.Body.Close(); errC != nil { - v.logger.Errorf("cannot close response body: %w", errC) - } - }() - - var gotToken pb.Token - err = pkgHttpPb.Unmarshal(resp.StatusCode, resp.Body, &gotToken) - if err != nil { - return err - } - - tr = TokenRecord{ - Blacklisted: gotToken.GetBlacklisted().GetFlag(), - } - if tr.Blacklisted { - v.tokenCache.AddBlacklisted(tokenID, issuer, pkgTime.Unix(gotToken.GetExpiration(), 0)) - return ErrBlackListedToken - } - //// TODO: configure expiration interval -> for now use 10s - if !v.tokenCache.AddWhitelisted(tokenID, issuer) { - return ErrBlackListedToken - } - return nil + return v.tokenCache.VerifyTrust(ctx, issuer, token, claims) } func (v *Validator) Parse(token string) (jwt.MapClaims, error) { diff --git a/pkg/sync/task/future/future_test.go b/pkg/sync/task/future/future_test.go index 452a9edd9..cd34eb6f0 100644 --- a/pkg/sync/task/future/future_test.go +++ b/pkg/sync/task/future/future_test.go @@ -7,11 +7,12 @@ import ( "testing" "time" - "github.com/plgd-dev/hub/v2/test/config" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) +const TEST_TIMEOUT = time.Second * 10 + func TestFutureReady(t *testing.T) { fut, set := New() require.False(t, fut.Ready()) @@ -50,7 +51,7 @@ func TestFutureGetTimeout(t *testing.T) { func TestFutureGetMultithreaded(t *testing.T) { fut, set := New() - ctx, cancel := context.WithTimeout(context.Background(), config.TEST_TIMEOUT) + ctx, cancel := context.WithTimeout(context.Background(), TEST_TIMEOUT) defer cancel() const val = "test" @@ -72,7 +73,7 @@ func TestFutureGetMultithreaded(t *testing.T) { func TestFutureGetAfterSet(t *testing.T) { fut, set := New() - ctx, cancel := context.WithTimeout(context.Background(), config.TEST_TIMEOUT) + ctx, cancel := context.WithTimeout(context.Background(), TEST_TIMEOUT) defer cancel() const val = "test" @@ -84,7 +85,7 @@ func TestFutureGetAfterSet(t *testing.T) { } func TestFutureSetFromWorkerRoutine(t *testing.T) { - ctx, cancel := context.WithTimeout(context.Background(), config.TEST_TIMEOUT) + ctx, cancel := context.WithTimeout(context.Background(), TEST_TIMEOUT) defer cancel() fut, set := New() @@ -100,7 +101,7 @@ func TestFutureSetFromWorkerRoutine(t *testing.T) { } func TestFutureRepeatedSet(t *testing.T) { - ctx, cancel := context.WithTimeout(context.Background(), config.TEST_TIMEOUT) + ctx, cancel := context.WithTimeout(context.Background(), TEST_TIMEOUT) defer cancel() fut, set := New() From 90f952abcef0cbc4326a99d4db4254cc2c5f3416 Mon Sep 17 00:00:00 2001 From: Daniel Adam Date: Thu, 25 Jul 2024 22:12:17 +0200 Subject: [PATCH 30/31] Periodically clean-up token cache --- grpc-gateway/service/getResources_test.go | 37 +++++-- pkg/security/jwt/tokenCache.go | 113 +++++++++++++--------- pkg/security/jwt/validator.go | 12 ++- pkg/security/jwt/validator/validator.go | 2 +- 4 files changed, 108 insertions(+), 56 deletions(-) diff --git a/grpc-gateway/service/getResources_test.go b/grpc-gateway/service/getResources_test.go index 65dc6d074..8ad37813f 100644 --- a/grpc-gateway/service/getResources_test.go +++ b/grpc-gateway/service/getResources_test.go @@ -218,8 +218,7 @@ func TestRequestHandlerGetResources(t *testing.T) { func TestRequestHandlerGetResourcesWithM2MTokenVerification(t *testing.T) { deviceID := test.MustFindDeviceByName(test.TestDeviceName) - - ctx, cancel := context.WithTimeout(context.Background(), config.TEST_TIMEOUT*100) + ctx, cancel := context.WithTimeout(context.Background(), config.TEST_TIMEOUT) defer cancel() grpcCfg := grpcgwTest.MakeConfig(t) @@ -272,6 +271,7 @@ func TestRequestHandlerGetResourcesWithM2MTokenVerification(t *testing.T) { tokenID, err := token.GetID() require.NoError(t, err) m2mOauthTest.BlacklistTokens(ctx, t, []string{tokenID}, validTokenStr) + // request should fail _, err = getResources(pkgGrpc.CtxWithToken(ctx, tokenStr), c, req) require.ErrorContains(t, err, pkgJwt.ErrBlackListedToken.Error()) @@ -285,17 +285,38 @@ func TestRequestHandlerGetResourcesWithM2MTokenVerification(t *testing.T) { require.NotEmpty(t, values) pbTest.CmpResourceValues(t, []*pb.Resource{exp.Clone()}, values) - // parallel requests -> cache should be used, only a single request should be made + // parallel whitelisted requests -> cache should be used, only a single request should be made var wg sync.WaitGroup - newValidTokenStr := m2mOauthTest.GetDefaultAccessToken(t) + tokenStr2 := m2mOauthTest.GetDefaultAccessToken(t) + for range 5 { + wg.Add(1) + go func() { + defer wg.Done() + values2, err2 := getResources(pkgGrpc.CtxWithToken(ctx, tokenStr2), c, req) + assert.NoError(t, err2) + assert.NotEmpty(t, values2) + pbTest.CmpResourceValues(t, []*pb.Resource{exp.Clone()}, values2) + }() + } + wg.Wait() + + // wait for expiration + time.Sleep(grpcCfg.APIs.GRPC.Authorization.TokenVerification.CacheExpiration) + + // blacklist the token + tokenStr3 := m2mOauthTest.GetDefaultAccessToken(t) + token, err = pkgJwt.ParseToken(tokenStr3) + require.NoError(t, err) + tokenID, err = token.GetID() + require.NoError(t, err) + m2mOauthTest.BlacklistTokens(ctx, t, []string{tokenID}, validTokenStr) + // parallel blacklisted requests -> cache should be used, only a single request should be made for range 5 { wg.Add(1) go func() { defer wg.Done() - values, err := getResources(pkgGrpc.CtxWithToken(ctx, newValidTokenStr), c, req) - assert.NoError(t, err) - assert.NotEmpty(t, values) - pbTest.CmpResourceValues(t, []*pb.Resource{exp.Clone()}, values) + _, err2 := getResources(pkgGrpc.CtxWithToken(ctx, tokenStr3), c, req) + assert.ErrorContains(t, err2, pkgJwt.ErrBlackListedToken.Error()) }() } wg.Wait() diff --git a/pkg/security/jwt/tokenCache.go b/pkg/security/jwt/tokenCache.go index e8b274583..97b7d9a92 100644 --- a/pkg/security/jwt/tokenCache.go +++ b/pkg/security/jwt/tokenCache.go @@ -124,6 +124,58 @@ func (tc *tokenIssuerCache) setTokenRecord(tokenID uuid.UUID, tr *tokenRecord) { tc.tokens[tokenID] = tf } +func (tc *tokenIssuerCache) checkExpirations(now time.Time) { + expired := make(map[uuid.UUID]*tokenRecord, 8) + tc.mutex.Lock() + for tokenID, tf := range tc.tokens { + if tr, ok := tf.tokenOrFuture.(*tokenRecord); ok && tr.IsExpired(now) { + if tr.onExpire != nil { + expired[tokenID] = tr + } + delete(tc.tokens, tokenID) + } + } + tc.mutex.Unlock() + for tokenID, tr := range expired { + tr.onExpire(tokenID) + } +} + +func (tc *tokenIssuerCache) verifyTokenByRequest(ctx context.Context, token, tokenID string) (*pb.Token, error) { + uri, err := url.Parse(tc.tokenEndpoint) + if err != nil { + return nil, fmt.Errorf("cannot parse tokenEndpoint %v: %w", tc.tokenEndpoint, err) + } + query := uri.Query() + query.Add("idFilter", tokenID) + query.Add("includeBlacklisted", "true") + uri.RawQuery = query.Encode() + + req, err := http.NewRequestWithContext(ctx, http.MethodGet, uri.String(), nil) + if err != nil { + return nil, fmt.Errorf("cannot create request for GET %v: %w", uri.String(), err) + } + + // TODO: "Accept" -> pktNetHttp.AcceptHeaderKey: import cycle, must move to another package + req.Header.Set("Accept", "application/protojson") + req.Header.Set("Authorization", "bearer "+token) + resp, err := tc.client.Do(req) + if err != nil { + return nil, fmt.Errorf("cannot send request for GET %v: %w", tc.tokenEndpoint, err) + } + + defer func() { + _ = resp.Body.Close() + }() + + var gotToken pb.Token + err = pkgHttpPb.Unmarshal(resp.StatusCode, resp.Body, &gotToken) + if err != nil { + return nil, err + } + return &gotToken, nil +} + type TokenCache struct { expiration time.Duration cache map[string]*tokenIssuerCache @@ -135,11 +187,9 @@ func NewTokenCache(clients map[string]*Client, expiration time.Duration, logger expiration: expiration, logger: logger, } - if len(clients) > 0 { - tc.cache = make(map[string]*tokenIssuerCache) - for issuer, client := range clients { - tc.cache[issuer] = newTokenIssuerCache(client) - } + tc.cache = make(map[string]*tokenIssuerCache) + for issuer, client := range clients { + tc.cache[issuer] = newTokenIssuerCache(client) } return tc } @@ -161,7 +211,7 @@ func (t *TokenCache) getValidUntil(token *pb.Token) time.Time { } func (t *TokenCache) VerifyTrust(ctx context.Context, issuer, token string, tokenClaims jwt.Claims) error { - ic, ok := t.cache[issuer] + tc, ok := t.cache[issuer] if !ok { t.logger.Debugf("client not set for issuer %v, trust verification skipped", issuer) return nil @@ -175,7 +225,7 @@ func (t *TokenCache) VerifyTrust(ctx context.Context, issuer, token string, toke return err } t.logger.Debugf("checking trust for issuer(%v) for token(id=%s)", issuer, tokenID) - tf, set := ic.getValidTokenRecordOrFuture(tokenUUID) + tf, set := tc.getValidTokenRecordOrFuture(tokenUUID) if set == nil { tv, errG := tf.Get(ctx) if errG != nil { @@ -188,43 +238,10 @@ func (t *TokenCache) VerifyTrust(ctx context.Context, issuer, token string, toke return nil } - uri, err := url.Parse(ic.tokenEndpoint) - if err != nil { - ic.removeToken(tokenUUID) - set(nil, err) - return fmt.Errorf("cannot parse tokenEndpoint %v: %w", ic.tokenEndpoint, err) - } - query := uri.Query() - query.Add("idFilter", tokenID) - query.Add("includeBlacklisted", "true") - uri.RawQuery = query.Encode() - t.logger.Debugf("requesting token(id=%s) verification by m2m", tokenID) - req, err := http.NewRequestWithContext(ctx, http.MethodGet, uri.String(), nil) - if err != nil { - ic.removeToken(tokenUUID) - set(nil, err) - return fmt.Errorf("cannot create request for GET %v: %w", uri.String(), err) - } - - req.Header.Set("Accept", "application/protojson") - req.Header.Set("Authorization", "bearer "+token) - resp, err := ic.client.Do(req) + respToken, err := tc.verifyTokenByRequest(ctx, token, tokenID) if err != nil { - ic.removeToken(tokenUUID) - set(nil, err) - return fmt.Errorf("cannot send request for GET %v: %w", ic.tokenEndpoint, err) - } - defer func() { - if errC := resp.Body.Close(); errC != nil { - t.logger.Errorf("cannot close response body: %w", errC) - } - }() - - var gotToken pb.Token - err = pkgHttpPb.Unmarshal(resp.StatusCode, resp.Body, &gotToken) - if err != nil { - ic.removeToken(tokenUUID) + tc.removeToken(tokenUUID) set(nil, err) return err } @@ -236,11 +253,11 @@ func (t *TokenCache) VerifyTrust(ctx context.Context, issuer, token string, toke } } - blacklisted := gotToken.GetBlacklisted().GetFlag() - validUntil := t.getValidUntil(&gotToken) + blacklisted := respToken.GetBlacklisted().GetFlag() + validUntil := t.getValidUntil(respToken) tr := newTokenRecord(blacklisted, validUntil, onExpire) t.logger.Debugf("token(id=%s) set (blacklisted=%v, validUntil=%v)", tokenID, blacklisted, validUntil) - ic.setTokenRecord(tokenUUID, tr) + tc.setTokenRecord(tokenUUID, tr) set(tr, nil) if blacklisted { @@ -248,3 +265,9 @@ func (t *TokenCache) VerifyTrust(ctx context.Context, issuer, token string, toke } return nil } + +func (t *TokenCache) CheckExpirations(now time.Time) { + for _, ic := range t.cache { + ic.checkExpirations(now) + } +} diff --git a/pkg/security/jwt/validator.go b/pkg/security/jwt/validator.go index 77cfb6498..e2f0fc63e 100644 --- a/pkg/security/jwt/validator.go +++ b/pkg/security/jwt/validator.go @@ -7,6 +7,7 @@ import ( "time" "github.com/golang-jwt/jwt/v5" + "github.com/plgd-dev/go-coap/v3/pkg/runner/periodic" "github.com/plgd-dev/hub/v2/pkg/log" pkgHttpUri "github.com/plgd-dev/hub/v2/pkg/net/http/uri" ) @@ -33,6 +34,7 @@ type config struct { verifyTrust bool clients map[string]*Client cacheExpiration time.Duration + stop <-chan struct{} } type Option interface { @@ -45,11 +47,12 @@ func (o optionFunc) apply(c *config) { o(c) } -func WithTrustVerification(clients map[string]*Client, cacheExpiration time.Duration) Option { +func WithTrustVerification(clients map[string]*Client, cacheExpiration time.Duration, stop <-chan struct{}) Option { return optionFunc(func(c *config) { c.verifyTrust = true c.clients = clients c.cacheExpiration = cacheExpiration + c.stop = stop }) } @@ -62,8 +65,13 @@ func NewValidator(keyCache KeyCacheI, logger log.Logger, opts ...Option) *Valida keys: keyCache, verifyTrust: c.verifyTrust, } - if c.verifyTrust { + if c.verifyTrust && len(c.clients) > 0 { v.tokenCache = NewTokenCache(c.clients, c.cacheExpiration, logger) + add := periodic.New(c.stop, c.cacheExpiration/2) + add(func(now time.Time) bool { + v.tokenCache.CheckExpirations(now) + return true + }) } return v } diff --git a/pkg/security/jwt/validator/validator.go b/pkg/security/jwt/validator/validator.go index a9c40709a..d0064d513 100644 --- a/pkg/security/jwt/validator/validator.go +++ b/pkg/security/jwt/validator/validator.go @@ -95,7 +95,7 @@ func New(ctx context.Context, config Config, fileWatcher *fsnotify.Watcher, logg var vopts []jwtValidator.Option if len(clients) > 0 { - vopts = append(vopts, jwtValidator.WithTrustVerification(clients, config.TokenVerification.CacheExpiration)) + vopts = append(vopts, jwtValidator.WithTrustVerification(clients, config.TokenVerification.CacheExpiration, ctx.Done())) } return &Validator{ From 8d82170661ecf13293abfe0524d67e759b2c5d21 Mon Sep 17 00:00:00 2001 From: Daniel Adam Date: Mon, 29 Jul 2024 09:16:00 +0200 Subject: [PATCH 31/31] Fix static analysis issues and add tests --- grpc-gateway/service/getResources_test.go | 2 - pkg/net/http/pb/protojson.go | 1 - pkg/net/http/pb/protojson_test.go | 102 ++++++++++++++ pkg/security/jwt/tokenCache.go | 44 ++++-- pkg/security/jwt/tokenCache_internal_test.go | 141 +++++++++++++++++++ 5 files changed, 274 insertions(+), 16 deletions(-) create mode 100644 pkg/net/http/pb/protojson_test.go create mode 100644 pkg/security/jwt/tokenCache_internal_test.go diff --git a/grpc-gateway/service/getResources_test.go b/grpc-gateway/service/getResources_test.go index 8ad37813f..df2950b0b 100644 --- a/grpc-gateway/service/getResources_test.go +++ b/grpc-gateway/service/getResources_test.go @@ -13,7 +13,6 @@ import ( "github.com/plgd-dev/hub/v2/grpc-gateway/pb" grpcgwTest "github.com/plgd-dev/hub/v2/grpc-gateway/test" m2mOauthTest "github.com/plgd-dev/hub/v2/m2m-oauth-server/test" - "github.com/plgd-dev/hub/v2/pkg/log" pkgGrpc "github.com/plgd-dev/hub/v2/pkg/net/grpc" pkgJwt "github.com/plgd-dev/hub/v2/pkg/security/jwt" "github.com/plgd-dev/hub/v2/resource-aggregate/commands" @@ -223,7 +222,6 @@ func TestRequestHandlerGetResourcesWithM2MTokenVerification(t *testing.T) { grpcCfg := grpcgwTest.MakeConfig(t) grpcCfg.APIs.GRPC.Authorization.TokenVerification.CacheExpiration = time.Second * 2 - grpcCfg.Log.Level = log.DebugLevel tearDown := service.SetUp(ctx, t, service.WithGRPCGWConfig(grpcCfg)) defer tearDown() validTokenStr := oauthTest.GetDefaultAccessToken(t) diff --git a/pkg/net/http/pb/protojson.go b/pkg/net/http/pb/protojson.go index 619a5cbfe..af3399580 100644 --- a/pkg/net/http/pb/protojson.go +++ b/pkg/net/http/pb/protojson.go @@ -43,7 +43,6 @@ func (d *Decoder) Unmarshal(code int, input io.Reader, v protoreflect.ProtoMessa if err != nil { return err } - d.logger.Debugf("data: %s\n", data) if code != http.StatusOK { diff --git a/pkg/net/http/pb/protojson_test.go b/pkg/net/http/pb/protojson_test.go new file mode 100644 index 000000000..2a06c6cd3 --- /dev/null +++ b/pkg/net/http/pb/protojson_test.go @@ -0,0 +1,102 @@ +package pb_test + +import ( + "bytes" + "net/http" + "testing" + + "github.com/plgd-dev/hub/v2/pkg/net/http/pb" + "github.com/plgd-dev/hub/v2/test" + "github.com/stretchr/testify/require" + "google.golang.org/genproto/googleapis/rpc/status" + grpcStatus "google.golang.org/grpc/status" + "google.golang.org/protobuf/encoding/protojson" + "google.golang.org/protobuf/reflect/protoreflect" + "google.golang.org/protobuf/types/known/structpb" +) + +func TestUnmarshalError(t *testing.T) { + s := &status.Status{ + Code: http.StatusInternalServerError, + Message: "test error", + } + data, err := protojson.Marshal(s) + require.NoError(t, err) + + err = pb.UnmarshalError(data) + require.Error(t, err) + + st, ok := grpcStatus.FromError(err) + require.True(t, ok) + require.Equal(t, s.GetCode(), int32(st.Code())) + require.Equal(t, s.GetMessage(), st.Message()) +} + +func TestUnmarshal(t *testing.T) { + tests := []struct { + name string + code int + input []byte + wantGrpcError error + wantErr bool + want protoreflect.ProtoMessage + }{ + { + name: "Unmarshal success", + code: http.StatusOK, + input: func() []byte { + data, err := protojson.Marshal(structpb.NewStringValue("test")) + require.NoError(t, err) + return []byte(`{"result":` + string(data) + `}`) + }(), + want: structpb.NewStringValue("test"), + }, + { + name: "Unmarshal error status", + code: http.StatusInternalServerError, + input: []byte(`{"code": 500, "message": "test error"}`), + wantGrpcError: grpcStatus.ErrorProto(&status.Status{ + Code: http.StatusInternalServerError, + Message: "test error", + }), + }, + { + name: "Unmarshal error status (2)", + code: http.StatusOK, + input: []byte(`{"error": {"code": 500, "message": "test error"}}`), + wantGrpcError: grpcStatus.ErrorProto(&status.Status{ + Code: http.StatusInternalServerError, + Message: "test error", + }), + }, + { + name: "Invalid JSON", + code: http.StatusOK, + input: []byte(`invalid json`), + wantErr: true, + }, + { + name: "Empty result and error fields", + code: http.StatusOK, + input: []byte(`{}`), + want: &structpb.Struct{}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var v structpb.Value + err := pb.Unmarshal(tt.code, bytes.NewReader(tt.input), &v) + if tt.wantGrpcError != nil { + require.ErrorIs(t, err, tt.wantGrpcError) + return + } + if tt.wantErr { + require.Error(t, err) + return + } + require.NoError(t, err) + test.CheckProtobufs(t, tt.want, &v, test.RequireToCheckFunc(require.Equal)) + }) + } +} diff --git a/pkg/security/jwt/tokenCache.go b/pkg/security/jwt/tokenCache.go index 97b7d9a92..032061386 100644 --- a/pkg/security/jwt/tokenCache.go +++ b/pkg/security/jwt/tokenCache.go @@ -113,15 +113,27 @@ func (tc *tokenIssuerCache) getValidTokenRecordOrFuture(tokenID uuid.UUID) (toke return tf, nil } -func (tc *tokenIssuerCache) removeToken(tokenID uuid.UUID) { +func (tc *tokenIssuerCache) removeTokenRecord(tokenID uuid.UUID) { + tc.mutex.Lock() + defer tc.mutex.Unlock() delete(tc.tokens, tokenID) } -func (tc *tokenIssuerCache) setTokenRecord(tokenID uuid.UUID, tr *tokenRecord) { +func (tc *tokenIssuerCache) removeTokenRecordAndSetErrorOnFuture(tokenUUID uuid.UUID, setTRFuture future.SetFunc, err error) { + tc.removeTokenRecord(tokenUUID) + setTRFuture(nil, err) +} + +func (tc *tokenIssuerCache) setTokenRecord(tokenUUID uuid.UUID, tr *tokenRecord) { tf := makeTokenOrFuture(tr, nil) tc.mutex.Lock() defer tc.mutex.Unlock() - tc.tokens[tokenID] = tf + tc.tokens[tokenUUID] = tf +} + +func (tc *tokenIssuerCache) setTokenRecordAndWaitingFuture(tokenUUID uuid.UUID, tr *tokenRecord, setTRFuture future.SetFunc) { + tc.setTokenRecord(tokenUUID, tr) + setTRFuture(tr, nil) } func (tc *tokenIssuerCache) checkExpirations(now time.Time) { @@ -156,7 +168,6 @@ func (tc *tokenIssuerCache) verifyTokenByRequest(ctx context.Context, token, tok return nil, fmt.Errorf("cannot create request for GET %v: %w", uri.String(), err) } - // TODO: "Accept" -> pktNetHttp.AcceptHeaderKey: import cycle, must move to another package req.Header.Set("Accept", "application/protojson") req.Header.Set("Authorization", "bearer "+token) resp, err := tc.client.Do(req) @@ -210,17 +221,26 @@ func (t *TokenCache) getValidUntil(token *pb.Token) time.Time { return time.Now().Add(t.expiration) } +func getTokenUUID(tokenClaims jwt.Claims) (string, uuid.UUID, error) { + tokenID, err := getID(tokenClaims) + if err != nil { + return "", uuid.Nil, err + } + tokenUUID, err := uuid.Parse(tokenID) + if err != nil { + return "", uuid.Nil, err + } + return tokenID, tokenUUID, nil +} + func (t *TokenCache) VerifyTrust(ctx context.Context, issuer, token string, tokenClaims jwt.Claims) error { tc, ok := t.cache[issuer] if !ok { t.logger.Debugf("client not set for issuer %v, trust verification skipped", issuer) return nil } - tokenID, err := getID(tokenClaims) - if err != nil { - return err - } - tokenUUID, err := uuid.Parse(tokenID) + + tokenID, tokenUUID, err := getTokenUUID(tokenClaims) if err != nil { return err } @@ -241,8 +261,7 @@ func (t *TokenCache) VerifyTrust(ctx context.Context, issuer, token string, toke t.logger.Debugf("requesting token(id=%s) verification by m2m", tokenID) respToken, err := tc.verifyTokenByRequest(ctx, token, tokenID) if err != nil { - tc.removeToken(tokenUUID) - set(nil, err) + tc.removeTokenRecordAndSetErrorOnFuture(tokenUUID, set, err) return err } @@ -257,8 +276,7 @@ func (t *TokenCache) VerifyTrust(ctx context.Context, issuer, token string, toke validUntil := t.getValidUntil(respToken) tr := newTokenRecord(blacklisted, validUntil, onExpire) t.logger.Debugf("token(id=%s) set (blacklisted=%v, validUntil=%v)", tokenID, blacklisted, validUntil) - tc.setTokenRecord(tokenUUID, tr) - set(tr, nil) + tc.setTokenRecordAndWaitingFuture(tokenUUID, tr, set) if blacklisted { return ErrBlackListedToken diff --git a/pkg/security/jwt/tokenCache_internal_test.go b/pkg/security/jwt/tokenCache_internal_test.go new file mode 100644 index 000000000..e0e8ec240 --- /dev/null +++ b/pkg/security/jwt/tokenCache_internal_test.go @@ -0,0 +1,141 @@ +package jwt + +import ( + "context" + "errors" + "net/http" + "testing" + "time" + + "github.com/google/uuid" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +const TEST_TIMEOUT = time.Second * 10 + +func TestTokenRecord_IsExpired(t *testing.T) { + now := time.Now() + tests := []struct { + name string + recordTime time.Time + want bool + }{ + {"Not expired", now.Add(time.Hour), false}, + {"Expired", now.Add(-time.Hour), true}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + record := newTokenRecord(false, tt.recordTime, nil) + require.Equal(t, tt.want, record.IsExpired(now)) + }) + } +} + +func TestTokenIssuerCacheSetAndGetToken(t *testing.T) { + cache := newTokenIssuerCache(&Client{Client: &http.Client{}, tokenEndpoint: "http://example.com"}) + + ctx, cancel := context.WithTimeout(context.Background(), TEST_TIMEOUT) + defer cancel() + tokenID := uuid.New() + // token doesn't exist yet, so we should get a future with a set function + _, setTokenOrError := cache.getValidTokenRecordOrFuture(tokenID) + require.NotNil(t, setTokenOrError) + + // we can wait on the future in other goroutine + // -> setting error on the future should unblock the goroutine + waiting := make(chan struct{}) + done := make(chan struct{}) + go func() { + defer close(done) + tf2, setToken2 := cache.getValidTokenRecordOrFuture(tokenID) + assert.Nil(t, setToken2) + close(waiting) + _, err := tf2.Get(ctx) + assert.Error(t, err) + }() + + <-waiting + cache.removeTokenRecordAndSetErrorOnFuture(tokenID, setTokenOrError, errors.New("test")) + select { + case <-done: + case <-ctx.Done(): + require.Fail(t, "timeout") + } + + // get a new future + _, setTokenOrError = cache.getValidTokenRecordOrFuture(tokenID) + require.NotNil(t, setTokenOrError) + + // -> setting an expired token record should result in a future with a set function being returned + expiredIDs := []uuid.UUID{} + tr := newTokenRecord(false, time.Now().Add(-time.Hour), func(u uuid.UUID) { + expiredIDs = append(expiredIDs, u) + }) + cache.setTokenRecord(tokenID, tr) + _, setTokenOrError = cache.getValidTokenRecordOrFuture(tokenID) + require.NotNil(t, setTokenOrError) + require.Len(t, expiredIDs, 1) + require.Equal(t, tokenID, expiredIDs[0]) + + // -> finally, set valid token record + tr = newTokenRecord(false, time.Now().Add(time.Hour), nil) + waiting = make(chan struct{}) + done = make(chan struct{}) + go func() { + defer close(done) + tf2, setToken2 := cache.getValidTokenRecordOrFuture(tokenID) + assert.Nil(t, setToken2) + close(waiting) + result, err := tf2.Get(ctx) + assert.NoError(t, err) + assert.Equal(t, tr, result) + }() + + <-waiting + cache.setTokenRecordAndWaitingFuture(tokenID, tr, setTokenOrError) + select { + case <-done: + case <-ctx.Done(): + require.Fail(t, "timeout") + } + + // cache should return a token record now, not a future + tf, _ := cache.getValidTokenRecordOrFuture(tokenID) + _, ok := tf.tokenOrFuture.(*tokenRecord) + require.True(t, ok) +} + +func TestTokenIssuerCacheCheckExpirations(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), TEST_TIMEOUT) + defer cancel() + cache := newTokenIssuerCache(&Client{Client: &http.Client{}, tokenEndpoint: "http://example.com"}) + + now := time.Now() + tokenID1 := uuid.New() + expiredIDs := []uuid.UUID{} + onExpire := func(u uuid.UUID) { + expiredIDs = append(expiredIDs, u) + } + tokenRecord1 := newTokenRecord(false, now.Add(-time.Hour), onExpire) + cache.setTokenRecord(tokenID1, tokenRecord1) + + tokenID2 := uuid.New() + tokenRecord2 := newTokenRecord(false, now.Add(time.Hour), onExpire) + cache.setTokenRecord(tokenID2, tokenRecord2) + + cache.checkExpirations(now) + + // tokenRecord1 should have been removed and we should get a future with a set function + _, setTf1 := cache.getValidTokenRecordOrFuture(tokenID1) + require.NotNil(t, setTf1) + require.Len(t, expiredIDs, 1) + require.Equal(t, tokenID1, expiredIDs[0]) + // tokenRecord2 should still be there + tf2, setTf2 := cache.getValidTokenRecordOrFuture(tokenID2) + require.Nil(t, setTf2) + result, err := tf2.Get(ctx) + require.NoError(t, err) + require.Equal(t, tokenRecord2, result) +}