diff --git a/.github/workflows/check-generated-files.yml b/.github/workflows/check-generated-files.yml index c50e121ce9..e60ad814f3 100644 --- a/.github/workflows/check-generated-files.yml +++ b/.github/workflows/check-generated-files.yml @@ -62,8 +62,8 @@ jobs: - name: Set up protoc if: steps.changes.outputs.proto == 'true' run: | - PROTOC_VERSION=25.1 - PROTOC_GEN_VERSION=v1.31.0 + PROTOC_VERSION=25.3 + PROTOC_GEN_VERSION=v1.32.0 PROTOC_GRPC_VERSION=v1.3.0 # Download and install protoc diff --git a/auth.pb.go b/auth.pb.go index fc9b881932..6d9ace3530 100644 --- a/auth.pb.go +++ b/auth.pb.go @@ -3,8 +3,8 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.31.0 -// protoc v4.25.1 +// protoc-gen-go v1.32.0 +// protoc v4.25.3 // source: auth.proto package magistrala @@ -204,9 +204,12 @@ type IssueReq struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - UserId string `protobuf:"bytes,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` - DomainId *string `protobuf:"bytes,2,opt,name=domain_id,json=domainId,proto3,oneof" json:"domain_id,omitempty"` - Type uint32 `protobuf:"varint,3,opt,name=type,proto3" json:"type,omitempty"` + UserId string `protobuf:"bytes,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` + DomainId *string `protobuf:"bytes,2,opt,name=domain_id,json=domainId,proto3,oneof" json:"domain_id,omitempty"` + Type uint32 `protobuf:"varint,3,opt,name=type,proto3" json:"type,omitempty"` + OauthProvider string `protobuf:"bytes,4,opt,name=oauth_provider,json=oauthProvider,proto3" json:"oauth_provider,omitempty"` + OauthAccessToken string `protobuf:"bytes,5,opt,name=oauth_access_token,json=oauthAccessToken,proto3" json:"oauth_access_token,omitempty"` + OauthRefreshToken string `protobuf:"bytes,6,opt,name=oauth_refresh_token,json=oauthRefreshToken,proto3" json:"oauth_refresh_token,omitempty"` } func (x *IssueReq) Reset() { @@ -262,6 +265,27 @@ func (x *IssueReq) GetType() uint32 { return 0 } +func (x *IssueReq) GetOauthProvider() string { + if x != nil { + return x.OauthProvider + } + return "" +} + +func (x *IssueReq) GetOauthAccessToken() string { + if x != nil { + return x.OauthAccessToken + } + return "" +} + +func (x *IssueReq) GetOauthRefreshToken() string { + if x != nil { + return x.OauthRefreshToken + } + return "" +} + type RefreshReq struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -1877,217 +1901,162 @@ var file_auth_proto_rawDesc = []byte{ 0x69, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, - 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x49, 0x64, 0x22, 0x67, 0x0a, 0x08, 0x49, 0x73, 0x73, 0x75, - 0x65, 0x52, 0x65, 0x71, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x20, 0x0a, - 0x09, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, - 0x48, 0x00, 0x52, 0x08, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x49, 0x64, 0x88, 0x01, 0x01, 0x12, - 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x04, 0x74, - 0x79, 0x70, 0x65, 0x42, 0x0c, 0x0a, 0x0a, 0x5f, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x5f, 0x69, - 0x64, 0x22, 0x61, 0x0a, 0x0a, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x52, 0x65, 0x71, 0x12, - 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x72, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x54, - 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x20, 0x0a, 0x09, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x5f, 0x69, - 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x08, 0x64, 0x6f, 0x6d, 0x61, 0x69, - 0x6e, 0x49, 0x64, 0x88, 0x01, 0x01, 0x42, 0x0c, 0x0a, 0x0a, 0x5f, 0x64, 0x6f, 0x6d, 0x61, 0x69, - 0x6e, 0x5f, 0x69, 0x64, 0x22, 0xa6, 0x02, 0x0a, 0x0c, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, - 0x7a, 0x65, 0x52, 0x65, 0x71, 0x12, 0x16, 0x0a, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x21, 0x0a, - 0x0c, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x0b, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x54, 0x79, 0x70, 0x65, - 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x6b, 0x69, 0x6e, 0x64, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x4b, - 0x69, 0x6e, 0x64, 0x12, 0x29, 0x0a, 0x10, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x72, - 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x73, - 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x52, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x18, - 0x0a, 0x07, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x07, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x72, 0x65, 0x6c, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x72, 0x65, 0x6c, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1e, 0x0a, 0x0a, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, - 0x6f, 0x6e, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, - 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x16, 0x0a, 0x06, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x18, 0x08, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x12, 0x1f, 0x0a, 0x0b, - 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x0a, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x54, 0x79, 0x70, 0x65, 0x22, 0x3e, 0x0a, - 0x0c, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x52, 0x65, 0x73, 0x12, 0x1e, 0x0a, - 0x0a, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x08, 0x52, 0x0a, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x64, 0x12, 0x0e, 0x0a, - 0x02, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x22, 0xc7, 0x02, - 0x0a, 0x0c, 0x41, 0x64, 0x64, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x65, 0x71, 0x12, 0x16, - 0x0a, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, - 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, - 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x73, 0x75, - 0x62, 0x6a, 0x65, 0x63, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x29, 0x0a, 0x10, 0x73, 0x75, 0x62, - 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x0f, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x52, 0x65, 0x6c, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, - 0x6b, 0x69, 0x6e, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x73, 0x75, 0x62, 0x6a, - 0x65, 0x63, 0x74, 0x4b, 0x69, 0x6e, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x75, 0x62, 0x6a, 0x65, - 0x63, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, - 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x06, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x08, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1e, 0x0a, - 0x0a, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x07, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x0a, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x16, 0x0a, - 0x06, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6f, - 0x62, 0x6a, 0x65, 0x63, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, - 0x6b, 0x69, 0x6e, 0x64, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x6f, 0x62, 0x6a, 0x65, - 0x63, 0x74, 0x4b, 0x69, 0x6e, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, - 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x6f, 0x62, 0x6a, - 0x65, 0x63, 0x74, 0x54, 0x79, 0x70, 0x65, 0x22, 0x52, 0x0a, 0x0e, 0x41, 0x64, 0x64, 0x50, 0x6f, - 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x52, 0x65, 0x71, 0x12, 0x40, 0x0a, 0x0e, 0x61, 0x64, 0x64, - 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x52, 0x65, 0x71, 0x18, 0x01, 0x20, 0x03, 0x28, - 0x0b, 0x32, 0x18, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, 0x2e, 0x41, - 0x64, 0x64, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x65, 0x71, 0x52, 0x0e, 0x61, 0x64, 0x64, - 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x52, 0x65, 0x71, 0x22, 0x24, 0x0a, 0x0c, 0x41, - 0x64, 0x64, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x65, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x61, - 0x64, 0x64, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, 0x61, 0x64, 0x64, 0x65, - 0x64, 0x22, 0x26, 0x0a, 0x0e, 0x41, 0x64, 0x64, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, - 0x52, 0x65, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x61, 0x64, 0x64, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x08, 0x52, 0x05, 0x61, 0x64, 0x64, 0x65, 0x64, 0x22, 0xca, 0x02, 0x0a, 0x0f, 0x44, 0x65, - 0x6c, 0x65, 0x74, 0x65, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x65, 0x71, 0x12, 0x16, 0x0a, - 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x64, - 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, - 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x73, 0x75, 0x62, - 0x6a, 0x65, 0x63, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x29, 0x0a, 0x10, 0x73, 0x75, 0x62, 0x6a, - 0x65, 0x63, 0x74, 0x5f, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x0f, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x52, 0x65, 0x6c, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x6b, - 0x69, 0x6e, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x73, 0x75, 0x62, 0x6a, 0x65, - 0x63, 0x74, 0x4b, 0x69, 0x6e, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, - 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, - 0x12, 0x1a, 0x0a, 0x08, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x08, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1e, 0x0a, 0x0a, - 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x0a, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x16, 0x0a, 0x06, - 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6f, 0x62, - 0x6a, 0x65, 0x63, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x6b, - 0x69, 0x6e, 0x64, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x6f, 0x62, 0x6a, 0x65, 0x63, - 0x74, 0x4b, 0x69, 0x6e, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, - 0x74, 0x79, 0x70, 0x65, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x6f, 0x62, 0x6a, 0x65, - 0x63, 0x74, 0x54, 0x79, 0x70, 0x65, 0x22, 0x5e, 0x0a, 0x11, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, - 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x52, 0x65, 0x71, 0x12, 0x49, 0x0a, 0x11, 0x64, - 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x52, 0x65, 0x71, - 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, - 0x61, 0x6c, 0x61, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, - 0x52, 0x65, 0x71, 0x52, 0x11, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x6f, 0x6c, 0x69, 0x63, - 0x69, 0x65, 0x73, 0x52, 0x65, 0x71, 0x22, 0x2b, 0x0a, 0x0f, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, - 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x65, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x64, 0x65, 0x6c, - 0x65, 0x74, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x64, 0x65, 0x6c, 0x65, - 0x74, 0x65, 0x64, 0x22, 0x2d, 0x0a, 0x11, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x6f, 0x6c, - 0x69, 0x63, 0x69, 0x65, 0x73, 0x52, 0x65, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x64, 0x65, 0x6c, 0x65, - 0x74, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x64, 0x65, 0x6c, 0x65, 0x74, - 0x65, 0x64, 0x22, 0xc1, 0x02, 0x0a, 0x0e, 0x4c, 0x69, 0x73, 0x74, 0x4f, 0x62, 0x6a, 0x65, 0x63, - 0x74, 0x73, 0x52, 0x65, 0x71, 0x12, 0x16, 0x0a, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x21, 0x0a, - 0x0c, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x0b, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x54, 0x79, 0x70, 0x65, - 0x12, 0x29, 0x0a, 0x10, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x72, 0x65, 0x6c, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x73, 0x75, 0x62, 0x6a, - 0x65, 0x63, 0x74, 0x52, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x18, 0x0a, 0x07, 0x73, - 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x75, - 0x62, 0x6a, 0x65, 0x63, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x12, 0x1e, 0x0a, 0x0a, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x18, - 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, - 0x6e, 0x12, 0x16, 0x0a, 0x06, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x06, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x6f, 0x62, 0x6a, - 0x65, 0x63, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, - 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x24, 0x0a, 0x0d, 0x6e, 0x65, - 0x78, 0x74, 0x50, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x09, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x0d, 0x6e, 0x65, 0x78, 0x74, 0x50, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, - 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x05, 0x52, - 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x22, 0x52, 0x0a, 0x0e, 0x4c, 0x69, 0x73, 0x74, 0x4f, 0x62, - 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x6f, 0x6c, 0x69, - 0x63, 0x69, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x08, 0x70, 0x6f, 0x6c, 0x69, - 0x63, 0x69, 0x65, 0x73, 0x12, 0x24, 0x0a, 0x0d, 0x6e, 0x65, 0x78, 0x74, 0x50, 0x61, 0x67, 0x65, - 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x6e, 0x65, 0x78, - 0x74, 0x50, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0xac, 0x02, 0x0a, 0x0f, 0x43, - 0x6f, 0x75, 0x6e, 0x74, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, 0x71, 0x12, 0x16, - 0x0a, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, - 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, - 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x73, 0x75, - 0x62, 0x6a, 0x65, 0x63, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x29, 0x0a, 0x10, 0x73, 0x75, 0x62, - 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, + 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x49, 0x64, 0x22, 0xec, 0x01, 0x0a, 0x08, 0x49, 0x73, 0x73, + 0x75, 0x65, 0x52, 0x65, 0x71, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x20, + 0x0a, 0x09, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x09, 0x48, 0x00, 0x52, 0x08, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x49, 0x64, 0x88, 0x01, 0x01, + 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x04, + 0x74, 0x79, 0x70, 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x6f, 0x61, 0x75, 0x74, 0x68, 0x5f, 0x70, 0x72, + 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x6f, 0x61, + 0x75, 0x74, 0x68, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x12, 0x2c, 0x0a, 0x12, 0x6f, + 0x61, 0x75, 0x74, 0x68, 0x5f, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x5f, 0x74, 0x6f, 0x6b, 0x65, + 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x6f, 0x61, 0x75, 0x74, 0x68, 0x41, 0x63, + 0x63, 0x65, 0x73, 0x73, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x2e, 0x0a, 0x13, 0x6f, 0x61, 0x75, + 0x74, 0x68, 0x5f, 0x72, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, + 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x11, 0x6f, 0x61, 0x75, 0x74, 0x68, 0x52, 0x65, 0x66, + 0x72, 0x65, 0x73, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x42, 0x0c, 0x0a, 0x0a, 0x5f, 0x64, 0x6f, + 0x6d, 0x61, 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x22, 0x61, 0x0a, 0x0a, 0x52, 0x65, 0x66, 0x72, 0x65, + 0x73, 0x68, 0x52, 0x65, 0x71, 0x12, 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, + 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x72, 0x65, + 0x66, 0x72, 0x65, 0x73, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x20, 0x0a, 0x09, 0x64, 0x6f, + 0x6d, 0x61, 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, + 0x08, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x49, 0x64, 0x88, 0x01, 0x01, 0x42, 0x0c, 0x0a, 0x0a, + 0x5f, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x22, 0xa6, 0x02, 0x0a, 0x0c, 0x41, + 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x52, 0x65, 0x71, 0x12, 0x16, 0x0a, 0x06, 0x64, + 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x64, 0x6f, 0x6d, + 0x61, 0x69, 0x6e, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x74, + 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x73, 0x75, 0x62, 0x6a, 0x65, + 0x63, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, + 0x74, 0x5f, 0x6b, 0x69, 0x6e, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x73, 0x75, + 0x62, 0x6a, 0x65, 0x63, 0x74, 0x4b, 0x69, 0x6e, 0x64, 0x12, 0x29, 0x0a, 0x10, 0x73, 0x75, 0x62, + 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x52, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x18, - 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x12, 0x1a, - 0x0a, 0x08, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, + 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x12, 0x1a, + 0x0a, 0x08, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1e, 0x0a, 0x0a, 0x70, 0x65, - 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, + 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x16, 0x0a, 0x06, 0x6f, 0x62, - 0x6a, 0x65, 0x63, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6f, 0x62, 0x6a, 0x65, + 0x6a, 0x65, 0x63, 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x74, 0x79, 0x70, - 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x54, - 0x79, 0x70, 0x65, 0x12, 0x24, 0x0a, 0x0d, 0x6e, 0x65, 0x78, 0x74, 0x50, 0x61, 0x67, 0x65, 0x54, - 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x6e, 0x65, 0x78, 0x74, - 0x50, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0x27, 0x0a, 0x0f, 0x43, 0x6f, 0x75, - 0x6e, 0x74, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, 0x73, 0x12, 0x14, 0x0a, 0x05, - 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x63, 0x6f, 0x75, - 0x6e, 0x74, 0x22, 0xc2, 0x02, 0x0a, 0x0f, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x75, 0x62, 0x6a, 0x65, - 0x63, 0x74, 0x73, 0x52, 0x65, 0x71, 0x12, 0x16, 0x0a, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x21, - 0x0a, 0x0c, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x54, 0x79, 0x70, - 0x65, 0x12, 0x29, 0x0a, 0x10, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x72, 0x65, 0x6c, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x73, 0x75, 0x62, - 0x6a, 0x65, 0x63, 0x74, 0x52, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x18, 0x0a, 0x07, - 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, + 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x54, + 0x79, 0x70, 0x65, 0x22, 0x3e, 0x0a, 0x0c, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, + 0x52, 0x65, 0x73, 0x12, 0x1e, 0x0a, 0x0a, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, + 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, + 0x7a, 0x65, 0x64, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x02, 0x69, 0x64, 0x22, 0xc7, 0x02, 0x0a, 0x0c, 0x41, 0x64, 0x64, 0x50, 0x6f, 0x6c, 0x69, 0x63, + 0x79, 0x52, 0x65, 0x71, 0x12, 0x16, 0x0a, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x21, 0x0a, 0x0c, + 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x0b, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, + 0x29, 0x0a, 0x10, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x72, 0x65, 0x6c, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x73, 0x75, 0x62, 0x6a, 0x65, + 0x63, 0x74, 0x52, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x75, + 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x6b, 0x69, 0x6e, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x0b, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x4b, 0x69, 0x6e, 0x64, 0x12, 0x18, 0x0a, + 0x07, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, + 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x72, 0x65, 0x6c, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x72, 0x65, 0x6c, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x12, 0x1e, 0x0a, 0x0a, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, + 0x6e, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, + 0x69, 0x6f, 0x6e, 0x12, 0x16, 0x0a, 0x06, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x18, 0x08, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x06, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x6f, + 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x6b, 0x69, 0x6e, 0x64, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x0a, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x4b, 0x69, 0x6e, 0x64, 0x12, 0x1f, 0x0a, 0x0b, + 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x0a, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x0a, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x54, 0x79, 0x70, 0x65, 0x22, 0x52, 0x0a, + 0x0e, 0x41, 0x64, 0x64, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x52, 0x65, 0x71, 0x12, + 0x40, 0x0a, 0x0e, 0x61, 0x64, 0x64, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x52, 0x65, + 0x71, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, + 0x72, 0x61, 0x6c, 0x61, 0x2e, 0x41, 0x64, 0x64, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x65, + 0x71, 0x52, 0x0e, 0x61, 0x64, 0x64, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x52, 0x65, + 0x71, 0x22, 0x24, 0x0a, 0x0c, 0x41, 0x64, 0x64, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x65, + 0x73, 0x12, 0x14, 0x0a, 0x05, 0x61, 0x64, 0x64, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, + 0x52, 0x05, 0x61, 0x64, 0x64, 0x65, 0x64, 0x22, 0x26, 0x0a, 0x0e, 0x41, 0x64, 0x64, 0x50, 0x6f, + 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x52, 0x65, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x61, 0x64, 0x64, + 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, 0x61, 0x64, 0x64, 0x65, 0x64, 0x22, + 0xca, 0x02, 0x0a, 0x0f, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, + 0x52, 0x65, 0x71, 0x12, 0x16, 0x0a, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x21, 0x0a, 0x0c, 0x73, + 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x0b, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x29, + 0x0a, 0x10, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, + 0x74, 0x52, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x75, 0x62, + 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x6b, 0x69, 0x6e, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x0b, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x4b, 0x69, 0x6e, 0x64, 0x12, 0x18, 0x0a, 0x07, + 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1e, 0x0a, 0x0a, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, - 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, - 0x6f, 0x6e, 0x12, 0x16, 0x0a, 0x06, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x18, 0x07, 0x20, 0x01, + 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, + 0x6f, 0x6e, 0x12, 0x16, 0x0a, 0x06, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x6f, 0x62, - 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x0a, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x24, 0x0a, 0x0d, 0x6e, - 0x65, 0x78, 0x74, 0x50, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x09, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x0d, 0x6e, 0x65, 0x78, 0x74, 0x50, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, - 0x6e, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x05, - 0x52, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x22, 0x53, 0x0a, 0x0f, 0x4c, 0x69, 0x73, 0x74, 0x53, - 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x6f, - 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x08, 0x70, 0x6f, - 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x12, 0x24, 0x0a, 0x0d, 0x6e, 0x65, 0x78, 0x74, 0x50, 0x61, - 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x6e, - 0x65, 0x78, 0x74, 0x50, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0xad, 0x02, 0x0a, - 0x10, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, - 0x71, 0x12, 0x16, 0x0a, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x75, 0x62, - 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x0b, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x29, 0x0a, 0x10, - 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x52, - 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x75, 0x62, 0x6a, 0x65, - 0x63, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, - 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x05, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x08, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1e, 0x0a, - 0x0a, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x0a, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x16, 0x0a, - 0x06, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6f, - 0x62, 0x6a, 0x65, 0x63, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, - 0x74, 0x79, 0x70, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x6f, 0x62, 0x6a, 0x65, - 0x63, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x24, 0x0a, 0x0d, 0x6e, 0x65, 0x78, 0x74, 0x50, 0x61, - 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x6e, - 0x65, 0x78, 0x74, 0x50, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0x28, 0x0a, 0x10, - 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, 0x73, - 0x12, 0x14, 0x0a, 0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, - 0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0xfc, 0x01, 0x0a, 0x12, 0x4c, 0x69, 0x73, 0x74, 0x50, - 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x12, 0x16, 0x0a, - 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x64, - 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, - 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x73, 0x75, 0x62, - 0x6a, 0x65, 0x63, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x29, 0x0a, 0x10, 0x73, 0x75, 0x62, 0x6a, - 0x65, 0x63, 0x74, 0x5f, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x0f, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x52, 0x65, 0x6c, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x18, 0x04, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x12, 0x16, 0x0a, - 0x06, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6f, - 0x62, 0x6a, 0x65, 0x63, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, - 0x74, 0x79, 0x70, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x6f, 0x62, 0x6a, 0x65, - 0x63, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x2d, 0x0a, 0x12, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, - 0x5f, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x07, 0x20, 0x03, - 0x28, 0x09, 0x52, 0x11, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, - 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0xef, 0x01, 0x0a, 0x12, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x65, - 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x12, 0x16, 0x0a, 0x06, + 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x6b, 0x69, 0x6e, 0x64, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x0a, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x4b, 0x69, 0x6e, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x6f, + 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x0a, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x54, 0x79, 0x70, 0x65, 0x22, 0x5e, 0x0a, 0x11, + 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x52, 0x65, + 0x71, 0x12, 0x49, 0x0a, 0x11, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x6f, 0x6c, 0x69, 0x63, + 0x69, 0x65, 0x73, 0x52, 0x65, 0x71, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x6d, + 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, + 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x65, 0x71, 0x52, 0x11, 0x64, 0x65, 0x6c, 0x65, 0x74, + 0x65, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x52, 0x65, 0x71, 0x22, 0x2b, 0x0a, 0x0f, + 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x65, 0x73, 0x12, + 0x18, 0x0a, 0x07, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, + 0x52, 0x07, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x22, 0x2d, 0x0a, 0x11, 0x44, 0x65, 0x6c, + 0x65, 0x74, 0x65, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x52, 0x65, 0x73, 0x12, 0x18, + 0x0a, 0x07, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, + 0x07, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x22, 0xc1, 0x02, 0x0a, 0x0e, 0x4c, 0x69, 0x73, + 0x74, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, 0x71, 0x12, 0x16, 0x0a, 0x06, 0x64, + 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x64, 0x6f, 0x6d, + 0x61, 0x69, 0x6e, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x74, + 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x73, 0x75, 0x62, 0x6a, 0x65, + 0x63, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x29, 0x0a, 0x10, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, + 0x74, 0x5f, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x0f, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x52, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x18, 0x04, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x07, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x72, + 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x72, + 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1e, 0x0a, 0x0a, 0x70, 0x65, 0x72, 0x6d, 0x69, + 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x70, 0x65, 0x72, + 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x16, 0x0a, 0x06, 0x6f, 0x62, 0x6a, 0x65, 0x63, + 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x12, + 0x1f, 0x0a, 0x0b, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x08, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x54, 0x79, 0x70, 0x65, + 0x12, 0x24, 0x0a, 0x0d, 0x6e, 0x65, 0x78, 0x74, 0x50, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, + 0x6e, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x6e, 0x65, 0x78, 0x74, 0x50, 0x61, 0x67, + 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, + 0x0a, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x22, 0x52, 0x0a, 0x0e, + 0x4c, 0x69, 0x73, 0x74, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, 0x73, 0x12, 0x1a, + 0x0a, 0x08, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, + 0x52, 0x08, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x12, 0x24, 0x0a, 0x0d, 0x6e, 0x65, + 0x78, 0x74, 0x50, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x0d, 0x6e, 0x65, 0x78, 0x74, 0x50, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, + 0x22, 0xac, 0x02, 0x0a, 0x0f, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, + 0x73, 0x52, 0x65, 0x71, 0x12, 0x16, 0x0a, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x21, 0x0a, 0x0c, + 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x0b, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, + 0x29, 0x0a, 0x10, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x72, 0x65, 0x6c, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x73, 0x75, 0x62, 0x6a, 0x65, + 0x63, 0x74, 0x52, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x75, + 0x62, 0x6a, 0x65, 0x63, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x75, 0x62, + 0x6a, 0x65, 0x63, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x12, 0x1e, 0x0a, 0x0a, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x06, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, + 0x12, 0x16, 0x0a, 0x06, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x06, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x6f, 0x62, 0x6a, 0x65, + 0x63, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x6f, + 0x62, 0x6a, 0x65, 0x63, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x24, 0x0a, 0x0d, 0x6e, 0x65, 0x78, + 0x74, 0x50, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x0d, 0x6e, 0x65, 0x78, 0x74, 0x50, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x22, + 0x27, 0x0a, 0x0f, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, + 0x65, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x03, 0x52, 0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0xc2, 0x02, 0x0a, 0x0f, 0x4c, 0x69, 0x73, + 0x74, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, 0x71, 0x12, 0x16, 0x0a, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x73, 0x75, 0x62, 0x6a, @@ -2095,88 +2064,152 @@ var file_auth_proto_rawDesc = []byte{ 0x63, 0x74, 0x5f, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x52, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x18, 0x04, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x12, 0x16, 0x0a, 0x06, - 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6f, 0x62, - 0x6a, 0x65, 0x63, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x74, - 0x79, 0x70, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x6f, 0x62, 0x6a, 0x65, 0x63, - 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, - 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x07, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0b, 0x70, 0x65, 0x72, 0x6d, - 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x32, 0x51, 0x0a, 0x0c, 0x41, 0x75, 0x74, 0x68, 0x7a, - 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x41, 0x0a, 0x09, 0x41, 0x75, 0x74, 0x68, 0x6f, - 0x72, 0x69, 0x7a, 0x65, 0x12, 0x18, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, - 0x61, 0x2e, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x52, 0x65, 0x71, 0x1a, 0x18, - 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, 0x2e, 0x41, 0x75, 0x74, 0x68, - 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x52, 0x65, 0x73, 0x22, 0x00, 0x32, 0xc6, 0x08, 0x0a, 0x0b, 0x41, - 0x75, 0x74, 0x68, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x32, 0x0a, 0x05, 0x49, 0x73, - 0x73, 0x75, 0x65, 0x12, 0x14, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, - 0x2e, 0x49, 0x73, 0x73, 0x75, 0x65, 0x52, 0x65, 0x71, 0x1a, 0x11, 0x2e, 0x6d, 0x61, 0x67, 0x69, - 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, 0x2e, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0x00, 0x12, 0x36, - 0x0a, 0x07, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x12, 0x16, 0x2e, 0x6d, 0x61, 0x67, 0x69, - 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, 0x2e, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x52, 0x65, - 0x71, 0x1a, 0x11, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, 0x2e, 0x54, - 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0x00, 0x12, 0x3e, 0x0a, 0x08, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, - 0x66, 0x79, 0x12, 0x17, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, 0x2e, - 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x52, 0x65, 0x71, 0x1a, 0x17, 0x2e, 0x6d, 0x61, - 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, 0x2e, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, - 0x79, 0x52, 0x65, 0x73, 0x22, 0x00, 0x12, 0x41, 0x0a, 0x09, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, - 0x69, 0x7a, 0x65, 0x12, 0x18, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, - 0x2e, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x52, 0x65, 0x71, 0x1a, 0x18, 0x2e, - 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, 0x2e, 0x41, 0x75, 0x74, 0x68, 0x6f, - 0x72, 0x69, 0x7a, 0x65, 0x52, 0x65, 0x73, 0x22, 0x00, 0x12, 0x41, 0x0a, 0x09, 0x41, 0x64, 0x64, - 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x12, 0x18, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, - 0x61, 0x6c, 0x61, 0x2e, 0x41, 0x64, 0x64, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x65, 0x71, - 0x1a, 0x18, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, 0x2e, 0x41, 0x64, - 0x64, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x65, 0x73, 0x22, 0x00, 0x12, 0x47, 0x0a, 0x0b, - 0x41, 0x64, 0x64, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x12, 0x1a, 0x2e, 0x6d, 0x61, - 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, 0x2e, 0x41, 0x64, 0x64, 0x50, 0x6f, 0x6c, 0x69, - 0x63, 0x69, 0x65, 0x73, 0x52, 0x65, 0x71, 0x1a, 0x1a, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, - 0x72, 0x61, 0x6c, 0x61, 0x2e, 0x41, 0x64, 0x64, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, - 0x52, 0x65, 0x73, 0x22, 0x00, 0x12, 0x4a, 0x0a, 0x0c, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, - 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x12, 0x1b, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, - 0x6c, 0x61, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, - 0x65, 0x71, 0x1a, 0x1b, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, 0x2e, - 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x65, 0x73, 0x22, - 0x00, 0x12, 0x50, 0x0a, 0x0e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x6f, 0x6c, 0x69, 0x63, - 0x69, 0x65, 0x73, 0x12, 0x1d, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, - 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x52, - 0x65, 0x71, 0x1a, 0x1d, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, 0x2e, - 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x52, 0x65, - 0x73, 0x22, 0x00, 0x12, 0x47, 0x0a, 0x0b, 0x4c, 0x69, 0x73, 0x74, 0x4f, 0x62, 0x6a, 0x65, 0x63, - 0x74, 0x73, 0x12, 0x1a, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, 0x2e, - 0x4c, 0x69, 0x73, 0x74, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, 0x71, 0x1a, 0x1a, - 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, 0x2e, 0x4c, 0x69, 0x73, 0x74, - 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, 0x73, 0x22, 0x00, 0x12, 0x4a, 0x0a, 0x0e, - 0x4c, 0x69, 0x73, 0x74, 0x41, 0x6c, 0x6c, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x12, 0x1a, - 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, 0x2e, 0x4c, 0x69, 0x73, 0x74, - 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, 0x71, 0x1a, 0x1a, 0x2e, 0x6d, 0x61, 0x67, - 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4f, 0x62, 0x6a, 0x65, - 0x63, 0x74, 0x73, 0x52, 0x65, 0x73, 0x22, 0x00, 0x12, 0x4a, 0x0a, 0x0c, 0x43, 0x6f, 0x75, 0x6e, - 0x74, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x12, 0x1b, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, - 0x74, 0x72, 0x61, 0x6c, 0x61, 0x2e, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x4f, 0x62, 0x6a, 0x65, 0x63, - 0x74, 0x73, 0x52, 0x65, 0x71, 0x1a, 0x1b, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, - 0x6c, 0x61, 0x2e, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, - 0x65, 0x73, 0x22, 0x00, 0x12, 0x4a, 0x0a, 0x0c, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x75, 0x62, 0x6a, - 0x65, 0x63, 0x74, 0x73, 0x12, 0x1b, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, - 0x61, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, - 0x71, 0x1a, 0x1b, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, 0x2e, 0x4c, - 0x69, 0x73, 0x74, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, 0x73, 0x22, 0x00, - 0x12, 0x4d, 0x0a, 0x0f, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x6c, 0x6c, 0x53, 0x75, 0x62, 0x6a, 0x65, - 0x63, 0x74, 0x73, 0x12, 0x1b, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, - 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, 0x71, - 0x1a, 0x1b, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, 0x2e, 0x4c, 0x69, - 0x73, 0x74, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, 0x73, 0x22, 0x00, 0x12, - 0x4d, 0x0a, 0x0d, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, - 0x12, 0x1c, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, 0x2e, 0x43, 0x6f, - 0x75, 0x6e, 0x74, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, 0x71, 0x1a, 0x1c, + 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x12, 0x1a, 0x0a, 0x08, + 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, + 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1e, 0x0a, 0x0a, 0x70, 0x65, 0x72, 0x6d, + 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x70, 0x65, + 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x16, 0x0a, 0x06, 0x6f, 0x62, 0x6a, 0x65, + 0x63, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, + 0x12, 0x1f, 0x0a, 0x0b, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, + 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x54, 0x79, 0x70, + 0x65, 0x12, 0x24, 0x0a, 0x0d, 0x6e, 0x65, 0x78, 0x74, 0x50, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, + 0x65, 0x6e, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x6e, 0x65, 0x78, 0x74, 0x50, 0x61, + 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, + 0x18, 0x0a, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x22, 0x53, 0x0a, + 0x0f, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, 0x73, + 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, + 0x28, 0x09, 0x52, 0x08, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x12, 0x24, 0x0a, 0x0d, + 0x6e, 0x65, 0x78, 0x74, 0x50, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x0d, 0x6e, 0x65, 0x78, 0x74, 0x50, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, + 0x65, 0x6e, 0x22, 0xad, 0x02, 0x0a, 0x10, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x53, 0x75, 0x62, 0x6a, + 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, 0x71, 0x12, 0x16, 0x0a, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, + 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, + 0x21, 0x0a, 0x0c, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x54, 0x79, + 0x70, 0x65, 0x12, 0x29, 0x0a, 0x10, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x72, 0x65, + 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x73, 0x75, + 0x62, 0x6a, 0x65, 0x63, 0x74, 0x52, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x18, 0x0a, + 0x07, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, + 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x72, 0x65, 0x6c, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x72, 0x65, 0x6c, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x12, 0x1e, 0x0a, 0x0a, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, + 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, + 0x69, 0x6f, 0x6e, 0x12, 0x16, 0x0a, 0x06, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x18, 0x07, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x06, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x6f, + 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x0a, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x24, 0x0a, 0x0d, + 0x6e, 0x65, 0x78, 0x74, 0x50, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x09, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x0d, 0x6e, 0x65, 0x78, 0x74, 0x50, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, + 0x65, 0x6e, 0x22, 0x28, 0x0a, 0x10, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x53, 0x75, 0x62, 0x6a, 0x65, + 0x63, 0x74, 0x73, 0x52, 0x65, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0xfc, 0x01, 0x0a, + 0x12, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, + 0x52, 0x65, 0x71, 0x12, 0x16, 0x0a, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x21, 0x0a, 0x0c, 0x73, + 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x0b, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x29, + 0x0a, 0x10, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, + 0x74, 0x52, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x75, 0x62, + 0x6a, 0x65, 0x63, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x75, 0x62, 0x6a, + 0x65, 0x63, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x18, 0x05, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x06, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x6f, + 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x0a, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x2d, 0x0a, 0x12, + 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x5f, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, + 0x6e, 0x73, 0x18, 0x07, 0x20, 0x03, 0x28, 0x09, 0x52, 0x11, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, + 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0xef, 0x01, 0x0a, 0x12, + 0x4c, 0x69, 0x73, 0x74, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x52, + 0x65, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x75, + 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x0b, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x29, 0x0a, + 0x10, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, + 0x52, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x75, 0x62, 0x6a, + 0x65, 0x63, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x75, 0x62, 0x6a, 0x65, + 0x63, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x18, 0x05, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x06, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x6f, 0x62, + 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x0a, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x70, + 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x07, 0x20, 0x03, 0x28, 0x09, + 0x52, 0x0b, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x32, 0x51, 0x0a, + 0x0c, 0x41, 0x75, 0x74, 0x68, 0x7a, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x41, 0x0a, + 0x09, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x12, 0x18, 0x2e, 0x6d, 0x61, 0x67, + 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, 0x2e, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, + 0x65, 0x52, 0x65, 0x71, 0x1a, 0x18, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, + 0x61, 0x2e, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x52, 0x65, 0x73, 0x22, 0x00, + 0x32, 0xc6, 0x08, 0x0a, 0x0b, 0x41, 0x75, 0x74, 0x68, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, + 0x12, 0x32, 0x0a, 0x05, 0x49, 0x73, 0x73, 0x75, 0x65, 0x12, 0x14, 0x2e, 0x6d, 0x61, 0x67, 0x69, + 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, 0x2e, 0x49, 0x73, 0x73, 0x75, 0x65, 0x52, 0x65, 0x71, 0x1a, + 0x11, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, 0x2e, 0x54, 0x6f, 0x6b, + 0x65, 0x6e, 0x22, 0x00, 0x12, 0x36, 0x0a, 0x07, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x12, + 0x16, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, 0x2e, 0x52, 0x65, 0x66, + 0x72, 0x65, 0x73, 0x68, 0x52, 0x65, 0x71, 0x1a, 0x11, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, + 0x72, 0x61, 0x6c, 0x61, 0x2e, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0x00, 0x12, 0x3e, 0x0a, 0x08, + 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x79, 0x12, 0x17, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, + 0x74, 0x72, 0x61, 0x6c, 0x61, 0x2e, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x52, 0x65, + 0x71, 0x1a, 0x17, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, 0x2e, 0x49, + 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x52, 0x65, 0x73, 0x22, 0x00, 0x12, 0x41, 0x0a, 0x09, + 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x12, 0x18, 0x2e, 0x6d, 0x61, 0x67, 0x69, + 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, 0x2e, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, + 0x52, 0x65, 0x71, 0x1a, 0x18, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, + 0x2e, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x52, 0x65, 0x73, 0x22, 0x00, 0x12, + 0x41, 0x0a, 0x09, 0x41, 0x64, 0x64, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x12, 0x18, 0x2e, 0x6d, + 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, 0x2e, 0x41, 0x64, 0x64, 0x50, 0x6f, 0x6c, + 0x69, 0x63, 0x79, 0x52, 0x65, 0x71, 0x1a, 0x18, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, + 0x61, 0x6c, 0x61, 0x2e, 0x41, 0x64, 0x64, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x65, 0x73, + 0x22, 0x00, 0x12, 0x47, 0x0a, 0x0b, 0x41, 0x64, 0x64, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, + 0x73, 0x12, 0x1a, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, 0x2e, 0x41, + 0x64, 0x64, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x52, 0x65, 0x71, 0x1a, 0x1a, 0x2e, + 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, 0x2e, 0x41, 0x64, 0x64, 0x50, 0x6f, + 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x52, 0x65, 0x73, 0x22, 0x00, 0x12, 0x4a, 0x0a, 0x0c, 0x44, + 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x12, 0x1b, 0x2e, 0x6d, 0x61, + 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, + 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x65, 0x71, 0x1a, 0x1b, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, + 0x74, 0x72, 0x61, 0x6c, 0x61, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x6f, 0x6c, 0x69, + 0x63, 0x79, 0x52, 0x65, 0x73, 0x22, 0x00, 0x12, 0x50, 0x0a, 0x0e, 0x44, 0x65, 0x6c, 0x65, 0x74, + 0x65, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x12, 0x1d, 0x2e, 0x6d, 0x61, 0x67, 0x69, + 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x6f, 0x6c, + 0x69, 0x63, 0x69, 0x65, 0x73, 0x52, 0x65, 0x71, 0x1a, 0x1d, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, + 0x74, 0x72, 0x61, 0x6c, 0x61, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x6f, 0x6c, 0x69, + 0x63, 0x69, 0x65, 0x73, 0x52, 0x65, 0x73, 0x22, 0x00, 0x12, 0x47, 0x0a, 0x0b, 0x4c, 0x69, 0x73, + 0x74, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x12, 0x1a, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, + 0x74, 0x72, 0x61, 0x6c, 0x61, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, + 0x73, 0x52, 0x65, 0x71, 0x1a, 0x1a, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, + 0x61, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, 0x73, + 0x22, 0x00, 0x12, 0x4a, 0x0a, 0x0e, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x6c, 0x6c, 0x4f, 0x62, 0x6a, + 0x65, 0x63, 0x74, 0x73, 0x12, 0x1a, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, + 0x61, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, 0x71, + 0x1a, 0x1a, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, 0x2e, 0x4c, 0x69, + 0x73, 0x74, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, 0x73, 0x22, 0x00, 0x12, 0x4a, + 0x0a, 0x0c, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x12, 0x1b, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, 0x2e, 0x43, 0x6f, 0x75, 0x6e, - 0x74, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, 0x73, 0x22, 0x00, 0x12, 0x53, - 0x0a, 0x0f, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, - 0x73, 0x12, 0x1e, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, 0x2e, 0x4c, - 0x69, 0x73, 0x74, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, - 0x71, 0x1a, 0x1e, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, 0x2e, 0x4c, - 0x69, 0x73, 0x74, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, - 0x73, 0x22, 0x00, 0x42, 0x0e, 0x5a, 0x0c, 0x2e, 0x2f, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, - 0x61, 0x6c, 0x61, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x74, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, 0x71, 0x1a, 0x1b, 0x2e, 0x6d, 0x61, + 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, 0x2e, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x4f, 0x62, + 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, 0x73, 0x22, 0x00, 0x12, 0x4a, 0x0a, 0x0c, 0x4c, 0x69, + 0x73, 0x74, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x12, 0x1b, 0x2e, 0x6d, 0x61, 0x67, + 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x75, 0x62, 0x6a, + 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, 0x71, 0x1a, 0x1b, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, + 0x72, 0x61, 0x6c, 0x61, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, + 0x73, 0x52, 0x65, 0x73, 0x22, 0x00, 0x12, 0x4d, 0x0a, 0x0f, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x6c, + 0x6c, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x12, 0x1b, 0x2e, 0x6d, 0x61, 0x67, 0x69, + 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x75, 0x62, 0x6a, 0x65, + 0x63, 0x74, 0x73, 0x52, 0x65, 0x71, 0x1a, 0x1b, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, + 0x61, 0x6c, 0x61, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, + 0x52, 0x65, 0x73, 0x22, 0x00, 0x12, 0x4d, 0x0a, 0x0d, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x53, 0x75, + 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x12, 0x1c, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, + 0x61, 0x6c, 0x61, 0x2e, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, + 0x73, 0x52, 0x65, 0x71, 0x1a, 0x1c, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, + 0x61, 0x2e, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, + 0x65, 0x73, 0x22, 0x00, 0x12, 0x53, 0x0a, 0x0f, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x65, 0x72, 0x6d, + 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x1e, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, + 0x72, 0x61, 0x6c, 0x61, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, + 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x1a, 0x1e, 0x2e, 0x6d, 0x61, 0x67, 0x69, 0x73, 0x74, + 0x72, 0x61, 0x6c, 0x61, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, + 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x22, 0x00, 0x42, 0x0e, 0x5a, 0x0c, 0x2e, 0x2f, 0x6d, + 0x61, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x61, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x33, } var ( diff --git a/auth.proto b/auth.proto index 3b370b995b..114b25409d 100644 --- a/auth.proto +++ b/auth.proto @@ -57,6 +57,9 @@ message IssueReq { string user_id = 1; optional string domain_id = 2; uint32 type = 3; + string oauth_provider = 4; + string oauth_access_token = 5; + string oauth_refresh_token = 6; } message RefreshReq { diff --git a/auth/api/grpc/client.go b/auth/api/grpc/client.go index 566a47bc34..4b0c8f8d33 100644 --- a/auth/api/grpc/client.go +++ b/auth/api/grpc/client.go @@ -174,7 +174,16 @@ func (client grpcClient) Issue(ctx context.Context, req *magistrala.IssueReq, _ ctx, cancel := context.WithTimeout(ctx, client.timeout) defer cancel() - res, err := client.issue(ctx, issueReq{userID: req.GetUserId(), domainID: req.GetDomainId(), keyType: auth.KeyType(req.Type)}) + res, err := client.issue(ctx, issueReq{ + userID: req.GetUserId(), + domainID: req.GetDomainId(), + keyType: auth.KeyType(req.GetType()), + oauthToken: auth.OAuthToken{ + Provider: req.GetOauthProvider(), + AccessToken: req.GetOauthAccessToken(), + RefreshToken: req.GetOauthRefreshToken(), + }, + }) if err != nil { return &magistrala.Token{}, decodeError(err) } @@ -183,7 +192,14 @@ func (client grpcClient) Issue(ctx context.Context, req *magistrala.IssueReq, _ func encodeIssueRequest(_ context.Context, grpcReq interface{}) (interface{}, error) { req := grpcReq.(issueReq) - return &magistrala.IssueReq{UserId: req.userID, DomainId: &req.domainID, Type: uint32(req.keyType)}, nil + return &magistrala.IssueReq{ + UserId: req.userID, + DomainId: &req.domainID, + Type: uint32(req.keyType), + OauthProvider: req.oauthToken.Provider, + OauthAccessToken: req.oauthToken.AccessToken, + OauthRefreshToken: req.oauthToken.RefreshToken, + }, nil } func decodeIssueResponse(_ context.Context, grpcRes interface{}) (interface{}, error) { diff --git a/auth/api/grpc/endpoint.go b/auth/api/grpc/endpoint.go index c691ea732e..f2db6b6530 100644 --- a/auth/api/grpc/endpoint.go +++ b/auth/api/grpc/endpoint.go @@ -21,6 +21,7 @@ func issueEndpoint(svc auth.Service) endpoint.Endpoint { Type: req.keyType, User: req.userID, Domain: req.domainID, + OAuth: req.oauthToken, } tkn, err := svc.Issue(ctx, "", key) if err != nil { diff --git a/auth/api/grpc/requests.go b/auth/api/grpc/requests.go index 7df318cee2..057d82c01f 100644 --- a/auth/api/grpc/requests.go +++ b/auth/api/grpc/requests.go @@ -21,9 +21,10 @@ func (req identityReq) validate() error { } type issueReq struct { - userID string - domainID string // optional - keyType auth.KeyType + userID string + domainID string // optional + keyType auth.KeyType + oauthToken auth.OAuthToken } func (req issueReq) validate() error { diff --git a/auth/api/grpc/server.go b/auth/api/grpc/server.go index 7af9e075a5..1bac27e1fd 100644 --- a/auth/api/grpc/server.go +++ b/auth/api/grpc/server.go @@ -273,7 +273,16 @@ func (s *grpcServer) ListPermissions(ctx context.Context, req *magistrala.ListPe func decodeIssueRequest(_ context.Context, grpcReq interface{}) (interface{}, error) { req := grpcReq.(*magistrala.IssueReq) - return issueReq{userID: req.GetUserId(), domainID: req.GetDomainId(), keyType: auth.KeyType(req.GetType())}, nil + return issueReq{ + userID: req.GetUserId(), + domainID: req.GetDomainId(), + keyType: auth.KeyType(req.GetType()), + oauthToken: auth.OAuthToken{ + Provider: req.GetOauthProvider(), + AccessToken: req.GetOauthAccessToken(), + RefreshToken: req.GetOauthRefreshToken(), + }, + }, nil } func decodeRefreshRequest(_ context.Context, grpcReq interface{}) (interface{}, error) { diff --git a/auth/api/http/keys/endpoint_test.go b/auth/api/http/keys/endpoint_test.go index 0f2ec5eba8..7721124b8d 100644 --- a/auth/api/http/keys/endpoint_test.go +++ b/auth/api/http/keys/endpoint_test.go @@ -21,6 +21,7 @@ import ( "github.com/absmach/magistrala/internal/apiutil" mglog "github.com/absmach/magistrala/logger" svcerr "github.com/absmach/magistrala/pkg/errors/service" + oauth2mocks "github.com/absmach/magistrala/pkg/oauth2/mocks" "github.com/absmach/magistrala/pkg/uuid" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" @@ -72,7 +73,9 @@ func newService() (auth.Service, *mocks.KeyRepository) { drepo := new(mocks.DomainsRepository) idProvider := uuid.NewMock() - t := jwt.New([]byte(secret)) + provider := new(oauth2mocks.Provider) + provider.On("Name").Return("test") + t := jwt.New([]byte(secret), provider) return auth.New(krepo, drepo, idProvider, t, prepo, loginDuration, refreshDuration, invalidDuration), krepo } diff --git a/auth/jwt/token_test.go b/auth/jwt/token_test.go index 4996e1d24f..9a15e0483d 100644 --- a/auth/jwt/token_test.go +++ b/auth/jwt/token_test.go @@ -4,18 +4,24 @@ package jwt_test import ( + "context" "fmt" + "strings" "testing" "time" "github.com/absmach/magistrala/auth" authjwt "github.com/absmach/magistrala/auth/jwt" + "github.com/absmach/magistrala/internal/testsutil" "github.com/absmach/magistrala/pkg/errors" svcerr "github.com/absmach/magistrala/pkg/errors/service" + oauth2mocks "github.com/absmach/magistrala/pkg/oauth2/mocks" "github.com/lestrrat-go/jwx/v2/jwa" "github.com/lestrrat-go/jwx/v2/jwt" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" + "golang.org/x/oauth2" ) const ( @@ -31,18 +37,6 @@ var ( reposecret = []byte("test") ) -func key() auth.Key { - exp := time.Now().UTC().Add(10 * time.Minute).Round(time.Second) - return auth.Key{ - ID: "66af4a67-3823-438a-abd7-efdb613eaef6", - Type: auth.AccessKey, - Issuer: "magistrala.auth", - Subject: "66af4a67-3823-438a-abd7-efdb613eaef6", - IssuedAt: time.Now().UTC().Add(-10 * time.Second).Round(time.Second), - ExpiresAt: exp, - } -} - func newToken(issuerName string, key auth.Key) string { builder := jwt.NewBuilder() builder. @@ -62,7 +56,9 @@ func newToken(issuerName string, key auth.Key) string { } func TestIssue(t *testing.T) { - tokenizer := authjwt.New([]byte(secret)) + provider := new(oauth2mocks.Provider) + provider.On("Name").Return("test") + tokenizer := authjwt.New([]byte(secret), provider) cases := []struct { desc string @@ -74,6 +70,24 @@ func TestIssue(t *testing.T) { key: key(), err: nil, }, + { + desc: "issue token with OAuth token", + key: auth.Key{ + ID: testsutil.GenerateUUID(t), + Type: auth.AccessKey, + Subject: testsutil.GenerateUUID(t), + User: testsutil.GenerateUUID(t), + Domain: testsutil.GenerateUUID(t), + IssuedAt: time.Now().Add(-10 * time.Second).Round(time.Second), + ExpiresAt: time.Now().Add(10 * time.Minute).Round(time.Second), + OAuth: auth.OAuthToken{ + Provider: "test", + AccessToken: strings.Repeat("a", 1024), + RefreshToken: strings.Repeat("b", 1024), + }, + }, + err: nil, + }, } for _, tc := range cases { @@ -86,7 +100,9 @@ func TestIssue(t *testing.T) { } func TestParse(t *testing.T) { - tokenizer := authjwt.New([]byte(secret)) + provider := new(oauth2mocks.Provider) + provider.On("Name").Return("test") + tokenizer := authjwt.New([]byte(secret), provider) token, err := tokenizer.Issue(key()) require.Nil(t, err, fmt.Sprintf("issuing key expected to succeed: %s", err)) @@ -156,3 +172,155 @@ func TestParse(t *testing.T) { } } } + +func TestParseOAuthToken(t *testing.T) { + provider := new(oauth2mocks.Provider) + provider.On("Name").Return("test") + tokenizer := authjwt.New([]byte(secret), provider) + + validKey := oauthKey(t) + invalidKey := oauthKey(t) + invalidKey.OAuth.Provider = "invalid" + + cases := []struct { + desc string + token auth.Key + issuedToken string + key auth.Key + validateErr error + refreshToken oauth2.Token + refreshErr error + err error + }{ + { + desc: "parse valid key", + token: validKey, + issuedToken: "", + key: validKey, + validateErr: nil, + refreshErr: nil, + err: nil, + }, + { + desc: "parse invalid key but refreshed", + token: validKey, + issuedToken: "", + key: validKey, + validateErr: svcerr.ErrAuthentication, + refreshToken: oauth2.Token{ + AccessToken: strings.Repeat("a", 10), + RefreshToken: strings.Repeat("b", 10), + }, + refreshErr: nil, + err: nil, + }, + { + desc: "parse invalid key but not refreshed", + token: validKey, + issuedToken: "", + key: validKey, + validateErr: svcerr.ErrAuthentication, + refreshToken: oauth2.Token{}, + refreshErr: svcerr.ErrAuthentication, + err: svcerr.ErrAuthentication, + }, + { + desc: "parse invalid key with different provider", + issuedToken: invalidOauthToken(t, invalidKey, "invalid", "a", "b"), + err: svcerr.ErrAuthentication, + }, + { + desc: "parse invalid key with invalid access token", + issuedToken: invalidOauthToken(t, invalidKey, "invalid", 123, "b"), + err: svcerr.ErrAuthentication, + }, + { + desc: "parse invalid key with invalid refresh token", + issuedToken: invalidOauthToken(t, invalidKey, "invalid", "a", 123), + err: svcerr.ErrAuthentication, + }, + { + desc: "parse invalid key with invalid provider", + issuedToken: invalidOauthToken(t, invalidKey, "test", "a", "b"), + err: svcerr.ErrAuthentication, + }, + } + + for _, tc := range cases { + tokenCall := provider.On("Name").Return("test") + tokenCall1 := provider.On("Validate", context.Background(), mock.Anything).Return(tc.validateErr) + tokenCall2 := provider.On("Refresh", context.Background(), mock.Anything).Return(tc.refreshToken, tc.refreshErr) + if tc.issuedToken == "" { + var err error + tc.issuedToken, err = tokenizer.Issue(tc.token) + require.Nil(t, err, fmt.Sprintf("issuing key expected to succeed: %s", err)) + } + key, err := tokenizer.Parse(tc.issuedToken) + assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s expected %s, got %s", tc.desc, tc.err, err)) + if err == nil { + assert.Equal(t, tc.key, key, fmt.Sprintf("%s expected %v, got %v", tc.desc, tc.key, key)) + } + tokenCall.Unset() + tokenCall1.Unset() + tokenCall2.Unset() + } +} + +func key() auth.Key { + exp := time.Now().UTC().Add(10 * time.Minute).Round(time.Second) + return auth.Key{ + ID: "66af4a67-3823-438a-abd7-efdb613eaef6", + Type: auth.AccessKey, + Issuer: "magistrala.auth", + Subject: "66af4a67-3823-438a-abd7-efdb613eaef6", + IssuedAt: time.Now().UTC().Add(-10 * time.Second).Round(time.Second), + ExpiresAt: exp, + } +} + +func oauthKey(t *testing.T) auth.Key { + return auth.Key{ + ID: testsutil.GenerateUUID(t), + Type: auth.AccessKey, + Issuer: "magistrala.auth", + Subject: testsutil.GenerateUUID(t), + User: testsutil.GenerateUUID(t), + Domain: testsutil.GenerateUUID(t), + IssuedAt: time.Now().UTC().Add(-10 * time.Second).Round(time.Second), + ExpiresAt: time.Now().UTC().Add(10 * time.Minute).Round(time.Second), + OAuth: auth.OAuthToken{ + Provider: "test", + AccessToken: strings.Repeat("a", 10), + RefreshToken: strings.Repeat("b", 10), + }, + } +} + +func invalidOauthToken(t *testing.T, key auth.Key, provider, accessToken, refreshToken interface{}) string { + builder := jwt.NewBuilder() + builder. + Issuer(issuerName). + IssuedAt(key.IssuedAt). + Subject(key.Subject). + Claim(tokenType, key.Type). + Expiration(key.ExpiresAt) + builder.Claim(userField, key.User) + builder.Claim(domainField, key.Domain) + if provider != nil { + builder.Claim("oauth_provider", provider) + if accessToken != nil { + builder.Claim(provider.(string), map[string]interface{}{"access_token": accessToken}) + } + if refreshToken != nil { + builder.Claim(provider.(string), map[string]interface{}{"refresh_token": refreshToken}) + } + } + if key.ID != "" { + builder.JwtID(key.ID) + } + tkn, err := builder.Build() + require.Nil(t, err, fmt.Sprintf("building token expected to succeed: %s", err)) + signedTkn, err := jwt.Sign(tkn, jwt.WithKey(jwa.HS512, reposecret)) + require.Nil(t, err, fmt.Sprintf("signing token expected to succeed: %s", err)) + return string(signedTkn) +} diff --git a/auth/jwt/tokenizer.go b/auth/jwt/tokenizer.go index b3fc92e63e..5627d0609a 100644 --- a/auth/jwt/tokenizer.go +++ b/auth/jwt/tokenizer.go @@ -12,6 +12,7 @@ import ( "github.com/absmach/magistrala/auth" "github.com/absmach/magistrala/pkg/errors" svcerr "github.com/absmach/magistrala/pkg/errors/service" + "github.com/absmach/magistrala/pkg/oauth2" "github.com/lestrrat-go/jwx/v2/jwa" "github.com/lestrrat-go/jwx/v2/jwt" ) @@ -32,29 +33,41 @@ var ( ErrValidateJWTToken = errors.New("failed to validate jwt token") // ErrJSONHandle indicates an error in handling JSON. ErrJSONHandle = errors.New("failed to perform operation JSON") + + // errInvalidProvider indicates an invalid OAuth2.0 provider. + errInvalidProvider = errors.New("invalid OAuth2.0 provider") ) const ( - issuerName = "magistrala.auth" - tokenType = "type" - userField = "user" - domainField = "domain" + issuerName = "magistrala.auth" + tokenType = "type" + userField = "user" + domainField = "domain" + oauthProviderField = "oauth_provider" + oauthAccessTokenField = "access_token" + oauthRefreshTokenField = "refresh_token" ) type tokenizer struct { - secret []byte + secret []byte + providers map[string]oauth2.Provider } var _ auth.Tokenizer = (*tokenizer)(nil) // NewRepository instantiates an implementation of Token repository. -func New(secret []byte) auth.Tokenizer { +func New(secret []byte, providers ...oauth2.Provider) auth.Tokenizer { + providersMap := make(map[string]oauth2.Provider) + for _, provider := range providers { + providersMap[provider.Name()] = provider + } return &tokenizer{ - secret: secret, + secret: secret, + providers: providersMap, } } -func (repo *tokenizer) Issue(key auth.Key) (string, error) { +func (tok *tokenizer) Issue(key auth.Key) (string, error) { builder := jwt.NewBuilder() builder. Issuer(issuerName). @@ -64,6 +77,19 @@ func (repo *tokenizer) Issue(key auth.Key) (string, error) { Expiration(key.ExpiresAt) builder.Claim(userField, key.User) builder.Claim(domainField, key.Domain) + + if key.OAuth.Provider != "" { + provider, ok := tok.providers[key.OAuth.Provider] + if !ok { + return "", errors.Wrap(svcerr.ErrAuthentication, errInvalidProvider) + } + builder.Claim(oauthProviderField, provider.Name()) + builder.Claim(provider.Name(), map[string]interface{}{ + oauthAccessTokenField: key.OAuth.AccessToken, + oauthRefreshTokenField: key.OAuth.RefreshToken, + }) + } + if key.ID != "" { builder.JwtID(key.ID) } @@ -71,18 +97,18 @@ func (repo *tokenizer) Issue(key auth.Key) (string, error) { if err != nil { return "", errors.Wrap(svcerr.ErrAuthentication, err) } - signedTkn, err := jwt.Sign(tkn, jwt.WithKey(jwa.HS512, repo.secret)) + signedTkn, err := jwt.Sign(tkn, jwt.WithKey(jwa.HS512, tok.secret)) if err != nil { return "", errors.Wrap(ErrSignJWT, err) } return string(signedTkn), nil } -func (repo *tokenizer) Parse(token string) (auth.Key, error) { +func (tok *tokenizer) Parse(token string) (auth.Key, error) { tkn, err := jwt.Parse( []byte(token), jwt.WithValidate(true), - jwt.WithKey(jwa.HS512, repo.secret), + jwt.WithKey(jwa.HS512, tok.secret), ) if err != nil { if errors.Contains(err, errJWTExpiryKey) { @@ -125,5 +151,66 @@ func (repo *tokenizer) Parse(token string) (auth.Key, error) { key.Subject = tkn.Subject() key.IssuedAt = tkn.IssuedAt() key.ExpiresAt = tkn.Expiration() + + oauthProvider, ok := tkn.Get(oauthProviderField) + if ok { + provider, ok := oauthProvider.(string) + if !ok { + return auth.Key{}, errors.Wrap(svcerr.ErrAuthentication, errInvalidProvider) + } + if provider != "" { + prov, ok := tok.providers[provider] + if !ok { + return auth.Key{}, errors.Wrap(svcerr.ErrAuthentication, errInvalidProvider) + } + key.OAuth.Provider = prov.Name() + + key, err = parseOAuthToken(context.Background(), prov, tkn, key) + if err != nil { + return auth.Key{}, errors.Wrap(svcerr.ErrAuthentication, err) + } + + return key, nil + } + } + + return key, nil +} + +func parseOAuthToken(ctx context.Context, provider oauth2.Provider, token jwt.Token, key auth.Key) (auth.Key, error) { + oauthToken, ok := token.Get(provider.Name()) + if ok { + claims, ok := oauthToken.(map[string]interface{}) + if !ok { + return auth.Key{}, errors.Wrap(ErrParseToken, fmt.Errorf("invalid claims for %s token", provider.Name())) + } + accessToken, ok := claims[oauthAccessTokenField].(string) + if !ok { + return auth.Key{}, errors.Wrap(ErrParseToken, fmt.Errorf("invalid access token claim for %s token", provider.Name())) + } + refreshToken, ok := claims[oauthRefreshTokenField].(string) + if !ok { + return auth.Key{}, errors.Wrap(ErrParseToken, fmt.Errorf("invalid refresh token claim for %s token", provider.Name())) + } + + switch provider.Validate(ctx, accessToken) { + case nil: + key.OAuth.AccessToken = accessToken + default: + token, err := provider.Refresh(ctx, refreshToken) + if err != nil { + return auth.Key{}, errors.Wrap(svcerr.ErrAuthentication, err) + } + key.OAuth.AccessToken = token.AccessToken + key.OAuth.RefreshToken = token.RefreshToken + + return key, nil + } + + key.OAuth.RefreshToken = refreshToken + + return key, nil + } + return key, nil } diff --git a/auth/keys.go b/auth/keys.go index 030852e1ba..b5801b0443 100644 --- a/auth/keys.go +++ b/auth/keys.go @@ -58,16 +58,24 @@ func (kt KeyType) String() string { } } +// OAuthToken represents OAuth token. +type OAuthToken struct { + Provider string `json:"provider,omitempty"` + AccessToken string `json:"access_token,omitempty"` + RefreshToken string `json:"refresh_token,omitempty"` +} + // Key represents API key. type Key struct { - ID string `json:"id,omitempty"` - Type KeyType `json:"type,omitempty"` - Issuer string `json:"issuer,omitempty"` - Subject string `json:"subject,omitempty"` // user ID - User string `json:"user,omitempty"` - Domain string `json:"domain,omitempty"` // domain user ID - IssuedAt time.Time `json:"issued_at,omitempty"` - ExpiresAt time.Time `json:"expires_at,omitempty"` + ID string `json:"id,omitempty"` + Type KeyType `json:"type,omitempty"` + Issuer string `json:"issuer,omitempty"` + Subject string `json:"subject,omitempty"` // user ID + User string `json:"user,omitempty"` + Domain string `json:"domain,omitempty"` // domain user ID + IssuedAt time.Time `json:"issued_at,omitempty"` + ExpiresAt time.Time `json:"expires_at,omitempty"` + OAuth OAuthToken `json:"oauth,omitempty"` } func (key Key) String() string { diff --git a/auth/service.go b/auth/service.go index 0626036c6e..cdead70227 100644 --- a/auth/service.go +++ b/auth/service.go @@ -428,6 +428,10 @@ func (svc service) refreshKey(ctx context.Context, token string, key Key) (Token key.User = k.User key.Type = AccessKey + key.OAuth.Provider = k.OAuth.Provider + key.OAuth.AccessToken = k.OAuth.AccessToken + key.OAuth.RefreshToken = k.OAuth.RefreshToken + key.Subject, err = svc.checkUserDomain(ctx, key) if err != nil { return Token{}, errors.Wrap(svcerr.ErrAuthorization, err) diff --git a/auth/service_test.go b/auth/service_test.go index 6e16c59589..f46a5246c5 100644 --- a/auth/service_test.go +++ b/auth/service_test.go @@ -15,6 +15,7 @@ import ( "github.com/absmach/magistrala/pkg/errors" repoerr "github.com/absmach/magistrala/pkg/errors/repository" svcerr "github.com/absmach/magistrala/pkg/errors/service" + oauth2mocks "github.com/absmach/magistrala/pkg/oauth2/mocks" "github.com/absmach/magistrala/pkg/uuid" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" @@ -69,7 +70,9 @@ func newService() (auth.Service, string) { drepo = new(mocks.DomainsRepository) idProvider := uuid.NewMock() - t := jwt.New([]byte(secret)) + provider := new(oauth2mocks.Provider) + provider.On("Name").Return("test") + t := jwt.New([]byte(secret), provider) key := auth.Key{ IssuedAt: time.Now(), ExpiresAt: time.Now().Add(refreshDuration), @@ -86,7 +89,10 @@ func newService() (auth.Service, string) { func TestIssue(t *testing.T) { svc, accessToken := newService() - n := jwt.New([]byte(secret)) + provider := new(oauth2mocks.Provider) + provider.On("Name").Return("test") + n := jwt.New([]byte(secret), provider) + apikey := auth.Key{ IssuedAt: time.Now(), ExpiresAt: time.Now().Add(refreshDuration), @@ -627,7 +633,9 @@ func TestIdentify(t *testing.T) { assert.Nil(t, err, fmt.Sprintf("Issuing expired login key expected to succeed: %s", err)) repocall4.Unset() - te := jwt.New([]byte(secret)) + provider := new(oauth2mocks.Provider) + provider.On("Name").Return("test") + te := jwt.New([]byte(secret), provider) key := auth.Key{ IssuedAt: time.Now(), ExpiresAt: time.Now().Add(refreshDuration), @@ -721,7 +729,9 @@ func TestAuthorize(t *testing.T) { repocall2.Unset() repocall3.Unset() - te := jwt.New([]byte(secret)) + provider := new(oauth2mocks.Provider) + provider.On("Name").Return("test") + te := jwt.New([]byte(secret), provider) key := auth.Key{ IssuedAt: time.Now(), ExpiresAt: time.Now().Add(refreshDuration), diff --git a/auth_grpc.pb.go b/auth_grpc.pb.go index d02da3e57d..c60c51381c 100644 --- a/auth_grpc.pb.go +++ b/auth_grpc.pb.go @@ -4,7 +4,7 @@ // Code generated by protoc-gen-go-grpc. DO NOT EDIT. // versions: // - protoc-gen-go-grpc v1.3.0 -// - protoc v4.25.1 +// - protoc v4.25.3 // source: auth.proto package magistrala diff --git a/cmd/auth/main.go b/cmd/auth/main.go index bacbeccd9c..7349685bbf 100644 --- a/cmd/auth/main.go +++ b/cmd/auth/main.go @@ -30,6 +30,8 @@ import ( grpcserver "github.com/absmach/magistrala/internal/server/grpc" httpserver "github.com/absmach/magistrala/internal/server/http" mglog "github.com/absmach/magistrala/logger" + "github.com/absmach/magistrala/pkg/oauth2" + "github.com/absmach/magistrala/pkg/oauth2/google" "github.com/absmach/magistrala/pkg/uuid" v1 "github.com/authzed/authzed-go/proto/authzed/api/v1" "github.com/authzed/authzed-go/v1" @@ -44,13 +46,14 @@ import ( ) const ( - svcName = "auth" - envPrefixHTTP = "MG_AUTH_HTTP_" - envPrefixGrpc = "MG_AUTH_GRPC_" - envPrefixDB = "MG_AUTH_DB_" - defDB = "auth" - defSvcHTTPPort = "8180" - defSvcGRPCPort = "8181" + svcName = "auth" + envPrefixHTTP = "MG_AUTH_HTTP_" + envPrefixGrpc = "MG_AUTH_GRPC_" + envPrefixDB = "MG_AUTH_DB_" + envPrefixGoogle = "MG_GOOGLE_" + defDB = "auth" + defSvcHTTPPort = "8180" + defSvcGRPCPort = "8181" ) type config struct { @@ -127,7 +130,15 @@ func main() { return } - svc := newService(db, tracer, cfg, dbConfig, logger, spicedbclient) + oauthConfig := oauth2.Config{} + if err := env.ParseWithOptions(&oauthConfig, env.Options{Prefix: envPrefixGoogle}); err != nil { + logger.Error(fmt.Sprintf("failed to load %s Google configuration : %s", svcName, err.Error())) + exitCode = 1 + return + } + oauthProvider := google.NewProvider(oauthConfig, "", "") + + svc := newService(db, tracer, cfg, dbConfig, logger, spicedbclient, oauthProvider) httpServerConfig := server.Config{Port: defSvcHTTPPort} if err := env.ParseWithOptions(&httpServerConfig, env.Options{Prefix: envPrefixHTTP}); err != nil { @@ -201,13 +212,14 @@ func initSchema(ctx context.Context, client *authzed.ClientWithExperimental, sch return nil } -func newService(db *sqlx.DB, tracer trace.Tracer, cfg config, dbConfig pgclient.Config, logger *slog.Logger, spicedbClient *authzed.ClientWithExperimental) auth.Service { +func newService(db *sqlx.DB, tracer trace.Tracer, cfg config, dbConfig pgclient.Config, logger *slog.Logger, spicedbClient *authzed.ClientWithExperimental, providers ...oauth2.Provider) auth.Service { database := postgres.NewDatabase(db, dbConfig, tracer) keysRepo := apostgres.New(database) domainsRepo := apostgres.NewDomainRepository(database) pa := spicedb.NewPolicyAgent(spicedbClient, logger) idProvider := uuid.New() - t := jwt.New([]byte(cfg.SecretKey)) + + t := jwt.New([]byte(cfg.SecretKey), providers...) svc := auth.New(keysRepo, domainsRepo, idProvider, t, pa, cfg.AccessDuration, cfg.RefreshDuration, cfg.InvitationDuration) svc = api.LoggingMiddleware(svc, logger) diff --git a/cmd/users/main.go b/cmd/users/main.go index 98a9437541..ae84549b94 100644 --- a/cmd/users/main.go +++ b/cmd/users/main.go @@ -34,6 +34,8 @@ import ( mgclients "github.com/absmach/magistrala/pkg/clients" svcerr "github.com/absmach/magistrala/pkg/errors/service" "github.com/absmach/magistrala/pkg/groups" + "github.com/absmach/magistrala/pkg/oauth2" + googleoauth "github.com/absmach/magistrala/pkg/oauth2/google" "github.com/absmach/magistrala/pkg/uuid" "github.com/absmach/magistrala/users" capi "github.com/absmach/magistrala/users/api" @@ -50,29 +52,32 @@ import ( ) const ( - svcName = "users" - envPrefixDB = "MG_USERS_DB_" - envPrefixHTTP = "MG_USERS_HTTP_" - envPrefixAuth = "MG_AUTH_GRPC_" - defDB = "users" - defSvcHTTPPort = "9002" + svcName = "users" + envPrefixDB = "MG_USERS_DB_" + envPrefixHTTP = "MG_USERS_HTTP_" + envPrefixAuth = "MG_AUTH_GRPC_" + envPrefixGoogle = "MG_GOOGLE_" + defDB = "users" + defSvcHTTPPort = "9002" streamID = "magistrala.users" ) type config struct { - LogLevel string `env:"MG_USERS_LOG_LEVEL" envDefault:"info"` - AdminEmail string `env:"MG_USERS_ADMIN_EMAIL" envDefault:"admin@example.com"` - AdminPassword string `env:"MG_USERS_ADMIN_PASSWORD" envDefault:"12345678"` - PassRegexText string `env:"MG_USERS_PASS_REGEX" envDefault:"^.{8,}$"` - ResetURL string `env:"MG_TOKEN_RESET_ENDPOINT" envDefault:"/reset-request"` - JaegerURL url.URL `env:"MG_JAEGER_URL" envDefault:"http://localhost:14268/api/traces"` - SendTelemetry bool `env:"MG_SEND_TELEMETRY" envDefault:"true"` - InstanceID string `env:"MG_USERS_INSTANCE_ID" envDefault:""` - ESURL string `env:"MG_ES_URL" envDefault:"nats://localhost:4222"` - TraceRatio float64 `env:"MG_JAEGER_TRACE_RATIO" envDefault:"1.0"` - SelfRegister bool `env:"MG_USERS_ALLOW_SELF_REGISTER" envDefault:"false"` - PassRegex *regexp.Regexp + LogLevel string `env:"MG_USERS_LOG_LEVEL" envDefault:"info"` + AdminEmail string `env:"MG_USERS_ADMIN_EMAIL" envDefault:"admin@example.com"` + AdminPassword string `env:"MG_USERS_ADMIN_PASSWORD" envDefault:"12345678"` + PassRegexText string `env:"MG_USERS_PASS_REGEX" envDefault:"^.{8,}$"` + ResetURL string `env:"MG_TOKEN_RESET_ENDPOINT" envDefault:"/reset-request"` + JaegerURL url.URL `env:"MG_JAEGER_URL" envDefault:"http://localhost:14268/api/traces"` + SendTelemetry bool `env:"MG_SEND_TELEMETRY" envDefault:"true"` + InstanceID string `env:"MG_USERS_INSTANCE_ID" envDefault:""` + ESURL string `env:"MG_ES_URL" envDefault:"nats://localhost:4222"` + TraceRatio float64 `env:"MG_JAEGER_TRACE_RATIO" envDefault:"1.0"` + SelfRegister bool `env:"MG_USERS_ALLOW_SELF_REGISTER" envDefault:"false"` + OAuthUIRedirectURL string `env:"MG_OAUTH_UI_REDIRECT_URL" envDefault:"http://localhost:9095/domains"` + OAuthUIErrorURL string `env:"MG_OAUTH_UI_ERROR_URL" envDefault:"http://localhost:9095/error"` + PassRegex *regexp.Regexp } func main() { @@ -172,8 +177,16 @@ func main() { return } + oauthConfig := oauth2.Config{} + if err := env.ParseWithOptions(&oauthConfig, env.Options{Prefix: envPrefixGoogle}); err != nil { + logger.Error(fmt.Sprintf("failed to load %s Google configuration : %s", svcName, err.Error())) + exitCode = 1 + return + } + oauthProvider := googleoauth.NewProvider(oauthConfig, cfg.OAuthUIRedirectURL, cfg.OAuthUIErrorURL) + mux := chi.NewRouter() - httpSrv := httpserver.New(ctx, cancel, svcName, httpServerConfig, capi.MakeHandler(csvc, gsvc, mux, logger, cfg.InstanceID), logger) + httpSrv := httpserver.New(ctx, cancel, svcName, httpServerConfig, capi.MakeHandler(csvc, gsvc, mux, logger, cfg.InstanceID, oauthProvider), logger) if cfg.SendTelemetry { chc := chclient.New(svcName, magistrala.Version, logger, cancel) diff --git a/docker/.env b/docker/.env index 2f6910de42..5860920b3e 100644 --- a/docker/.env +++ b/docker/.env @@ -165,6 +165,8 @@ MG_USERS_DB_SSL_ROOT_CERT= MG_USERS_RESET_PWD_TEMPLATE=users.tmpl MG_USERS_INSTANCE_ID= MG_USERS_ALLOW_SELF_REGISTER=true +MG_OAUTH_UI_REDIRECT_URL="http://localhost:9095/domains" +MG_OAUTH_UI_ERROR_URL="http://localhost:9095/error" ### Email utility MG_EMAIL_HOST=smtp.mailtrap.io @@ -175,6 +177,12 @@ MG_EMAIL_FROM_ADDRESS=from@example.com MG_EMAIL_FROM_NAME=Example MG_EMAIL_TEMPLATE=email.tmpl +### Google OAuth2 +MG_GOOGLE_CLIENT_ID= +MG_GOOGLE_CLIENT_SECRET= +MG_GOOGLE_REDIRECT_URL= +MG_GOOGLE_STATE= + ### Things MG_THINGS_LOG_LEVEL=debug MG_THINGS_STANDALONE_ID= diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index 93781c863e..2fd6152613 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -125,6 +125,10 @@ services: MG_JAEGER_TRACE_RATIO: ${MG_JAEGER_TRACE_RATIO} MG_SEND_TELEMETRY: ${MG_SEND_TELEMETRY} MG_AUTH_ADAPTER_INSTANCE_ID: ${MG_AUTH_ADAPTER_INSTANCE_ID} + MG_GOOGLE_CLIENT_ID: ${MG_GOOGLE_CLIENT_ID} + MG_GOOGLE_CLIENT_SECRET: ${MG_GOOGLE_CLIENT_SECRET} + MG_GOOGLE_REDIRECT_URL: ${MG_GOOGLE_REDIRECT_URL} + MG_GOOGLE_STATE: ${MG_GOOGLE_STATE} ports: - ${MG_AUTH_HTTP_PORT}:${MG_AUTH_HTTP_PORT} - ${MG_AUTH_GRPC_PORT}:${MG_AUTH_GRPC_PORT} @@ -441,6 +445,12 @@ services: MG_AUTH_GRPC_CLIENT_CERT: ${MG_AUTH_GRPC_CLIENT_CERT:+/auth-grpc-client.crt} MG_AUTH_GRPC_CLIENT_KEY: ${MG_AUTH_GRPC_CLIENT_KEY:+/auth-grpc-client.key} MG_AUTH_GRPC_SERVER_CA_CERTS: ${MG_AUTH_GRPC_SERVER_CA_CERTS:+/auth-grpc-server-ca.crt} + MG_GOOGLE_CLIENT_ID: ${MG_GOOGLE_CLIENT_ID} + MG_GOOGLE_CLIENT_SECRET: ${MG_GOOGLE_CLIENT_SECRET} + MG_GOOGLE_REDIRECT_URL: ${MG_GOOGLE_REDIRECT_URL} + MG_GOOGLE_STATE: ${MG_GOOGLE_STATE} + MG_OAUTH_UI_REDIRECT_URL: ${MG_OAUTH_UI_REDIRECT_URL} + MG_OAUTH_UI_ERROR_URL: ${MG_OAUTH_UI_ERROR_URL} ports: - ${MG_USERS_HTTP_PORT}:${MG_USERS_HTTP_PORT} networks: @@ -725,6 +735,10 @@ services: MG_UI_DB_SSL_CERT: ${MG_UI_DB_SSL_CERT} MG_UI_DB_SSL_KEY: ${MG_UI_DB_SSL_KEY} MG_UI_DB_SSL_ROOT_CERT: ${MG_UI_DB_SSL_ROOT_CERT} + MG_GOOGLE_CLIENT_ID: ${MG_GOOGLE_CLIENT_ID} + MG_GOOGLE_CLIENT_SECRET: ${MG_GOOGLE_CLIENT_SECRET} + MG_GOOGLE_REDIRECT_URL: ${MG_GOOGLE_REDIRECT_URL} + MG_GOOGLE_STATE: ${MG_GOOGLE_STATE} ports: - ${MG_UI_PORT}:${MG_UI_PORT} networks: diff --git a/docker/nginx/nginx-key.conf b/docker/nginx/nginx-key.conf index b33a0667b0..f561229a9b 100644 --- a/docker/nginx/nginx-key.conf +++ b/docker/nginx/nginx-key.conf @@ -120,7 +120,7 @@ http { } # Proxy pass to users service - location ~ ^/(users|groups|password|authorize) { + location ~ ^/(users|groups|password|authorize|oauth/callback/[^/]+) { include snippets/proxy-headers.conf; add_header Access-Control-Expose-Headers Location; proxy_pass http://users:${MG_USERS_HTTP_PORT}; diff --git a/docker/nginx/nginx-x509.conf b/docker/nginx/nginx-x509.conf index 69cc38c759..49f0170e60 100644 --- a/docker/nginx/nginx-x509.conf +++ b/docker/nginx/nginx-x509.conf @@ -129,7 +129,7 @@ http { } # Proxy pass to users service - location ~ ^/(users|groups|password|authorize) { + location ~ ^/(users|groups|password|authorize|oauth/callback/[^/]+) { include snippets/proxy-headers.conf; add_header Access-Control-Expose-Headers Location; proxy_pass http://users:${MG_USERS_HTTP_PORT}; diff --git a/go.mod b/go.mod index 6f3bb409ed..fb5e018e49 100644 --- a/go.mod +++ b/go.mod @@ -57,6 +57,7 @@ require ( go.opentelemetry.io/otel/trace v1.22.0 golang.org/x/crypto v0.19.0 golang.org/x/net v0.21.0 + golang.org/x/oauth2 v0.17.0 golang.org/x/sync v0.6.0 gonum.org/v1/gonum v0.14.0 google.golang.org/genproto/googleapis/rpc v0.0.0-20240116215550-a9fa1716bcac @@ -66,6 +67,8 @@ require ( ) require ( + cloud.google.com/go/compute v1.23.3 // indirect + cloud.google.com/go/compute/metadata v0.2.3 // indirect github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect github.com/Microsoft/go-winio v0.6.1 // indirect github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect @@ -179,6 +182,7 @@ require ( golang.org/x/text v0.14.0 // indirect golang.org/x/time v0.5.0 // indirect golang.org/x/tools v0.17.0 // indirect + google.golang.org/appengine v1.6.8 // indirect google.golang.org/genproto v0.0.0-20240116215550-a9fa1716bcac // indirect google.golang.org/genproto/googleapis/api v0.0.0-20240116215550-a9fa1716bcac // indirect gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect diff --git a/go.sum b/go.sum index 162ef1973c..f1186c3a31 100644 --- a/go.sum +++ b/go.sum @@ -1,6 +1,5 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.111.0 h1:YHLKNupSD1KqjDbQ3+LVdQ81h/UJbJyZG203cEfnQgM= cloud.google.com/go/compute v1.23.3 h1:6sVlXXBmbd7jNX0Ipq0trII3e4n1/MsADLK6a+aiVlk= cloud.google.com/go/compute v1.23.3/go.mod h1:VCgBUoMnIVIR0CscqQiPJLAG25E3ZRZMzcFZeQ+h8CI= cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= @@ -170,6 +169,7 @@ github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:W github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= @@ -661,8 +661,8 @@ golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.16.0 h1:aDkGMBSYxElaoP81NpoUoz2oo2R2wHdZpGToUxfyQrQ= -golang.org/x/oauth2 v0.16.0/go.mod h1:hqZ+0LWXsiVoZpeld6jVt06P3adbS2Uu911W1SsJv2o= +golang.org/x/oauth2 v0.17.0 h1:6m3ZPmLEFdVxKKWnKq4VqZ60gutO35zm+zrAHVmHyDQ= +golang.org/x/oauth2 v0.17.0/go.mod h1:OzPDGQiuQMguemayvdylqddI7qcD9lnSDb+1FiwQ5HA= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= diff --git a/pkg/messaging/message.pb.go b/pkg/messaging/message.pb.go index 971df627ac..4767c3ecec 100644 --- a/pkg/messaging/message.pb.go +++ b/pkg/messaging/message.pb.go @@ -3,8 +3,8 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.31.0 -// protoc v4.25.1 +// protoc-gen-go v1.32.0 +// protoc v4.25.3 // source: pkg/messaging/message.proto package messaging diff --git a/pkg/oauth2/doc.go b/pkg/oauth2/doc.go new file mode 100644 index 0000000000..2d7e006f53 --- /dev/null +++ b/pkg/oauth2/doc.go @@ -0,0 +1,6 @@ +// Copyright (c) Abstract Machines +// SPDX-License-Identifier: Apache-2.0 + +// Package oauth2 contains the domain concept definitions needed to support +// Magistrala ui service OAuth2 functionality. +package oauth2 diff --git a/pkg/oauth2/google/doc.go b/pkg/oauth2/google/doc.go new file mode 100644 index 0000000000..74f7ada5e4 --- /dev/null +++ b/pkg/oauth2/google/doc.go @@ -0,0 +1,6 @@ +// Copyright (c) Abstract Machines +// SPDX-License-Identifier: Apache-2.0 + +// Package google contains the domain concept definitions needed to support +// Magistrala services for Google OAuth2 functionality. +package google diff --git a/pkg/oauth2/google/provider.go b/pkg/oauth2/google/provider.go new file mode 100644 index 0000000000..daba42b6e4 --- /dev/null +++ b/pkg/oauth2/google/provider.go @@ -0,0 +1,183 @@ +// Copyright (c) Abstract Machines +// SPDX-License-Identifier: Apache-2.0 + +package google + +import ( + "context" + "encoding/json" + "fmt" + "io" + "net/http" + "net/url" + "strings" + "time" + + mfclients "github.com/absmach/magistrala/pkg/clients" + svcerr "github.com/absmach/magistrala/pkg/errors/service" + mgoauth2 "github.com/absmach/magistrala/pkg/oauth2" + "golang.org/x/oauth2" + googleoauth2 "golang.org/x/oauth2/google" +) + +const ( + providerName = "google" + defTimeout = 1 * time.Minute + userInfoURL = "https://www.googleapis.com/oauth2/v2/userinfo?access_token=" + tokenInfoURL = "https://oauth2.googleapis.com/tokeninfo?access_token=" +) + +var scopes = []string{ + "https://www.googleapis.com/auth/userinfo.email", + "https://www.googleapis.com/auth/userinfo.profile", +} + +var _ mgoauth2.Provider = (*config)(nil) + +type config struct { + config *oauth2.Config + state string + uiRedirectURL string + errorURL string +} + +// NewProvider returns a new Google OAuth provider. +func NewProvider(cfg mgoauth2.Config, uiRedirectURL, errorURL string) mgoauth2.Provider { + return &config{ + config: &oauth2.Config{ + ClientID: cfg.ClientID, + ClientSecret: cfg.ClientSecret, + Endpoint: googleoauth2.Endpoint, + RedirectURL: cfg.RedirectURL, + Scopes: scopes, + }, + state: cfg.State, + uiRedirectURL: uiRedirectURL, + errorURL: errorURL, + } +} + +func (cfg *config) Name() string { + return providerName +} + +func (cfg *config) State() string { + return cfg.state +} + +func (cfg *config) RedirectURL() string { + return cfg.uiRedirectURL +} + +func (cfg *config) ErrorURL() string { + return cfg.errorURL +} + +func (cfg *config) IsEnabled() bool { + return cfg.config.ClientID != "" && cfg.config.ClientSecret != "" +} + +func (cfg *config) UserDetails(ctx context.Context, code string) (mfclients.Client, oauth2.Token, error) { + token, err := cfg.config.Exchange(ctx, code) + if err != nil { + return mfclients.Client{}, oauth2.Token{}, err + } + if token.RefreshToken == "" { + return mfclients.Client{}, oauth2.Token{}, svcerr.ErrAuthentication + } + + resp, err := http.Get(userInfoURL + url.QueryEscape(token.AccessToken)) + if err != nil { + return mfclients.Client{}, oauth2.Token{}, err + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return mfclients.Client{}, oauth2.Token{}, svcerr.ErrAuthentication + } + + data, err := io.ReadAll(resp.Body) + if err != nil { + return mfclients.Client{}, oauth2.Token{}, err + } + + var user struct { + ID string `json:"id"` + Name string `json:"name"` + Email string `json:"email"` + Picture string `json:"picture"` + } + if err := json.Unmarshal(data, &user); err != nil { + return mfclients.Client{}, oauth2.Token{}, err + } + + if user.ID == "" || user.Name == "" || user.Email == "" { + return mfclients.Client{}, oauth2.Token{}, svcerr.ErrAuthentication + } + + client := mfclients.Client{ + ID: user.ID, + Name: user.Name, + Credentials: mfclients.Credentials{ + Identity: user.Email, + }, + Metadata: map[string]interface{}{ + "oauth_provider": providerName, + "profile_picture": user.Picture, + }, + Status: mfclients.EnabledStatus, + } + + return client, *token, nil +} + +func (cfg *config) Validate(ctx context.Context, token string) error { + client := &http.Client{ + Timeout: defTimeout, + } + req, err := http.NewRequest(http.MethodGet, tokenInfoURL+token, http.NoBody) + if err != nil { + return svcerr.ErrAuthentication + } + res, err := client.Do(req) + if err != nil { + return svcerr.ErrAuthentication + } + defer res.Body.Close() + + if res.StatusCode != http.StatusOK { + return svcerr.ErrAuthentication + } + + return nil +} + +func (cfg *config) Refresh(ctx context.Context, token string) (oauth2.Token, error) { + payload := strings.NewReader(fmt.Sprintf("grant_type=refresh_token&refresh_token=%s&client_id=%s&client_secret=%s", token, cfg.config.ClientID, cfg.config.ClientSecret)) + client := &http.Client{ + Timeout: defTimeout, + } + req, err := http.NewRequest(http.MethodPost, cfg.config.Endpoint.TokenURL, payload) + if err != nil { + return oauth2.Token{}, err + } + req.Header.Add("Content-Type", "application/x-www-form-urlencoded") + + res, err := client.Do(req) + if err != nil { + return oauth2.Token{}, err + } + defer res.Body.Close() + + body, err := io.ReadAll(res.Body) + if err != nil { + return oauth2.Token{}, err + } + var tokenData oauth2.Token + if err := json.Unmarshal(body, &tokenData); err != nil { + return oauth2.Token{}, err + } + tokenData.RefreshToken = token + + return tokenData, nil +} diff --git a/pkg/oauth2/mocks/provider.go b/pkg/oauth2/mocks/provider.go new file mode 100644 index 0000000000..ff3ad9fa69 --- /dev/null +++ b/pkg/oauth2/mocks/provider.go @@ -0,0 +1,205 @@ +// Code generated by mockery v2.38.0. DO NOT EDIT. + +// Copyright (c) Abstract Machines + +package mocks + +import ( + context "context" + + clients "github.com/absmach/magistrala/pkg/clients" + + mock "github.com/stretchr/testify/mock" + + xoauth2 "golang.org/x/oauth2" +) + +// Provider is an autogenerated mock type for the Provider type +type Provider struct { + mock.Mock +} + +// ErrorURL provides a mock function with given fields: +func (_m *Provider) ErrorURL() string { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for ErrorURL") + } + + var r0 string + if rf, ok := ret.Get(0).(func() string); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(string) + } + + return r0 +} + +// IsEnabled provides a mock function with given fields: +func (_m *Provider) IsEnabled() bool { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for IsEnabled") + } + + var r0 bool + if rf, ok := ret.Get(0).(func() bool); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(bool) + } + + return r0 +} + +// Name provides a mock function with given fields: +func (_m *Provider) Name() string { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for Name") + } + + var r0 string + if rf, ok := ret.Get(0).(func() string); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(string) + } + + return r0 +} + +// RedirectURL provides a mock function with given fields: +func (_m *Provider) RedirectURL() string { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for RedirectURL") + } + + var r0 string + if rf, ok := ret.Get(0).(func() string); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(string) + } + + return r0 +} + +// Refresh provides a mock function with given fields: ctx, token +func (_m *Provider) Refresh(ctx context.Context, token string) (xoauth2.Token, error) { + ret := _m.Called(ctx, token) + + if len(ret) == 0 { + panic("no return value specified for Refresh") + } + + var r0 xoauth2.Token + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string) (xoauth2.Token, error)); ok { + return rf(ctx, token) + } + if rf, ok := ret.Get(0).(func(context.Context, string) xoauth2.Token); ok { + r0 = rf(ctx, token) + } else { + r0 = ret.Get(0).(xoauth2.Token) + } + + if rf, ok := ret.Get(1).(func(context.Context, string) error); ok { + r1 = rf(ctx, token) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// State provides a mock function with given fields: +func (_m *Provider) State() string { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for State") + } + + var r0 string + if rf, ok := ret.Get(0).(func() string); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(string) + } + + return r0 +} + +// UserDetails provides a mock function with given fields: ctx, code +func (_m *Provider) UserDetails(ctx context.Context, code string) (clients.Client, xoauth2.Token, error) { + ret := _m.Called(ctx, code) + + if len(ret) == 0 { + panic("no return value specified for UserDetails") + } + + var r0 clients.Client + var r1 xoauth2.Token + var r2 error + if rf, ok := ret.Get(0).(func(context.Context, string) (clients.Client, xoauth2.Token, error)); ok { + return rf(ctx, code) + } + if rf, ok := ret.Get(0).(func(context.Context, string) clients.Client); ok { + r0 = rf(ctx, code) + } else { + r0 = ret.Get(0).(clients.Client) + } + + if rf, ok := ret.Get(1).(func(context.Context, string) xoauth2.Token); ok { + r1 = rf(ctx, code) + } else { + r1 = ret.Get(1).(xoauth2.Token) + } + + if rf, ok := ret.Get(2).(func(context.Context, string) error); ok { + r2 = rf(ctx, code) + } else { + r2 = ret.Error(2) + } + + return r0, r1, r2 +} + +// Validate provides a mock function with given fields: ctx, token +func (_m *Provider) Validate(ctx context.Context, token string) error { + ret := _m.Called(ctx, token) + + if len(ret) == 0 { + panic("no return value specified for Validate") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, string) error); ok { + r0 = rf(ctx, token) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// NewProvider creates a new instance of Provider. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewProvider(t interface { + mock.TestingT + Cleanup(func()) +}) *Provider { + mock := &Provider{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/pkg/oauth2/oauth2.go b/pkg/oauth2/oauth2.go new file mode 100644 index 0000000000..0a9bf00e9f --- /dev/null +++ b/pkg/oauth2/oauth2.go @@ -0,0 +1,83 @@ +// Copyright (c) Abstract Machines +// SPDX-License-Identifier: Apache-2.0 + +package oauth2 + +import ( + "context" + "errors" + + mfclients "github.com/absmach/magistrala/pkg/clients" + "golang.org/x/oauth2" +) + +// State is the state of the OAuth2 flow. +type State uint8 + +const ( + // SignIn is the state for the sign-in flow. + SignIn State = iota + // SignUp is the state for the sign-up flow. + SignUp +) + +func (s State) String() string { + switch s { + case SignIn: + return "signin" + case SignUp: + return "signup" + default: + return "unknown" + } +} + +// ToState converts string value to a valid OAuth2 state. +func ToState(state string) (State, error) { + switch state { + case "signin": + return SignIn, nil + case "signup": + return SignUp, nil + } + + return State(0), errors.New("invalid state") +} + +// Config is the configuration for the OAuth2 provider. +type Config struct { + ClientID string `env:"CLIENT_ID" envDefault:""` + ClientSecret string `env:"CLIENT_SECRET" envDefault:""` + State string `env:"STATE" envDefault:""` + RedirectURL string `env:"REDIRECT_URL" envDefault:""` +} + +// Provider is an interface that provides the OAuth2 flow for a specific provider +// (e.g. Google, GitHub, etc.) +// +//go:generate mockery --name Provider --output=./mocks --filename provider.go --quiet --note "Copyright (c) Abstract Machines" +type Provider interface { + // Name returns the name of the OAuth2 provider. + Name() string + + // State returns the current state for the OAuth2 flow. + State() string + + // RedirectURL returns the URL to redirect the user to after completing the OAuth2 flow. + RedirectURL() string + + // ErrorURL returns the URL to redirect the user to in case of an error during the OAuth2 flow. + ErrorURL() string + + // IsEnabled checks if the OAuth2 provider is enabled. + IsEnabled() bool + + // UserDetails retrieves the user's details and OAuth tokens from the OAuth2 provider. + UserDetails(ctx context.Context, code string) (mfclients.Client, oauth2.Token, error) + + // Validate checks the validity of the access token. + Validate(ctx context.Context, token string) error + + // Refresh refreshes the access token using the refresh token. + Refresh(ctx context.Context, token string) (oauth2.Token, error) +} diff --git a/pkg/sdk/go/groups_test.go b/pkg/sdk/go/groups_test.go index 6bd6f59e8e..9ac23c7620 100644 --- a/pkg/sdk/go/groups_test.go +++ b/pkg/sdk/go/groups_test.go @@ -22,6 +22,7 @@ import ( svcerr "github.com/absmach/magistrala/pkg/errors/service" mggroups "github.com/absmach/magistrala/pkg/groups" "github.com/absmach/magistrala/pkg/groups/mocks" + oauth2mocks "github.com/absmach/magistrala/pkg/oauth2/mocks" sdk "github.com/absmach/magistrala/pkg/sdk/go" "github.com/absmach/magistrala/users" "github.com/absmach/magistrala/users/api" @@ -41,7 +42,9 @@ func setupGroups() (*httptest.Server, *mocks.Repository, *authmocks.AuthClient) logger := mglog.NewMock() mux := chi.NewRouter() - api.MakeHandler(csvc, gsvc, mux, logger, "") + provider := new(oauth2mocks.Provider) + provider.On("Name").Return("test") + api.MakeHandler(csvc, gsvc, mux, logger, "", provider) return httptest.NewServer(mux), grepo, auth } diff --git a/pkg/sdk/go/users_test.go b/pkg/sdk/go/users_test.go index 2483fb3fad..ca5191d160 100644 --- a/pkg/sdk/go/users_test.go +++ b/pkg/sdk/go/users_test.go @@ -21,6 +21,7 @@ import ( repoerr "github.com/absmach/magistrala/pkg/errors/repository" svcerr "github.com/absmach/magistrala/pkg/errors/service" gmocks "github.com/absmach/magistrala/pkg/groups/mocks" + oauth2mocks "github.com/absmach/magistrala/pkg/oauth2/mocks" sdk "github.com/absmach/magistrala/pkg/sdk/go" "github.com/absmach/magistrala/users" "github.com/absmach/magistrala/users/api" @@ -47,7 +48,9 @@ func setupUsers() (*httptest.Server, *umocks.Repository, *gmocks.Repository, *au logger := mglog.NewMock() mux := chi.NewRouter() - api.MakeHandler(csvc, gsvc, mux, logger, "") + provider := new(oauth2mocks.Provider) + provider.On("Name").Return("test") + api.MakeHandler(csvc, gsvc, mux, logger, "", provider) return httptest.NewServer(mux), crepo, gRepo, auth } diff --git a/users/api/clients.go b/users/api/clients.go index 84283d4e72..991728d8c6 100644 --- a/users/api/clients.go +++ b/users/api/clients.go @@ -15,6 +15,7 @@ import ( "github.com/absmach/magistrala/internal/apiutil" mgclients "github.com/absmach/magistrala/pkg/clients" "github.com/absmach/magistrala/pkg/errors" + "github.com/absmach/magistrala/pkg/oauth2" "github.com/absmach/magistrala/users" "github.com/go-chi/chi/v5" kithttp "github.com/go-kit/kit/transport/http" @@ -22,7 +23,7 @@ import ( ) // MakeHandler returns a HTTP handler for API endpoints. -func clientsHandler(svc users.Service, r *chi.Mux, logger *slog.Logger) http.Handler { +func clientsHandler(svc users.Service, r *chi.Mux, logger *slog.Logger, providers ...oauth2.Provider) http.Handler { opts := []kithttp.ServerOption{ kithttp.ServerErrorEncoder(apiutil.LoggingErrorEncoder(logger, api.EncodeError)), } @@ -170,6 +171,11 @@ func clientsHandler(svc users.Service, r *chi.Mux, logger *slog.Logger) http.Han api.EncodeResponse, opts..., ), "list_users_by_domain_id").ServeHTTP) + + for _, provider := range providers { + r.HandleFunc("/oauth/callback/"+provider.Name(), oauth2CallbackHandler(provider, svc)) + } + return r } @@ -515,3 +521,64 @@ func queryPageParams(r *http.Request, defPermission string) (mgclients.Page, err ListPerms: lp, }, nil } + +// oauth2CallbackHandler is a http.HandlerFunc that handles OAuth2 callbacks. +func oauth2CallbackHandler(oauth oauth2.Provider, svc users.Service) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + if !oauth.IsEnabled() { + http.Redirect(w, r, oauth.ErrorURL()+"?error=oauth%20provider%20is%20disabled", http.StatusSeeOther) + return + } + // state is prefixed with signin- or signup- to indicate which flow we should use + var state string + var flow oauth2.State + var err error + if strings.Contains(r.FormValue("state"), "-") { + state = strings.Split(r.FormValue("state"), "-")[1] + flow, err = oauth2.ToState(strings.Split(r.FormValue("state"), "-")[0]) + if err != nil { + http.Redirect(w, r, oauth.ErrorURL()+"?error="+err.Error(), http.StatusSeeOther) //nolint:goconst + return + } + } + + if state != oauth.State() { + http.Redirect(w, r, oauth.ErrorURL()+"?error=invalid%20state", http.StatusSeeOther) + return + } + + if code := r.FormValue("code"); code != "" { + client, token, err := oauth.UserDetails(r.Context(), code) + if err != nil { + http.Redirect(w, r, oauth.ErrorURL()+"?error="+err.Error(), http.StatusSeeOther) + return + } + + jwt, err := svc.OAuthCallback(r.Context(), oauth.Name(), flow, token, client) + if err != nil { + http.Redirect(w, r, oauth.ErrorURL()+"?error="+err.Error(), http.StatusSeeOther) + return + } + + http.SetCookie(w, &http.Cookie{ + Name: "access_token", + Value: jwt.AccessToken, + Path: "/", + HttpOnly: true, + Secure: true, + }) + http.SetCookie(w, &http.Cookie{ + Name: "refresh_token", + Value: *jwt.RefreshToken, + Path: "/", + HttpOnly: true, + Secure: true, + }) + + http.Redirect(w, r, oauth.RedirectURL(), http.StatusFound) + return + } + + http.Redirect(w, r, oauth.ErrorURL()+"?error=empty%20code", http.StatusSeeOther) + } +} diff --git a/users/api/endpoint_test.go b/users/api/endpoint_test.go index ba9a78e015..3ec2ea791d 100644 --- a/users/api/endpoint_test.go +++ b/users/api/endpoint_test.go @@ -23,6 +23,7 @@ import ( "github.com/absmach/magistrala/pkg/errors" svcerr "github.com/absmach/magistrala/pkg/errors/service" gmocks "github.com/absmach/magistrala/pkg/groups/mocks" + oauth2mocks "github.com/absmach/magistrala/pkg/oauth2/mocks" "github.com/absmach/magistrala/pkg/uuid" httpapi "github.com/absmach/magistrala/users/api" "github.com/absmach/magistrala/users/mocks" @@ -89,7 +90,9 @@ func newUsersServer() (*httptest.Server, *mocks.Service) { logger := mglog.NewMock() mux := chi.NewRouter() - httpapi.MakeHandler(svc, gsvc, mux, logger, "") + provider := new(oauth2mocks.Provider) + provider.On("Name").Return("test") + httpapi.MakeHandler(svc, gsvc, mux, logger, "", provider) return httptest.NewServer(mux), svc } diff --git a/users/api/logging.go b/users/api/logging.go index 70b693694f..e570995206 100644 --- a/users/api/logging.go +++ b/users/api/logging.go @@ -10,7 +10,9 @@ import ( "github.com/absmach/magistrala" mgclients "github.com/absmach/magistrala/pkg/clients" + mgoauth2 "github.com/absmach/magistrala/pkg/oauth2" "github.com/absmach/magistrala/users" + "golang.org/x/oauth2" ) var _ users.Service = (*loggingMiddleware)(nil) @@ -397,3 +399,21 @@ func (lm *loggingMiddleware) Identify(ctx context.Context, token string) (id str }(time.Now()) return lm.svc.Identify(ctx, token) } + +func (lm *loggingMiddleware) OAuthCallback(ctx context.Context, provider string, state mgoauth2.State, oauthToken oauth2.Token, client mgclients.Client) (token *magistrala.Token, err error) { + defer func(begin time.Time) { + args := []any{ + slog.String("duration", time.Since(begin).String()), + slog.String("provider", provider), + slog.String("state", state.String()), + slog.String("user_id", client.ID), + } + if err != nil { + args = append(args, slog.Any("error", err)) + lm.logger.Warn("OAuth callback failed to complete successfully", args...) + return + } + lm.logger.Info("OAuth callback completed successfully", args...) + }(time.Now()) + return lm.svc.OAuthCallback(ctx, provider, state, oauthToken, client) +} diff --git a/users/api/metrics.go b/users/api/metrics.go index dd17297fab..8c93d29b1f 100644 --- a/users/api/metrics.go +++ b/users/api/metrics.go @@ -9,8 +9,10 @@ import ( "github.com/absmach/magistrala" mgclients "github.com/absmach/magistrala/pkg/clients" + mgoauth2 "github.com/absmach/magistrala/pkg/oauth2" "github.com/absmach/magistrala/users" "github.com/go-kit/kit/metrics" + "golang.org/x/oauth2" ) var _ users.Service = (*metricsMiddleware)(nil) @@ -191,3 +193,12 @@ func (ms *metricsMiddleware) Identify(ctx context.Context, token string) (string }(time.Now()) return ms.svc.Identify(ctx, token) } + +func (ms *metricsMiddleware) OAuthCallback(ctx context.Context, provider string, state mgoauth2.State, token oauth2.Token, client mgclients.Client) (*magistrala.Token, error) { + method := provider + "_oauth_callback_" + state.String() + defer func(begin time.Time) { + ms.counter.With("method", method).Add(1) + ms.latency.With("method", method).Observe(time.Since(begin).Seconds()) + }(time.Now()) + return ms.svc.OAuthCallback(ctx, provider, state, token, client) +} diff --git a/users/api/transport.go b/users/api/transport.go index af6255833b..354d7b5d5f 100644 --- a/users/api/transport.go +++ b/users/api/transport.go @@ -9,14 +9,15 @@ import ( "github.com/absmach/magistrala" "github.com/absmach/magistrala/pkg/groups" + "github.com/absmach/magistrala/pkg/oauth2" "github.com/absmach/magistrala/users" "github.com/go-chi/chi/v5" "github.com/prometheus/client_golang/prometheus/promhttp" ) // MakeHandler returns a HTTP handler for Users and Groups API endpoints. -func MakeHandler(cls users.Service, grps groups.Service, mux *chi.Mux, logger *slog.Logger, instanceID string) http.Handler { - clientsHandler(cls, mux, logger) +func MakeHandler(cls users.Service, grps groups.Service, mux *chi.Mux, logger *slog.Logger, instanceID string, providers ...oauth2.Provider) http.Handler { + clientsHandler(cls, mux, logger, providers...) groupsHandler(grps, mux, logger) mux.Get("/health", magistrala.Health("users", instanceID)) diff --git a/users/clients.go b/users/clients.go index ed1871f47d..d6b3c328e4 100644 --- a/users/clients.go +++ b/users/clients.go @@ -8,6 +8,8 @@ import ( "github.com/absmach/magistrala" "github.com/absmach/magistrala/pkg/clients" + mgoauth2 "github.com/absmach/magistrala/pkg/oauth2" + "golang.org/x/oauth2" ) // Service specifies an API that must be fullfiled by the domain service @@ -73,4 +75,8 @@ type Service interface { // After an access token expires, the refresh token is used to get // a new pair of access and refresh tokens. RefreshToken(ctx context.Context, accessToken, domainID string) (*magistrala.Token, error) + + // OAuthCallback handles the callback from any supported OAuth provider. + // It processes the OAuth tokens and either signs in or signs up the user based on the provided state. + OAuthCallback(ctx context.Context, provider string, state mgoauth2.State, token oauth2.Token, client clients.Client) (*magistrala.Token, error) } diff --git a/users/events/events.go b/users/events/events.go index 95bba47903..9b1692c2ff 100644 --- a/users/events/events.go +++ b/users/events/events.go @@ -28,6 +28,7 @@ const ( refreshToken = clientPrefix + "refresh_token" resetSecret = clientPrefix + "reset_secret" sendPasswordReset = clientPrefix + "send_password_reset" + oauthCallback = clientPrefix + "oauth_callback" ) var ( @@ -44,6 +45,7 @@ var ( _ events.Event = (*refreshTokenEvent)(nil) _ events.Event = (*resetSecretEvent)(nil) _ events.Event = (*sendPasswordResetEvent)(nil) + _ events.Event = (*oauthCallbackEvent)(nil) ) type createClientEvent struct { @@ -410,3 +412,18 @@ func (spre sendPasswordResetEvent) Encode() (map[string]interface{}, error) { "user": spre.user, }, nil } + +type oauthCallbackEvent struct { + state string + provider string + clientID string +} + +func (oce oauthCallbackEvent) Encode() (map[string]interface{}, error) { + return map[string]interface{}{ + "operation": oauthCallback, + "state": oce.state, + "provider": oce.provider, + "client_id": oce.clientID, + }, nil +} diff --git a/users/events/streams.go b/users/events/streams.go index c810413c28..1b9b801564 100644 --- a/users/events/streams.go +++ b/users/events/streams.go @@ -10,7 +10,9 @@ import ( mgclients "github.com/absmach/magistrala/pkg/clients" "github.com/absmach/magistrala/pkg/events" "github.com/absmach/magistrala/pkg/events/store" + mgoauth2 "github.com/absmach/magistrala/pkg/oauth2" "github.com/absmach/magistrala/users" + "golang.org/x/oauth2" ) const streamID = "magistrala.users" @@ -295,3 +297,22 @@ func (es *eventStore) SendPasswordReset(ctx context.Context, host, email, user, return es.Publish(ctx, event) } + +func (es *eventStore) OAuthCallback(ctx context.Context, provider string, state mgoauth2.State, oauthToken oauth2.Token, client mgclients.Client) (*magistrala.Token, error) { + token, err := es.svc.OAuthCallback(ctx, provider, state, oauthToken, client) + if err != nil { + return token, err + } + + event := oauthCallbackEvent{ + provider: provider, + state: state.String(), + clientID: client.ID, + } + + if err := es.Publish(ctx, event); err != nil { + return token, err + } + + return token, nil +} diff --git a/users/mocks/service.go b/users/mocks/service.go index 1546c4858b..69bd46130d 100644 --- a/users/mocks/service.go +++ b/users/mocks/service.go @@ -12,6 +12,10 @@ import ( magistrala "github.com/absmach/magistrala" mock "github.com/stretchr/testify/mock" + + oauth2 "github.com/absmach/magistrala/pkg/oauth2" + + xoauth2 "golang.org/x/oauth2" ) // Service is an autogenerated mock type for the Service type @@ -207,6 +211,36 @@ func (_m *Service) ListMembers(ctx context.Context, token string, objectKind str return r0, r1 } +// OAuthCallback provides a mock function with given fields: ctx, provider, state, token, client +func (_m *Service) OAuthCallback(ctx context.Context, provider string, state oauth2.State, token xoauth2.Token, client clients.Client) (*magistrala.Token, error) { + ret := _m.Called(ctx, provider, state, token, client) + + if len(ret) == 0 { + panic("no return value specified for OAuthCallback") + } + + var r0 *magistrala.Token + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string, oauth2.State, xoauth2.Token, clients.Client) (*magistrala.Token, error)); ok { + return rf(ctx, provider, state, token, client) + } + if rf, ok := ret.Get(0).(func(context.Context, string, oauth2.State, xoauth2.Token, clients.Client) *magistrala.Token); ok { + r0 = rf(ctx, provider, state, token, client) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*magistrala.Token) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, string, oauth2.State, xoauth2.Token, clients.Client) error); ok { + r1 = rf(ctx, provider, state, token, client) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + // RefreshToken provides a mock function with given fields: ctx, accessToken, domainID func (_m *Service) RefreshToken(ctx context.Context, accessToken string, domainID string) (*magistrala.Token, error) { ret := _m.Called(ctx, accessToken, domainID) diff --git a/users/postgres/init.go b/users/postgres/init.go index 9e8a4903ff..85e88fafd3 100644 --- a/users/postgres/init.go +++ b/users/postgres/init.go @@ -37,6 +37,14 @@ func Migration() *migrate.MemoryMigrationSource { `DROP TABLE IF EXISTS clients`, }, }, + { + // To support creation of clients from Oauth2 provider + Id: "clients_02", + Up: []string{ + `ALTER TABLE clients ALTER COLUMN secret DROP NOT NULL`, + }, + Down: []string{}, + }, }, } } diff --git a/users/service.go b/users/service.go index 8db31fe802..5d7fd1998b 100644 --- a/users/service.go +++ b/users/service.go @@ -5,6 +5,7 @@ package users import ( "context" + "fmt" "regexp" "time" @@ -14,7 +15,9 @@ import ( "github.com/absmach/magistrala/pkg/errors" repoerr "github.com/absmach/magistrala/pkg/errors/repository" svcerr "github.com/absmach/magistrala/pkg/errors/service" + mgoauth2 "github.com/absmach/magistrala/pkg/oauth2" "github.com/absmach/magistrala/users/postgres" + "golang.org/x/oauth2" "golang.org/x/sync/errgroup" ) @@ -80,14 +83,14 @@ func (svc service) RegisterClient(ctx context.Context, token string, cli mgclien return mgclients.Client{}, err } - if cli.Credentials.Secret == "" { - return mgclients.Client{}, errors.Wrap(repoerr.ErrMalformedEntity, repoerr.ErrMissingSecret) - } - hash, err := svc.hasher.Hash(cli.Credentials.Secret) - if err != nil { - return mgclients.Client{}, errors.Wrap(repoerr.ErrMalformedEntity, err) + if cli.Credentials.Secret != "" { + hash, err := svc.hasher.Hash(cli.Credentials.Secret) + if err != nil { + return mgclients.Client{}, errors.Wrap(repoerr.ErrMalformedEntity, err) + } + cli.Credentials.Secret = hash } - cli.Credentials.Secret = hash + if cli.Status != mgclients.DisabledStatus && cli.Status != mgclients.EnabledStatus { return mgclients.Client{}, svcerr.ErrInvalidStatus } @@ -585,6 +588,45 @@ func (svc *service) authorize(ctx context.Context, subjType, subjKind, subj, per return res.GetId(), nil } +func (svc service) OAuthCallback(ctx context.Context, provider string, state mgoauth2.State, token oauth2.Token, client mgclients.Client) (*magistrala.Token, error) { + switch state { + case mgoauth2.SignIn: + rclient, err := svc.clients.RetrieveByIdentity(ctx, client.Credentials.Identity) + if err != nil { + if errors.Contains(err, repoerr.ErrNotFound) { + return &magistrala.Token{}, errors.New("user not signed up") + } + return &magistrala.Token{}, err + } + claims := &magistrala.IssueReq{ + UserId: rclient.ID, + Type: 0, + OauthProvider: provider, + OauthAccessToken: token.AccessToken, + OauthRefreshToken: token.RefreshToken, + } + return svc.auth.Issue(ctx, claims) + case mgoauth2.SignUp: + rclient, err := svc.RegisterClient(ctx, "", client) + if err != nil { + if errors.Contains(err, repoerr.ErrConflict) { + return &magistrala.Token{}, errors.New("user already exists") + } + return &magistrala.Token{}, err + } + claims := &magistrala.IssueReq{ + UserId: rclient.ID, + Type: 0, + OauthProvider: provider, + OauthAccessToken: token.AccessToken, + OauthRefreshToken: token.RefreshToken, + } + return svc.auth.Issue(ctx, claims) + default: + return &magistrala.Token{}, fmt.Errorf("unknown state %s", state) + } +} + func (svc service) Identify(ctx context.Context, token string) (string, error) { user, err := svc.auth.Identify(ctx, &magistrala.IdentityReq{Token: token}) if err != nil { diff --git a/users/service_test.go b/users/service_test.go index 698587821a..94b24651b3 100644 --- a/users/service_test.go +++ b/users/service_test.go @@ -18,6 +18,7 @@ import ( "github.com/absmach/magistrala/pkg/errors" repoerr "github.com/absmach/magistrala/pkg/errors/repository" svcerr "github.com/absmach/magistrala/pkg/errors/service" + mgoauth2 "github.com/absmach/magistrala/pkg/oauth2" "github.com/absmach/magistrala/pkg/uuid" "github.com/absmach/magistrala/users" "github.com/absmach/magistrala/users/hasher" @@ -25,6 +26,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" + "golang.org/x/oauth2" ) var ( @@ -159,7 +161,7 @@ func TestRegisterClient(t *testing.T) { }, addPoliciesResponse: &magistrala.AddPoliciesRes{Added: true}, deletePoliciesResponse: &magistrala.DeletePoliciesRes{Deleted: true}, - err: repoerr.ErrMissingSecret, + err: nil, }, { desc: "register a new client with a weak secret", @@ -2464,3 +2466,160 @@ func TestViewProfile(t *testing.T) { repoCall1.Unset() } } + +func TestOAuthCallback(t *testing.T) { + svc, cRepo, auth, _ := newService(true) + + cases := []struct { + desc string + provider string + state mgoauth2.State + token oauth2.Token + client mgclients.Client + retrieveByIdentityResponse mgclients.Client + retrieveByIdentityErr error + addPoliciesResponse *magistrala.AddPoliciesRes + addPoliciesErr error + deletePoliciesResponse *magistrala.DeletePoliciesRes + deletePoliciesErr error + saveResponse mgclients.Client + saveErr error + issueResponse *magistrala.Token + issueErr error + err error + }{ + { + desc: "oauth signin callback with successfully", + provider: "google", + state: mgoauth2.SignIn, + token: oauth2.Token{ + AccessToken: strings.Repeat("a", 10), + RefreshToken: strings.Repeat("b", 10), + }, + client: mgclients.Client{ + Credentials: mgclients.Credentials{ + Identity: "test@example.com", + }, + }, + retrieveByIdentityResponse: mgclients.Client{ + ID: testsutil.GenerateUUID(t), + }, + issueResponse: &magistrala.Token{ + AccessToken: strings.Repeat("a", 10), + RefreshToken: &validToken, + AccessType: "Bearer", + }, + err: nil, + }, + { + desc: "oauth signin callback with error", + provider: "google", + state: mgoauth2.SignIn, + token: oauth2.Token{ + AccessToken: strings.Repeat("a", 10), + RefreshToken: strings.Repeat("b", 10), + }, + client: mgclients.Client{ + Credentials: mgclients.Credentials{ + Identity: "test@example.com", + }, + }, + retrieveByIdentityResponse: mgclients.Client{}, + retrieveByIdentityErr: repoerr.ErrNotFound, + issueResponse: &magistrala.Token{}, + err: errors.New("user not signed up"), + }, + { + desc: "oauth signup callback with successfully", + provider: "google", + state: mgoauth2.SignUp, + token: oauth2.Token{ + AccessToken: strings.Repeat("a", 10), + RefreshToken: strings.Repeat("b", 10), + }, + client: mgclients.Client{ + Credentials: mgclients.Credentials{ + Identity: "test@example.com", + }, + }, + retrieveByIdentityResponse: mgclients.Client{ + ID: testsutil.GenerateUUID(t), + }, + addPoliciesResponse: &magistrala.AddPoliciesRes{ + Added: true, + }, + addPoliciesErr: nil, + saveResponse: mgclients.Client{ + ID: testsutil.GenerateUUID(t), + }, + saveErr: nil, + issueResponse: &magistrala.Token{ + AccessToken: strings.Repeat("a", 10), + RefreshToken: &validToken, + AccessType: "Bearer", + }, + err: nil, + }, + { + desc: "oauth signup callback with error", + provider: "google", + state: mgoauth2.SignUp, + token: oauth2.Token{ + AccessToken: strings.Repeat("a", 10), + RefreshToken: strings.Repeat("b", 10), + }, + client: mgclients.Client{ + Credentials: mgclients.Credentials{ + Identity: "test@example.com", + }, + }, + retrieveByIdentityResponse: mgclients.Client{ + ID: testsutil.GenerateUUID(t), + }, + addPoliciesResponse: &magistrala.AddPoliciesRes{ + Added: true, + }, + addPoliciesErr: nil, + deletePoliciesResponse: &magistrala.DeletePoliciesRes{ + Deleted: true, + }, + deletePoliciesErr: nil, + saveResponse: mgclients.Client{}, + saveErr: repoerr.ErrConflict, + issueResponse: &magistrala.Token{}, + err: errors.New("user already exists"), + }, + } + + for _, tc := range cases { + switch tc.state { + case mgoauth2.SignUp: + repoCall := cRepo.On("Save", context.Background(), mock.Anything).Return(tc.saveResponse, tc.saveErr) + repoCall1 := auth.On("AddPolicies", mock.Anything, mock.Anything).Return(tc.addPoliciesResponse, tc.addPoliciesErr) + repoCall2 := auth.On("DeletePolicies", mock.Anything, mock.Anything).Return(tc.deletePoliciesResponse, tc.deletePoliciesErr) + repoCall3 := auth.On("Issue", mock.Anything, mock.Anything).Return(tc.issueResponse, tc.issueErr) + token, err := svc.OAuthCallback(context.Background(), tc.provider, tc.state, tc.token, tc.client) + if err == nil { + assert.Equal(t, tc.issueResponse.AccessToken, token.AccessToken) + assert.Equal(t, tc.issueResponse.RefreshToken, token.RefreshToken) + } + assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) + repoCall.Unset() + repoCall1.Unset() + repoCall2.Unset() + repoCall3.Unset() + case mgoauth2.SignIn: + repoCall := cRepo.On("RetrieveByIdentity", context.Background(), tc.client.Credentials.Identity).Return(tc.retrieveByIdentityResponse, tc.retrieveByIdentityErr) + repoCall1 := auth.On("Issue", mock.Anything, mock.Anything).Return(tc.issueResponse, tc.issueErr) + token, err := svc.OAuthCallback(context.Background(), tc.provider, tc.state, tc.token, tc.client) + if err == nil { + assert.Equal(t, tc.issueResponse.AccessToken, token.AccessToken) + assert.Equal(t, tc.issueResponse.RefreshToken, token.RefreshToken) + } + assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) + repoCall.Parent.AssertCalled(t, "RetrieveByIdentity", context.Background(), tc.client.Credentials.Identity) + repoCall.Unset() + repoCall1.Unset() + } + } +} diff --git a/users/tracing/tracing.go b/users/tracing/tracing.go index 547b6319c7..29788dbfec 100644 --- a/users/tracing/tracing.go +++ b/users/tracing/tracing.go @@ -8,9 +8,11 @@ import ( "github.com/absmach/magistrala" mgclients "github.com/absmach/magistrala/pkg/clients" + mgoauth2 "github.com/absmach/magistrala/pkg/oauth2" "github.com/absmach/magistrala/users" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/trace" + "golang.org/x/oauth2" ) var _ users.Service = (*tracingMiddleware)(nil) @@ -192,3 +194,15 @@ func (tm *tracingMiddleware) Identify(ctx context.Context, token string) (string return tm.svc.Identify(ctx, token) } + +// OAuthCallback traces the "OAuthCallback" operation of the wrapped clients.Service. +func (tm *tracingMiddleware) OAuthCallback(ctx context.Context, provider string, state mgoauth2.State, token oauth2.Token, client mgclients.Client) (*magistrala.Token, error) { + ctx, span := tm.tracer.Start(ctx, "svc_oauth_callback", trace.WithAttributes( + attribute.String("provider", provider), + attribute.String("state", state.String()), + attribute.String("client_id", client.ID), + )) + defer span.End() + + return tm.svc.OAuthCallback(ctx, provider, state, token, client) +}