mirror of
https://github.com/sourcegraph/sourcegraph.git
synced 2026-02-06 11:01:44 +00:00
feat/enterpriseportal: all subscriptions APIs use enterprise portal DB (#63959)
This change follows https://github.com/sourcegraph/sourcegraph/pull/63858 by making the _all_ subscriptions APIs read and write to the Enterprise Portal database, instead of dotcomdb, using the data that we sync from dotcomdb into Enterprise Portal. With this PR, all initially proposed subscriptions APIs are at least partially implemented. Uses https://github.com/hexops/valast/pull/27 for custom `autogold` rendering of `utctime.Time` Closes https://linear.app/sourcegraph/issue/CORE-156 Part of https://linear.app/sourcegraph/issue/CORE-158 ## Test plan - [x] Unit tests on API level - [x] Adapters unit testing - [x] Simple E2E test: https://github.com/sourcegraph/sourcegraph/pull/64057
This commit is contained in:
parent
8296e9804f
commit
e2c646ad92
@ -33,6 +33,8 @@ type StoreV1 interface {
|
||||
|
||||
// GetCodyGatewayUsage retrieves recent Cody Gateway usage data.
|
||||
// The subscriptionID should not be prefixed.
|
||||
//
|
||||
// Returns errStoreUnimplemented if the data source not configured.
|
||||
GetCodyGatewayUsage(ctx context.Context, subscriptionID string) (*codyaccessv1.CodyGatewayUsage, error)
|
||||
|
||||
// GetCodyGatewayAccessBySubscription retrieves Cody Gateway access by
|
||||
|
||||
@ -245,7 +245,7 @@ func (i *Importer) importSubscription(ctx context.Context, dotcomSub *dotcomdb.S
|
||||
}
|
||||
return pointers.Ptr(utctime.FromTime(*dotcomSub.ArchivedAt))
|
||||
}(),
|
||||
SalesforceSubscriptionID: activeLicense.SalesforceSubscriptionID,
|
||||
SalesforceSubscriptionID: database.NewNullStringPtr(activeLicense.SalesforceSubscriptionID),
|
||||
},
|
||||
conditions...,
|
||||
); err != nil {
|
||||
|
||||
@ -96,7 +96,7 @@ VALUES (
|
||||
)`, pgx.NamedArgs{
|
||||
"licenseID": licenseID,
|
||||
// Convert to string representation of EnterpriseSubscriptionLicenseCondition
|
||||
"status": subscriptionsv1.EnterpriseSubscriptionLicenseCondition_Status_name[int32(opts.Status)],
|
||||
"status": opts.Status.String(),
|
||||
"message": pointers.NilIfZero(opts.Message),
|
||||
"transitionTime": opts.TransitionTime,
|
||||
})
|
||||
|
||||
@ -362,7 +362,7 @@ VALUES (
|
||||
`, pgx.NamedArgs{
|
||||
"licenseID": licenseID,
|
||||
"subscriptionID": subscriptionID,
|
||||
"licenseType": subscriptionsv1.EnterpriseSubscriptionLicenseType_name[int32(licenseType)],
|
||||
"licenseType": licenseType.String(),
|
||||
"licenseData": licenseData,
|
||||
"createdAt": opts.Time,
|
||||
"expireAt": opts.ExpireTime,
|
||||
@ -422,6 +422,9 @@ WHERE id = @licenseID
|
||||
"revokedAt": opts.Time,
|
||||
"licenseID": licenseID,
|
||||
}); err != nil {
|
||||
if errors.Is(err, pgx.ErrNoRows) {
|
||||
return nil, ErrSubscriptionLicenseNotFound
|
||||
}
|
||||
return nil, errors.Wrap(err, "revoke license")
|
||||
}
|
||||
|
||||
|
||||
@ -151,7 +151,7 @@ func (opts ListEnterpriseSubscriptionsOptions) toQueryConditions() (where, limit
|
||||
if *opts.IsArchived {
|
||||
whereConds = append(whereConds, "archived_at IS NOT NULL")
|
||||
} else {
|
||||
whereConds = append(whereConds, "archived IS NUlL")
|
||||
whereConds = append(whereConds, "archived_at IS NUlL")
|
||||
}
|
||||
}
|
||||
if len(opts.DisplayNameSubstring) > 0 {
|
||||
@ -221,7 +221,7 @@ type UpsertSubscriptionOptions struct {
|
||||
CreatedAt utctime.Time
|
||||
ArchivedAt *utctime.Time
|
||||
|
||||
SalesforceSubscriptionID *string
|
||||
SalesforceSubscriptionID *sql.NullString
|
||||
|
||||
// ForceUpdate indicates whether to force update all fields of the subscription
|
||||
// record.
|
||||
@ -249,9 +249,14 @@ func (opts UpsertSubscriptionOptions) apply(ctx context.Context, db upsert.Exece
|
||||
return b.Exec(ctx, db)
|
||||
}
|
||||
|
||||
var ErrInvalidArgument = errors.New("invalid argument")
|
||||
|
||||
// Upsert upserts a subscription record based on the given options. If the
|
||||
// operation has additional application meaning, conditions can be provided
|
||||
// for insert as well.
|
||||
//
|
||||
// Constraint errors are returned as a human-friendly error that wraps
|
||||
// ErrInvalidArgument.
|
||||
func (s *Store) Upsert(
|
||||
ctx context.Context,
|
||||
subscriptionID string,
|
||||
@ -281,7 +286,12 @@ func (s *Store) Upsert(
|
||||
if err := opts.apply(ctx, tx, subscriptionID); err != nil {
|
||||
if pgxerrors.IsContraintError(err, "idx_enterprise_portal_subscriptions_display_name") {
|
||||
return nil, errors.WithSafeDetails(
|
||||
errors.Newf("display_name %q is already in use", opts.DisplayName.String),
|
||||
errors.Wrapf(ErrInvalidArgument, "display_name %q is already in use", opts.DisplayName.String),
|
||||
"%+v", err)
|
||||
}
|
||||
if pgxerrors.IsContraintError(err, "idx_enterprise_portal_subscriptions_instance_domain") {
|
||||
return nil, errors.WithSafeDetails(
|
||||
errors.Wrapf(ErrInvalidArgument, "instance_domain %q is assigned to another subscription", opts.DisplayName.String),
|
||||
"%+v", err)
|
||||
}
|
||||
return nil, errors.Wrap(err, "upsert")
|
||||
|
||||
@ -97,7 +97,7 @@ VALUES (
|
||||
)`, pgx.NamedArgs{
|
||||
"subscriptionID": subscriptionID,
|
||||
// Convert to string representation of EnterpriseSubscriptionCondition
|
||||
"status": subscriptionsv1.EnterpriseSubscriptionCondition_Status_name[int32(opts.Status)],
|
||||
"status": opts.Status.String(),
|
||||
"message": pointers.NilIfZero(opts.Message),
|
||||
"transitionTime": opts.TransitionTime,
|
||||
})
|
||||
|
||||
@ -61,7 +61,7 @@ func SubscriptionsStoreList(t *testing.T, ctx context.Context, s *subscriptions.
|
||||
subscriptions.UpsertSubscriptionOptions{
|
||||
DisplayName: database.NewNullString("Subscription 1"),
|
||||
InstanceDomain: database.NewNullString("s1.sourcegraph.com"),
|
||||
SalesforceSubscriptionID: pointers.Ptr("sf_sub_id"),
|
||||
SalesforceSubscriptionID: database.NewNullString("sf_sub_id"),
|
||||
},
|
||||
)
|
||||
require.NoError(t, err)
|
||||
@ -199,6 +199,22 @@ func SubscriptionsStoreList(t *testing.T, ctx context.Context, s *subscriptions.
|
||||
assert.Equal(t, s1.ID, ss[0].ID)
|
||||
})
|
||||
|
||||
t.Run("list by not archived", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ss, err := s.List(
|
||||
ctx,
|
||||
subscriptions.ListEnterpriseSubscriptionsOptions{
|
||||
IsArchived: pointers.Ptr(false),
|
||||
},
|
||||
)
|
||||
require.NoError(t, err)
|
||||
assert.NotEmpty(t, ss)
|
||||
for _, s := range ss {
|
||||
assert.Nil(t, s.ArchivedAt)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("list with page size", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
||||
@ -13,6 +13,17 @@ func NewNullString(v string) *sql.NullString {
|
||||
}
|
||||
}
|
||||
|
||||
// NewNullString creates an *sql.NullString that indicates "invalid", i.e. null,
|
||||
// if v is nil or an empty string. It returns a pointer because many use cases
|
||||
// require a pointer - it is safe to immediately deref the return value if you
|
||||
// need to, since it always returns a non-nil value.
|
||||
func NewNullStringPtr(v *string) *sql.NullString {
|
||||
if v == nil {
|
||||
return &sql.NullString{}
|
||||
}
|
||||
return NewNullString(*v)
|
||||
}
|
||||
|
||||
// NewNullInt32 is like NewNullString, but always produces a valid value.
|
||||
func NewNullInt32[T int | int32 | int64 | uint64](v T) *sql.NullInt32 {
|
||||
return &sql.NullInt32{
|
||||
|
||||
@ -2,11 +2,15 @@ load("@io_bazel_rules_go//go:def.bzl", "go_library")
|
||||
|
||||
go_library(
|
||||
name = "utctime",
|
||||
srcs = ["utctime.go"],
|
||||
srcs = [
|
||||
"utctime.go",
|
||||
"valast.go",
|
||||
],
|
||||
importpath = "github.com/sourcegraph/sourcegraph/cmd/enterprise-portal/internal/database/utctime",
|
||||
visibility = ["//cmd/enterprise-portal:__subpackages__"],
|
||||
deps = [
|
||||
"//lib/errors",
|
||||
"//lib/pointers",
|
||||
"@com_github_hexops_valast//:valast",
|
||||
],
|
||||
)
|
||||
|
||||
@ -29,6 +29,11 @@ func Now() Time { return Time(time.Now()) }
|
||||
// FromTime returns a utctime.Time from a time.Time.
|
||||
func FromTime(t time.Time) Time { return Time(t.UTC().Round(time.Microsecond)) }
|
||||
|
||||
// Date is analagous to time.Date, but only represents UTC time.
|
||||
func Date(year int, month time.Month, day, hour, min, sec, nsec int) Time {
|
||||
return FromTime(time.Date(year, month, day, hour, min, sec, nsec, time.UTC))
|
||||
}
|
||||
|
||||
var _ sql.Scanner = (*Time)(nil)
|
||||
|
||||
func (t *Time) Scan(src any) error {
|
||||
|
||||
31
cmd/enterprise-portal/internal/database/utctime/valast.go
Normal file
31
cmd/enterprise-portal/internal/database/utctime/valast.go
Normal file
@ -0,0 +1,31 @@
|
||||
package utctime
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/token"
|
||||
|
||||
"github.com/hexops/valast"
|
||||
)
|
||||
|
||||
// Register custom representation for autogold.
|
||||
func init() {
|
||||
valast.RegisterType(func(ut Time) ast.Expr {
|
||||
t := ut.AsTime()
|
||||
return &ast.CallExpr{
|
||||
Fun: &ast.SelectorExpr{
|
||||
X: &ast.Ident{Name: "utctime"},
|
||||
Sel: &ast.Ident{Name: "Date"},
|
||||
},
|
||||
Args: []ast.Expr{
|
||||
&ast.BasicLit{Kind: token.INT, Value: fmt.Sprintf("%d", t.Year())},
|
||||
&ast.BasicLit{Kind: token.INT, Value: fmt.Sprintf("%d", t.Month())},
|
||||
&ast.BasicLit{Kind: token.INT, Value: fmt.Sprintf("%d", t.Day())},
|
||||
&ast.BasicLit{Kind: token.INT, Value: fmt.Sprintf("%d", t.Hour())},
|
||||
&ast.BasicLit{Kind: token.INT, Value: fmt.Sprintf("%d", t.Minute())},
|
||||
&ast.BasicLit{Kind: token.INT, Value: fmt.Sprintf("%d", t.Second())},
|
||||
&ast.BasicLit{Kind: token.INT, Value: fmt.Sprintf("%d", t.Nanosecond())},
|
||||
},
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -16,9 +16,12 @@ go_library(
|
||||
"//cmd/enterprise-portal/internal/connectutil",
|
||||
"//cmd/enterprise-portal/internal/database",
|
||||
"//cmd/enterprise-portal/internal/database/subscriptions",
|
||||
"//cmd/enterprise-portal/internal/database/utctime",
|
||||
"//cmd/enterprise-portal/internal/dotcomdb",
|
||||
"//cmd/enterprise-portal/internal/samsm2m",
|
||||
"//internal/collections",
|
||||
"//internal/license",
|
||||
"//internal/licensing",
|
||||
"//internal/trace",
|
||||
"//lib/enterpriseportal/subscriptions/v1:subscriptions",
|
||||
"//lib/enterpriseportal/subscriptions/v1/v1connect",
|
||||
@ -26,11 +29,13 @@ go_library(
|
||||
"//lib/managedservicesplatform/iam",
|
||||
"//lib/pointers",
|
||||
"@com_connectrpc_connect//:connect",
|
||||
"@com_github_google_uuid//:uuid",
|
||||
"@com_github_sourcegraph_log//:log",
|
||||
"@com_github_sourcegraph_sourcegraph_accounts_sdk_go//:sourcegraph-accounts-sdk-go",
|
||||
"@com_github_sourcegraph_sourcegraph_accounts_sdk_go//clients/v1:clients",
|
||||
"@com_github_sourcegraph_sourcegraph_accounts_sdk_go//scopes",
|
||||
"@org_golang_google_protobuf//types/known/timestamppb",
|
||||
"@org_golang_x_crypto//ssh",
|
||||
"@org_golang_x_exp//maps",
|
||||
],
|
||||
)
|
||||
@ -45,19 +50,28 @@ go_test(
|
||||
embed = [":subscriptionsservice"],
|
||||
deps = [
|
||||
"//cmd/enterprise-portal/internal/database/subscriptions",
|
||||
"//cmd/enterprise-portal/internal/database/utctime",
|
||||
"//cmd/enterprise-portal/internal/samsm2m",
|
||||
"//internal/license",
|
||||
"//lib/enterpriseportal/subscriptions/v1:subscriptions",
|
||||
"//lib/errors",
|
||||
"//lib/managedservicesplatform/iam",
|
||||
"//lib/pointers",
|
||||
"@com_connectrpc_connect//:connect",
|
||||
"@com_github_derision_test_go_mockgen_v2//testutil/require",
|
||||
"@com_github_google_uuid//:uuid",
|
||||
"@com_github_hexops_autogold_v2//:autogold",
|
||||
"@com_github_hexops_valast//:valast",
|
||||
"@com_github_sourcegraph_log//logtest",
|
||||
"@com_github_sourcegraph_sourcegraph_accounts_sdk_go//:sourcegraph-accounts-sdk-go",
|
||||
"@com_github_sourcegraph_sourcegraph_accounts_sdk_go//clients/v1:clients",
|
||||
"@com_github_sourcegraph_sourcegraph_accounts_sdk_go//scopes",
|
||||
"@com_github_stretchr_testify//assert",
|
||||
"@com_github_stretchr_testify//require",
|
||||
"@org_golang_google_protobuf//encoding/protojson",
|
||||
"@org_golang_google_protobuf//reflect/protoreflect",
|
||||
"@org_golang_google_protobuf//types/known/fieldmaskpb",
|
||||
"@org_golang_google_protobuf//types/known/timestamppb",
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
@ -2,10 +2,16 @@ package subscriptionsservice
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"connectrpc.com/connect"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
|
||||
"github.com/sourcegraph/sourcegraph/cmd/enterprise-portal/internal/database/subscriptions"
|
||||
"github.com/sourcegraph/sourcegraph/cmd/enterprise-portal/internal/database/utctime"
|
||||
"github.com/sourcegraph/sourcegraph/internal/license"
|
||||
"github.com/sourcegraph/sourcegraph/internal/licensing"
|
||||
subscriptionsv1 "github.com/sourcegraph/sourcegraph/lib/enterpriseportal/subscriptions/v1"
|
||||
"github.com/sourcegraph/sourcegraph/lib/errors"
|
||||
"github.com/sourcegraph/sourcegraph/lib/managedservicesplatform/iam"
|
||||
@ -34,7 +40,7 @@ func convertLicenseToProto(license *subscriptions.LicenseWithConditions) (*subsc
|
||||
case subscriptionsv1.EnterpriseSubscriptionLicenseType_ENTERPRISE_SUBSCRIPTION_LICENSE_TYPE_KEY.String():
|
||||
var data subscriptions.DataLicenseKey
|
||||
if err := json.Unmarshal(license.LicenseData, &data); err != nil {
|
||||
return proto, errors.Wrap(err, "unmarshal license data")
|
||||
return proto, errors.Wrapf(err, "unmarshal license data: %q", string(license.LicenseData))
|
||||
}
|
||||
proto.License = &subscriptionsv1.EnterpriseSubscriptionLicense_Key{
|
||||
Key: &subscriptionsv1.EnterpriseSubscriptionLicenseKey{
|
||||
@ -46,9 +52,11 @@ func convertLicenseToProto(license *subscriptions.LicenseWithConditions) (*subsc
|
||||
SalesforceSubscriptionId: pointers.DerefZero(data.Info.SalesforceSubscriptionID),
|
||||
SalesforceOpportunityId: pointers.DerefZero(data.Info.SalesforceOpportunityID),
|
||||
},
|
||||
LicenseKey: data.SignedKey,
|
||||
LicenseKey: data.SignedKey,
|
||||
PlanDisplayName: licensing.ProductNameWithBrand(data.Info.Tags),
|
||||
},
|
||||
}
|
||||
|
||||
default:
|
||||
return proto, errors.Newf("unknown license type %q", t)
|
||||
}
|
||||
@ -105,8 +113,72 @@ func convertProtoToIAMTupleRelation(action subscriptionsv1.PermissionRelation) i
|
||||
func convertProtoRoleToIAMTupleObject(role subscriptionsv1.Role, subscriptionID string) iam.TupleObject {
|
||||
switch role {
|
||||
case subscriptionsv1.Role_ROLE_SUBSCRIPTION_CUSTOMER_ADMIN:
|
||||
return iam.ToTupleObject(iam.TupleTypeCustomerAdmin, subscriptionID)
|
||||
return iam.ToTupleObject(iam.TupleTypeCustomerAdmin,
|
||||
strings.TrimPrefix(subscriptionID, subscriptionsv1.EnterpriseSubscriptionIDPrefix))
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
// convertLicenseKeyToLicenseKeyData converts a create-license request into an
|
||||
// actual license key for creating a database entry.
|
||||
//
|
||||
// It may return Connect errors - all other errors should be considered internal
|
||||
// errors.
|
||||
func convertLicenseKeyToLicenseKeyData(
|
||||
createdAt utctime.Time,
|
||||
sub *subscriptions.Subscription,
|
||||
key *subscriptionsv1.EnterpriseSubscriptionLicenseKey,
|
||||
// StoreV1.GetRequiredEnterpriseSubscriptionLicenseKeyTags
|
||||
requiredTags []string,
|
||||
// StoreV1.SignEnterpriseSubscriptionLicenseKey
|
||||
signKeyFn func(license.Info) (string, error),
|
||||
) (*subscriptions.DataLicenseKey, error) {
|
||||
if key.GetInfo().GetUserCount() == 0 {
|
||||
return nil, connect.NewError(connect.CodeInvalidArgument, errors.New("user_count is invalid"))
|
||||
}
|
||||
expires := key.GetInfo().GetExpireTime().AsTime()
|
||||
if expires.Before(createdAt.AsTime()) {
|
||||
return nil, connect.NewError(connect.CodeInvalidArgument, errors.New("expiry must be in the future"))
|
||||
}
|
||||
tags := key.GetInfo().GetTags()
|
||||
providedTagPrefixes := map[string]struct{}{}
|
||||
for _, t := range tags {
|
||||
providedTagPrefixes[strings.SplitN(t, ":", 2)[0]] = struct{}{}
|
||||
}
|
||||
if _, exists := providedTagPrefixes["customer"]; !exists && sub.DisplayName != nil {
|
||||
tags = append(tags, fmt.Sprintf("customer:%s", *sub.DisplayName))
|
||||
}
|
||||
for _, r := range requiredTags {
|
||||
if _, ok := providedTagPrefixes[r]; !ok {
|
||||
return nil, connect.NewError(connect.CodeInvalidArgument,
|
||||
errors.Newf("key tags [%s] are required", strings.Join(requiredTags, ", ")))
|
||||
}
|
||||
}
|
||||
|
||||
info := license.Info{
|
||||
Tags: tags,
|
||||
UserCount: uint(key.GetInfo().GetUserCount()),
|
||||
CreatedAt: createdAt.AsTime(),
|
||||
// Cast expiry to utctime and back for uniform representation
|
||||
ExpiresAt: utctime.FromTime(expires).AsTime(),
|
||||
// Provided at creation
|
||||
SalesforceOpportunityID: pointers.NilIfZero(key.GetInfo().GetSalesforceOpportunityId()),
|
||||
// Inherited from subscription
|
||||
SalesforceSubscriptionID: sub.SalesforceSubscriptionID,
|
||||
}
|
||||
signedKey, err := signKeyFn(info)
|
||||
if err != nil {
|
||||
// See StoreV1.SignEnterpriseSubscriptionLicenseKey
|
||||
if errors.Is(err, errStoreUnimplemented) {
|
||||
return nil, connect.NewError(connect.CodeUnimplemented,
|
||||
errors.Wrap(err, "key signing not available"))
|
||||
}
|
||||
return nil, errors.Wrap(err, "sign key")
|
||||
}
|
||||
|
||||
return &subscriptions.DataLicenseKey{
|
||||
Info: info,
|
||||
SignedKey: signedKey,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@ -1,13 +1,160 @@
|
||||
package subscriptionsservice
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/hexops/autogold/v2"
|
||||
"github.com/hexops/valast"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"google.golang.org/protobuf/encoding/protojson"
|
||||
"google.golang.org/protobuf/reflect/protoreflect"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
|
||||
"github.com/sourcegraph/sourcegraph/cmd/enterprise-portal/internal/database/subscriptions"
|
||||
"github.com/sourcegraph/sourcegraph/cmd/enterprise-portal/internal/database/utctime"
|
||||
"github.com/sourcegraph/sourcegraph/internal/license"
|
||||
subscriptionsv1 "github.com/sourcegraph/sourcegraph/lib/enterpriseportal/subscriptions/v1"
|
||||
"github.com/sourcegraph/sourcegraph/lib/pointers"
|
||||
)
|
||||
|
||||
// Protojson output isn't stable by injecting randomized whitespace,
|
||||
// so we re-marshal it to stabilize the output for golden tests.
|
||||
// https://github.com/golang/protobuf/issues/1082
|
||||
func mustMarshalStableProtoJSON(t *testing.T, m protoreflect.ProtoMessage) string {
|
||||
t.Helper()
|
||||
|
||||
protoJSON, err := protojson.Marshal(m)
|
||||
require.NoError(t, err)
|
||||
|
||||
var gotJSON map[string]any
|
||||
require.NoError(t, json.Unmarshal(protoJSON, &gotJSON))
|
||||
return mustMarshal(json.MarshalIndent(gotJSON, "", " "))
|
||||
}
|
||||
|
||||
func mustMarshal(d []byte, err error) string {
|
||||
if err != nil {
|
||||
return err.Error()
|
||||
}
|
||||
return string(d)
|
||||
}
|
||||
|
||||
func TestConvertLicenseToProto(t *testing.T) {
|
||||
created := utctime.FromTime(newMockTime())
|
||||
expired := utctime.FromTime(newMockTime().Add(1 * time.Hour))
|
||||
got, err := convertLicenseToProto(&subscriptions.LicenseWithConditions{
|
||||
SubscriptionLicense: subscriptions.SubscriptionLicense{
|
||||
SubscriptionID: "subscription_id",
|
||||
ID: "license_id",
|
||||
CreatedAt: created,
|
||||
ExpireAt: expired,
|
||||
LicenseType: "ENTERPRISE_SUBSCRIPTION_LICENSE_TYPE_KEY",
|
||||
// In format subscriptions.DataLicenseKey
|
||||
LicenseData: json.RawMessage(fmt.Sprintf(`{
|
||||
"SignedKey": "asdf",
|
||||
"Info": {"e":%s,"c":%s,"t":["foo"]}
|
||||
}`,
|
||||
mustMarshal(expired.AsTime().MarshalJSON()),
|
||||
mustMarshal(created.AsTime().MarshalJSON()))),
|
||||
},
|
||||
Conditions: []subscriptions.SubscriptionLicenseCondition{{
|
||||
TransitionTime: created,
|
||||
Status: "STATUS_CREATED",
|
||||
}},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
autogold.Expect(`{
|
||||
"conditions": [
|
||||
{
|
||||
"lastTransitionTime": "2024-01-01T01:01:00Z",
|
||||
"status": "STATUS_CREATED"
|
||||
}
|
||||
],
|
||||
"id": "esl_license_id",
|
||||
"key": {
|
||||
"info": {
|
||||
"expireTime": "2024-01-01T02:01:00Z",
|
||||
"tags": [
|
||||
"foo"
|
||||
]
|
||||
},
|
||||
"infoVersion": 1,
|
||||
"licenseKey": "asdf",
|
||||
"planDisplayName": "Sourcegraph Enterprise"
|
||||
},
|
||||
"subscriptionId": "es_subscription_id"
|
||||
}`).Equal(t, mustMarshalStableProtoJSON(t, got))
|
||||
}
|
||||
|
||||
func TestConvertSubscriptionToProto(t *testing.T) {
|
||||
created := newMockTime()
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
sub *subscriptions.SubscriptionWithConditions
|
||||
want autogold.Value
|
||||
}{{
|
||||
name: "without salesforce details",
|
||||
sub: &subscriptions.SubscriptionWithConditions{
|
||||
Subscription: subscriptions.Subscription{
|
||||
ID: "subscription_id",
|
||||
InstanceDomain: pointers.Ptr("sourcegraph.com"),
|
||||
CreatedAt: utctime.Time(created),
|
||||
},
|
||||
Conditions: []subscriptions.SubscriptionCondition{{
|
||||
TransitionTime: utctime.Time(created),
|
||||
Status: "STATUS_CREATED",
|
||||
}},
|
||||
},
|
||||
want: autogold.Expect(`{
|
||||
"conditions": [
|
||||
{
|
||||
"lastTransitionTime": "2024-01-01T01:01:00Z",
|
||||
"status": "STATUS_CREATED"
|
||||
}
|
||||
],
|
||||
"id": "es_subscription_id",
|
||||
"instanceDomain": "sourcegraph.com"
|
||||
}`),
|
||||
}, {
|
||||
name: "with salesforce details",
|
||||
sub: &subscriptions.SubscriptionWithConditions{
|
||||
Subscription: subscriptions.Subscription{
|
||||
ID: "subscription_id",
|
||||
DisplayName: pointers.Ptr("s2"),
|
||||
CreatedAt: utctime.Time(created),
|
||||
SalesforceSubscriptionID: pointers.Ptr("sf_sub_id"),
|
||||
},
|
||||
Conditions: []subscriptions.SubscriptionCondition{{
|
||||
TransitionTime: utctime.Time(created),
|
||||
Status: "STATUS_CREATED",
|
||||
}},
|
||||
},
|
||||
want: autogold.Expect(`{
|
||||
"conditions": [
|
||||
{
|
||||
"lastTransitionTime": "2024-01-01T01:01:00Z",
|
||||
"status": "STATUS_CREATED"
|
||||
}
|
||||
],
|
||||
"displayName": "s2",
|
||||
"id": "es_subscription_id",
|
||||
"salesforce": {
|
||||
"subscriptionId": "sf_sub_id"
|
||||
}
|
||||
}`),
|
||||
}} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
got := convertSubscriptionToProto(tc.sub)
|
||||
|
||||
tc.want.Equal(t, mustMarshalStableProtoJSON(t, got))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestConvertProtoToIAMTupleObjectType(t *testing.T) {
|
||||
// Assert full coverage on API enum values.
|
||||
for tid, name := range subscriptionsv1.PermissionType_name {
|
||||
@ -49,3 +196,196 @@ func TestConvertProtoRoleToIAMTupleObject(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestConvertLicenseKeyToLicenseKeyData(t *testing.T) {
|
||||
created := utctime.FromTime(newMockTime())
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
sub *subscriptions.Subscription
|
||||
key *subscriptionsv1.EnterpriseSubscriptionLicenseKey
|
||||
requiredTags []string
|
||||
|
||||
wantError autogold.Value
|
||||
wantData autogold.Value
|
||||
}{{
|
||||
name: "invalid expiry",
|
||||
sub: &subscriptions.Subscription{},
|
||||
key: &subscriptionsv1.EnterpriseSubscriptionLicenseKey{
|
||||
Info: &subscriptionsv1.EnterpriseSubscriptionLicenseKey_Info{
|
||||
UserCount: 123,
|
||||
ExpireTime: timestamppb.New(created.AsTime().Add(-time.Hour)),
|
||||
},
|
||||
},
|
||||
wantError: autogold.Expect("invalid_argument: expiry must be in the future"),
|
||||
}, {
|
||||
name: "missing required tag",
|
||||
sub: &subscriptions.Subscription{},
|
||||
key: &subscriptionsv1.EnterpriseSubscriptionLicenseKey{
|
||||
Info: &subscriptionsv1.EnterpriseSubscriptionLicenseKey_Info{
|
||||
UserCount: 123,
|
||||
ExpireTime: timestamppb.New(created.AsTime().Add(time.Hour)),
|
||||
},
|
||||
},
|
||||
requiredTags: []string{"dev"},
|
||||
wantError: autogold.Expect("invalid_argument: key tags [dev] are required"),
|
||||
}, {
|
||||
name: "has required tag",
|
||||
sub: &subscriptions.Subscription{},
|
||||
key: &subscriptionsv1.EnterpriseSubscriptionLicenseKey{
|
||||
Info: &subscriptionsv1.EnterpriseSubscriptionLicenseKey_Info{
|
||||
UserCount: 123,
|
||||
ExpireTime: timestamppb.New(created.AsTime().Add(time.Hour)),
|
||||
Tags: []string{"dev", "plan"},
|
||||
},
|
||||
},
|
||||
requiredTags: []string{"dev"},
|
||||
wantData: autogold.Expect(&subscriptions.DataLicenseKey{
|
||||
Info: license.Info{
|
||||
Tags: []string{
|
||||
"dev",
|
||||
"plan",
|
||||
},
|
||||
UserCount: 123,
|
||||
CreatedAt: time.Date(2024,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
time.UTC),
|
||||
ExpiresAt: time.Date(2024,
|
||||
1,
|
||||
1,
|
||||
2,
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
time.UTC),
|
||||
},
|
||||
SignedKey: "signed-key",
|
||||
}),
|
||||
}, {
|
||||
name: "adds display name as customer tag",
|
||||
sub: &subscriptions.Subscription{
|
||||
DisplayName: pointers.Ptr(t.Name()),
|
||||
},
|
||||
key: &subscriptionsv1.EnterpriseSubscriptionLicenseKey{
|
||||
Info: &subscriptionsv1.EnterpriseSubscriptionLicenseKey_Info{
|
||||
UserCount: 123,
|
||||
ExpireTime: timestamppb.New(created.AsTime().Add(time.Hour)),
|
||||
},
|
||||
},
|
||||
wantData: autogold.Expect(&subscriptions.DataLicenseKey{
|
||||
Info: license.Info{
|
||||
Tags: []string{"customer:TestConvertLicenseKeyToLicenseKeyData"},
|
||||
UserCount: 123,
|
||||
CreatedAt: time.Date(2024,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
time.UTC),
|
||||
ExpiresAt: time.Date(2024,
|
||||
1,
|
||||
1,
|
||||
2,
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
time.UTC),
|
||||
},
|
||||
SignedKey: "signed-key",
|
||||
}),
|
||||
}, {
|
||||
name: "respects existing customer tag",
|
||||
sub: &subscriptions.Subscription{
|
||||
DisplayName: pointers.Ptr(t.Name()),
|
||||
},
|
||||
key: &subscriptionsv1.EnterpriseSubscriptionLicenseKey{
|
||||
Info: &subscriptionsv1.EnterpriseSubscriptionLicenseKey_Info{
|
||||
UserCount: 123,
|
||||
ExpireTime: timestamppb.New(created.AsTime().Add(time.Hour)),
|
||||
Tags: []string{"customer:custom-customer"},
|
||||
},
|
||||
},
|
||||
wantData: autogold.Expect(&subscriptions.DataLicenseKey{
|
||||
Info: license.Info{
|
||||
Tags: []string{"customer:custom-customer"},
|
||||
UserCount: 123,
|
||||
CreatedAt: time.Date(2024,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
time.UTC),
|
||||
ExpiresAt: time.Date(2024,
|
||||
1,
|
||||
1,
|
||||
2,
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
time.UTC),
|
||||
},
|
||||
SignedKey: "signed-key",
|
||||
}),
|
||||
}, {
|
||||
name: "adds salesforce metadata",
|
||||
sub: &subscriptions.Subscription{
|
||||
SalesforceSubscriptionID: pointers.Ptr("sf_sub_id"),
|
||||
},
|
||||
key: &subscriptionsv1.EnterpriseSubscriptionLicenseKey{
|
||||
Info: &subscriptionsv1.EnterpriseSubscriptionLicenseKey_Info{
|
||||
UserCount: 123,
|
||||
ExpireTime: timestamppb.New(created.AsTime().Add(time.Hour)),
|
||||
},
|
||||
},
|
||||
wantData: autogold.Expect(&subscriptions.DataLicenseKey{
|
||||
Info: license.Info{
|
||||
UserCount: 123,
|
||||
CreatedAt: time.Date(2024,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
time.UTC),
|
||||
ExpiresAt: time.Date(2024,
|
||||
1,
|
||||
1,
|
||||
2,
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
time.UTC),
|
||||
SalesforceSubscriptionID: valast.Ptr("sf_sub_id"),
|
||||
},
|
||||
SignedKey: "signed-key",
|
||||
}),
|
||||
}} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
got, err := convertLicenseKeyToLicenseKeyData(
|
||||
created,
|
||||
tc.sub,
|
||||
tc.key,
|
||||
tc.requiredTags,
|
||||
func(i license.Info) (string, error) {
|
||||
return "signed-key", nil
|
||||
},
|
||||
)
|
||||
if tc.wantError != nil {
|
||||
require.Error(t, err)
|
||||
tc.wantError.Equal(t, err.Error())
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
tc.wantData.Equal(t, got)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -2,6 +2,7 @@ package subscriptionsservice
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
@ -16,10 +17,12 @@ import (
|
||||
subscriptionsv1connect "github.com/sourcegraph/sourcegraph/lib/enterpriseportal/subscriptions/v1/v1connect"
|
||||
"github.com/sourcegraph/sourcegraph/lib/errors"
|
||||
"github.com/sourcegraph/sourcegraph/lib/managedservicesplatform/iam"
|
||||
"github.com/sourcegraph/sourcegraph/lib/pointers"
|
||||
|
||||
"github.com/sourcegraph/sourcegraph/cmd/enterprise-portal/internal/connectutil"
|
||||
"github.com/sourcegraph/sourcegraph/cmd/enterprise-portal/internal/database"
|
||||
"github.com/sourcegraph/sourcegraph/cmd/enterprise-portal/internal/database/subscriptions"
|
||||
"github.com/sourcegraph/sourcegraph/cmd/enterprise-portal/internal/database/utctime"
|
||||
"github.com/sourcegraph/sourcegraph/cmd/enterprise-portal/internal/dotcomdb"
|
||||
"github.com/sourcegraph/sourcegraph/cmd/enterprise-portal/internal/samsm2m"
|
||||
"github.com/sourcegraph/sourcegraph/internal/collections"
|
||||
@ -46,19 +49,51 @@ func RegisterV1(
|
||||
}
|
||||
|
||||
type handlerV1 struct {
|
||||
subscriptionsv1connect.UnimplementedSubscriptionsServiceHandler
|
||||
|
||||
logger log.Logger
|
||||
store StoreV1
|
||||
}
|
||||
|
||||
var _ subscriptionsv1connect.SubscriptionsServiceHandler = (*handlerV1)(nil)
|
||||
|
||||
func (s *handlerV1) GetEnterpriseSubscription(ctx context.Context, req *connect.Request[subscriptionsv1.GetEnterpriseSubscriptionRequest]) (*connect.Response[subscriptionsv1.GetEnterpriseSubscriptionResponse], error) {
|
||||
logger := trace.Logger(ctx, s.logger)
|
||||
|
||||
// 🚨 SECURITY: Require appropriate M2M scope.
|
||||
requiredScope := samsm2m.EnterprisePortalScope(
|
||||
scopes.PermissionEnterprisePortalSubscription, scopes.ActionRead)
|
||||
clientAttrs, err := samsm2m.RequireScope(ctx, logger, s.store, requiredScope, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
logger = logger.With(clientAttrs...)
|
||||
|
||||
subscriptionID := req.Msg.GetId()
|
||||
if subscriptionID == "" {
|
||||
return nil, connect.NewError(connect.CodeInvalidArgument, errors.New("subscription_id is required"))
|
||||
}
|
||||
|
||||
sub, err := s.store.GetEnterpriseSubscription(ctx, subscriptionID)
|
||||
if err != nil {
|
||||
if errors.Is(err, subscriptions.ErrSubscriptionNotFound) {
|
||||
return nil, connect.NewError(connect.CodeNotFound, err)
|
||||
}
|
||||
return nil, connectutil.InternalError(ctx, logger, err, "failed to find subscription")
|
||||
}
|
||||
|
||||
proto := convertSubscriptionToProto(sub)
|
||||
logger.Scoped("audit").Info("GetEnterpriseSubscription",
|
||||
log.String("subscription", proto.Id))
|
||||
return connect.NewResponse(&subscriptionsv1.GetEnterpriseSubscriptionResponse{
|
||||
Subscription: proto,
|
||||
}), nil
|
||||
}
|
||||
|
||||
func (s *handlerV1) ListEnterpriseSubscriptions(ctx context.Context, req *connect.Request[subscriptionsv1.ListEnterpriseSubscriptionsRequest]) (*connect.Response[subscriptionsv1.ListEnterpriseSubscriptionsResponse], error) {
|
||||
logger := trace.Logger(ctx, s.logger)
|
||||
|
||||
// 🚨 SECURITY: Require appropriate M2M scope.
|
||||
requiredScope := samsm2m.EnterprisePortalScope("subscription", scopes.ActionRead)
|
||||
requiredScope := samsm2m.EnterprisePortalScope(
|
||||
scopes.PermissionEnterprisePortalSubscription, scopes.ActionRead)
|
||||
clientAttrs, err := samsm2m.RequireScope(ctx, logger, s.store, requiredScope, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -76,7 +111,7 @@ func (s *handlerV1) ListEnterpriseSubscriptions(ctx context.Context, req *connec
|
||||
filters := req.Msg.GetFilters()
|
||||
var (
|
||||
isArchived *bool
|
||||
subscriptionIDs = make(collections.Set[string], len(filters))
|
||||
internalSubscriptionIDs = make(collections.Set[string], len(filters))
|
||||
displayNameSubstring string
|
||||
salesforceSubscriptionIDs []string
|
||||
instanceDomains []string
|
||||
@ -91,7 +126,7 @@ func (s *handlerV1) ListEnterpriseSubscriptions(ctx context.Context, req *connec
|
||||
errors.New(`invalid filter: "subscription_id" provided but is empty`),
|
||||
)
|
||||
}
|
||||
subscriptionIDs.Add(
|
||||
internalSubscriptionIDs.Add(
|
||||
strings.TrimPrefix(f.SubscriptionId, subscriptionsv1.EnterpriseSubscriptionIDPrefix))
|
||||
case *subscriptionsv1.ListEnterpriseSubscriptionsFilter_IsArchived:
|
||||
isArchived = &f.IsArchived
|
||||
@ -184,18 +219,18 @@ func (s *handlerV1) ListEnterpriseSubscriptions(ctx context.Context, req *connec
|
||||
allowedSubscriptionIDs.Add(strings.TrimPrefix(objectID, "subscription_cody_analytics:"))
|
||||
}
|
||||
|
||||
if !subscriptionIDs.IsEmpty() {
|
||||
if !internalSubscriptionIDs.IsEmpty() {
|
||||
// If subscription IDs were provided, we only want to return the
|
||||
// subscriptions that are part of the provided IDs.
|
||||
subscriptionIDs = collections.Intersection(subscriptionIDs, allowedSubscriptionIDs)
|
||||
internalSubscriptionIDs = collections.Intersection(internalSubscriptionIDs, allowedSubscriptionIDs)
|
||||
} else {
|
||||
// Otherwise, only return the allowed subscriptions.
|
||||
subscriptionIDs = allowedSubscriptionIDs
|
||||
internalSubscriptionIDs = allowedSubscriptionIDs
|
||||
}
|
||||
|
||||
// 🚨 SECURITY: If permissions are used as filter, but we found no results, we
|
||||
// should directly return an empty response to not mistaken as list all.
|
||||
if len(subscriptionIDs) == 0 {
|
||||
if len(internalSubscriptionIDs) == 0 {
|
||||
return connect.NewResponse(&subscriptionsv1.ListEnterpriseSubscriptionsResponse{}), nil
|
||||
}
|
||||
}
|
||||
@ -203,7 +238,7 @@ func (s *handlerV1) ListEnterpriseSubscriptions(ctx context.Context, req *connec
|
||||
subs, err := s.store.ListEnterpriseSubscriptions(
|
||||
ctx,
|
||||
subscriptions.ListEnterpriseSubscriptionsOptions{
|
||||
IDs: subscriptionIDs.Values(),
|
||||
IDs: internalSubscriptionIDs.Values(),
|
||||
IsArchived: isArchived,
|
||||
InstanceDomains: instanceDomains,
|
||||
DisplayNameSubstring: displayNameSubstring,
|
||||
@ -240,7 +275,8 @@ func (s *handlerV1) ListEnterpriseSubscriptionLicenses(ctx context.Context, req
|
||||
logger := trace.Logger(ctx, s.logger)
|
||||
|
||||
// 🚨 SECURITY: Require appropriate M2M scope.
|
||||
requiredScope := samsm2m.EnterprisePortalScope("subscription", scopes.ActionRead)
|
||||
requiredScope := samsm2m.EnterprisePortalScope(
|
||||
scopes.PermissionEnterprisePortalSubscription, scopes.ActionRead)
|
||||
clientAttrs, err := samsm2m.RequireScope(ctx, logger, s.store, requiredScope, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -288,7 +324,7 @@ func (s *handlerV1) ListEnterpriseSubscriptionLicenses(ctx context.Context, req
|
||||
if opts.LicenseKeySubstring != "" {
|
||||
return nil, connect.NewError(
|
||||
connect.CodeInvalidArgument,
|
||||
errors.New(`invalid filter: "license_key_substring"" provided multiple times`),
|
||||
errors.New(`invalid filter: "license_key_substring" provided multiple times`),
|
||||
)
|
||||
}
|
||||
opts.LicenseKeySubstring = f.LicenseKeySubstring
|
||||
@ -297,13 +333,13 @@ func (s *handlerV1) ListEnterpriseSubscriptionLicenses(ctx context.Context, req
|
||||
if f.SubscriptionId == "" {
|
||||
return nil, connect.NewError(
|
||||
connect.CodeInvalidArgument,
|
||||
errors.New(`invalid filter: "subscription_id"" provided but is empty`),
|
||||
errors.New(`invalid filter: "subscription_id" provided but is empty`),
|
||||
)
|
||||
}
|
||||
if opts.SubscriptionID != "" {
|
||||
return nil, connect.NewError(
|
||||
connect.CodeInvalidArgument,
|
||||
errors.New(`invalid filter: "subscription_id"" provided multiple times`),
|
||||
errors.New(`invalid filter: "subscription_id" provided multiple times`),
|
||||
)
|
||||
}
|
||||
opts.SubscriptionID = f.SubscriptionId
|
||||
@ -355,7 +391,8 @@ func (s *handlerV1) ListEnterpriseSubscriptionLicenses(ctx context.Context, req
|
||||
for i, l := range licenses {
|
||||
resp.Licenses[i], err = convertLicenseToProto(l)
|
||||
if err != nil {
|
||||
return nil, connectutil.InternalError(ctx, logger, err,
|
||||
return nil, connectutil.InternalError(ctx, logger,
|
||||
errors.Wrap(err, l.ID),
|
||||
"failed to read Enterprise Subscription license")
|
||||
}
|
||||
accessedSubscriptions[resp.Licenses[i].GetSubscriptionId()] = struct{}{}
|
||||
@ -368,22 +405,100 @@ func (s *handlerV1) ListEnterpriseSubscriptionLicenses(ctx context.Context, req
|
||||
return connect.NewResponse(&resp), nil
|
||||
}
|
||||
|
||||
func (s *handlerV1) UpdateEnterpriseSubscription(ctx context.Context, req *connect.Request[subscriptionsv1.UpdateEnterpriseSubscriptionRequest]) (*connect.Response[subscriptionsv1.UpdateEnterpriseSubscriptionResponse], error) {
|
||||
func (s *handlerV1) CreateEnterpriseSubscription(ctx context.Context, req *connect.Request[subscriptionsv1.CreateEnterpriseSubscriptionRequest]) (*connect.Response[subscriptionsv1.CreateEnterpriseSubscriptionResponse], error) {
|
||||
logger := trace.Logger(ctx, s.logger)
|
||||
|
||||
// 🚨 SECURITY: Require appropriate M2M scope.
|
||||
requiredScope := samsm2m.EnterprisePortalScope("subscription", scopes.ActionWrite)
|
||||
requiredScope := samsm2m.EnterprisePortalScope(
|
||||
scopes.PermissionEnterprisePortalSubscription, scopes.ActionWrite)
|
||||
clientAttrs, err := samsm2m.RequireScope(ctx, logger, s.store, requiredScope, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
logger = logger.With(clientAttrs...)
|
||||
|
||||
subscriptionID := strings.TrimPrefix(req.Msg.GetSubscription().GetId(), subscriptionsv1.EnterpriseSubscriptionIDPrefix)
|
||||
sub := req.Msg.GetSubscription()
|
||||
if sub == nil {
|
||||
return nil, connect.NewError(connect.CodeInvalidArgument, errors.New("subscription details are required"))
|
||||
}
|
||||
|
||||
// Validate required arguments.
|
||||
if strings.TrimSpace(sub.GetDisplayName()) == "" {
|
||||
return nil, connect.NewError(connect.CodeInvalidArgument, errors.New("display_name is required"))
|
||||
}
|
||||
|
||||
// Generate a new ID for the subscription.
|
||||
if sub.Id != "" {
|
||||
return nil, connect.NewError(connect.CodeInvalidArgument, errors.New("subscription_id can not be set"))
|
||||
}
|
||||
sub.Id, err = s.store.GenerateSubscriptionID()
|
||||
if err != nil {
|
||||
return nil, connectutil.InternalError(ctx, s.logger, err, "failed to generate new subscription ID")
|
||||
}
|
||||
|
||||
// Check for an existing subscription, just in case.
|
||||
if _, err := s.store.GetEnterpriseSubscription(ctx, sub.Id); err == nil {
|
||||
return nil, connect.NewError(connect.CodeAlreadyExists, err)
|
||||
} else if !errors.Is(err, subscriptions.ErrSubscriptionNotFound) {
|
||||
return nil, connectutil.InternalError(ctx, logger, err,
|
||||
"failed to check for existing subscription")
|
||||
}
|
||||
|
||||
createdAt := s.store.Now()
|
||||
createdSub, err := s.store.UpsertEnterpriseSubscription(ctx, sub.Id,
|
||||
subscriptions.UpsertSubscriptionOptions{
|
||||
CreatedAt: createdAt,
|
||||
DisplayName: database.NewNullString(sub.GetDisplayName()),
|
||||
InstanceDomain: database.NewNullString(sub.GetInstanceDomain()),
|
||||
SalesforceSubscriptionID: database.NewNullString(sub.GetSalesforce().GetSubscriptionId()),
|
||||
},
|
||||
subscriptions.CreateSubscriptionConditionOptions{
|
||||
Status: subscriptionsv1.EnterpriseSubscriptionCondition_STATUS_CREATED,
|
||||
TransitionTime: createdAt,
|
||||
Message: req.Msg.GetMessage(),
|
||||
})
|
||||
if err != nil {
|
||||
if errors.Is(err, subscriptions.ErrInvalidArgument) {
|
||||
return nil, connect.NewError(connect.CodeInvalidArgument, err)
|
||||
}
|
||||
return nil, connectutil.InternalError(ctx, logger, err, "failed to create subscription")
|
||||
}
|
||||
|
||||
protoSub := convertSubscriptionToProto(createdSub)
|
||||
logger.Scoped("audit").Info("CreateEnterpriseSubscription",
|
||||
log.String("createdSubscription", protoSub.GetId()))
|
||||
return connect.NewResponse(&subscriptionsv1.CreateEnterpriseSubscriptionResponse{
|
||||
Subscription: protoSub,
|
||||
}), nil
|
||||
}
|
||||
|
||||
func (s *handlerV1) UpdateEnterpriseSubscription(ctx context.Context, req *connect.Request[subscriptionsv1.UpdateEnterpriseSubscriptionRequest]) (*connect.Response[subscriptionsv1.UpdateEnterpriseSubscriptionResponse], error) {
|
||||
logger := trace.Logger(ctx, s.logger)
|
||||
|
||||
// 🚨 SECURITY: Require appropriate M2M scope.
|
||||
requiredScope := samsm2m.EnterprisePortalScope(
|
||||
scopes.PermissionEnterprisePortalSubscription, scopes.ActionWrite)
|
||||
clientAttrs, err := samsm2m.RequireScope(ctx, logger, s.store, requiredScope, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
logger = logger.With(clientAttrs...)
|
||||
|
||||
subscriptionID := req.Msg.GetSubscription().GetId()
|
||||
if subscriptionID == "" {
|
||||
return nil, connect.NewError(connect.CodeInvalidArgument, errors.New("subscription.id is required"))
|
||||
}
|
||||
|
||||
if existing, err := s.store.GetEnterpriseSubscription(ctx, subscriptionID); err != nil {
|
||||
if errors.Is(err, subscriptions.ErrSubscriptionNotFound) {
|
||||
return nil, connect.NewError(connect.CodeNotFound, err)
|
||||
}
|
||||
return nil, connectutil.InternalError(ctx, logger, err, "failed to find subscription")
|
||||
} else if existing.ArchivedAt != nil {
|
||||
return nil, connect.NewError(connect.CodeInvalidArgument,
|
||||
errors.New("archived subscriptions cannot be updated"))
|
||||
}
|
||||
|
||||
var opts subscriptions.UpsertSubscriptionOptions
|
||||
|
||||
fieldPaths := req.Msg.GetUpdateMask().GetPaths()
|
||||
@ -395,22 +510,33 @@ func (s *handlerV1) UpdateEnterpriseSubscription(ctx context.Context, req *conne
|
||||
if v := req.Msg.GetSubscription().GetDisplayName(); v != "" {
|
||||
opts.DisplayName = database.NewNullString(v)
|
||||
}
|
||||
if v := req.Msg.GetSubscription().GetSalesforce().GetSubscriptionId(); v != "" {
|
||||
opts.SalesforceSubscriptionID = database.NewNullString(v)
|
||||
}
|
||||
} else {
|
||||
for _, p := range fieldPaths {
|
||||
switch p {
|
||||
case "instance_domain":
|
||||
opts.InstanceDomain =
|
||||
database.NewNullString(req.Msg.GetSubscription().GetInstanceDomain())
|
||||
case "display_name":
|
||||
opts.DisplayName =
|
||||
database.NewNullString(req.Msg.GetSubscription().GetDisplayName())
|
||||
case "*":
|
||||
var valid bool
|
||||
if p == "*" {
|
||||
valid = true
|
||||
opts.ForceUpdate = true
|
||||
}
|
||||
if p == "instance_domain" || p == "*" {
|
||||
valid = true
|
||||
opts.InstanceDomain =
|
||||
database.NewNullString(req.Msg.GetSubscription().GetInstanceDomain())
|
||||
}
|
||||
if p == "display_name" || p == "*" {
|
||||
valid = true
|
||||
opts.DisplayName =
|
||||
database.NewNullString(req.Msg.GetSubscription().GetDisplayName())
|
||||
default:
|
||||
}
|
||||
if p == "salesforce.subscription_id" || p == "*" {
|
||||
valid = true
|
||||
opts.SalesforceSubscriptionID =
|
||||
database.NewNullString(req.Msg.GetSubscription().GetSalesforce().GetSubscriptionId())
|
||||
}
|
||||
|
||||
if !valid {
|
||||
return nil, connect.NewError(connect.CodeInvalidArgument, errors.Newf("unknown field path: %s", p))
|
||||
}
|
||||
}
|
||||
@ -442,11 +568,208 @@ func (s *handlerV1) UpdateEnterpriseSubscription(ctx context.Context, req *conne
|
||||
), nil
|
||||
}
|
||||
|
||||
func (s *handlerV1) ArchiveEnterpriseSubscription(ctx context.Context, req *connect.Request[subscriptionsv1.ArchiveEnterpriseSubscriptionRequest]) (*connect.Response[subscriptionsv1.ArchiveEnterpriseSubscriptionResponse], error) {
|
||||
logger := trace.Logger(ctx, s.logger)
|
||||
|
||||
// 🚨 SECURITY: Require appropriate M2M scope.
|
||||
requiredScope := samsm2m.EnterprisePortalScope(
|
||||
scopes.PermissionEnterprisePortalSubscription, scopes.ActionWrite)
|
||||
clientAttrs, err := samsm2m.RequireScope(ctx, logger, s.store, requiredScope, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
logger = logger.With(clientAttrs...)
|
||||
|
||||
subscriptionID := req.Msg.GetSubscriptionId()
|
||||
if subscriptionID == "" {
|
||||
return nil, connect.NewError(connect.CodeInvalidArgument, errors.New("subscription_id is required"))
|
||||
}
|
||||
|
||||
if _, err := s.store.GetEnterpriseSubscription(ctx, subscriptionID); err != nil {
|
||||
if errors.Is(err, subscriptions.ErrSubscriptionNotFound) {
|
||||
return nil, connect.NewError(connect.CodeNotFound, err)
|
||||
}
|
||||
return nil, connectutil.InternalError(ctx, logger, err, "failed to find subscription")
|
||||
}
|
||||
|
||||
archivedAt := s.store.Now()
|
||||
|
||||
// First, revoke all licenses associated with this subscription
|
||||
licenses, err := s.store.ListEnterpriseSubscriptionLicenses(ctx, subscriptions.ListLicensesOpts{
|
||||
SubscriptionID: subscriptionID,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, connectutil.InternalError(ctx, logger, err, "failed to list licenses for subscription")
|
||||
}
|
||||
revokedLicenses := make([]string, 0, len(licenses))
|
||||
for _, lc := range licenses {
|
||||
// Already revoked - nothing to do
|
||||
if lc.RevokedAt != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
licenseRevokeReason := "Subscription archival"
|
||||
if reason := req.Msg.GetReason(); reason != "" {
|
||||
licenseRevokeReason = fmt.Sprintf("Subscription archival: %s", reason)
|
||||
}
|
||||
_, err := s.store.RevokeEnterpriseSubscriptionLicense(ctx, lc.ID, subscriptions.RevokeLicenseOpts{
|
||||
Message: licenseRevokeReason,
|
||||
Time: &archivedAt,
|
||||
})
|
||||
if err != nil {
|
||||
// Audit-log the licenses we did manage to revoke
|
||||
logger.Scoped("audit").Info("ArchiveEnterpriseSubscription",
|
||||
log.Strings("revokedLicenses", revokedLicenses))
|
||||
|
||||
return nil, connectutil.InternalError(ctx, logger, err,
|
||||
fmt.Sprintf("failed to revoke license %q", lc.ID))
|
||||
}
|
||||
|
||||
revokedLicenses = append(revokedLicenses, lc.ID)
|
||||
}
|
||||
|
||||
// Then, archive the parent subscription
|
||||
createdSub, err := s.store.UpsertEnterpriseSubscription(ctx, subscriptionID,
|
||||
subscriptions.UpsertSubscriptionOptions{
|
||||
ArchivedAt: pointers.Ptr(archivedAt),
|
||||
},
|
||||
subscriptions.CreateSubscriptionConditionOptions{
|
||||
Status: subscriptionsv1.EnterpriseSubscriptionCondition_STATUS_ARCHIVED,
|
||||
TransitionTime: archivedAt,
|
||||
Message: req.Msg.GetReason(),
|
||||
})
|
||||
if err != nil {
|
||||
if errors.Is(err, subscriptions.ErrInvalidArgument) {
|
||||
return nil, connect.NewError(connect.CodeInvalidArgument, err)
|
||||
}
|
||||
return nil, connectutil.InternalError(ctx, logger, err, "failed to create subscription")
|
||||
}
|
||||
|
||||
protoSub := convertSubscriptionToProto(createdSub)
|
||||
logger.Scoped("audit").Info("ArchiveEnterpriseSubscription",
|
||||
log.String("archivedSubscription", protoSub.GetId()),
|
||||
log.Strings("revokedLicenses", revokedLicenses))
|
||||
return connect.NewResponse(&subscriptionsv1.ArchiveEnterpriseSubscriptionResponse{}), nil
|
||||
}
|
||||
|
||||
func (s *handlerV1) CreateEnterpriseSubscriptionLicense(ctx context.Context, req *connect.Request[subscriptionsv1.CreateEnterpriseSubscriptionLicenseRequest]) (*connect.Response[subscriptionsv1.CreateEnterpriseSubscriptionLicenseResponse], error) {
|
||||
logger := trace.Logger(ctx, s.logger)
|
||||
|
||||
// 🚨 SECURITY: Require appropriate M2M scope.
|
||||
requiredScope := samsm2m.EnterprisePortalScope(
|
||||
scopes.PermissionEnterprisePortalSubscription, scopes.ActionWrite)
|
||||
clientAttrs, err := samsm2m.RequireScope(ctx, logger, s.store, requiredScope, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
logger = logger.With(clientAttrs...)
|
||||
|
||||
create := req.Msg.GetLicense()
|
||||
if create.GetId() != "" {
|
||||
return nil, connect.NewError(connect.CodeInvalidArgument, errors.New("license.id cannot be set"))
|
||||
}
|
||||
subscriptionID := create.GetSubscriptionId()
|
||||
if subscriptionID == "" {
|
||||
return nil, connect.NewError(connect.CodeInvalidArgument, errors.New("license.subscription_id is required"))
|
||||
}
|
||||
sub, err := s.store.GetEnterpriseSubscription(ctx, subscriptionID)
|
||||
if err != nil {
|
||||
if errors.Is(err, subscriptions.ErrSubscriptionNotFound) {
|
||||
return nil, connect.NewError(connect.CodeNotFound, err)
|
||||
}
|
||||
return nil, connectutil.InternalError(ctx, logger, err, "failed to find subscription")
|
||||
}
|
||||
if sub.ArchivedAt != nil {
|
||||
return nil, connect.NewError(connect.CodeInvalidArgument,
|
||||
errors.New("target subscription is archived"))
|
||||
}
|
||||
|
||||
createdAt := s.store.Now()
|
||||
|
||||
var createdLicense *subscriptions.LicenseWithConditions
|
||||
switch data := create.License.(type) {
|
||||
case *subscriptionsv1.EnterpriseSubscriptionLicense_Key:
|
||||
licenseKey, err := convertLicenseKeyToLicenseKeyData(
|
||||
createdAt,
|
||||
&sub.Subscription,
|
||||
data.Key,
|
||||
s.store.GetRequiredEnterpriseSubscriptionLicenseKeyTags(),
|
||||
s.store.SignEnterpriseSubscriptionLicenseKey)
|
||||
if err != nil {
|
||||
var connectErr *connect.Error
|
||||
if errors.As(err, &connectErr) {
|
||||
return nil, err
|
||||
}
|
||||
return nil, connectutil.InternalError(ctx, logger, err, "failed to initialize license key from inputs")
|
||||
}
|
||||
createdLicense, err = s.store.CreateEnterpriseSubscriptionLicenseKey(ctx, subscriptionID,
|
||||
licenseKey,
|
||||
subscriptions.CreateLicenseOpts{
|
||||
Message: req.Msg.GetMessage(),
|
||||
Time: &createdAt,
|
||||
ExpireTime: utctime.FromTime(licenseKey.Info.ExpiresAt),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, connectutil.InternalError(ctx, logger, err, "failed to create license key")
|
||||
}
|
||||
|
||||
default:
|
||||
return nil, connect.NewError(connect.CodeInvalidArgument, errors.Newf("unsupported licnese type %T", data))
|
||||
}
|
||||
|
||||
proto, err := convertLicenseToProto(createdLicense)
|
||||
if err != nil {
|
||||
return nil, connectutil.InternalError(ctx, logger,
|
||||
errors.Wrap(err, createdLicense.ID),
|
||||
"failed to parse license")
|
||||
}
|
||||
logger.Scoped("audit").Info("CreateEnterpriseSubscriptionLicense",
|
||||
log.String("subscription", subscriptionID),
|
||||
log.String("createdLicense", proto.GetId()))
|
||||
return connect.NewResponse(&subscriptionsv1.CreateEnterpriseSubscriptionLicenseResponse{
|
||||
License: proto,
|
||||
}), nil
|
||||
}
|
||||
|
||||
func (s *handlerV1) RevokeEnterpriseSubscriptionLicense(ctx context.Context, req *connect.Request[subscriptionsv1.RevokeEnterpriseSubscriptionLicenseRequest]) (*connect.Response[subscriptionsv1.RevokeEnterpriseSubscriptionLicenseResponse], error) {
|
||||
logger := trace.Logger(ctx, s.logger)
|
||||
|
||||
// 🚨 SECURITY: Require appropriate M2M scope.
|
||||
requiredScope := samsm2m.EnterprisePortalScope("subscription", scopes.ActionWrite)
|
||||
clientAttrs, err := samsm2m.RequireScope(ctx, logger, s.store, requiredScope, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
logger = logger.With(clientAttrs...)
|
||||
|
||||
licenseID := req.Msg.LicenseId
|
||||
if licenseID == "" {
|
||||
return nil, connect.NewError(connect.CodeInvalidArgument, errors.New("license_id is required"))
|
||||
}
|
||||
|
||||
license, err := s.store.RevokeEnterpriseSubscriptionLicense(ctx, licenseID, subscriptions.RevokeLicenseOpts{
|
||||
Message: req.Msg.GetReason(),
|
||||
Time: pointers.Ptr(s.store.Now()),
|
||||
})
|
||||
if err != nil {
|
||||
if errors.Is(err, subscriptions.ErrSubscriptionLicenseNotFound) {
|
||||
return nil, connect.NewError(connect.CodeNotFound, err)
|
||||
}
|
||||
return nil, connectutil.InternalError(ctx, logger, err, "failed to revoked license")
|
||||
}
|
||||
|
||||
logger.Scoped("audit").Info("RevokeEnterpriseSubscriptionLicense",
|
||||
log.String("subscription", license.SubscriptionID),
|
||||
log.String("revokedLicense", license.ID))
|
||||
return connect.NewResponse(&subscriptionsv1.RevokeEnterpriseSubscriptionLicenseResponse{}), nil
|
||||
}
|
||||
|
||||
func (s *handlerV1) UpdateEnterpriseSubscriptionMembership(ctx context.Context, req *connect.Request[subscriptionsv1.UpdateEnterpriseSubscriptionMembershipRequest]) (*connect.Response[subscriptionsv1.UpdateEnterpriseSubscriptionMembershipResponse], error) {
|
||||
logger := trace.Logger(ctx, s.logger)
|
||||
|
||||
// 🚨 SECURITY: Require appropriate M2M scope.
|
||||
requiredScope := samsm2m.EnterprisePortalScope("permission.subscription", scopes.ActionWrite)
|
||||
requiredScope := samsm2m.EnterprisePortalScope(
|
||||
scopes.PermissionEnterprisePortalSubscriptionPermission, scopes.ActionWrite)
|
||||
clientAttrs, err := samsm2m.RequireScope(ctx, logger, s.store, requiredScope, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -477,13 +800,11 @@ func (s *handlerV1) UpdateEnterpriseSubscriptionMembership(ctx context.Context,
|
||||
|
||||
if subscriptionID != "" {
|
||||
// Double check that the subscription ID is valid.
|
||||
subscriptionAttrs, err := s.store.ListEnterpriseSubscriptions(ctx, subscriptions.ListEnterpriseSubscriptionsOptions{
|
||||
IDs: []string{subscriptionID},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, connectutil.InternalError(ctx, logger, err, "get dotcom enterprise subscription")
|
||||
} else if len(subscriptionAttrs) != 1 {
|
||||
return nil, connect.NewError(connect.CodeNotFound, errors.New("subscription not found"))
|
||||
if _, err := s.store.GetEnterpriseSubscription(ctx, subscriptionID); err != nil {
|
||||
if errors.Is(err, subscriptions.ErrSubscriptionNotFound) {
|
||||
return nil, connect.NewError(connect.CodeNotFound, errors.New("subscription not found"))
|
||||
}
|
||||
return nil, connectutil.InternalError(ctx, logger, err, "get enterprise subscription")
|
||||
}
|
||||
} else if instanceDomain != "" {
|
||||
// Validate and normalize the domain
|
||||
@ -495,7 +816,7 @@ func (s *handlerV1) UpdateEnterpriseSubscriptionMembership(ctx context.Context,
|
||||
ctx,
|
||||
subscriptions.ListEnterpriseSubscriptionsOptions{
|
||||
InstanceDomains: []string{instanceDomain},
|
||||
PageSize: 1,
|
||||
PageSize: 1, // instanceDomain should be globally unique
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
|
||||
@ -2,12 +2,20 @@ package subscriptionsservice
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"golang.org/x/crypto/ssh"
|
||||
|
||||
sams "github.com/sourcegraph/sourcegraph-accounts-sdk-go"
|
||||
clientsv1 "github.com/sourcegraph/sourcegraph-accounts-sdk-go/clients/v1"
|
||||
|
||||
"github.com/sourcegraph/sourcegraph/cmd/enterprise-portal/internal/database"
|
||||
"github.com/sourcegraph/sourcegraph/cmd/enterprise-portal/internal/database/subscriptions"
|
||||
"github.com/sourcegraph/sourcegraph/cmd/enterprise-portal/internal/database/utctime"
|
||||
"github.com/sourcegraph/sourcegraph/internal/license"
|
||||
subscriptionsv1 "github.com/sourcegraph/sourcegraph/lib/enterpriseportal/subscriptions/v1"
|
||||
"github.com/sourcegraph/sourcegraph/lib/errors"
|
||||
"github.com/sourcegraph/sourcegraph/lib/managedservicesplatform/iam"
|
||||
)
|
||||
|
||||
@ -15,17 +23,36 @@ import (
|
||||
// is meant to abstract away and limit the exposure of the underlying data layer
|
||||
// to the handler through a thin-wrapper.
|
||||
type StoreV1 interface {
|
||||
// Now provides the current time. It should always be used instead of
|
||||
// utctime.Now() or time.Now() for ease of mocking in tests.
|
||||
Now() utctime.Time
|
||||
|
||||
// GenerateSubscriptionID generates a new subscription ID for subscription
|
||||
// creation.
|
||||
GenerateSubscriptionID() (string, error)
|
||||
// UpsertEnterpriseSubscription upserts a enterprise subscription record based
|
||||
// on the given options.
|
||||
UpsertEnterpriseSubscription(ctx context.Context, subscriptionID string, opts subscriptions.UpsertSubscriptionOptions) (*subscriptions.SubscriptionWithConditions, error)
|
||||
UpsertEnterpriseSubscription(ctx context.Context, subscriptionID string, opts subscriptions.UpsertSubscriptionOptions, conditions ...subscriptions.CreateSubscriptionConditionOptions) (*subscriptions.SubscriptionWithConditions, error)
|
||||
// ListEnterpriseSubscriptions returns a list of enterprise subscriptions based
|
||||
// on the given options.
|
||||
ListEnterpriseSubscriptions(ctx context.Context, opts subscriptions.ListEnterpriseSubscriptionsOptions) ([]*subscriptions.SubscriptionWithConditions, error)
|
||||
// GetEnterpriseSubscriptions returns a specific enterprise subscription.
|
||||
//
|
||||
// Returns subscriptions.ErrSubscriptionNotFound if the subscription does
|
||||
// not exist.
|
||||
GetEnterpriseSubscription(ctx context.Context, subscriptionID string) (*subscriptions.SubscriptionWithConditions, error)
|
||||
|
||||
// ListDotcomEnterpriseSubscriptionLicenses returns a list of enterprise
|
||||
// subscription license attributes with the given filters. It silently ignores
|
||||
// any non-matching filters. The caller should check the length of the returned
|
||||
// slice to ensure all requested licenses were found.
|
||||
ListEnterpriseSubscriptionLicenses(ctx context.Context, opts subscriptions.ListLicensesOpts) ([]*subscriptions.LicenseWithConditions, error)
|
||||
// RevokeEnterpriseSubscriptionLicense premanently revokes a license.
|
||||
RevokeEnterpriseSubscriptionLicense(ctx context.Context, licenseID string, opts subscriptions.RevokeLicenseOpts) (*subscriptions.LicenseWithConditions, error)
|
||||
|
||||
// Interfaces specific to 'ENTERPRISE_SUBSCRIPTION_LICENSE_TYPE_KEY', grouped
|
||||
// to clarify their purpose for future license key types.
|
||||
licenseKeysStore
|
||||
|
||||
// IntrospectSAMSToken takes a SAMS access token and returns relevant metadata.
|
||||
//
|
||||
@ -48,39 +75,124 @@ type StoreV1 interface {
|
||||
IAMCheck(ctx context.Context, opts iam.CheckOptions) (allowed bool, _ error)
|
||||
}
|
||||
|
||||
// licenseKeysStore groups mechanisms specific to the license type
|
||||
// 'ENTERPRISE_SUBSCRIPTION_LICENSE_TYPE_KEY'
|
||||
type licenseKeysStore interface {
|
||||
// GetRequiredEnterpriseSubscriptionLicenseKeyTags returns the license tags
|
||||
// that must be included on all generated license keys.
|
||||
GetRequiredEnterpriseSubscriptionLicenseKeyTags() []string
|
||||
// SignEnterpriseSubscriptionLicenseKey signs a new license key for
|
||||
// creation of 'ENTERPRISE_SUBSCRIPTION_LICENSE_TYPE_KEY' licenses.
|
||||
//
|
||||
// Returns errStoreUnimplemented if key signing is not configured.
|
||||
SignEnterpriseSubscriptionLicenseKey(license.Info) (string, error)
|
||||
// CreateLicense creates a new classic offline license for the given subscription.
|
||||
CreateEnterpriseSubscriptionLicenseKey(ctx context.Context, subscriptionID string, license *subscriptions.DataLicenseKey, opts subscriptions.CreateLicenseOpts) (*subscriptions.LicenseWithConditions, error)
|
||||
}
|
||||
|
||||
type storeV1 struct {
|
||||
db *database.DB
|
||||
SAMSClient *sams.ClientV1
|
||||
IAMClient *iam.ClientV1
|
||||
// LicenseKeySigner may be nil if not configured for key signing.
|
||||
LicenseKeySigner ssh.Signer
|
||||
LicenseKeyRequiredTags []string
|
||||
}
|
||||
|
||||
type NewStoreV1Options struct {
|
||||
DB *database.DB
|
||||
SAMSClient *sams.ClientV1
|
||||
IAMClient *iam.ClientV1
|
||||
|
||||
LicenseKeySigner ssh.Signer
|
||||
LicenseKeyRequiredTags []string
|
||||
}
|
||||
|
||||
var errStoreUnimplemented = errors.New("unimplemented")
|
||||
|
||||
// NewStoreV1 returns a new StoreV1 using the given resource handles.
|
||||
func NewStoreV1(opts NewStoreV1Options) StoreV1 {
|
||||
return &storeV1{
|
||||
db: opts.DB,
|
||||
SAMSClient: opts.SAMSClient,
|
||||
IAMClient: opts.IAMClient,
|
||||
|
||||
LicenseKeySigner: opts.LicenseKeySigner,
|
||||
LicenseKeyRequiredTags: opts.LicenseKeyRequiredTags,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *storeV1) UpsertEnterpriseSubscription(ctx context.Context, subscriptionID string, opts subscriptions.UpsertSubscriptionOptions) (*subscriptions.SubscriptionWithConditions, error) {
|
||||
return s.db.Subscriptions().Upsert(ctx, subscriptionID, opts)
|
||||
func (s *storeV1) Now() utctime.Time { return utctime.Now() }
|
||||
|
||||
func (s *storeV1) GenerateSubscriptionID() (string, error) {
|
||||
id, err := uuid.NewRandom()
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "uuid")
|
||||
}
|
||||
return id.String(), nil
|
||||
}
|
||||
|
||||
func (s *storeV1) UpsertEnterpriseSubscription(ctx context.Context, subscriptionID string, opts subscriptions.UpsertSubscriptionOptions, conditions ...subscriptions.CreateSubscriptionConditionOptions) (*subscriptions.SubscriptionWithConditions, error) {
|
||||
return s.db.Subscriptions().Upsert(
|
||||
ctx,
|
||||
strings.TrimPrefix(subscriptionID, subscriptionsv1.EnterpriseSubscriptionIDPrefix),
|
||||
opts,
|
||||
conditions...,
|
||||
)
|
||||
}
|
||||
|
||||
func (s *storeV1) ListEnterpriseSubscriptions(ctx context.Context, opts subscriptions.ListEnterpriseSubscriptionsOptions) ([]*subscriptions.SubscriptionWithConditions, error) {
|
||||
for idx := range opts.IDs {
|
||||
opts.IDs[idx] = strings.TrimPrefix(opts.IDs[idx], subscriptionsv1.EnterpriseSubscriptionIDPrefix)
|
||||
}
|
||||
return s.db.Subscriptions().List(ctx, opts)
|
||||
}
|
||||
|
||||
func (s *storeV1) GetEnterpriseSubscription(ctx context.Context, subscriptionID string) (*subscriptions.SubscriptionWithConditions, error) {
|
||||
return s.db.Subscriptions().Get(ctx,
|
||||
strings.TrimPrefix(subscriptionID, subscriptionsv1.EnterpriseSubscriptionIDPrefix))
|
||||
}
|
||||
|
||||
func (s *storeV1) ListEnterpriseSubscriptionLicenses(ctx context.Context, opts subscriptions.ListLicensesOpts) ([]*subscriptions.LicenseWithConditions, error) {
|
||||
opts.SubscriptionID = strings.TrimPrefix(opts.SubscriptionID, subscriptionsv1.EnterpriseSubscriptionIDPrefix)
|
||||
return s.db.Subscriptions().Licenses().List(ctx, opts)
|
||||
}
|
||||
|
||||
func (s *storeV1) GetRequiredEnterpriseSubscriptionLicenseKeyTags() []string {
|
||||
return s.LicenseKeyRequiredTags
|
||||
}
|
||||
|
||||
func (s *storeV1) SignEnterpriseSubscriptionLicenseKey(info license.Info) (string, error) {
|
||||
if s.LicenseKeySigner == nil {
|
||||
return "", errStoreUnimplemented
|
||||
}
|
||||
signedKey, _, err := license.GenerateSignedKey(info, s.LicenseKeySigner)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "generating signed key")
|
||||
}
|
||||
return signedKey, nil
|
||||
}
|
||||
|
||||
func (s *storeV1) CreateEnterpriseSubscriptionLicenseKey(ctx context.Context, subscriptionID string, license *subscriptions.DataLicenseKey, opts subscriptions.CreateLicenseOpts) (*subscriptions.LicenseWithConditions, error) {
|
||||
if opts.ImportLicenseID != "" {
|
||||
return nil, errors.New("import license ID not allowed via API")
|
||||
}
|
||||
return s.db.Subscriptions().Licenses().CreateLicenseKey(
|
||||
ctx,
|
||||
strings.TrimPrefix(subscriptionID, subscriptionsv1.EnterpriseSubscriptionIDPrefix),
|
||||
license,
|
||||
opts,
|
||||
)
|
||||
}
|
||||
|
||||
func (s *storeV1) RevokeEnterpriseSubscriptionLicense(ctx context.Context, licenseID string, opts subscriptions.RevokeLicenseOpts) (*subscriptions.LicenseWithConditions, error) {
|
||||
return s.db.Subscriptions().Licenses().Revoke(
|
||||
ctx,
|
||||
strings.TrimPrefix(licenseID, subscriptionsv1.EnterpriseSubscriptionLicenseIDPrefix),
|
||||
opts,
|
||||
)
|
||||
}
|
||||
|
||||
func (s *storeV1) IntrospectSAMSToken(ctx context.Context, token string) (*sams.IntrospectTokenResponse, error) {
|
||||
return s.SAMSClient.Tokens().IntrospectToken(ctx, token)
|
||||
}
|
||||
|
||||
@ -3,25 +3,35 @@ package subscriptionsservice
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"slices"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"connectrpc.com/connect"
|
||||
mockrequire "github.com/derision-test/go-mockgen/v2/testutil/require"
|
||||
"github.com/google/uuid"
|
||||
"github.com/hexops/autogold/v2"
|
||||
"github.com/hexops/valast"
|
||||
"github.com/sourcegraph/log/logtest"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"google.golang.org/protobuf/types/known/fieldmaskpb"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
|
||||
sams "github.com/sourcegraph/sourcegraph-accounts-sdk-go"
|
||||
"github.com/sourcegraph/sourcegraph-accounts-sdk-go/scopes"
|
||||
|
||||
"github.com/sourcegraph/sourcegraph/cmd/enterprise-portal/internal/database/subscriptions"
|
||||
"github.com/sourcegraph/sourcegraph/cmd/enterprise-portal/internal/database/utctime"
|
||||
"github.com/sourcegraph/sourcegraph/cmd/enterprise-portal/internal/samsm2m"
|
||||
"github.com/sourcegraph/sourcegraph/internal/license"
|
||||
subscriptionsv1 "github.com/sourcegraph/sourcegraph/lib/enterpriseportal/subscriptions/v1"
|
||||
"github.com/sourcegraph/sourcegraph/lib/errors"
|
||||
"github.com/sourcegraph/sourcegraph/lib/managedservicesplatform/iam"
|
||||
"github.com/sourcegraph/sourcegraph/lib/pointers"
|
||||
)
|
||||
|
||||
type testHandlerV1 struct {
|
||||
@ -29,22 +39,44 @@ type testHandlerV1 struct {
|
||||
mockStore *MockStoreV1
|
||||
}
|
||||
|
||||
func newTestHandlerV1() *testHandlerV1 {
|
||||
func newPredictableGenerator(ns string) func() (string, error) {
|
||||
var seq atomic.Int32
|
||||
return func() (string, error) {
|
||||
return fmt.Sprintf("%s-%d", ns, seq.Add(1)), nil
|
||||
}
|
||||
}
|
||||
|
||||
func newMockTime() time.Time {
|
||||
return time.Date(2024, 1, 1, 1, 1, 0, 0, time.UTC)
|
||||
}
|
||||
|
||||
func newTestHandlerV1(t *testing.T, tokenScopes ...scopes.Scope) *testHandlerV1 {
|
||||
mockStore := NewMockStoreV1()
|
||||
mockStore.IntrospectSAMSTokenFunc.SetDefaultReturn(
|
||||
&sams.IntrospectTokenResponse{
|
||||
Active: true,
|
||||
Scopes: scopes.Scopes{
|
||||
samsm2m.EnterprisePortalScope("subscription", scopes.ActionRead),
|
||||
samsm2m.EnterprisePortalScope("subscription", scopes.ActionWrite),
|
||||
samsm2m.EnterprisePortalScope("permission.subscription", scopes.ActionWrite),
|
||||
},
|
||||
Scopes: tokenScopes,
|
||||
},
|
||||
nil,
|
||||
)
|
||||
|
||||
mockStore.GenerateSubscriptionIDFunc.SetDefaultHook(
|
||||
newPredictableGenerator("uuid"))
|
||||
|
||||
keySigner := newPredictableGenerator("signedkey")
|
||||
mockStore.SignEnterpriseSubscriptionLicenseKeyFunc.SetDefaultHook(
|
||||
func(i license.Info) (string, error) { return keySigner() })
|
||||
|
||||
// Stable time generator that increments by 1 second on each call
|
||||
var timeSeq atomic.Int32
|
||||
mockStore.NowFunc.SetDefaultHook(func() utctime.Time {
|
||||
return utctime.FromTime(newMockTime().
|
||||
Add(time.Duration(timeSeq.Add(1)) * time.Second))
|
||||
})
|
||||
|
||||
return &testHandlerV1{
|
||||
handlerV1: &handlerV1{
|
||||
logger: logtest.NoOp(nil),
|
||||
logger: logtest.Scoped(t),
|
||||
store: mockStore,
|
||||
},
|
||||
mockStore: mockStore,
|
||||
@ -169,7 +201,12 @@ func TestHandlerV1_ListEnterpriseSubscriptions(t *testing.T) {
|
||||
req := connect.NewRequest(tc.list)
|
||||
req.Header().Add("Authorization", "Bearer foolmeifyoucan")
|
||||
|
||||
h := newTestHandlerV1()
|
||||
h := newTestHandlerV1(t,
|
||||
samsm2m.EnterprisePortalScope(
|
||||
scopes.PermissionEnterprisePortalSubscription,
|
||||
scopes.ActionRead,
|
||||
),
|
||||
)
|
||||
h.mockStore.IAMListObjectsFunc.SetDefaultHook(func(_ context.Context, opts iam.ListObjectsOptions) ([]string, error) {
|
||||
return tc.iamObjectsHook(opts)
|
||||
})
|
||||
@ -198,15 +235,194 @@ func TestHandlerV1_ListEnterpriseSubscriptions(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandlerV1_CreateEnterpriseSubscription(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
tokenScopes scopes.Scopes
|
||||
create *subscriptionsv1.CreateEnterpriseSubscriptionRequest
|
||||
wantError autogold.Value
|
||||
wantUpsertOpts autogold.Value
|
||||
}{
|
||||
{
|
||||
name: "no parameters",
|
||||
create: &subscriptionsv1.CreateEnterpriseSubscriptionRequest{
|
||||
Subscription: &subscriptionsv1.EnterpriseSubscription{},
|
||||
},
|
||||
wantError: autogold.Expect("invalid_argument: display_name is required"),
|
||||
},
|
||||
{
|
||||
name: "custom subscription ID",
|
||||
create: &subscriptionsv1.CreateEnterpriseSubscriptionRequest{
|
||||
Subscription: &subscriptionsv1.EnterpriseSubscription{
|
||||
Id: "not-allowed",
|
||||
DisplayName: t.Name(),
|
||||
},
|
||||
},
|
||||
wantError: autogold.Expect("invalid_argument: subscription_id can not be set"),
|
||||
},
|
||||
{
|
||||
name: "insufficient scopes",
|
||||
tokenScopes: scopes.Scopes{
|
||||
samsm2m.EnterprisePortalScope(
|
||||
scopes.PermissionEnterprisePortalSubscription,
|
||||
scopes.ActionRead,
|
||||
),
|
||||
},
|
||||
create: &subscriptionsv1.CreateEnterpriseSubscriptionRequest{
|
||||
Subscription: &subscriptionsv1.EnterpriseSubscription{
|
||||
Id: "not-allowed",
|
||||
DisplayName: t.Name(),
|
||||
},
|
||||
},
|
||||
wantError: autogold.Expect("permission_denied: insufficient scope"),
|
||||
},
|
||||
{
|
||||
name: "with required params only",
|
||||
create: &subscriptionsv1.CreateEnterpriseSubscriptionRequest{
|
||||
Subscription: &subscriptionsv1.EnterpriseSubscription{
|
||||
DisplayName: t.Name(),
|
||||
},
|
||||
},
|
||||
wantUpsertOpts: autogold.Expect(subscriptions.UpsertSubscriptionOptions{
|
||||
InstanceDomain: &sql.NullString{},
|
||||
DisplayName: &sql.NullString{
|
||||
String: "TestHandlerV1_CreateEnterpriseSubscription",
|
||||
Valid: true,
|
||||
},
|
||||
CreatedAt: utctime.Date(2024,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
0),
|
||||
SalesforceSubscriptionID: &sql.NullString{},
|
||||
}),
|
||||
},
|
||||
{
|
||||
name: "with message and optional fields",
|
||||
create: &subscriptionsv1.CreateEnterpriseSubscriptionRequest{
|
||||
Subscription: &subscriptionsv1.EnterpriseSubscription{
|
||||
DisplayName: t.Name(),
|
||||
Salesforce: &subscriptionsv1.EnterpriseSubscriptionSalesforceMetadata{
|
||||
SubscriptionId: "sf_sub",
|
||||
},
|
||||
},
|
||||
Message: "hello world",
|
||||
},
|
||||
wantUpsertOpts: autogold.Expect(subscriptions.UpsertSubscriptionOptions{
|
||||
InstanceDomain: &sql.NullString{},
|
||||
DisplayName: &sql.NullString{
|
||||
String: "TestHandlerV1_CreateEnterpriseSubscription",
|
||||
Valid: true,
|
||||
},
|
||||
CreatedAt: utctime.Date(2024,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
0),
|
||||
SalesforceSubscriptionID: &sql.NullString{
|
||||
String: "sf_sub",
|
||||
Valid: true,
|
||||
},
|
||||
}),
|
||||
},
|
||||
} {
|
||||
req := connect.NewRequest(tc.create)
|
||||
req.Header().Add("Authorization", "Bearer foolmeifyoucan")
|
||||
|
||||
if tc.tokenScopes == nil {
|
||||
tc.tokenScopes = scopes.Scopes{
|
||||
samsm2m.EnterprisePortalScope(
|
||||
scopes.PermissionEnterprisePortalSubscription,
|
||||
scopes.ActionWrite,
|
||||
),
|
||||
}
|
||||
}
|
||||
h := newTestHandlerV1(t, tc.tokenScopes...)
|
||||
h.mockStore.GetEnterpriseSubscriptionFunc.SetDefaultHook(func(_ context.Context, id string) (*subscriptions.SubscriptionWithConditions, error) {
|
||||
return nil, subscriptions.ErrSubscriptionNotFound
|
||||
})
|
||||
h.mockStore.UpsertEnterpriseSubscriptionFunc.SetDefaultHook(func(_ context.Context, _ string, opts subscriptions.UpsertSubscriptionOptions, conds ...subscriptions.CreateSubscriptionConditionOptions) (*subscriptions.SubscriptionWithConditions, error) {
|
||||
require.Len(t, conds, 1) // create must have condition
|
||||
|
||||
// Condition must match upsert
|
||||
assert.Equal(t, tc.create.GetMessage(), conds[0].Message)
|
||||
assert.Equal(t, opts.CreatedAt, conds[0].TransitionTime)
|
||||
assert.Equal(t, subscriptionsv1.EnterpriseSubscriptionCondition_STATUS_CREATED,
|
||||
conds[0].Status)
|
||||
|
||||
tc.wantUpsertOpts.Equal(t, opts)
|
||||
|
||||
return &subscriptions.SubscriptionWithConditions{}, nil
|
||||
})
|
||||
_, err := h.CreateEnterpriseSubscription(ctx, req)
|
||||
if tc.wantError != nil {
|
||||
require.Error(t, err)
|
||||
tc.wantError.Equal(t, err.Error())
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
if tc.wantUpsertOpts != nil {
|
||||
mockrequire.CalledOnce(t, h.mockStore.UpsertEnterpriseSubscriptionFunc)
|
||||
mockrequire.CalledOnce(t, h.mockStore.GetEnterpriseSubscriptionFunc)
|
||||
} else {
|
||||
mockrequire.NotCalled(t, h.mockStore.UpsertEnterpriseSubscriptionFunc)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandlerV1_UpdateEnterpriseSubscription(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
const mockSubscriptionID = "es_80ca12e2-54b4-448c-a61a-390b1a9c1224"
|
||||
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
tokenScopes scopes.Scopes
|
||||
update *subscriptionsv1.UpdateEnterpriseSubscriptionRequest
|
||||
wantUpdateOpts autogold.Value
|
||||
wantError autogold.Value
|
||||
}{
|
||||
{
|
||||
name: "insufficient scopes",
|
||||
tokenScopes: scopes.Scopes{
|
||||
samsm2m.EnterprisePortalScope(
|
||||
scopes.PermissionEnterprisePortalSubscription,
|
||||
scopes.ActionRead,
|
||||
),
|
||||
},
|
||||
update: &subscriptionsv1.UpdateEnterpriseSubscriptionRequest{
|
||||
Subscription: &subscriptionsv1.EnterpriseSubscription{
|
||||
Id: mockSubscriptionID,
|
||||
},
|
||||
UpdateMask: nil,
|
||||
},
|
||||
wantError: autogold.Expect("permission_denied: insufficient scope"),
|
||||
},
|
||||
{
|
||||
name: "subscription ID is required",
|
||||
update: &subscriptionsv1.UpdateEnterpriseSubscriptionRequest{
|
||||
Subscription: &subscriptionsv1.EnterpriseSubscription{
|
||||
Id: "",
|
||||
},
|
||||
UpdateMask: nil,
|
||||
},
|
||||
wantError: autogold.Expect("invalid_argument: subscription.id is required"),
|
||||
},
|
||||
{
|
||||
name: "subscription does not exist",
|
||||
update: &subscriptionsv1.UpdateEnterpriseSubscriptionRequest{
|
||||
Subscription: &subscriptionsv1.EnterpriseSubscription{
|
||||
Id: uuid.NewString(),
|
||||
},
|
||||
UpdateMask: nil,
|
||||
},
|
||||
wantError: autogold.Expect("not_found: subscription not found"),
|
||||
},
|
||||
{
|
||||
name: "no update mask",
|
||||
update: &subscriptionsv1.UpdateEnterpriseSubscriptionRequest{
|
||||
@ -229,6 +445,18 @@ func TestHandlerV1_UpdateEnterpriseSubscription(t *testing.T) {
|
||||
},
|
||||
}),
|
||||
},
|
||||
{
|
||||
name: "unknown field mask",
|
||||
update: &subscriptionsv1.UpdateEnterpriseSubscriptionRequest{
|
||||
Subscription: &subscriptionsv1.EnterpriseSubscription{
|
||||
Id: mockSubscriptionID,
|
||||
InstanceDomain: "s1.sourcegraph.com",
|
||||
DisplayName: "My Test Subscription",
|
||||
},
|
||||
UpdateMask: &fieldmaskpb.FieldMask{Paths: []string{"asdfasdf"}},
|
||||
},
|
||||
wantError: autogold.Expect("invalid_argument: unknown field path: asdfasdf"),
|
||||
},
|
||||
{
|
||||
name: "specified field mask",
|
||||
update: &subscriptionsv1.UpdateEnterpriseSubscriptionRequest{
|
||||
@ -257,9 +485,10 @@ func TestHandlerV1_UpdateEnterpriseSubscription(t *testing.T) {
|
||||
},
|
||||
// All update-able values should be set to their defaults explicitly
|
||||
wantUpdateOpts: autogold.Expect(subscriptions.UpsertSubscriptionOptions{
|
||||
InstanceDomain: &sql.NullString{},
|
||||
DisplayName: &sql.NullString{},
|
||||
ForceUpdate: true,
|
||||
InstanceDomain: &sql.NullString{},
|
||||
DisplayName: &sql.NullString{},
|
||||
SalesforceSubscriptionID: &sql.NullString{},
|
||||
ForceUpdate: true,
|
||||
}),
|
||||
},
|
||||
{
|
||||
@ -279,19 +508,37 @@ func TestHandlerV1_UpdateEnterpriseSubscription(t *testing.T) {
|
||||
req := connect.NewRequest(tc.update)
|
||||
req.Header().Add("Authorization", "Bearer foolmeifyoucan")
|
||||
|
||||
h := newTestHandlerV1()
|
||||
h.mockStore.ListEnterpriseSubscriptionsFunc.SetDefaultReturn(
|
||||
[]*subscriptions.SubscriptionWithConditions{
|
||||
{Subscription: subscriptions.Subscription{
|
||||
ID: "80ca12e2-54b4-448c-a61a-390b1a9c1224",
|
||||
}},
|
||||
}, nil)
|
||||
h.mockStore.UpsertEnterpriseSubscriptionFunc.SetDefaultHook(func(_ context.Context, _ string, opts subscriptions.UpsertSubscriptionOptions) (*subscriptions.SubscriptionWithConditions, error) {
|
||||
if tc.tokenScopes == nil {
|
||||
tc.tokenScopes = scopes.Scopes{
|
||||
samsm2m.EnterprisePortalScope(
|
||||
scopes.PermissionEnterprisePortalSubscription,
|
||||
scopes.ActionWrite,
|
||||
),
|
||||
}
|
||||
}
|
||||
h := newTestHandlerV1(t, tc.tokenScopes...)
|
||||
h.mockStore.GetEnterpriseSubscriptionFunc.SetDefaultHook(func(ctx context.Context, id string) (*subscriptions.SubscriptionWithConditions, error) {
|
||||
if id == mockSubscriptionID {
|
||||
return &subscriptions.SubscriptionWithConditions{
|
||||
Subscription: subscriptions.Subscription{
|
||||
ID: id,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
return nil, subscriptions.ErrSubscriptionNotFound
|
||||
})
|
||||
h.mockStore.UpsertEnterpriseSubscriptionFunc.SetDefaultHook(func(_ context.Context, _ string, opts subscriptions.UpsertSubscriptionOptions, conds ...subscriptions.CreateSubscriptionConditionOptions) (*subscriptions.SubscriptionWithConditions, error) {
|
||||
tc.wantUpdateOpts.Equal(t, opts)
|
||||
assert.Len(t, conds, 0) // no conditions for standard updates
|
||||
return &subscriptions.SubscriptionWithConditions{}, nil
|
||||
})
|
||||
_, err := h.UpdateEnterpriseSubscription(ctx, req)
|
||||
require.NoError(t, err)
|
||||
if tc.wantError != nil {
|
||||
require.Error(t, err)
|
||||
tc.wantError.Equal(t, err.Error())
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
if tc.wantUpdateOpts != nil {
|
||||
mockrequire.CalledOnce(t, h.mockStore.UpsertEnterpriseSubscriptionFunc)
|
||||
} else {
|
||||
@ -301,6 +548,418 @@ func TestHandlerV1_UpdateEnterpriseSubscription(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandlerV1_ArchiveEnterpriseSubscription(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
const mockSubscriptionID = "es_80ca12e2-54b4-448c-a61a-390b1a9c1224"
|
||||
const mockLicenseID = "esl_80ca12e2-54b4-448c-a61a-390b1a9c1224"
|
||||
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
tokenScopes scopes.Scopes
|
||||
archive *subscriptionsv1.ArchiveEnterpriseSubscriptionRequest
|
||||
wantUpsertOpts autogold.Value
|
||||
wantError autogold.Value
|
||||
}{
|
||||
{
|
||||
name: "insufficient scopes",
|
||||
tokenScopes: scopes.Scopes{
|
||||
samsm2m.EnterprisePortalScope(
|
||||
scopes.PermissionEnterprisePortalSubscription,
|
||||
scopes.ActionRead,
|
||||
),
|
||||
},
|
||||
archive: &subscriptionsv1.ArchiveEnterpriseSubscriptionRequest{},
|
||||
wantError: autogold.Expect("permission_denied: insufficient scope"),
|
||||
},
|
||||
{
|
||||
name: "subscription ID is required",
|
||||
archive: &subscriptionsv1.ArchiveEnterpriseSubscriptionRequest{},
|
||||
wantError: autogold.Expect("invalid_argument: subscription_id is required"),
|
||||
},
|
||||
{
|
||||
name: "subscription does not exist",
|
||||
archive: &subscriptionsv1.ArchiveEnterpriseSubscriptionRequest{
|
||||
SubscriptionId: uuid.NewString(),
|
||||
},
|
||||
wantError: autogold.Expect("not_found: subscription not found"),
|
||||
},
|
||||
{
|
||||
name: "ok with reason",
|
||||
archive: &subscriptionsv1.ArchiveEnterpriseSubscriptionRequest{
|
||||
SubscriptionId: mockSubscriptionID,
|
||||
Reason: t.Name(),
|
||||
},
|
||||
wantUpsertOpts: autogold.Expect(subscriptions.UpsertSubscriptionOptions{ArchivedAt: valast.Ptr(utctime.Date(2024,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
0))}),
|
||||
},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
req := connect.NewRequest(tc.archive)
|
||||
req.Header().Add("Authorization", "Bearer foolmeifyoucan")
|
||||
|
||||
if tc.tokenScopes == nil {
|
||||
tc.tokenScopes = scopes.Scopes{
|
||||
samsm2m.EnterprisePortalScope(
|
||||
scopes.PermissionEnterprisePortalSubscription,
|
||||
scopes.ActionWrite,
|
||||
),
|
||||
}
|
||||
}
|
||||
h := newTestHandlerV1(t, tc.tokenScopes...)
|
||||
h.mockStore.GetEnterpriseSubscriptionFunc.SetDefaultHook(func(_ context.Context, id string) (*subscriptions.SubscriptionWithConditions, error) {
|
||||
if id == mockSubscriptionID {
|
||||
return &subscriptions.SubscriptionWithConditions{
|
||||
Subscription: subscriptions.Subscription{
|
||||
ID: id,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
return nil, subscriptions.ErrSubscriptionNotFound
|
||||
})
|
||||
h.mockStore.ListEnterpriseSubscriptionLicensesFunc.SetDefaultHook(func(_ context.Context, opts subscriptions.ListLicensesOpts) ([]*subscriptions.LicenseWithConditions, error) {
|
||||
if opts.SubscriptionID == mockSubscriptionID {
|
||||
return []*subscriptions.LicenseWithConditions{{
|
||||
SubscriptionLicense: subscriptions.SubscriptionLicense{
|
||||
ID: mockLicenseID,
|
||||
},
|
||||
}, {
|
||||
SubscriptionLicense: subscriptions.SubscriptionLicense{
|
||||
ID: "esl_already_revoked",
|
||||
RevokedAt: pointers.Ptr(utctime.Now()),
|
||||
},
|
||||
}}, nil
|
||||
}
|
||||
return nil, errors.New("unexpected subscription ID")
|
||||
})
|
||||
h.mockStore.RevokeEnterpriseSubscriptionLicenseFunc.SetDefaultHook(func(_ context.Context, l string, opts subscriptions.RevokeLicenseOpts) (*subscriptions.LicenseWithConditions, error) {
|
||||
assert.Equal(t, mockLicenseID, l)
|
||||
assert.Contains(t, opts.Message, tc.archive.GetReason())
|
||||
require.NotNil(t, opts.Time)
|
||||
return &subscriptions.LicenseWithConditions{}, nil
|
||||
})
|
||||
h.mockStore.UpsertEnterpriseSubscriptionFunc.SetDefaultHook(func(_ context.Context, _ string, opts subscriptions.UpsertSubscriptionOptions, conds ...subscriptions.CreateSubscriptionConditionOptions) (*subscriptions.SubscriptionWithConditions, error) {
|
||||
require.Len(t, conds, 1) // create must have condition
|
||||
|
||||
// Condition must match upsert
|
||||
assert.Equal(t, tc.archive.GetReason(), conds[0].Message)
|
||||
require.NotNil(t, opts.ArchivedAt)
|
||||
assert.Equal(t, *opts.ArchivedAt, conds[0].TransitionTime)
|
||||
assert.Equal(t, subscriptionsv1.EnterpriseSubscriptionCondition_STATUS_ARCHIVED,
|
||||
conds[0].Status)
|
||||
|
||||
tc.wantUpsertOpts.Equal(t, opts, autogold.ExportedOnly())
|
||||
|
||||
return &subscriptions.SubscriptionWithConditions{}, nil
|
||||
})
|
||||
_, err := h.ArchiveEnterpriseSubscription(ctx, req)
|
||||
if tc.wantError != nil {
|
||||
require.Error(t, err)
|
||||
tc.wantError.Equal(t, err.Error())
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
if tc.wantUpsertOpts != nil {
|
||||
mockrequire.CalledOnce(t, h.mockStore.UpsertEnterpriseSubscriptionFunc)
|
||||
mockrequire.CalledOnce(t, h.mockStore.RevokeEnterpriseSubscriptionLicenseFunc)
|
||||
} else {
|
||||
mockrequire.NotCalled(t, h.mockStore.UpsertEnterpriseSubscriptionFunc)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandlerV1_CreateEnterpriseSubscriptionLicense(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
const mockSubscriptionID = "es_80ca12e2-54b4-448c-a61a-390b1a9c1224"
|
||||
const archivedSubscriptionID = "es_b7df32dd-509c-4114-a6bb-4e6d09090fba"
|
||||
requiredTags := []string{"test", "dev"}
|
||||
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
tokenScopes scopes.Scopes
|
||||
create *subscriptionsv1.CreateEnterpriseSubscriptionLicenseRequest
|
||||
wantKeyOpts autogold.Value
|
||||
wantError autogold.Value
|
||||
}{
|
||||
{
|
||||
name: "insufficient scopes",
|
||||
tokenScopes: scopes.Scopes{
|
||||
samsm2m.EnterprisePortalScope(
|
||||
scopes.PermissionEnterprisePortalSubscription,
|
||||
scopes.ActionRead,
|
||||
),
|
||||
},
|
||||
create: &subscriptionsv1.CreateEnterpriseSubscriptionLicenseRequest{},
|
||||
wantError: autogold.Expect("permission_denied: insufficient scope"),
|
||||
},
|
||||
{
|
||||
name: "subscription ID is required",
|
||||
create: &subscriptionsv1.CreateEnterpriseSubscriptionLicenseRequest{},
|
||||
wantError: autogold.Expect("invalid_argument: license.subscription_id is required"),
|
||||
},
|
||||
{
|
||||
name: "subscription does not exist",
|
||||
create: &subscriptionsv1.CreateEnterpriseSubscriptionLicenseRequest{
|
||||
License: &subscriptionsv1.EnterpriseSubscriptionLicense{
|
||||
SubscriptionId: uuid.NewString(),
|
||||
},
|
||||
},
|
||||
wantError: autogold.Expect("not_found: subscription not found"),
|
||||
},
|
||||
{
|
||||
name: "license data required",
|
||||
create: &subscriptionsv1.CreateEnterpriseSubscriptionLicenseRequest{
|
||||
License: &subscriptionsv1.EnterpriseSubscriptionLicense{
|
||||
SubscriptionId: mockSubscriptionID,
|
||||
},
|
||||
},
|
||||
wantError: autogold.Expect("invalid_argument: unsupported licnese type <nil>"),
|
||||
},
|
||||
{
|
||||
name: "license key: required tags not provided",
|
||||
create: &subscriptionsv1.CreateEnterpriseSubscriptionLicenseRequest{
|
||||
License: &subscriptionsv1.EnterpriseSubscriptionLicense{
|
||||
SubscriptionId: mockSubscriptionID,
|
||||
License: &subscriptionsv1.EnterpriseSubscriptionLicense_Key{},
|
||||
},
|
||||
},
|
||||
wantError: autogold.Expect("invalid_argument: user_count is invalid"),
|
||||
},
|
||||
{
|
||||
name: "license key: expiration is required",
|
||||
create: &subscriptionsv1.CreateEnterpriseSubscriptionLicenseRequest{
|
||||
License: &subscriptionsv1.EnterpriseSubscriptionLicense{
|
||||
SubscriptionId: mockSubscriptionID,
|
||||
License: &subscriptionsv1.EnterpriseSubscriptionLicense_Key{
|
||||
Key: &subscriptionsv1.EnterpriseSubscriptionLicenseKey{
|
||||
Info: &subscriptionsv1.EnterpriseSubscriptionLicenseKey_Info{
|
||||
Tags: requiredTags,
|
||||
UserCount: 100,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
wantError: autogold.Expect("invalid_argument: expiry must be in the future"),
|
||||
},
|
||||
{
|
||||
name: "license key: ok with reason",
|
||||
create: &subscriptionsv1.CreateEnterpriseSubscriptionLicenseRequest{
|
||||
License: &subscriptionsv1.EnterpriseSubscriptionLicense{
|
||||
SubscriptionId: mockSubscriptionID,
|
||||
License: &subscriptionsv1.EnterpriseSubscriptionLicense_Key{
|
||||
Key: &subscriptionsv1.EnterpriseSubscriptionLicenseKey{
|
||||
Info: &subscriptionsv1.EnterpriseSubscriptionLicenseKey_Info{
|
||||
Tags: requiredTags,
|
||||
UserCount: 100,
|
||||
// time in the future relative to newMockTime
|
||||
ExpireTime: timestamppb.New(newMockTime().Add(time.Hour)),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Message: t.Name(),
|
||||
},
|
||||
wantKeyOpts: autogold.Expect(&subscriptions.DataLicenseKey{
|
||||
Info: license.Info{
|
||||
Tags: []string{
|
||||
"test",
|
||||
"dev",
|
||||
},
|
||||
UserCount: 100,
|
||||
CreatedAt: time.Date(2024,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
0,
|
||||
time.UTC),
|
||||
ExpiresAt: time.Date(2024,
|
||||
1,
|
||||
1,
|
||||
2,
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
time.UTC),
|
||||
},
|
||||
SignedKey: "signedkey-1",
|
||||
}),
|
||||
},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
req := connect.NewRequest(tc.create)
|
||||
req.Header().Add("Authorization", "Bearer foolmeifyoucan")
|
||||
|
||||
if tc.tokenScopes == nil {
|
||||
tc.tokenScopes = scopes.Scopes{
|
||||
samsm2m.EnterprisePortalScope(
|
||||
scopes.PermissionEnterprisePortalSubscription,
|
||||
scopes.ActionWrite,
|
||||
),
|
||||
}
|
||||
}
|
||||
h := newTestHandlerV1(t, tc.tokenScopes...)
|
||||
h.mockStore.GetEnterpriseSubscriptionFunc.SetDefaultHook(func(ctx context.Context, id string) (*subscriptions.SubscriptionWithConditions, error) {
|
||||
if id == mockSubscriptionID {
|
||||
return &subscriptions.SubscriptionWithConditions{
|
||||
Subscription: subscriptions.Subscription{
|
||||
ID: id,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
if id == archivedSubscriptionID {
|
||||
return &subscriptions.SubscriptionWithConditions{
|
||||
Subscription: subscriptions.Subscription{
|
||||
ID: id,
|
||||
ArchivedAt: pointers.Ptr(utctime.FromTime(
|
||||
// Archived in the past relative to newMockTime
|
||||
newMockTime().Add(-1 * time.Hour)),
|
||||
),
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
return nil, subscriptions.ErrSubscriptionNotFound
|
||||
})
|
||||
h.mockStore.GetRequiredEnterpriseSubscriptionLicenseKeyTagsFunc.SetDefaultReturn(
|
||||
requiredTags,
|
||||
)
|
||||
h.mockStore.CreateEnterpriseSubscriptionLicenseKeyFunc.SetDefaultHook(func(_ context.Context, subscription string, key *subscriptions.DataLicenseKey, opts subscriptions.CreateLicenseOpts) (*subscriptions.LicenseWithConditions, error) {
|
||||
assert.Empty(t, opts.ImportLicenseID)
|
||||
// Condition must match upsert
|
||||
assert.Equal(t, tc.create.GetMessage(), opts.Message)
|
||||
require.NotNil(t, opts.Time)
|
||||
assert.Equal(t, key.Info.CreatedAt, opts.Time.AsTime())
|
||||
require.NotZero(t, opts.ExpireTime)
|
||||
assert.Equal(t, key.Info.ExpiresAt, opts.ExpireTime.AsTime())
|
||||
|
||||
tc.wantKeyOpts.Equal(t, key)
|
||||
|
||||
return &subscriptions.LicenseWithConditions{
|
||||
SubscriptionLicense: subscriptions.SubscriptionLicense{
|
||||
LicenseType: "ENTERPRISE_SUBSCRIPTION_LICENSE_TYPE_KEY",
|
||||
LicenseData: json.RawMessage("{}"),
|
||||
},
|
||||
}, nil
|
||||
})
|
||||
_, err := h.CreateEnterpriseSubscriptionLicense(ctx, req)
|
||||
if tc.wantError != nil {
|
||||
require.Error(t, err)
|
||||
tc.wantError.Equal(t, err.Error())
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
if tc.wantKeyOpts != nil {
|
||||
mockrequire.CalledOnce(t, h.mockStore.CreateEnterpriseSubscriptionLicenseKeyFunc)
|
||||
} else {
|
||||
mockrequire.NotCalled(t, h.mockStore.CreateEnterpriseSubscriptionLicenseKeyFunc)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandlerV1_RevokeEnterpriseSubscriptionLicense(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
const mockLicenseID = "es_80ca12e2-54b4-448c-a61a-390b1a9c1224"
|
||||
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
tokenScopes scopes.Scopes
|
||||
revoke *subscriptionsv1.RevokeEnterpriseSubscriptionLicenseRequest
|
||||
wantRevokeOpts autogold.Value
|
||||
wantError autogold.Value
|
||||
}{
|
||||
{
|
||||
name: "insufficient scopes",
|
||||
tokenScopes: scopes.Scopes{
|
||||
samsm2m.EnterprisePortalScope(
|
||||
scopes.PermissionEnterprisePortalSubscription,
|
||||
scopes.ActionRead,
|
||||
),
|
||||
},
|
||||
revoke: &subscriptionsv1.RevokeEnterpriseSubscriptionLicenseRequest{},
|
||||
wantError: autogold.Expect("permission_denied: insufficient scope"),
|
||||
},
|
||||
{
|
||||
name: "license ID is required",
|
||||
revoke: &subscriptionsv1.RevokeEnterpriseSubscriptionLicenseRequest{},
|
||||
wantError: autogold.Expect("invalid_argument: license_id is required"),
|
||||
},
|
||||
{
|
||||
name: "license does not exist",
|
||||
revoke: &subscriptionsv1.RevokeEnterpriseSubscriptionLicenseRequest{
|
||||
LicenseId: uuid.NewString(),
|
||||
},
|
||||
wantError: autogold.Expect("not_found: subscription license not found"),
|
||||
},
|
||||
{
|
||||
name: "revoke ok with reason",
|
||||
revoke: &subscriptionsv1.RevokeEnterpriseSubscriptionLicenseRequest{
|
||||
LicenseId: mockLicenseID,
|
||||
Reason: t.Name(),
|
||||
},
|
||||
wantRevokeOpts: autogold.Expect(subscriptions.RevokeLicenseOpts{
|
||||
Message: "TestHandlerV1_RevokeEnterpriseSubscriptionLicense",
|
||||
Time: valast.Ptr(utctime.Date(2024,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
0)),
|
||||
}),
|
||||
},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
req := connect.NewRequest(tc.revoke)
|
||||
req.Header().Add("Authorization", "Bearer foolmeifyoucan")
|
||||
|
||||
if tc.tokenScopes == nil {
|
||||
tc.tokenScopes = scopes.Scopes{
|
||||
samsm2m.EnterprisePortalScope(
|
||||
scopes.PermissionEnterprisePortalSubscription,
|
||||
scopes.ActionWrite,
|
||||
),
|
||||
}
|
||||
}
|
||||
h := newTestHandlerV1(t, tc.tokenScopes...)
|
||||
h.mockStore.RevokeEnterpriseSubscriptionLicenseFunc.SetDefaultHook(func(ctx context.Context, licenseID string, opts subscriptions.RevokeLicenseOpts) (*subscriptions.LicenseWithConditions, error) {
|
||||
if licenseID != mockLicenseID {
|
||||
return nil, subscriptions.ErrSubscriptionLicenseNotFound
|
||||
}
|
||||
|
||||
// Condition must match upsert
|
||||
assert.Equal(t, tc.revoke.GetReason(), opts.Message)
|
||||
|
||||
tc.wantRevokeOpts.Equal(t, opts)
|
||||
|
||||
return &subscriptions.LicenseWithConditions{
|
||||
SubscriptionLicense: subscriptions.SubscriptionLicense{
|
||||
LicenseType: "ENTERPRISE_SUBSCRIPTION_LICENSE_TYPE_KEY",
|
||||
LicenseData: json.RawMessage("{}"),
|
||||
},
|
||||
}, nil
|
||||
})
|
||||
_, err := h.RevokeEnterpriseSubscriptionLicense(ctx, req)
|
||||
if tc.wantError != nil {
|
||||
require.Error(t, err)
|
||||
tc.wantError.Equal(t, err.Error())
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
if tc.wantRevokeOpts != nil {
|
||||
mockrequire.CalledOnce(t, h.mockStore.RevokeEnterpriseSubscriptionLicenseFunc)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandlerV1_UpdateEnterpriseSubscriptionMembership(t *testing.T) {
|
||||
const (
|
||||
subscriptionID = "80ca12e2-54b4-448c-a61a-390b1a9c1224"
|
||||
@ -423,11 +1082,16 @@ func TestHandlerV1_UpdateEnterpriseSubscriptionMembership(t *testing.T) {
|
||||
req := connect.NewRequest(tc.req)
|
||||
req.Header().Add("Authorization", "Bearer foolmeifyoucan")
|
||||
|
||||
h := newTestHandlerV1()
|
||||
h := newTestHandlerV1(t,
|
||||
samsm2m.EnterprisePortalScope(
|
||||
scopes.PermissionEnterprisePortalSubscriptionPermission,
|
||||
scopes.ActionWrite,
|
||||
),
|
||||
)
|
||||
h.mockStore.ListEnterpriseSubscriptionsFunc.SetDefaultHook(
|
||||
func(_ context.Context, opts subscriptions.ListEnterpriseSubscriptionsOptions) ([]*subscriptions.SubscriptionWithConditions, error) {
|
||||
if slices.Contains(opts.IDs, subscriptionID) ||
|
||||
slices.Contains(opts.InstanceDomains, instanceDomain) {
|
||||
// List should only be called when updating via instance domain
|
||||
if slices.Contains(opts.InstanceDomains, instanceDomain) {
|
||||
return []*subscriptions.SubscriptionWithConditions{
|
||||
{Subscription: subscriptions.Subscription{ID: subscriptionID}},
|
||||
}, nil
|
||||
@ -435,6 +1099,18 @@ func TestHandlerV1_UpdateEnterpriseSubscriptionMembership(t *testing.T) {
|
||||
return nil, nil
|
||||
},
|
||||
)
|
||||
h.mockStore.GetEnterpriseSubscriptionFunc.SetDefaultHook(
|
||||
func(ctx context.Context, id string) (*subscriptions.SubscriptionWithConditions, error) {
|
||||
if id == subscriptionID {
|
||||
return &subscriptions.SubscriptionWithConditions{
|
||||
Subscription: subscriptions.Subscription{
|
||||
ID: subscriptionID,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
return nil, subscriptions.ErrSubscriptionNotFound
|
||||
},
|
||||
)
|
||||
h.mockStore.IAMCheckFunc.SetDefaultHook(func(_ context.Context, opts iam.CheckOptions) (bool, error) {
|
||||
if tc.iamCheckFunc != nil {
|
||||
return tc.iamCheckFunc(opts)
|
||||
|
||||
@ -25,6 +25,7 @@ go_library(
|
||||
"//internal/codygateway/codygatewayevents",
|
||||
"//internal/debugserver",
|
||||
"//internal/httpserver",
|
||||
"//internal/license",
|
||||
"//internal/redispool",
|
||||
"//internal/trace/policy",
|
||||
"//internal/version",
|
||||
@ -46,6 +47,7 @@ go_library(
|
||||
"@com_github_sourcegraph_sourcegraph_accounts_sdk_go//scopes",
|
||||
"@io_opentelemetry_go_contrib_instrumentation_net_http_otelhttp//:otelhttp",
|
||||
"@io_opentelemetry_go_otel//:otel",
|
||||
"@org_golang_x_crypto//ssh",
|
||||
"@org_golang_x_net//http2",
|
||||
"@org_golang_x_net//http2/h2c",
|
||||
],
|
||||
|
||||
@ -1,11 +1,17 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"golang.org/x/crypto/ssh"
|
||||
|
||||
sams "github.com/sourcegraph/sourcegraph-accounts-sdk-go"
|
||||
"github.com/sourcegraph/sourcegraph/cmd/enterprise-portal/internal/routines/licenseexpiration"
|
||||
"github.com/sourcegraph/sourcegraph/internal/codygateway/codygatewayevents"
|
||||
"github.com/sourcegraph/sourcegraph/internal/license"
|
||||
"github.com/sourcegraph/sourcegraph/lib/errors"
|
||||
"github.com/sourcegraph/sourcegraph/lib/managedservicesplatform/cloudsql"
|
||||
"github.com/sourcegraph/sourcegraph/lib/managedservicesplatform/runtime"
|
||||
"github.com/sourcegraph/sourcegraph/lib/pointers"
|
||||
@ -28,6 +34,15 @@ type Config struct {
|
||||
|
||||
SAMS SAMSConfig
|
||||
|
||||
// Configuration specific to 'ENTERPRISE_SUBSCRIPTION_LICENSE_TYPE_KEY'
|
||||
LicenseKeys struct {
|
||||
// Signer is the private key used to generate license keys.
|
||||
Signer ssh.Signer
|
||||
// RequiredTags are the tags required on all licenses created in this
|
||||
// Enterprise Portal instance.
|
||||
RequiredTags []string
|
||||
}
|
||||
|
||||
LicenseExpirationChecker licenseexpiration.Config
|
||||
}
|
||||
|
||||
@ -71,6 +86,33 @@ func (c *Config) Load(env *runtime.Env) {
|
||||
}
|
||||
}
|
||||
|
||||
c.LicenseKeys.Signer = func() ssh.Signer {
|
||||
// We use a unconventional env name here to align with existing usages
|
||||
// of this key, for convenience.
|
||||
privateKey := env.GetOptional("SOURCEGRAPH_LICENSE_GENERATION_KEY",
|
||||
fmt.Sprintf("The PEM-encoded form of the private key used to sign product license keys (%s)",
|
||||
license.GenerationPrivateKeyURL))
|
||||
if privateKey == nil {
|
||||
// Not having this just disables the generation of new licenses, it
|
||||
// does not block startup.
|
||||
return nil
|
||||
}
|
||||
signer, err := ssh.ParsePrivateKey([]byte(*privateKey))
|
||||
if err != nil {
|
||||
env.AddError(errors.Wrap(err,
|
||||
"Failed to parse private key in SOURCEGRAPH_LICENSE_GENERATION_KEY env var"))
|
||||
}
|
||||
return signer
|
||||
}()
|
||||
c.LicenseKeys.RequiredTags = func() []string {
|
||||
tags := env.GetOptional("LICENSE_KEY_REQUIRED_TAGS",
|
||||
"Comma-delimited list of tags required on all license keys generated on this Enterprise Portal instance")
|
||||
if tags == nil {
|
||||
return nil
|
||||
}
|
||||
return strings.Split(*tags, ",")
|
||||
}()
|
||||
|
||||
c.LicenseExpirationChecker.Interval = env.GetOptionalInterval(
|
||||
"LICENSE_EXPIRATION_CHECKER_INTERVAL",
|
||||
"Interval at which to run license expiration checks. If not set, checks are not run.")
|
||||
|
||||
@ -120,9 +120,11 @@ func (Service) Initialize(ctx context.Context, logger log.Logger, contract runti
|
||||
httpServer,
|
||||
subscriptionsservice.NewStoreV1(
|
||||
subscriptionsservice.NewStoreV1Options{
|
||||
DB: dbHandle,
|
||||
SAMSClient: samsClient,
|
||||
IAMClient: iamClient,
|
||||
DB: dbHandle,
|
||||
SAMSClient: samsClient,
|
||||
IAMClient: iamClient,
|
||||
LicenseKeySigner: config.LicenseKeys.Signer,
|
||||
LicenseKeyRequiredTags: config.LicenseKeys.RequiredTags,
|
||||
},
|
||||
),
|
||||
connect.WithInterceptors(otelConnctInterceptor),
|
||||
|
||||
5
deps.bzl
5
deps.bzl
@ -3430,8 +3430,9 @@ def go_dependencies():
|
||||
name = "com_github_hexops_valast",
|
||||
build_file_proto_mode = "disable_global",
|
||||
importpath = "github.com/hexops/valast",
|
||||
sum = "h1:rETyycw+/L2ZVJHHNxEBgh8KUn+87WugH9MxcEv9PGs=",
|
||||
version = "v1.4.4",
|
||||
replace = "github.com/bobheadxi/valast",
|
||||
sum = "h1:cK/ixaJaSyXAKglOrY2CLE0pM9C+m2LbijsVW5k675E=",
|
||||
version = "v0.0.0-20240724215614-eb5cb82e0c6f",
|
||||
)
|
||||
go_repository(
|
||||
name = "com_github_hhatto_gocloc",
|
||||
|
||||
2
go.mod
2
go.mod
@ -43,6 +43,8 @@ replace (
|
||||
github.com/gomodule/redigo => github.com/gomodule/redigo v1.8.9
|
||||
// Pending: Renamed to github.com/google/gnostic. Transitive deps still use the old name (kubernetes/kubernetes).
|
||||
github.com/googleapis/gnostic => github.com/googleapis/gnostic v0.5.5
|
||||
// Pending: https://github.com/hexops/valast/pull/27
|
||||
github.com/hexops/valast => github.com/bobheadxi/valast v0.0.0-20240724215614-eb5cb82e0c6f
|
||||
// Pending: https://github.com/openfga/openfga/pull/1688
|
||||
github.com/openfga/openfga => github.com/sourcegraph/openfga v0.0.0-20240614204729-de6b563022de
|
||||
// We need to wait for https://github.com/prometheus/alertmanager to cut a
|
||||
|
||||
5
go.sum
5
go.sum
@ -910,6 +910,8 @@ github.com/bmatcuk/doublestar v1.3.4 h1:gPypJ5xD31uhX6Tf54sDPUOBXTqKH4c9aPY66CyQ
|
||||
github.com/bmatcuk/doublestar v1.3.4/go.mod h1:wiQtGV+rzVYxB7WIlirSN++5HPtPlXEo9MEoZQC/PmE=
|
||||
github.com/bmatcuk/doublestar/v4 v4.6.1 h1:FH9SifrbvJhnlQpztAx++wlkk70QBf0iBWDwNy7PA4I=
|
||||
github.com/bmatcuk/doublestar/v4 v4.6.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=
|
||||
github.com/bobheadxi/valast v0.0.0-20240724215614-eb5cb82e0c6f h1:cK/ixaJaSyXAKglOrY2CLE0pM9C+m2LbijsVW5k675E=
|
||||
github.com/bobheadxi/valast v0.0.0-20240724215614-eb5cb82e0c6f/go.mod h1:Jcy1pNH7LNraVaAZDLyv21hHg2WBv9Nf9FL6fGxU7o4=
|
||||
github.com/boj/redistore v0.0.0-20180917114910-cd5dcc76aeff h1:RmdPFa+slIr4SCBg4st/l/vZWVe9QJKMXGO60Bxbe04=
|
||||
github.com/boj/redistore v0.0.0-20180917114910-cd5dcc76aeff/go.mod h1:+RTT1BOk5P97fT2CiHkbFQwkK3mjsFAP6zCYV2aXtjw=
|
||||
github.com/boombuler/barcode v1.0.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
|
||||
@ -1652,9 +1654,6 @@ github.com/hexops/autogold/v2 v2.2.1 h1:JPUXuZQGkcQMv7eeDXuNMovjfoRYaa0yVcm+F3vo
|
||||
github.com/hexops/autogold/v2 v2.2.1/go.mod h1:IJwxtUfj1BGLm0YsR/k+dIxYi6xbeLjqGke2bzcOTMI=
|
||||
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
|
||||
github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
|
||||
github.com/hexops/valast v1.4.3/go.mod h1:Iqx2kLj3Jn47wuXpj3wX40xn6F93QNFBHuiKBerkTGA=
|
||||
github.com/hexops/valast v1.4.4 h1:rETyycw+/L2ZVJHHNxEBgh8KUn+87WugH9MxcEv9PGs=
|
||||
github.com/hexops/valast v1.4.4/go.mod h1:Jcy1pNH7LNraVaAZDLyv21hHg2WBv9Nf9FL6fGxU7o4=
|
||||
github.com/hjson/hjson-go/v4 v4.0.0/go.mod h1:KaYt3bTw3zhBjYqnXkYywcYctk0A2nxeEFTse3rH13E=
|
||||
github.com/honeycombio/libhoney-go v1.15.8 h1:TECEltZ48K6J4NG1JVYqmi0vCJNnHYooFor83fgKesA=
|
||||
github.com/honeycombio/libhoney-go v1.15.8/go.mod h1:+tnL2etFnJmVx30yqmoUkVyQjp7uRJw0a2QGu48lSyY=
|
||||
|
||||
@ -33,7 +33,7 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
privateKeyFile = flag.String("private-key", "", "file containing private key to sign license")
|
||||
privateKeyFile = flag.String("private-key", "", fmt.Sprintf("file containing private key to sign license (e.g. the production key at %s)", license.GenerationPrivateKeyURL))
|
||||
tags = flag.String("tags", "", "comma-separated string tags to include in this license (e.g., \"starter,dev\")")
|
||||
users = flag.Uint("users", 0, "maximum number of users allowed by this license (0 = no limit)")
|
||||
expires = flag.Duration("expires", 0, "time until license expires (0 = no expiration)")
|
||||
|
||||
@ -21,6 +21,13 @@ import (
|
||||
"github.com/sourcegraph/sourcegraph/lib/errors"
|
||||
)
|
||||
|
||||
// GenerationPrivateKeyURL is the URL where Sourcegraph staff can find the private key for
|
||||
// generating licenses.
|
||||
//
|
||||
// NOTE: If you change this, use text search to replace other instances of it (in source code
|
||||
// comments).
|
||||
const GenerationPrivateKeyURL = "https://team-sourcegraph.1password.com/vaults/dnrhbauihkhjs5ag6vszsme45a/allitems/zkdx6gpw4uqejs3flzj7ef5j4i"
|
||||
|
||||
// Info contains information about a license key. In the signed license key that Sourcegraph
|
||||
// provides to customers, this value is signed but not encrypted. This value is not secret, and
|
||||
// anyone with a license key can view (but not forge) this information.
|
||||
|
||||
@ -188,17 +188,10 @@ func GetConfiguredProductLicenseInfoWithSignature() (*Info, string, error) {
|
||||
return GetFreeLicenseInfo(), "", nil
|
||||
}
|
||||
|
||||
// licenseGenerationPrivateKeyURL is the URL where Sourcegraph staff can find the private key for
|
||||
// generating licenses.
|
||||
//
|
||||
// NOTE: If you change this, use text search to replace other instances of it (in source code
|
||||
// comments).
|
||||
const licenseGenerationPrivateKeyURL = "https://team-sourcegraph.1password.com/vaults/dnrhbauihkhjs5ag6vszsme45a/allitems/zkdx6gpw4uqejs3flzj7ef5j4i"
|
||||
|
||||
// envLicenseGenerationPrivateKey (the env var SOURCEGRAPH_LICENSE_GENERATION_KEY) is the
|
||||
// PEM-encoded form of the private key used to sign product license keys. It is stored at
|
||||
// https://team-sourcegraph.1password.com/vaults/dnrhbauihkhjs5ag6vszsme45a/allitems/zkdx6gpw4uqejs3flzj7ef5j4i.
|
||||
var envLicenseGenerationPrivateKey = env.Get("SOURCEGRAPH_LICENSE_GENERATION_KEY", "", "the PEM-encoded form of the private key used to sign product license keys ("+licenseGenerationPrivateKeyURL+")")
|
||||
var envLicenseGenerationPrivateKey = env.Get("SOURCEGRAPH_LICENSE_GENERATION_KEY", "", "the PEM-encoded form of the private key used to sign product license keys ("+license.GenerationPrivateKeyURL+")")
|
||||
|
||||
// licenseGenerationPrivateKey is the private key used to generate license keys.
|
||||
var licenseGenerationPrivateKey = func() ssh.Signer {
|
||||
@ -221,7 +214,7 @@ func GenerateProductLicenseKey(info license.Info) (licenseKey string, version in
|
||||
const msg = "no product license generation private key was configured"
|
||||
if env.InsecureDev {
|
||||
// Show more helpful error message in local dev.
|
||||
return "", 0, errors.Errorf("%s (for testing by Sourcegraph staff: set the SOURCEGRAPH_LICENSE_GENERATION_KEY env var to the key obtained at %s)", msg, licenseGenerationPrivateKeyURL)
|
||||
return "", 0, errors.Errorf("%s (for testing by Sourcegraph staff: set the SOURCEGRAPH_LICENSE_GENERATION_KEY env var to the key obtained at %s)", msg, license.GenerationPrivateKeyURL)
|
||||
}
|
||||
return "", 0, errors.New(msg)
|
||||
}
|
||||
|
||||
@ -540,6 +540,9 @@ type EnterpriseSubscriptionLicenseKey struct {
|
||||
Info *EnterpriseSubscriptionLicenseKey_Info `protobuf:"bytes,2,opt,name=info,proto3" json:"info,omitempty"`
|
||||
// The signed license key.
|
||||
LicenseKey string `protobuf:"bytes,3,opt,name=license_key,json=licenseKey,proto3" json:"license_key,omitempty"`
|
||||
// Generated display name representing the plan and some high-level attributes
|
||||
// about the plan.
|
||||
PlanDisplayName string `protobuf:"bytes,4,opt,name=plan_display_name,json=planDisplayName,proto3" json:"plan_display_name,omitempty"`
|
||||
}
|
||||
|
||||
func (x *EnterpriseSubscriptionLicenseKey) Reset() {
|
||||
@ -595,6 +598,13 @@ func (x *EnterpriseSubscriptionLicenseKey) GetLicenseKey() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *EnterpriseSubscriptionLicenseKey) GetPlanDisplayName() string {
|
||||
if x != nil {
|
||||
return x.PlanDisplayName
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type EnterpriseSubscriptionLicenseCondition struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
@ -1453,6 +1463,8 @@ type CreateEnterpriseSubscriptionLicenseRequest struct {
|
||||
// - license.key.info.expire_time
|
||||
// - license.key.info.salesforce_opportunity_id
|
||||
License *EnterpriseSubscriptionLicense `protobuf:"bytes,1,opt,name=license,proto3" json:"license,omitempty"`
|
||||
// Message to associate with the license creation event.
|
||||
Message string `protobuf:"bytes,2,opt,name=message,proto3" json:"message,omitempty"`
|
||||
}
|
||||
|
||||
func (x *CreateEnterpriseSubscriptionLicenseRequest) Reset() {
|
||||
@ -1494,6 +1506,13 @@ func (x *CreateEnterpriseSubscriptionLicenseRequest) GetLicense() *EnterpriseSub
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *CreateEnterpriseSubscriptionLicenseRequest) GetMessage() string {
|
||||
if x != nil {
|
||||
return x.Message
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type CreateEnterpriseSubscriptionLicenseResponse struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
@ -1652,6 +1671,8 @@ type UpdateEnterpriseSubscriptionRequest struct {
|
||||
// Updatable fields are:
|
||||
// - instance_domain
|
||||
// - display_name
|
||||
// - salesforce.subscription_id
|
||||
// - salesforce.opportunity_id
|
||||
UpdateMask *fieldmaskpb.FieldMask `protobuf:"bytes,2,opt,name=update_mask,json=updateMask,proto3" json:"update_mask,omitempty"`
|
||||
}
|
||||
|
||||
@ -1858,6 +1879,8 @@ type CreateEnterpriseSubscriptionRequest struct {
|
||||
// - instance_domain
|
||||
// - salesforce.subscription_id
|
||||
Subscription *EnterpriseSubscription `protobuf:"bytes,1,opt,name=subscription,proto3" json:"subscription,omitempty"`
|
||||
// Message to associate with the subscription creation event.
|
||||
Message string `protobuf:"bytes,2,opt,name=message,proto3" json:"message,omitempty"`
|
||||
}
|
||||
|
||||
func (x *CreateEnterpriseSubscriptionRequest) Reset() {
|
||||
@ -1899,6 +1922,13 @@ func (x *CreateEnterpriseSubscriptionRequest) GetSubscription() *EnterpriseSubsc
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *CreateEnterpriseSubscriptionRequest) GetMessage() string {
|
||||
if x != nil {
|
||||
return x.Message
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type CreateEnterpriseSubscriptionResponse struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
@ -2327,7 +2357,7 @@ var file_subscriptions_proto_rawDesc = []byte{
|
||||
0x76, 0x31, 0x2e, 0x45, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x72, 0x69, 0x73, 0x65, 0x53, 0x75, 0x62,
|
||||
0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x61, 0x6c, 0x65, 0x73, 0x66, 0x6f,
|
||||
0x72, 0x63, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x0a, 0x73, 0x61, 0x6c,
|
||||
0x65, 0x73, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x22, 0xb7, 0x03, 0x0a, 0x20, 0x45, 0x6e, 0x74, 0x65,
|
||||
0x65, 0x73, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x22, 0xe3, 0x03, 0x0a, 0x20, 0x45, 0x6e, 0x74, 0x65,
|
||||
0x72, 0x70, 0x72, 0x69, 0x73, 0x65, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69,
|
||||
0x6f, 0x6e, 0x4c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x4b, 0x65, 0x79, 0x12, 0x21, 0x0a, 0x0c,
|
||||
0x69, 0x6e, 0x66, 0x6f, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01,
|
||||
@ -2339,233 +2369,239 @@ var file_subscriptions_proto_rawDesc = []byte{
|
||||
0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x4c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x4b,
|
||||
0x65, 0x79, 0x2e, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x04, 0x69, 0x6e, 0x66, 0x6f, 0x12, 0x1f, 0x0a,
|
||||
0x0b, 0x6c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x03, 0x20, 0x01,
|
||||
0x28, 0x09, 0x52, 0x0a, 0x6c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x4b, 0x65, 0x79, 0x1a, 0xf0,
|
||||
0x01, 0x0a, 0x04, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x61, 0x67, 0x73, 0x18,
|
||||
0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x04, 0x74, 0x61, 0x67, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x75,
|
||||
0x73, 0x65, 0x72, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52,
|
||||
0x09, 0x75, 0x73, 0x65, 0x72, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x3b, 0x0a, 0x0b, 0x65, 0x78,
|
||||
0x70, 0x69, 0x72, 0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32,
|
||||
0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75,
|
||||
0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0a, 0x65, 0x78, 0x70,
|
||||
0x69, 0x72, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x3c, 0x0a, 0x1a, 0x73, 0x61, 0x6c, 0x65, 0x73,
|
||||
0x66, 0x6f, 0x72, 0x63, 0x65, 0x5f, 0x73, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69,
|
||||
0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x18, 0x73, 0x61, 0x6c,
|
||||
0x65, 0x73, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74,
|
||||
0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x3a, 0x0a, 0x19, 0x73, 0x61, 0x6c, 0x65, 0x73, 0x66, 0x6f,
|
||||
0x72, 0x63, 0x65, 0x5f, 0x6f, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x75, 0x6e, 0x69, 0x74, 0x79, 0x5f,
|
||||
0x69, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x17, 0x73, 0x61, 0x6c, 0x65, 0x73, 0x66,
|
||||
0x6f, 0x72, 0x63, 0x65, 0x4f, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x75, 0x6e, 0x69, 0x74, 0x79, 0x49,
|
||||
0x64, 0x22, 0xc4, 0x02, 0x0a, 0x26, 0x45, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x72, 0x69, 0x73, 0x65,
|
||||
0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x4c, 0x69, 0x63, 0x65,
|
||||
0x6e, 0x73, 0x65, 0x43, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x4c, 0x0a, 0x14,
|
||||
0x6c, 0x61, 0x73, 0x74, 0x5f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x5f,
|
||||
0x74, 0x69, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f,
|
||||
0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d,
|
||||
0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x12, 0x6c, 0x61, 0x73, 0x74, 0x54, 0x72, 0x61, 0x6e,
|
||||
0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x68, 0x0a, 0x06, 0x73, 0x74,
|
||||
0x61, 0x74, 0x75, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x50, 0x2e, 0x65, 0x6e, 0x74,
|
||||
0x65, 0x72, 0x70, 0x72, 0x69, 0x73, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x61, 0x6c, 0x2e, 0x73, 0x75,
|
||||
0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x45,
|
||||
0x6e, 0x74, 0x65, 0x72, 0x70, 0x72, 0x69, 0x73, 0x65, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69,
|
||||
0x70, 0x74, 0x69, 0x6f, 0x6e, 0x4c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x43, 0x6f, 0x6e, 0x64,
|
||||
0x69, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74,
|
||||
0x61, 0x74, 0x75, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18,
|
||||
0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x48,
|
||||
0x0a, 0x06, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x16, 0x0a, 0x12, 0x53, 0x54, 0x41, 0x54,
|
||||
0x55, 0x53, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00,
|
||||
0x12, 0x12, 0x0a, 0x0e, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x43, 0x52, 0x45, 0x41, 0x54,
|
||||
0x45, 0x44, 0x10, 0x01, 0x12, 0x12, 0x0a, 0x0e, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x52,
|
||||
0x45, 0x56, 0x4f, 0x4b, 0x45, 0x44, 0x10, 0x02, 0x22, 0xa7, 0x02, 0x0a, 0x1d, 0x45, 0x6e, 0x74,
|
||||
0x65, 0x72, 0x70, 0x72, 0x69, 0x73, 0x65, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74,
|
||||
0x69, 0x6f, 0x6e, 0x4c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64,
|
||||
0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x27, 0x0a, 0x0f, 0x73, 0x75,
|
||||
0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20,
|
||||
0x01, 0x28, 0x09, 0x52, 0x0e, 0x73, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f,
|
||||
0x6e, 0x49, 0x64, 0x12, 0x69, 0x0a, 0x0a, 0x63, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e,
|
||||
0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x49, 0x2e, 0x65, 0x6e, 0x74, 0x65, 0x72, 0x70,
|
||||
0x28, 0x09, 0x52, 0x0a, 0x6c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x4b, 0x65, 0x79, 0x12, 0x2a,
|
||||
0x0a, 0x11, 0x70, 0x6c, 0x61, 0x6e, 0x5f, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x5f, 0x6e,
|
||||
0x61, 0x6d, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x70, 0x6c, 0x61, 0x6e, 0x44,
|
||||
0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x4e, 0x61, 0x6d, 0x65, 0x1a, 0xf0, 0x01, 0x0a, 0x04, 0x49,
|
||||
0x6e, 0x66, 0x6f, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x61, 0x67, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28,
|
||||
0x09, 0x52, 0x04, 0x74, 0x61, 0x67, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x75, 0x73, 0x65, 0x72, 0x5f,
|
||||
0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x09, 0x75, 0x73, 0x65,
|
||||
0x72, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x3b, 0x0a, 0x0b, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65,
|
||||
0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f,
|
||||
0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69,
|
||||
0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0a, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x54,
|
||||
0x69, 0x6d, 0x65, 0x12, 0x3c, 0x0a, 0x1a, 0x73, 0x61, 0x6c, 0x65, 0x73, 0x66, 0x6f, 0x72, 0x63,
|
||||
0x65, 0x5f, 0x73, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69,
|
||||
0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x18, 0x73, 0x61, 0x6c, 0x65, 0x73, 0x66, 0x6f,
|
||||
0x72, 0x63, 0x65, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x49,
|
||||
0x64, 0x12, 0x3a, 0x0a, 0x19, 0x73, 0x61, 0x6c, 0x65, 0x73, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x5f,
|
||||
0x6f, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x75, 0x6e, 0x69, 0x74, 0x79, 0x5f, 0x69, 0x64, 0x18, 0x05,
|
||||
0x20, 0x01, 0x28, 0x09, 0x52, 0x17, 0x73, 0x61, 0x6c, 0x65, 0x73, 0x66, 0x6f, 0x72, 0x63, 0x65,
|
||||
0x4f, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x75, 0x6e, 0x69, 0x74, 0x79, 0x49, 0x64, 0x22, 0xc4, 0x02,
|
||||
0x0a, 0x26, 0x45, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x72, 0x69, 0x73, 0x65, 0x53, 0x75, 0x62, 0x73,
|
||||
0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x4c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x43,
|
||||
0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x4c, 0x0a, 0x14, 0x6c, 0x61, 0x73, 0x74,
|
||||
0x5f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x74, 0x69, 0x6d, 0x65,
|
||||
0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e,
|
||||
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61,
|
||||
0x6d, 0x70, 0x52, 0x12, 0x6c, 0x61, 0x73, 0x74, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x69, 0x74, 0x69,
|
||||
0x6f, 0x6e, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x68, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73,
|
||||
0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x50, 0x2e, 0x65, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x72,
|
||||
0x69, 0x73, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x61, 0x6c, 0x2e, 0x73, 0x75, 0x62, 0x73, 0x63, 0x72,
|
||||
0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x45, 0x6e, 0x74, 0x65, 0x72,
|
||||
0x70, 0x72, 0x69, 0x73, 0x65, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f,
|
||||
0x6e, 0x4c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x43, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f,
|
||||
0x6e, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73,
|
||||
0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28,
|
||||
0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x48, 0x0a, 0x06, 0x53, 0x74,
|
||||
0x61, 0x74, 0x75, 0x73, 0x12, 0x16, 0x0a, 0x12, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x55,
|
||||
0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x12, 0x0a, 0x0e,
|
||||
0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x43, 0x52, 0x45, 0x41, 0x54, 0x45, 0x44, 0x10, 0x01,
|
||||
0x12, 0x12, 0x0a, 0x0e, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x52, 0x45, 0x56, 0x4f, 0x4b,
|
||||
0x45, 0x44, 0x10, 0x02, 0x22, 0xa7, 0x02, 0x0a, 0x1d, 0x45, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x72,
|
||||
0x69, 0x73, 0x65, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x4c,
|
||||
0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01,
|
||||
0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x27, 0x0a, 0x0f, 0x73, 0x75, 0x62, 0x73, 0x63, 0x72,
|
||||
0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52,
|
||||
0x0e, 0x73, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12,
|
||||
0x69, 0x0a, 0x0a, 0x63, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x03, 0x20,
|
||||
0x03, 0x28, 0x0b, 0x32, 0x49, 0x2e, 0x65, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x72, 0x69, 0x73, 0x65,
|
||||
0x70, 0x6f, 0x72, 0x74, 0x61, 0x6c, 0x2e, 0x73, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74,
|
||||
0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x45, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x72, 0x69,
|
||||
0x73, 0x65, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x4c, 0x69,
|
||||
0x63, 0x65, 0x6e, 0x73, 0x65, 0x43, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0a,
|
||||
0x63, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x57, 0x0a, 0x03, 0x6b, 0x65,
|
||||
0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x43, 0x2e, 0x65, 0x6e, 0x74, 0x65, 0x72, 0x70,
|
||||
0x72, 0x69, 0x73, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x61, 0x6c, 0x2e, 0x73, 0x75, 0x62, 0x73, 0x63,
|
||||
0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x45, 0x6e, 0x74, 0x65,
|
||||
0x72, 0x70, 0x72, 0x69, 0x73, 0x65, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69,
|
||||
0x6f, 0x6e, 0x4c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x43, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69,
|
||||
0x6f, 0x6e, 0x52, 0x0a, 0x63, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x57,
|
||||
0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x43, 0x2e, 0x65, 0x6e,
|
||||
0x74, 0x65, 0x72, 0x70, 0x72, 0x69, 0x73, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x61, 0x6c, 0x2e, 0x73,
|
||||
0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x76, 0x31, 0x2e,
|
||||
0x45, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x72, 0x69, 0x73, 0x65, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72,
|
||||
0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x4c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x4b, 0x65, 0x79,
|
||||
0x48, 0x00, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x42, 0x09, 0x0a, 0x07, 0x6c, 0x69, 0x63, 0x65, 0x6e,
|
||||
0x73, 0x65, 0x22, 0x3d, 0x0a, 0x20, 0x47, 0x65, 0x74, 0x45, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x72,
|
||||
0x69, 0x73, 0x65, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x52,
|
||||
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x10, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01,
|
||||
0x28, 0x09, 0x48, 0x00, 0x52, 0x02, 0x69, 0x64, 0x42, 0x07, 0x0a, 0x05, 0x71, 0x75, 0x65, 0x72,
|
||||
0x79, 0x22, 0x82, 0x01, 0x0a, 0x21, 0x47, 0x65, 0x74, 0x45, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x72,
|
||||
0x69, 0x73, 0x65, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x52,
|
||||
0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x5d, 0x0a, 0x0c, 0x73, 0x75, 0x62, 0x73, 0x63,
|
||||
0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x39, 0x2e,
|
||||
0x65, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x72, 0x69, 0x73, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x61, 0x6c,
|
||||
0x2e, 0x73, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x76,
|
||||
0x31, 0x2e, 0x45, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x72, 0x69, 0x73, 0x65, 0x53, 0x75, 0x62, 0x73,
|
||||
0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0c, 0x73, 0x75, 0x62, 0x73, 0x63, 0x72,
|
||||
0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x8b, 0x03, 0x0a, 0x21, 0x4c, 0x69, 0x73, 0x74, 0x45,
|
||||
0x6e, 0x74, 0x65, 0x72, 0x70, 0x72, 0x69, 0x73, 0x65, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69,
|
||||
0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x12, 0x29, 0x0a, 0x0f,
|
||||
0x73, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18,
|
||||
0x01, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x0e, 0x73, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69,
|
||||
0x70, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x21, 0x0a, 0x0b, 0x69, 0x73, 0x5f, 0x61, 0x72,
|
||||
0x63, 0x68, 0x69, 0x76, 0x65, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x48, 0x00, 0x52, 0x0a,
|
||||
0x69, 0x73, 0x41, 0x72, 0x63, 0x68, 0x69, 0x76, 0x65, 0x64, 0x12, 0x4f, 0x0a, 0x0a, 0x70, 0x65,
|
||||
0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2d,
|
||||
0x2e, 0x65, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x72, 0x69, 0x73, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x61,
|
||||
0x6c, 0x2e, 0x73, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e,
|
||||
0x76, 0x31, 0x2e, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x48, 0x00, 0x52,
|
||||
0x0a, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x23, 0x0a, 0x0c, 0x64,
|
||||
0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28,
|
||||
0x09, 0x48, 0x00, 0x52, 0x0b, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x4e, 0x61, 0x6d, 0x65,
|
||||
0x12, 0x6d, 0x0a, 0x0a, 0x73, 0x61, 0x6c, 0x65, 0x73, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x18, 0x05,
|
||||
0x20, 0x01, 0x28, 0x0b, 0x32, 0x4b, 0x2e, 0x65, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x72, 0x69, 0x73,
|
||||
0x6f, 0x6e, 0x4c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x4b, 0x65, 0x79, 0x48, 0x00, 0x52, 0x03,
|
||||
0x6b, 0x65, 0x79, 0x42, 0x09, 0x0a, 0x07, 0x6c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x22, 0x3d,
|
||||
0x0a, 0x20, 0x47, 0x65, 0x74, 0x45, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x72, 0x69, 0x73, 0x65, 0x53,
|
||||
0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65,
|
||||
0x73, 0x74, 0x12, 0x10, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00,
|
||||
0x52, 0x02, 0x69, 0x64, 0x42, 0x07, 0x0a, 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, 0x22, 0x82, 0x01,
|
||||
0x0a, 0x21, 0x47, 0x65, 0x74, 0x45, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x72, 0x69, 0x73, 0x65, 0x53,
|
||||
0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f,
|
||||
0x6e, 0x73, 0x65, 0x12, 0x5d, 0x0a, 0x0c, 0x73, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74,
|
||||
0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x39, 0x2e, 0x65, 0x6e, 0x74, 0x65,
|
||||
0x72, 0x70, 0x72, 0x69, 0x73, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x61, 0x6c, 0x2e, 0x73, 0x75, 0x62,
|
||||
0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x45, 0x6e,
|
||||
0x74, 0x65, 0x72, 0x70, 0x72, 0x69, 0x73, 0x65, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70,
|
||||
0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0c, 0x73, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69,
|
||||
0x6f, 0x6e, 0x22, 0x8b, 0x03, 0x0a, 0x21, 0x4c, 0x69, 0x73, 0x74, 0x45, 0x6e, 0x74, 0x65, 0x72,
|
||||
0x70, 0x72, 0x69, 0x73, 0x65, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f,
|
||||
0x6e, 0x73, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x12, 0x29, 0x0a, 0x0f, 0x73, 0x75, 0x62, 0x73,
|
||||
0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28,
|
||||
0x09, 0x48, 0x00, 0x52, 0x0e, 0x73, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f,
|
||||
0x6e, 0x49, 0x64, 0x12, 0x21, 0x0a, 0x0b, 0x69, 0x73, 0x5f, 0x61, 0x72, 0x63, 0x68, 0x69, 0x76,
|
||||
0x65, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x48, 0x00, 0x52, 0x0a, 0x69, 0x73, 0x41, 0x72,
|
||||
0x63, 0x68, 0x69, 0x76, 0x65, 0x64, 0x12, 0x4f, 0x0a, 0x0a, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73,
|
||||
0x73, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2d, 0x2e, 0x65, 0x6e, 0x74,
|
||||
0x65, 0x72, 0x70, 0x72, 0x69, 0x73, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x61, 0x6c, 0x2e, 0x73, 0x75,
|
||||
0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x50,
|
||||
0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x48, 0x00, 0x52, 0x0a, 0x70, 0x65, 0x72,
|
||||
0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x23, 0x0a, 0x0c, 0x64, 0x69, 0x73, 0x70, 0x6c,
|
||||
0x61, 0x79, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52,
|
||||
0x0b, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x6d, 0x0a, 0x0a,
|
||||
0x73, 0x61, 0x6c, 0x65, 0x73, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b,
|
||||
0x32, 0x4b, 0x2e, 0x65, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x72, 0x69, 0x73, 0x65, 0x70, 0x6f, 0x72,
|
||||
0x74, 0x61, 0x6c, 0x2e, 0x73, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e,
|
||||
0x73, 0x2e, 0x76, 0x31, 0x2e, 0x45, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x72, 0x69, 0x73, 0x65, 0x53,
|
||||
0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x61, 0x6c, 0x65, 0x73,
|
||||
0x66, 0x6f, 0x72, 0x63, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x48, 0x00, 0x52,
|
||||
0x0a, 0x73, 0x61, 0x6c, 0x65, 0x73, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x12, 0x29, 0x0a, 0x0f, 0x69,
|
||||
0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x06,
|
||||
0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x0e, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65,
|
||||
0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x42, 0x08, 0x0a, 0x06, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72,
|
||||
0x22, 0xc0, 0x01, 0x0a, 0x22, 0x4c, 0x69, 0x73, 0x74, 0x45, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x72,
|
||||
0x69, 0x73, 0x65, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73,
|
||||
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x61, 0x67, 0x65, 0x5f,
|
||||
0x73, 0x69, 0x7a, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x70, 0x61, 0x67, 0x65,
|
||||
0x53, 0x69, 0x7a, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x74, 0x6f, 0x6b,
|
||||
0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x70, 0x61, 0x67, 0x65, 0x54, 0x6f,
|
||||
0x6b, 0x65, 0x6e, 0x12, 0x5e, 0x0a, 0x07, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x73, 0x18, 0x03,
|
||||
0x20, 0x03, 0x28, 0x0b, 0x32, 0x44, 0x2e, 0x65, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x72, 0x69, 0x73,
|
||||
0x65, 0x70, 0x6f, 0x72, 0x74, 0x61, 0x6c, 0x2e, 0x73, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70,
|
||||
0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x45, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x72,
|
||||
0x69, 0x73, 0x65, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x53,
|
||||
0x61, 0x6c, 0x65, 0x73, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74,
|
||||
0x61, 0x48, 0x00, 0x52, 0x0a, 0x73, 0x61, 0x6c, 0x65, 0x73, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x12,
|
||||
0x29, 0x0a, 0x0f, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x64, 0x6f, 0x6d, 0x61,
|
||||
0x69, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x0e, 0x69, 0x6e, 0x73, 0x74,
|
||||
0x61, 0x6e, 0x63, 0x65, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x42, 0x08, 0x0a, 0x06, 0x66, 0x69,
|
||||
0x6c, 0x74, 0x65, 0x72, 0x22, 0xc0, 0x01, 0x0a, 0x22, 0x4c, 0x69, 0x73, 0x74, 0x45, 0x6e, 0x74,
|
||||
0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x45, 0x6e, 0x74,
|
||||
0x65, 0x72, 0x70, 0x72, 0x69, 0x73, 0x65, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74,
|
||||
0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x70,
|
||||
0x61, 0x67, 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08,
|
||||
0x70, 0x61, 0x67, 0x65, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x70, 0x61, 0x67, 0x65,
|
||||
0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x70, 0x61,
|
||||
0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x5e, 0x0a, 0x07, 0x66, 0x69, 0x6c, 0x74, 0x65,
|
||||
0x72, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x44, 0x2e, 0x65, 0x6e, 0x74, 0x65, 0x72,
|
||||
0x70, 0x72, 0x69, 0x73, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x61, 0x6c, 0x2e, 0x73, 0x75, 0x62, 0x73,
|
||||
0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73,
|
||||
0x74, 0x45, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x72, 0x69, 0x73, 0x65, 0x53, 0x75, 0x62, 0x73, 0x63,
|
||||
0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x52, 0x07,
|
||||
0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x73, 0x22, 0xae, 0x01, 0x0a, 0x23, 0x4c, 0x69, 0x73, 0x74,
|
||||
0x45, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x72, 0x69, 0x73, 0x65, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72,
|
||||
0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12,
|
||||
0x26, 0x0a, 0x0f, 0x6e, 0x65, 0x78, 0x74, 0x5f, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x74, 0x6f, 0x6b,
|
||||
0x65, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x6e, 0x65, 0x78, 0x74, 0x50, 0x61,
|
||||
0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x5f, 0x0a, 0x0d, 0x73, 0x75, 0x62, 0x73, 0x63,
|
||||
0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x39,
|
||||
0x2e, 0x65, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x72, 0x69, 0x73, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x61,
|
||||
0x6c, 0x2e, 0x73, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e,
|
||||
0x76, 0x31, 0x2e, 0x45, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x72, 0x69, 0x73, 0x65, 0x53, 0x75, 0x62,
|
||||
0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0d, 0x73, 0x75, 0x62, 0x73, 0x63,
|
||||
0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0xd0, 0x02, 0x0a, 0x28, 0x4c, 0x69, 0x73,
|
||||
0x74, 0x45, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x72, 0x69, 0x73, 0x65, 0x53, 0x75, 0x62, 0x73, 0x63,
|
||||
0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x4c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x73, 0x46,
|
||||
0x69, 0x6c, 0x74, 0x65, 0x72, 0x12, 0x29, 0x0a, 0x0f, 0x73, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69,
|
||||
0x70, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00,
|
||||
0x52, 0x0e, 0x73, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64,
|
||||
0x12, 0x5a, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x44,
|
||||
0x2e, 0x65, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x72, 0x69, 0x73, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x61,
|
||||
0x6c, 0x2e, 0x73, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e,
|
||||
0x76, 0x31, 0x2e, 0x45, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x72, 0x69, 0x73, 0x65, 0x53, 0x75, 0x62,
|
||||
0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x4c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65,
|
||||
0x54, 0x79, 0x70, 0x65, 0x48, 0x00, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x1f, 0x0a, 0x0a,
|
||||
0x69, 0x73, 0x5f, 0x72, 0x65, 0x76, 0x6f, 0x6b, 0x65, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08,
|
||||
0x48, 0x00, 0x52, 0x09, 0x69, 0x73, 0x52, 0x65, 0x76, 0x6f, 0x6b, 0x65, 0x64, 0x12, 0x34, 0x0a,
|
||||
0x15, 0x6c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x73, 0x75, 0x62,
|
||||
0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x13,
|
||||
0x6c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x4b, 0x65, 0x79, 0x53, 0x75, 0x62, 0x73, 0x74, 0x72,
|
||||
0x69, 0x6e, 0x67, 0x12, 0x3c, 0x0a, 0x19, 0x73, 0x61, 0x6c, 0x65, 0x73, 0x66, 0x6f, 0x72, 0x63,
|
||||
0x65, 0x5f, 0x6f, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x75, 0x6e, 0x69, 0x74, 0x79, 0x5f, 0x69, 0x64,
|
||||
0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x17, 0x73, 0x61, 0x6c, 0x65, 0x73, 0x66,
|
||||
0x6f, 0x72, 0x63, 0x65, 0x4f, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x75, 0x6e, 0x69, 0x74, 0x79, 0x49,
|
||||
0x64, 0x42, 0x08, 0x0a, 0x06, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x22, 0xce, 0x01, 0x0a, 0x29,
|
||||
0x4c, 0x69, 0x73, 0x74, 0x45, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x72, 0x69, 0x73, 0x65, 0x53, 0x75,
|
||||
0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x4c, 0x69, 0x63, 0x65, 0x6e, 0x73,
|
||||
0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x61, 0x67,
|
||||
0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x70, 0x61,
|
||||
0x67, 0x65, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x74,
|
||||
0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x70, 0x61, 0x67, 0x65,
|
||||
0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x65, 0x0a, 0x07, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x73,
|
||||
0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x4b, 0x2e, 0x65, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x72,
|
||||
0x69, 0x73, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x61, 0x6c, 0x2e, 0x73, 0x75, 0x62, 0x73, 0x63, 0x72,
|
||||
0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x45,
|
||||
0x6e, 0x74, 0x65, 0x72, 0x70, 0x72, 0x69, 0x73, 0x65, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69,
|
||||
0x70, 0x74, 0x69, 0x6f, 0x6e, 0x4c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x73, 0x46, 0x69, 0x6c,
|
||||
0x74, 0x65, 0x72, 0x52, 0x07, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x73, 0x22, 0xb2, 0x01, 0x0a,
|
||||
0x2a, 0x4c, 0x69, 0x73, 0x74, 0x45, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x72, 0x69, 0x73, 0x65, 0x53,
|
||||
0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x4c, 0x69, 0x63, 0x65, 0x6e,
|
||||
0x73, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x26, 0x0a, 0x0f, 0x6e,
|
||||
0x69, 0x6f, 0x6e, 0x73, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x52, 0x07, 0x66, 0x69, 0x6c, 0x74,
|
||||
0x65, 0x72, 0x73, 0x22, 0xae, 0x01, 0x0a, 0x23, 0x4c, 0x69, 0x73, 0x74, 0x45, 0x6e, 0x74, 0x65,
|
||||
0x72, 0x70, 0x72, 0x69, 0x73, 0x65, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69,
|
||||
0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x26, 0x0a, 0x0f, 0x6e,
|
||||
0x65, 0x78, 0x74, 0x5f, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x01,
|
||||
0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x6e, 0x65, 0x78, 0x74, 0x50, 0x61, 0x67, 0x65, 0x54, 0x6f,
|
||||
0x6b, 0x65, 0x6e, 0x12, 0x5c, 0x0a, 0x08, 0x6c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x73, 0x18,
|
||||
0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x40, 0x2e, 0x65, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x72, 0x69,
|
||||
0x73, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x61, 0x6c, 0x2e, 0x73, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69,
|
||||
0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x45, 0x6e, 0x74, 0x65, 0x72, 0x70,
|
||||
0x72, 0x69, 0x73, 0x65, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e,
|
||||
0x4c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x52, 0x08, 0x6c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65,
|
||||
0x73, 0x22, 0x88, 0x01, 0x0a, 0x2a, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x45, 0x6e, 0x74, 0x65,
|
||||
0x72, 0x70, 0x72, 0x69, 0x73, 0x65, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69,
|
||||
0x6f, 0x6e, 0x4c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
|
||||
0x12, 0x5a, 0x0a, 0x07, 0x6c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28,
|
||||
0x6b, 0x65, 0x6e, 0x12, 0x5f, 0x0a, 0x0d, 0x73, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74,
|
||||
0x69, 0x6f, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x39, 0x2e, 0x65, 0x6e, 0x74,
|
||||
0x65, 0x72, 0x70, 0x72, 0x69, 0x73, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x61, 0x6c, 0x2e, 0x73, 0x75,
|
||||
0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x45,
|
||||
0x6e, 0x74, 0x65, 0x72, 0x70, 0x72, 0x69, 0x73, 0x65, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69,
|
||||
0x70, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0d, 0x73, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74,
|
||||
0x69, 0x6f, 0x6e, 0x73, 0x22, 0xd0, 0x02, 0x0a, 0x28, 0x4c, 0x69, 0x73, 0x74, 0x45, 0x6e, 0x74,
|
||||
0x65, 0x72, 0x70, 0x72, 0x69, 0x73, 0x65, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74,
|
||||
0x69, 0x6f, 0x6e, 0x4c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x73, 0x46, 0x69, 0x6c, 0x74, 0x65,
|
||||
0x72, 0x12, 0x29, 0x0a, 0x0f, 0x73, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f,
|
||||
0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x0e, 0x73, 0x75,
|
||||
0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x5a, 0x0a, 0x04,
|
||||
0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x44, 0x2e, 0x65, 0x6e, 0x74,
|
||||
0x65, 0x72, 0x70, 0x72, 0x69, 0x73, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x61, 0x6c, 0x2e, 0x73, 0x75,
|
||||
0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x45,
|
||||
0x6e, 0x74, 0x65, 0x72, 0x70, 0x72, 0x69, 0x73, 0x65, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69,
|
||||
0x70, 0x74, 0x69, 0x6f, 0x6e, 0x4c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x54, 0x79, 0x70, 0x65,
|
||||
0x48, 0x00, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x1f, 0x0a, 0x0a, 0x69, 0x73, 0x5f, 0x72,
|
||||
0x65, 0x76, 0x6f, 0x6b, 0x65, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x48, 0x00, 0x52, 0x09,
|
||||
0x69, 0x73, 0x52, 0x65, 0x76, 0x6f, 0x6b, 0x65, 0x64, 0x12, 0x34, 0x0a, 0x15, 0x6c, 0x69, 0x63,
|
||||
0x65, 0x6e, 0x73, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x73, 0x75, 0x62, 0x73, 0x74, 0x72, 0x69,
|
||||
0x6e, 0x67, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x13, 0x6c, 0x69, 0x63, 0x65,
|
||||
0x6e, 0x73, 0x65, 0x4b, 0x65, 0x79, 0x53, 0x75, 0x62, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x12,
|
||||
0x3c, 0x0a, 0x19, 0x73, 0x61, 0x6c, 0x65, 0x73, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x5f, 0x6f, 0x70,
|
||||
0x70, 0x6f, 0x72, 0x74, 0x75, 0x6e, 0x69, 0x74, 0x79, 0x5f, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01,
|
||||
0x28, 0x09, 0x48, 0x00, 0x52, 0x17, 0x73, 0x61, 0x6c, 0x65, 0x73, 0x66, 0x6f, 0x72, 0x63, 0x65,
|
||||
0x4f, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x75, 0x6e, 0x69, 0x74, 0x79, 0x49, 0x64, 0x42, 0x08, 0x0a,
|
||||
0x06, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x22, 0xce, 0x01, 0x0a, 0x29, 0x4c, 0x69, 0x73, 0x74,
|
||||
0x45, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x72, 0x69, 0x73, 0x65, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72,
|
||||
0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x4c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x73, 0x52, 0x65,
|
||||
0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x73, 0x69,
|
||||
0x7a, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x70, 0x61, 0x67, 0x65, 0x53, 0x69,
|
||||
0x7a, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e,
|
||||
0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x70, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65,
|
||||
0x6e, 0x12, 0x65, 0x0a, 0x07, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x73, 0x18, 0x03, 0x20, 0x03,
|
||||
0x28, 0x0b, 0x32, 0x4b, 0x2e, 0x65, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x72, 0x69, 0x73, 0x65, 0x70,
|
||||
0x6f, 0x72, 0x74, 0x61, 0x6c, 0x2e, 0x73, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69,
|
||||
0x6f, 0x6e, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x45, 0x6e, 0x74, 0x65, 0x72,
|
||||
0x70, 0x72, 0x69, 0x73, 0x65, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f,
|
||||
0x6e, 0x4c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x73, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x52,
|
||||
0x07, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x73, 0x22, 0xb2, 0x01, 0x0a, 0x2a, 0x4c, 0x69, 0x73,
|
||||
0x74, 0x45, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x72, 0x69, 0x73, 0x65, 0x53, 0x75, 0x62, 0x73, 0x63,
|
||||
0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x4c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x73, 0x52,
|
||||
0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x26, 0x0a, 0x0f, 0x6e, 0x65, 0x78, 0x74, 0x5f,
|
||||
0x70, 0x61, 0x67, 0x65, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,
|
||||
0x52, 0x0d, 0x6e, 0x65, 0x78, 0x74, 0x50, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12,
|
||||
0x5c, 0x0a, 0x08, 0x6c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28,
|
||||
0x0b, 0x32, 0x40, 0x2e, 0x65, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x72, 0x69, 0x73, 0x65, 0x70, 0x6f,
|
||||
0x72, 0x74, 0x61, 0x6c, 0x2e, 0x73, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f,
|
||||
0x6e, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x45, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x72, 0x69, 0x73, 0x65,
|
||||
0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x4c, 0x69, 0x63, 0x65,
|
||||
0x6e, 0x73, 0x65, 0x52, 0x07, 0x6c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x22, 0x89, 0x01, 0x0a,
|
||||
0x2b, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x45, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x72, 0x69, 0x73,
|
||||
0x65, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x4c, 0x69, 0x63,
|
||||
0x65, 0x6e, 0x73, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x5a, 0x0a, 0x07,
|
||||
0x6e, 0x73, 0x65, 0x52, 0x08, 0x6c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x73, 0x22, 0xa2, 0x01,
|
||||
0x0a, 0x2a, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x45, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x72, 0x69,
|
||||
0x73, 0x65, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x4c, 0x69,
|
||||
0x63, 0x65, 0x6e, 0x73, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x5a, 0x0a, 0x07,
|
||||
0x6c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x40, 0x2e,
|
||||
0x65, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x72, 0x69, 0x73, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x61, 0x6c,
|
||||
0x2e, 0x73, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x76,
|
||||
0x31, 0x2e, 0x45, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x72, 0x69, 0x73, 0x65, 0x53, 0x75, 0x62, 0x73,
|
||||
0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x4c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x52,
|
||||
0x07, 0x6c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x22, 0x63, 0x0a, 0x2a, 0x52, 0x65, 0x76, 0x6f,
|
||||
0x6b, 0x65, 0x45, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x72, 0x69, 0x73, 0x65, 0x53, 0x75, 0x62, 0x73,
|
||||
0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x4c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x52,
|
||||
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x6c, 0x69, 0x63, 0x65, 0x6e, 0x73,
|
||||
0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6c, 0x69, 0x63, 0x65,
|
||||
0x6e, 0x73, 0x65, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18,
|
||||
0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x22, 0x2d, 0x0a,
|
||||
0x2b, 0x52, 0x65, 0x76, 0x6f, 0x6b, 0x65, 0x45, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x72, 0x69, 0x73,
|
||||
0x65, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x4c, 0x69, 0x63,
|
||||
0x65, 0x6e, 0x73, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0xc1, 0x01, 0x0a,
|
||||
0x23, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x45, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x72, 0x69, 0x73,
|
||||
0x65, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71,
|
||||
0x75, 0x65, 0x73, 0x74, 0x12, 0x5d, 0x0a, 0x0c, 0x73, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70,
|
||||
0x74, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x39, 0x2e, 0x65, 0x6e, 0x74,
|
||||
0x65, 0x72, 0x70, 0x72, 0x69, 0x73, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x61, 0x6c, 0x2e, 0x73, 0x75,
|
||||
0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x45,
|
||||
0x6e, 0x74, 0x65, 0x72, 0x70, 0x72, 0x69, 0x73, 0x65, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69,
|
||||
0x70, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0c, 0x73, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74,
|
||||
0x69, 0x6f, 0x6e, 0x12, 0x3b, 0x0a, 0x0b, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x5f, 0x6d, 0x61,
|
||||
0x73, 0x6b, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c,
|
||||
0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x46, 0x69, 0x65, 0x6c, 0x64,
|
||||
0x4d, 0x61, 0x73, 0x6b, 0x52, 0x0a, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4d, 0x61, 0x73, 0x6b,
|
||||
0x22, 0x85, 0x01, 0x0a, 0x24, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x45, 0x6e, 0x74, 0x65, 0x72,
|
||||
0x70, 0x72, 0x69, 0x73, 0x65, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f,
|
||||
0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x5d, 0x0a, 0x0c, 0x73, 0x75, 0x62,
|
||||
0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32,
|
||||
0x39, 0x2e, 0x65, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x72, 0x69, 0x73, 0x65, 0x70, 0x6f, 0x72, 0x74,
|
||||
0x61, 0x6c, 0x2e, 0x73, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73,
|
||||
0x2e, 0x76, 0x31, 0x2e, 0x45, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x72, 0x69, 0x73, 0x65, 0x53, 0x75,
|
||||
0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0c, 0x73, 0x75, 0x62, 0x73,
|
||||
0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x67, 0x0a, 0x24, 0x41, 0x72, 0x63, 0x68,
|
||||
0x69, 0x76, 0x65, 0x45, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x72, 0x69, 0x73, 0x65, 0x53, 0x75, 0x62,
|
||||
0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
|
||||
0x12, 0x27, 0x0a, 0x0f, 0x73, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e,
|
||||
0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x73, 0x75, 0x62, 0x73, 0x63,
|
||||
0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x61,
|
||||
0x73, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f,
|
||||
0x6e, 0x22, 0x27, 0x0a, 0x25, 0x41, 0x72, 0x63, 0x68, 0x69, 0x76, 0x65, 0x45, 0x6e, 0x74, 0x65,
|
||||
0x72, 0x70, 0x72, 0x69, 0x73, 0x65, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69,
|
||||
0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x84, 0x01, 0x0a, 0x23, 0x43,
|
||||
0x72, 0x65, 0x61, 0x74, 0x65, 0x45, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x72, 0x69, 0x73, 0x65, 0x53,
|
||||
0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65,
|
||||
0x73, 0x74, 0x12, 0x5d, 0x0a, 0x0c, 0x73, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69,
|
||||
0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x39, 0x2e, 0x65, 0x6e, 0x74, 0x65, 0x72,
|
||||
0x70, 0x72, 0x69, 0x73, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x61, 0x6c, 0x2e, 0x73, 0x75, 0x62, 0x73,
|
||||
0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x45, 0x6e, 0x74,
|
||||
0x07, 0x6c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73,
|
||||
0x61, 0x67, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61,
|
||||
0x67, 0x65, 0x22, 0x89, 0x01, 0x0a, 0x2b, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x45, 0x6e, 0x74,
|
||||
0x65, 0x72, 0x70, 0x72, 0x69, 0x73, 0x65, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74,
|
||||
0x69, 0x6f, 0x6e, 0x52, 0x0c, 0x73, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f,
|
||||
0x6e, 0x22, 0x85, 0x01, 0x0a, 0x24, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x45, 0x6e, 0x74, 0x65,
|
||||
0x69, 0x6f, 0x6e, 0x4c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
|
||||
0x73, 0x65, 0x12, 0x5a, 0x0a, 0x07, 0x6c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x18, 0x01, 0x20,
|
||||
0x01, 0x28, 0x0b, 0x32, 0x40, 0x2e, 0x65, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x72, 0x69, 0x73, 0x65,
|
||||
0x70, 0x6f, 0x72, 0x74, 0x61, 0x6c, 0x2e, 0x73, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74,
|
||||
0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x45, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x72, 0x69,
|
||||
0x73, 0x65, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x4c, 0x69,
|
||||
0x63, 0x65, 0x6e, 0x73, 0x65, 0x52, 0x07, 0x6c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x22, 0x63,
|
||||
0x0a, 0x2a, 0x52, 0x65, 0x76, 0x6f, 0x6b, 0x65, 0x45, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x72, 0x69,
|
||||
0x73, 0x65, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x4c, 0x69,
|
||||
0x63, 0x65, 0x6e, 0x73, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a,
|
||||
0x6c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,
|
||||
0x52, 0x09, 0x6c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x72,
|
||||
0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x61,
|
||||
0x73, 0x6f, 0x6e, 0x22, 0x2d, 0x0a, 0x2b, 0x52, 0x65, 0x76, 0x6f, 0x6b, 0x65, 0x45, 0x6e, 0x74,
|
||||
0x65, 0x72, 0x70, 0x72, 0x69, 0x73, 0x65, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74,
|
||||
0x69, 0x6f, 0x6e, 0x4c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
|
||||
0x73, 0x65, 0x22, 0xc1, 0x01, 0x0a, 0x23, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x45, 0x6e, 0x74,
|
||||
0x65, 0x72, 0x70, 0x72, 0x69, 0x73, 0x65, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74,
|
||||
0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x5d, 0x0a, 0x0c, 0x73, 0x75,
|
||||
0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b,
|
||||
0x32, 0x39, 0x2e, 0x65, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x72, 0x69, 0x73, 0x65, 0x70, 0x6f, 0x72,
|
||||
0x74, 0x61, 0x6c, 0x2e, 0x73, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e,
|
||||
0x73, 0x2e, 0x76, 0x31, 0x2e, 0x45, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x72, 0x69, 0x73, 0x65, 0x53,
|
||||
0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0c, 0x73, 0x75, 0x62,
|
||||
0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x3b, 0x0a, 0x0b, 0x75, 0x70, 0x64,
|
||||
0x61, 0x74, 0x65, 0x5f, 0x6d, 0x61, 0x73, 0x6b, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a,
|
||||
0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66,
|
||||
0x2e, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x4d, 0x61, 0x73, 0x6b, 0x52, 0x0a, 0x75, 0x70, 0x64, 0x61,
|
||||
0x74, 0x65, 0x4d, 0x61, 0x73, 0x6b, 0x22, 0x85, 0x01, 0x0a, 0x24, 0x55, 0x70, 0x64, 0x61, 0x74,
|
||||
0x65, 0x45, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x72, 0x69, 0x73, 0x65, 0x53, 0x75, 0x62, 0x73, 0x63,
|
||||
0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12,
|
||||
0x5d, 0x0a, 0x0c, 0x73, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18,
|
||||
0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x39, 0x2e, 0x65, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x72, 0x69,
|
||||
0x73, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x61, 0x6c, 0x2e, 0x73, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69,
|
||||
0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x45, 0x6e, 0x74, 0x65, 0x72, 0x70,
|
||||
0x72, 0x69, 0x73, 0x65, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e,
|
||||
0x52, 0x0c, 0x73, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x67,
|
||||
0x0a, 0x24, 0x41, 0x72, 0x63, 0x68, 0x69, 0x76, 0x65, 0x45, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x72,
|
||||
0x69, 0x73, 0x65, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x52,
|
||||
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x27, 0x0a, 0x0f, 0x73, 0x75, 0x62, 0x73, 0x63, 0x72,
|
||||
0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52,
|
||||
0x0e, 0x73, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12,
|
||||
0x16, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52,
|
||||
0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x22, 0x27, 0x0a, 0x25, 0x41, 0x72, 0x63, 0x68, 0x69,
|
||||
0x76, 0x65, 0x45, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x72, 0x69, 0x73, 0x65, 0x53, 0x75, 0x62, 0x73,
|
||||
0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
|
||||
0x22, 0x9e, 0x01, 0x0a, 0x23, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x45, 0x6e, 0x74, 0x65, 0x72,
|
||||
0x70, 0x72, 0x69, 0x73, 0x65, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f,
|
||||
0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x5d, 0x0a, 0x0c, 0x73, 0x75, 0x62, 0x73,
|
||||
0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x39,
|
||||
0x2e, 0x65, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x72, 0x69, 0x73, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x61,
|
||||
0x6c, 0x2e, 0x73, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e,
|
||||
0x76, 0x31, 0x2e, 0x45, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x72, 0x69, 0x73, 0x65, 0x53, 0x75, 0x62,
|
||||
0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0c, 0x73, 0x75, 0x62, 0x73, 0x63,
|
||||
0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61,
|
||||
0x67, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67,
|
||||
0x65, 0x22, 0x85, 0x01, 0x0a, 0x24, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x45, 0x6e, 0x74, 0x65,
|
||||
0x72, 0x70, 0x72, 0x69, 0x73, 0x65, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69,
|
||||
0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x5d, 0x0a, 0x0c, 0x73, 0x75,
|
||||
0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b,
|
||||
|
||||
@ -145,6 +145,9 @@ message EnterpriseSubscriptionLicenseKey {
|
||||
Info info = 2;
|
||||
// The signed license key.
|
||||
string license_key = 3;
|
||||
// Generated display name representing the plan and some high-level attributes
|
||||
// about the plan.
|
||||
string plan_display_name = 4;
|
||||
}
|
||||
|
||||
message EnterpriseSubscriptionLicenseCondition {
|
||||
@ -319,6 +322,9 @@ message CreateEnterpriseSubscriptionLicenseRequest {
|
||||
// - license.key.info.expire_time
|
||||
// - license.key.info.salesforce_opportunity_id
|
||||
EnterpriseSubscriptionLicense license = 1;
|
||||
|
||||
// Message to associate with the license creation event.
|
||||
string message = 2;
|
||||
}
|
||||
|
||||
message CreateEnterpriseSubscriptionLicenseResponse {
|
||||
@ -346,6 +352,8 @@ message UpdateEnterpriseSubscriptionRequest {
|
||||
// Updatable fields are:
|
||||
// - instance_domain
|
||||
// - display_name
|
||||
// - salesforce.subscription_id
|
||||
// - salesforce.opportunity_id
|
||||
google.protobuf.FieldMask update_mask = 2;
|
||||
}
|
||||
|
||||
@ -373,6 +381,9 @@ message CreateEnterpriseSubscriptionRequest {
|
||||
// - instance_domain
|
||||
// - salesforce.subscription_id
|
||||
EnterpriseSubscription subscription = 1;
|
||||
|
||||
// Message to associate with the subscription creation event.
|
||||
string message = 2;
|
||||
}
|
||||
|
||||
message CreateEnterpriseSubscriptionResponse {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user