mirror of
https://github.com/sourcegraph/sourcegraph.git
synced 2026-02-06 15:12:02 +00:00
feat/enterprise-portal: ConnectRPC layer for {Get/List}CodyGatewayAccess (#62771)
This PR exposes the data layer implemented in https://github.com/sourcegraph/sourcegraph/pull/62706 via the Enterprise Portal API. We register the services proposed in #62263 and also set up tooling like gRPC UI locally for DX. Auth is via SAMS M2M; https://github.com/sourcegraph/sourcegraph-accounts-sdk-go/pull/28 and https://github.com/sourcegraph/sourcegraph-accounts/pull/227 rolls out the new scopes, and https://github.com/sourcegraph/managed-services/pull/1474 adds credentials for the enterprise-portal-dev deployment. Closes CORE-112 ## Test plan https://github.com/sourcegraph/sourcegraph/pull/62706 has extensive testing of the data layer, and this PR expands on it a little bit. I tested the RPC layer by hand: Create SAMS client for Enterprise Portal Dev in **accounts.sgdev.org**: ```sh curl -s -X POST \ -H "Authorization: Bearer $MANAGEMENT_SECRET" \ https://accounts.sgdev.org/api/management/v1/identity-provider/clients \ --data '{"name": "enterprise-portal-dev", "scopes": [], "redirect_uris": ["https://enterprise-portal.sgdev.org"]}' | jq ``` Configure `sg.config.overwrite.yaml` ```yaml enterprise-portal: env: SRC_LOG_LEVEL: debug # sams-dev SAMS_URL: https://accounts.sgdev.org ENTERPRISE_PORTAL_SAMS_CLIENT_ID: "sams_cid_..." ENTERPRISE_PORTAL_SAMS_CLIENT_SECRET: "sams_cs_..." ``` Create a test client (later, we will do the same thing for Cody Gateway), also in **accounts.sgdev.org**: ```sh curl -s -X POST \ -H "Authorization: Bearer $MANAGEMENT_SECRET" \ https://accounts.sgdev.org/api/management/v1/identity-provider/clients \ --data '{"name": "enterprise-portal-dev-reader", "scopes": ["enterprise_portal::codyaccess::read", "enterprise_portal::subscription::read"], "redirect_uris": ["https://enterprise-portal.sgdev.org"]}' | jq ``` Then: ``` sg run enterprise-portal ``` Navigate to the locally-enabled gRPC debug UI at http://localhost:6081/debug/grcpui, using https://github.com/sourcegraph/sourcegraph/pull/62883 to get an access token from our test client to add in the request metadata: ```sh sg sams create-client-token -s 'enterprise_portal::codyaccess::read' ``` I'm using some local subscriptions I've made previously in `sg start dotcom`:   Without a valid authorization header:  Verified a lookup using the returned access tokens also works --------- Co-authored-by: Jean-Hadrien Chabran <jh@chabran.fr> Co-authored-by: Joe Chen <joe@sourcegraph.com>
This commit is contained in:
parent
72fcf13241
commit
704b36a143
25
cmd/enterprise-portal/internal/codyaccessservice/BUILD.bazel
Normal file
25
cmd/enterprise-portal/internal/codyaccessservice/BUILD.bazel
Normal file
@ -0,0 +1,25 @@
|
||||
load("@io_bazel_rules_go//go:def.bzl", "go_library")
|
||||
|
||||
go_library(
|
||||
name = "codyaccessservice",
|
||||
srcs = [
|
||||
"adapters.go",
|
||||
"v1.go",
|
||||
],
|
||||
importpath = "github.com/sourcegraph/sourcegraph/cmd/enterprise-portal/internal/codyaccessservice",
|
||||
visibility = ["//cmd/enterprise-portal:__subpackages__"],
|
||||
deps = [
|
||||
"//cmd/enterprise-portal/internal/connectutil",
|
||||
"//cmd/enterprise-portal/internal/dotcomdb",
|
||||
"//cmd/enterprise-portal/internal/samsm2m",
|
||||
"//internal/trace",
|
||||
"//lib/enterpriseportal/codyaccess/v1:codyaccess",
|
||||
"//lib/enterpriseportal/codyaccess/v1/v1connect",
|
||||
"//lib/enterpriseportal/subscriptions/v1:subscriptions",
|
||||
"//lib/errors",
|
||||
"@com_connectrpc_connect//:connect",
|
||||
"@com_github_sourcegraph_log//:log",
|
||||
"@com_github_sourcegraph_sourcegraph_accounts_sdk_go//scopes",
|
||||
"@org_golang_google_protobuf//types/known/durationpb",
|
||||
],
|
||||
)
|
||||
54
cmd/enterprise-portal/internal/codyaccessservice/adapters.go
Normal file
54
cmd/enterprise-portal/internal/codyaccessservice/adapters.go
Normal file
@ -0,0 +1,54 @@
|
||||
package codyaccessservice
|
||||
|
||||
import (
|
||||
"google.golang.org/protobuf/types/known/durationpb"
|
||||
|
||||
"github.com/sourcegraph/sourcegraph/cmd/enterprise-portal/internal/dotcomdb"
|
||||
codyaccessv1 "github.com/sourcegraph/sourcegraph/lib/enterpriseportal/codyaccess/v1"
|
||||
subscriptionsv1 "github.com/sourcegraph/sourcegraph/lib/enterpriseportal/subscriptions/v1"
|
||||
)
|
||||
|
||||
func convertAccessAttrsToProto(attrs *dotcomdb.CodyGatewayAccessAttributes) *codyaccessv1.CodyGatewayAccess {
|
||||
// Provide ID in prefixed format.
|
||||
subscriptionID := subscriptionsv1.EnterpriseSubscriptionIDPrefix + attrs.SubscriptionID
|
||||
|
||||
// If not enabled, return a minimal response.
|
||||
if !attrs.CodyGatewayEnabled {
|
||||
return &codyaccessv1.CodyGatewayAccess{
|
||||
SubscriptionId: subscriptionID,
|
||||
Enabled: false,
|
||||
}
|
||||
}
|
||||
|
||||
// If enabled, return the full response.
|
||||
limits := attrs.EvaluateRateLimits()
|
||||
return &codyaccessv1.CodyGatewayAccess{
|
||||
SubscriptionId: subscriptionID,
|
||||
Enabled: attrs.CodyGatewayEnabled,
|
||||
ChatCompletionsRateLimit: &codyaccessv1.CodyGatewayRateLimit{
|
||||
Source: limits.ChatSource,
|
||||
Limit: limits.Chat.Limit,
|
||||
IntervalDuration: durationpb.New(limits.Chat.IntervalDuration()),
|
||||
},
|
||||
CodeCompletionsRateLimit: &codyaccessv1.CodyGatewayRateLimit{
|
||||
Source: limits.CodeSource,
|
||||
Limit: limits.Code.Limit,
|
||||
IntervalDuration: durationpb.New(limits.Code.IntervalDuration()),
|
||||
},
|
||||
EmbeddingsRateLimit: &codyaccessv1.CodyGatewayRateLimit{
|
||||
Source: limits.EmbeddingsSource,
|
||||
Limit: limits.Embeddings.Limit,
|
||||
IntervalDuration: durationpb.New(limits.Embeddings.IntervalDuration()),
|
||||
},
|
||||
AccessTokens: func() []*codyaccessv1.CodyGatewayAccessToken {
|
||||
accessTokens := attrs.GenerateAccessTokens()
|
||||
results := make([]*codyaccessv1.CodyGatewayAccessToken, len(accessTokens))
|
||||
for i, token := range accessTokens {
|
||||
results[i] = &codyaccessv1.CodyGatewayAccessToken{
|
||||
Token: token,
|
||||
}
|
||||
}
|
||||
return results
|
||||
}(),
|
||||
}
|
||||
}
|
||||
123
cmd/enterprise-portal/internal/codyaccessservice/v1.go
Normal file
123
cmd/enterprise-portal/internal/codyaccessservice/v1.go
Normal file
@ -0,0 +1,123 @@
|
||||
package codyaccessservice
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"connectrpc.com/connect"
|
||||
"github.com/sourcegraph/log"
|
||||
|
||||
"github.com/sourcegraph/sourcegraph-accounts-sdk-go/scopes"
|
||||
|
||||
"github.com/sourcegraph/sourcegraph/cmd/enterprise-portal/internal/connectutil"
|
||||
"github.com/sourcegraph/sourcegraph/cmd/enterprise-portal/internal/dotcomdb"
|
||||
"github.com/sourcegraph/sourcegraph/cmd/enterprise-portal/internal/samsm2m"
|
||||
"github.com/sourcegraph/sourcegraph/internal/trace"
|
||||
codyaccessv1 "github.com/sourcegraph/sourcegraph/lib/enterpriseportal/codyaccess/v1"
|
||||
codyaccessv1connect "github.com/sourcegraph/sourcegraph/lib/enterpriseportal/codyaccess/v1/v1connect"
|
||||
"github.com/sourcegraph/sourcegraph/lib/errors"
|
||||
)
|
||||
|
||||
const Name = codyaccessv1connect.CodyAccessServiceName
|
||||
|
||||
type DotComDB interface {
|
||||
GetCodyGatewayAccessAttributesBySubscription(ctx context.Context, subscriptionID string) (*dotcomdb.CodyGatewayAccessAttributes, error)
|
||||
GetCodyGatewayAccessAttributesByAccessToken(ctx context.Context, subscriptionID string) (*dotcomdb.CodyGatewayAccessAttributes, error)
|
||||
GetAllCodyGatewayAccessAttributes(ctx context.Context) ([]*dotcomdb.CodyGatewayAccessAttributes, error)
|
||||
}
|
||||
|
||||
func RegisterV1(logger log.Logger, mux *http.ServeMux, samsClient samsm2m.TokenIntrospector, dotcom DotComDB) {
|
||||
mux.Handle(codyaccessv1connect.NewCodyAccessServiceHandler(&handlerV1{
|
||||
logger: logger.Scoped("codyaccess.v1"),
|
||||
samsClient: samsClient,
|
||||
dotcom: dotcom,
|
||||
}))
|
||||
}
|
||||
|
||||
type handlerV1 struct {
|
||||
codyaccessv1connect.UnimplementedCodyAccessServiceHandler
|
||||
logger log.Logger
|
||||
|
||||
samsClient samsm2m.TokenIntrospector
|
||||
dotcom DotComDB
|
||||
}
|
||||
|
||||
var _ codyaccessv1connect.CodyAccessServiceHandler = (*handlerV1)(nil)
|
||||
|
||||
func (s *handlerV1) GetCodyGatewayAccess(ctx context.Context, req *connect.Request[codyaccessv1.GetCodyGatewayAccessRequest]) (*connect.Response[codyaccessv1.GetCodyGatewayAccessResponse], error) {
|
||||
logger := trace.Logger(ctx, s.logger).
|
||||
With(log.String("queryType", fmt.Sprintf("%T", req.Msg.GetQuery())))
|
||||
|
||||
// 🚨 SECURITY: Require approrpiate M2M scope.
|
||||
requiredScope := samsm2m.EnterprisePortalScope("codyaccess", scopes.ActionRead)
|
||||
if err := samsm2m.RequireScope(ctx, logger, s.samsClient, requiredScope, req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var attr *dotcomdb.CodyGatewayAccessAttributes
|
||||
var err error
|
||||
switch query := req.Msg.GetQuery().(type) {
|
||||
case *codyaccessv1.GetCodyGatewayAccessRequest_SubscriptionId:
|
||||
if len(query.SubscriptionId) == 0 {
|
||||
return nil, connect.NewError(connect.CodeInvalidArgument, errors.New("invalid query: subscription ID"))
|
||||
}
|
||||
attr, err = s.dotcom.GetCodyGatewayAccessAttributesBySubscription(ctx, query.SubscriptionId)
|
||||
|
||||
case *codyaccessv1.GetCodyGatewayAccessRequest_AccessToken:
|
||||
if len(query.AccessToken) == 0 {
|
||||
return nil, connect.NewError(connect.CodeInvalidArgument, errors.New("invalid query: access token"))
|
||||
}
|
||||
attr, err = s.dotcom.GetCodyGatewayAccessAttributesByAccessToken(ctx, query.AccessToken)
|
||||
|
||||
default:
|
||||
return nil, connect.NewError(connect.CodeInvalidArgument, errors.New("invalid query"))
|
||||
}
|
||||
if err != nil {
|
||||
if err == dotcomdb.ErrCodyGatewayAccessNotFound {
|
||||
return nil, connect.NewError(connect.CodeNotFound, err)
|
||||
}
|
||||
return nil, connectutil.InternalError(ctx, logger, err,
|
||||
"failed to get Cody Gateway access attributes")
|
||||
}
|
||||
return connect.NewResponse(&codyaccessv1.GetCodyGatewayAccessResponse{
|
||||
Access: convertAccessAttrsToProto(attr),
|
||||
}), nil
|
||||
}
|
||||
|
||||
func (s *handlerV1) ListCodyGatewayAccesses(ctx context.Context, req *connect.Request[codyaccessv1.ListCodyGatewayAccessesRequest]) (*connect.Response[codyaccessv1.ListCodyGatewayAccessesResponse], error) {
|
||||
logger := trace.Logger(ctx, s.logger)
|
||||
|
||||
// 🚨 SECURITY: Require approrpiate M2M scope.
|
||||
requiredScope := samsm2m.EnterprisePortalScope("codyaccess", scopes.ActionRead)
|
||||
if err := samsm2m.RequireScope(ctx, logger, s.samsClient, requiredScope, req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Pagination is unimplemented: https://linear.app/sourcegraph/issue/CORE-134
|
||||
if req.Msg.PageSize != 0 {
|
||||
return nil, connect.NewError(connect.CodeUnimplemented, errors.New("pagination not implemented"))
|
||||
}
|
||||
if req.Msg.PageToken != "" {
|
||||
return nil, connect.NewError(connect.CodeUnimplemented, errors.New("pagination not implemented"))
|
||||
}
|
||||
|
||||
attrs, err := s.dotcom.GetAllCodyGatewayAccessAttributes(ctx)
|
||||
if err != nil {
|
||||
if err == dotcomdb.ErrCodyGatewayAccessNotFound {
|
||||
return nil, connect.NewError(connect.CodeNotFound, err)
|
||||
}
|
||||
return nil, connectutil.InternalError(ctx, logger, err,
|
||||
"failed to list Cody Gateway access attributes")
|
||||
}
|
||||
resp := codyaccessv1.ListCodyGatewayAccessesResponse{
|
||||
// Never a next page, pagination is not implemented yet:
|
||||
// https://linear.app/sourcegraph/issue/CORE-134
|
||||
NextPageToken: "",
|
||||
Accesses: make([]*codyaccessv1.CodyGatewayAccess, len(attrs)),
|
||||
}
|
||||
for i, attr := range attrs {
|
||||
resp.Accesses[i] = convertAccessAttrsToProto(attr)
|
||||
}
|
||||
return connect.NewResponse(&resp), nil
|
||||
}
|
||||
16
cmd/enterprise-portal/internal/connectutil/BUILD.bazel
Normal file
16
cmd/enterprise-portal/internal/connectutil/BUILD.bazel
Normal file
@ -0,0 +1,16 @@
|
||||
load("@io_bazel_rules_go//go:def.bzl", "go_library")
|
||||
|
||||
go_library(
|
||||
name = "connectutil",
|
||||
srcs = ["connectutil.go"],
|
||||
importpath = "github.com/sourcegraph/sourcegraph/cmd/enterprise-portal/internal/connectutil",
|
||||
visibility = ["//cmd/enterprise-portal:__subpackages__"],
|
||||
deps = [
|
||||
"//internal/trace",
|
||||
"//lib/errors",
|
||||
"@com_connectrpc_connect//:connect",
|
||||
"@com_github_sourcegraph_log//:log",
|
||||
"@io_opentelemetry_go_otel//attribute",
|
||||
"@io_opentelemetry_go_otel_trace//:trace",
|
||||
],
|
||||
)
|
||||
30
cmd/enterprise-portal/internal/connectutil/connectutil.go
Normal file
30
cmd/enterprise-portal/internal/connectutil/connectutil.go
Normal file
@ -0,0 +1,30 @@
|
||||
package connectutil
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"connectrpc.com/connect"
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
|
||||
"github.com/sourcegraph/log"
|
||||
|
||||
sgtrace "github.com/sourcegraph/sourcegraph/internal/trace"
|
||||
"github.com/sourcegraph/sourcegraph/lib/errors"
|
||||
)
|
||||
|
||||
// InternalError logs an error, adds it to the trace, and returns a connect
|
||||
// error with a safe message.
|
||||
func InternalError(ctx context.Context, logger log.Logger, err error, safeMsg string) error {
|
||||
trace.SpanFromContext(ctx).
|
||||
SetAttributes(
|
||||
attribute.String("full_error", err.Error()),
|
||||
)
|
||||
sgtrace.Logger(ctx, logger).
|
||||
AddCallerSkip(1).
|
||||
Error(safeMsg,
|
||||
log.String("code", connect.CodeInternal.String()),
|
||||
log.Error(err),
|
||||
)
|
||||
return connect.NewError(connect.CodeInternal, errors.New(safeMsg))
|
||||
}
|
||||
@ -11,6 +11,7 @@ go_library(
|
||||
"//internal/licensing",
|
||||
"//internal/productsubscription",
|
||||
"//lib/enterpriseportal/codyaccess/v1:codyaccess",
|
||||
"//lib/enterpriseportal/subscriptions/v1:subscriptions",
|
||||
"//lib/errors",
|
||||
"@com_github_jackc_pgx_v5//:pgx",
|
||||
],
|
||||
@ -19,7 +20,10 @@ go_library(
|
||||
go_test(
|
||||
name = "dotcomdb_test",
|
||||
srcs = ["dotcomdb_test.go"],
|
||||
tags = ["requires-network"],
|
||||
tags = [
|
||||
TAG_INFRA_CORESERVICES,
|
||||
"requires-network",
|
||||
],
|
||||
deps = [
|
||||
":dotcomdb",
|
||||
"//cmd/frontend/dotcomproductsubscriptiontest",
|
||||
|
||||
@ -17,6 +17,7 @@ import (
|
||||
"github.com/sourcegraph/sourcegraph/internal/licensing"
|
||||
"github.com/sourcegraph/sourcegraph/internal/productsubscription"
|
||||
codyaccessv1 "github.com/sourcegraph/sourcegraph/lib/enterpriseportal/codyaccess/v1"
|
||||
subscriptionsv1 "github.com/sourcegraph/sourcegraph/lib/enterpriseportal/subscriptions/v1"
|
||||
"github.com/sourcegraph/sourcegraph/lib/errors"
|
||||
)
|
||||
|
||||
@ -176,8 +177,12 @@ FROM product_subscriptions subscription
|
||||
) tokens ON tokens.product_subscription_id = subscription.id`
|
||||
|
||||
clauses := []string{rawClause}
|
||||
// Add WHERE clause, amending it to include a condition that the subscription
|
||||
// must not be archived.
|
||||
if conds.whereClause != "" {
|
||||
clauses = append(clauses, "WHERE "+conds.whereClause)
|
||||
clauses = append(clauses, "WHERE "+conds.whereClause+" AND subscription.archived_at IS NULL")
|
||||
} else {
|
||||
clauses = append(clauses, "WHERE subscription.archived_at IS NULL")
|
||||
}
|
||||
clauses = append(clauses, "GROUP BY subscription.id") // required, after WHERE clause
|
||||
if conds.havingClause != "" {
|
||||
@ -195,7 +200,8 @@ func (r *Reader) GetCodyGatewayAccessAttributesBySubscription(ctx context.Contex
|
||||
query := newCodyGatewayAccessQuery(queryConditions{
|
||||
whereClause: "subscription.id = $1",
|
||||
})
|
||||
row := r.conn.QueryRow(ctx, query, subscriptionID)
|
||||
row := r.conn.QueryRow(ctx, query,
|
||||
strings.TrimPrefix(subscriptionID, subscriptionsv1.EnterpriseSubscriptionIDPrefix))
|
||||
return scanCodyGatewayAccessAttributes(row)
|
||||
}
|
||||
|
||||
|
||||
@ -73,6 +73,8 @@ type mockAccess struct {
|
||||
}
|
||||
|
||||
func setupDBAndInsertMockLicense(t *testing.T, dotcomdb database.DB, info license.Info, cgAccess graphqlbackend.UpdateCodyGatewayAccessInput) mockAccess {
|
||||
start := time.Now()
|
||||
|
||||
ctx := context.Background()
|
||||
subdb := dotcomproductsubscriptiontest.NewSubscriptionsDB(t, dotcomdb)
|
||||
ldb := dotcomproductsubscriptiontest.NewLicensesDB(t, dotcomdb)
|
||||
@ -92,6 +94,22 @@ func setupDBAndInsertMockLicense(t *testing.T, dotcomdb database.DB, info licens
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
{
|
||||
// Create a different subscription and license that's archived,
|
||||
// created at the same time, to ensure we don't use it
|
||||
u, err := dotcomdb.Users().Create(ctx, database.NewUser{Username: "archived"})
|
||||
require.NoError(t, err)
|
||||
sub, err := subdb.Create(ctx, u.ID, u.Username)
|
||||
require.NoError(t, err)
|
||||
_, err = ldb.Create(ctx, sub, t.Name()+"-archived", 2, license.Info{
|
||||
CreatedAt: info.CreatedAt,
|
||||
ExpiresAt: info.ExpiresAt,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
// Archive the subscription
|
||||
require.NoError(t, subdb.Archive(ctx, sub))
|
||||
}
|
||||
|
||||
// Create the subscription we will assert against
|
||||
u, err := dotcomdb.Users().Create(ctx, database.NewUser{Username: "user"})
|
||||
require.NoError(t, err)
|
||||
@ -130,6 +148,7 @@ func setupDBAndInsertMockLicense(t *testing.T, dotcomdb database.DB, info licens
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
t.Logf("Setup complete in %s", time.Since(start).String())
|
||||
return result
|
||||
}
|
||||
|
||||
@ -201,6 +220,13 @@ func TestGetCodyGatewayAccessAttributes(t *testing.T) {
|
||||
attr, err := dotcomreader.GetCodyGatewayAccessAttributesByAccessToken(ctx, token)
|
||||
require.NoError(t, err)
|
||||
validateAccessAttributes(t, dotcomdb, mock, attr, tc.info)
|
||||
|
||||
t.Run("compare with dotcom tokens DB", func(t *testing.T) {
|
||||
subID, err := dotcomproductsubscriptiontest.NewTokensDB(t, dotcomdb).
|
||||
LookupProductSubscriptionIDByAccessToken(ctx, token)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, subID, attr.SubscriptionID)
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
37
cmd/enterprise-portal/internal/samsm2m/BUILD.bazel
Normal file
37
cmd/enterprise-portal/internal/samsm2m/BUILD.bazel
Normal file
@ -0,0 +1,37 @@
|
||||
load("@io_bazel_rules_go//go:def.bzl", "go_library")
|
||||
load("//dev:go_defs.bzl", "go_test")
|
||||
|
||||
go_library(
|
||||
name = "samsm2m",
|
||||
srcs = ["samsm2m.go"],
|
||||
importpath = "github.com/sourcegraph/sourcegraph/cmd/enterprise-portal/internal/samsm2m",
|
||||
visibility = ["//cmd/enterprise-portal:__subpackages__"],
|
||||
deps = [
|
||||
"//cmd/enterprise-portal/internal/connectutil",
|
||||
"//internal/authbearer",
|
||||
"//lib/errors",
|
||||
"@com_connectrpc_connect//:connect",
|
||||
"@com_github_sourcegraph_log//:log",
|
||||
"@com_github_sourcegraph_sourcegraph_accounts_sdk_go//:sourcegraph-accounts-sdk-go",
|
||||
"@com_github_sourcegraph_sourcegraph_accounts_sdk_go//scopes",
|
||||
"@io_opentelemetry_go_otel//:otel",
|
||||
"@io_opentelemetry_go_otel//attribute",
|
||||
"@io_opentelemetry_go_otel//codes",
|
||||
"@io_opentelemetry_go_otel_trace//:trace",
|
||||
],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "samsm2m_test",
|
||||
srcs = ["samsm2m_test.go"],
|
||||
embed = [":samsm2m"],
|
||||
deps = [
|
||||
"//lib/errors",
|
||||
"@com_github_hexops_autogold_v2//:autogold",
|
||||
"@com_github_sourcegraph_log//logtest",
|
||||
"@com_github_sourcegraph_sourcegraph_accounts_sdk_go//:sourcegraph-accounts-sdk-go",
|
||||
"@com_github_sourcegraph_sourcegraph_accounts_sdk_go//scopes",
|
||||
"@com_github_stretchr_testify//assert",
|
||||
"@com_github_stretchr_testify//require",
|
||||
],
|
||||
)
|
||||
99
cmd/enterprise-portal/internal/samsm2m/samsm2m.go
Normal file
99
cmd/enterprise-portal/internal/samsm2m/samsm2m.go
Normal file
@ -0,0 +1,99 @@
|
||||
package samsm2m
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
"connectrpc.com/connect"
|
||||
"go.opentelemetry.io/otel"
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
otelcodes "go.opentelemetry.io/otel/codes"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
|
||||
"github.com/sourcegraph/log"
|
||||
|
||||
sams "github.com/sourcegraph/sourcegraph-accounts-sdk-go"
|
||||
"github.com/sourcegraph/sourcegraph-accounts-sdk-go/scopes"
|
||||
|
||||
"github.com/sourcegraph/sourcegraph/cmd/enterprise-portal/internal/connectutil"
|
||||
"github.com/sourcegraph/sourcegraph/internal/authbearer"
|
||||
"github.com/sourcegraph/sourcegraph/lib/errors"
|
||||
)
|
||||
|
||||
// EnterprisePortalScope returns the Enterprise Portal service scope for the
|
||||
// given permission and action.
|
||||
func EnterprisePortalScope(permission scopes.Permission, action scopes.Action) scopes.Scope {
|
||||
return scopes.ToScope(scopes.ServiceEnterprisePortal, permission, action)
|
||||
}
|
||||
|
||||
var tracer = otel.GetTracerProvider().Tracer("telemetry-gateway/samsm2m")
|
||||
|
||||
type TokenIntrospector interface {
|
||||
IntrospectToken(ctx context.Context, token string) (*sams.IntrospectTokenResponse, error)
|
||||
}
|
||||
|
||||
type Request interface {
|
||||
Header() http.Header
|
||||
}
|
||||
|
||||
// RequireScope ensures the request context has a valid SAMS M2M token
|
||||
// with requiredScope. It returns a ConnectRPC status error suitable to be
|
||||
// returned directly from a ConnectRPC implementation.
|
||||
//
|
||||
// See: go/sams-m2m
|
||||
func RequireScope(ctx context.Context, logger log.Logger, tokens TokenIntrospector, requiredScope scopes.Scope, req Request) (err error) {
|
||||
logger = logger.Scoped("samsm2m")
|
||||
|
||||
var span trace.Span
|
||||
ctx, span = tracer.Start(ctx, "RequireScope")
|
||||
defer func() {
|
||||
if err != nil {
|
||||
span.RecordError(err)
|
||||
span.SetStatus(otelcodes.Error, "check failed")
|
||||
}
|
||||
span.End()
|
||||
}()
|
||||
|
||||
var token string
|
||||
if v := req.Header().Values("authorization"); len(v) == 1 && v[0] != "" {
|
||||
var err error
|
||||
token, err = authbearer.ExtractBearerContents(v[0])
|
||||
if err != nil {
|
||||
return connect.NewError(connect.CodeUnauthenticated,
|
||||
errors.Wrap(err, "invalid authorization header"))
|
||||
}
|
||||
} else {
|
||||
return connect.NewError(connect.CodeUnauthenticated,
|
||||
errors.New("no authorization header"))
|
||||
}
|
||||
|
||||
// TODO: as part of go/sams-m2m we need to build out a SDK for SAMS M2M
|
||||
// consumers that has a recommended short-caching mechanism. Avoid doing it
|
||||
// for now until we have a concerted effort.
|
||||
result, err := tokens.IntrospectToken(ctx, token)
|
||||
if err != nil {
|
||||
return connectutil.InternalError(ctx, logger, err, "unable to validate token")
|
||||
}
|
||||
span.SetAttributes(attribute.String("client_id", result.ClientID))
|
||||
|
||||
// Active encapsulates whether the token is active, including expiration.
|
||||
if !result.Active {
|
||||
// Record detailed error in span, and return an opaque one
|
||||
span.SetAttributes(attribute.String("full_error", "inactive token"))
|
||||
return connect.NewError(connect.CodePermissionDenied, errors.New("permission denied"))
|
||||
}
|
||||
|
||||
// Check for our required scope.
|
||||
if !result.Scopes.Match(requiredScope) {
|
||||
// Record detailed error in span and logs
|
||||
err = errors.Newf("got scopes %+v, required: %+v", result.Scopes, requiredScope)
|
||||
span.SetAttributes(attribute.String("full_error", err.Error()))
|
||||
logger.Error("attempt to authenticate using SAMS token without required scope",
|
||||
log.String("clientID", result.ClientID),
|
||||
log.Error(err))
|
||||
// Return an opaque error
|
||||
return connect.NewError(connect.CodePermissionDenied, errors.New("insufficient scope"))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
108
cmd/enterprise-portal/internal/samsm2m/samsm2m_test.go
Normal file
108
cmd/enterprise-portal/internal/samsm2m/samsm2m_test.go
Normal file
@ -0,0 +1,108 @@
|
||||
package samsm2m
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/hexops/autogold/v2"
|
||||
"github.com/sourcegraph/log/logtest"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
sams "github.com/sourcegraph/sourcegraph-accounts-sdk-go"
|
||||
"github.com/sourcegraph/sourcegraph-accounts-sdk-go/scopes"
|
||||
"github.com/sourcegraph/sourcegraph/lib/errors"
|
||||
)
|
||||
|
||||
type mockSAMSClient struct {
|
||||
result *sams.IntrospectTokenResponse
|
||||
error error
|
||||
}
|
||||
|
||||
func (m mockSAMSClient) IntrospectToken(context.Context, string) (*sams.IntrospectTokenResponse, error) {
|
||||
return m.result, m.error
|
||||
}
|
||||
|
||||
type request map[string]string
|
||||
|
||||
func (r request) Header() http.Header {
|
||||
h := make(http.Header)
|
||||
for k, v := range r {
|
||||
h.Add(k, v)
|
||||
}
|
||||
return h
|
||||
}
|
||||
|
||||
func TestRequireScope(t *testing.T) {
|
||||
requiredScope := EnterprisePortalScope("codyaccess", scopes.ActionRead)
|
||||
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
metadata map[string]string
|
||||
samsClient TokenIntrospector
|
||||
wantErr autogold.Value
|
||||
}{
|
||||
{
|
||||
name: "no metadata",
|
||||
metadata: nil,
|
||||
samsClient: nil, // will not be used
|
||||
wantErr: autogold.Expect("unauthenticated: no authorization header"),
|
||||
},
|
||||
{
|
||||
name: "no authorization header",
|
||||
metadata: map[string]string{"somethingelse": "foobar"},
|
||||
samsClient: nil, // will not be used
|
||||
wantErr: autogold.Expect("unauthenticated: no authorization header"),
|
||||
},
|
||||
{
|
||||
name: "malformed authorization header",
|
||||
metadata: map[string]string{"authorization": "bearer"},
|
||||
samsClient: nil, // will not be used
|
||||
wantErr: autogold.Expect("unauthenticated: invalid authorization header: token type missing in Authorization header"),
|
||||
},
|
||||
{
|
||||
name: "token ok, introspect failed",
|
||||
metadata: map[string]string{"authorization": "bearer foobar"},
|
||||
samsClient: mockSAMSClient{error: errors.New("introspection failed")},
|
||||
wantErr: autogold.Expect("internal: unable to validate token"),
|
||||
},
|
||||
{
|
||||
name: "token ok, but inactive",
|
||||
metadata: map[string]string{"authorization": "bearer foobar"},
|
||||
samsClient: mockSAMSClient{result: &sams.IntrospectTokenResponse{Active: false}},
|
||||
wantErr: autogold.Expect("permission_denied: permission denied"),
|
||||
},
|
||||
{
|
||||
name: "token ok and active, but invalid scope",
|
||||
metadata: map[string]string{"authorization": "bearer foobar"},
|
||||
samsClient: mockSAMSClient{result: &sams.IntrospectTokenResponse{
|
||||
Active: true,
|
||||
Scopes: scopes.ToScopes([]string{"foo", "bar"}),
|
||||
}},
|
||||
wantErr: autogold.Expect("permission_denied: insufficient scope"),
|
||||
},
|
||||
{
|
||||
name: "token ok and active and valid scope",
|
||||
metadata: map[string]string{"authorization": "bearer foobar"},
|
||||
samsClient: mockSAMSClient{
|
||||
result: &sams.IntrospectTokenResponse{
|
||||
Active: true,
|
||||
Scopes: append(scopes.ToScopes([]string{"foo", "bar"}), requiredScope),
|
||||
},
|
||||
},
|
||||
wantErr: nil, // success
|
||||
},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
err := RequireScope(ctx, logtest.Scoped(t), tc.samsClient, requiredScope, request(tc.metadata))
|
||||
if tc.wantErr == nil {
|
||||
assert.NoError(t, err)
|
||||
} else {
|
||||
require.Error(t, err)
|
||||
tc.wantErr.Equal(t, err.Error())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,12 @@
|
||||
load("@io_bazel_rules_go//go:def.bzl", "go_library")
|
||||
|
||||
go_library(
|
||||
name = "subscriptionsservice",
|
||||
srcs = ["v1.go"],
|
||||
importpath = "github.com/sourcegraph/sourcegraph/cmd/enterprise-portal/internal/subscriptionsservice",
|
||||
visibility = ["//cmd/enterprise-portal:__subpackages__"],
|
||||
deps = [
|
||||
"//lib/enterpriseportal/subscriptions/v1/v1connect",
|
||||
"@com_github_sourcegraph_log//:log",
|
||||
],
|
||||
)
|
||||
25
cmd/enterprise-portal/internal/subscriptionsservice/v1.go
Normal file
25
cmd/enterprise-portal/internal/subscriptionsservice/v1.go
Normal file
@ -0,0 +1,25 @@
|
||||
package subscriptionsservice
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/sourcegraph/log"
|
||||
|
||||
subscriptionsv1connect "github.com/sourcegraph/sourcegraph/lib/enterpriseportal/subscriptions/v1/v1connect"
|
||||
)
|
||||
|
||||
const Name = subscriptionsv1connect.SubscriptionsServiceName
|
||||
|
||||
func RegisterV1(logger log.Logger, mux *http.ServeMux) {
|
||||
mux.Handle(subscriptionsv1connect.NewSubscriptionsServiceHandler(&handlerV1{
|
||||
logger: logger.Scoped("subscriptions.v1"),
|
||||
}))
|
||||
}
|
||||
|
||||
type handlerV1 struct {
|
||||
subscriptionsv1connect.UnimplementedSubscriptionsServiceHandler
|
||||
|
||||
logger log.Logger
|
||||
}
|
||||
|
||||
var _ subscriptionsv1connect.SubscriptionsServiceHandler = (*handlerV1)(nil)
|
||||
@ -11,7 +11,10 @@ go_library(
|
||||
importpath = "github.com/sourcegraph/sourcegraph/cmd/enterprise-portal/service",
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"//cmd/enterprise-portal/internal/codyaccessservice",
|
||||
"//cmd/enterprise-portal/internal/dotcomdb",
|
||||
"//cmd/enterprise-portal/internal/subscriptionsservice",
|
||||
"//internal/debugserver",
|
||||
"//internal/httpserver",
|
||||
"//internal/trace/policy",
|
||||
"//internal/version",
|
||||
@ -19,7 +22,13 @@ go_library(
|
||||
"//lib/errors",
|
||||
"//lib/managedservicesplatform/cloudsql",
|
||||
"//lib/managedservicesplatform/runtime",
|
||||
"@com_connectrpc_grpcreflect//:grpcreflect",
|
||||
"@com_github_jackc_pgx_v5//:pgx",
|
||||
"@com_github_sourcegraph_log//:log",
|
||||
"@com_github_sourcegraph_sourcegraph_accounts_sdk_go//:sourcegraph-accounts-sdk-go",
|
||||
"@com_github_sourcegraph_sourcegraph_accounts_sdk_go//scopes",
|
||||
"@io_opentelemetry_go_contrib_instrumentation_net_http_otelhttp//:otelhttp",
|
||||
"@org_golang_x_net//http2",
|
||||
"@org_golang_x_net//http2/h2c",
|
||||
],
|
||||
)
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
sams "github.com/sourcegraph/sourcegraph-accounts-sdk-go"
|
||||
"github.com/sourcegraph/sourcegraph/lib/managedservicesplatform/cloudsql"
|
||||
"github.com/sourcegraph/sourcegraph/lib/managedservicesplatform/runtime"
|
||||
)
|
||||
@ -12,6 +13,14 @@ type Config struct {
|
||||
|
||||
PGDSNOverride *string
|
||||
}
|
||||
|
||||
SAMS SAMSConfig
|
||||
}
|
||||
|
||||
type SAMSConfig struct {
|
||||
sams.ConnConfig
|
||||
ClientID string
|
||||
ClientSecret string
|
||||
}
|
||||
|
||||
func (c *Config) Load(env *runtime.Env) {
|
||||
@ -23,4 +32,10 @@ func (c *Config) Load(env *runtime.Env) {
|
||||
}
|
||||
c.DotComDB.PGDSNOverride = env.GetOptional("DOTCOM_PGDSN_OVERRIDE",
|
||||
"For local dev: custom PostgreSQL DSN, overrides DOTCOM_CLOUDSQL_* options")
|
||||
|
||||
c.SAMS.ConnConfig = sams.NewConnConfigFromEnv(env)
|
||||
c.SAMS.ClientID = env.Get("ENTERPRISE_PORTAL_SAMS_CLIENT_ID", "",
|
||||
"Sourcegraph Accounts Management System client ID")
|
||||
c.SAMS.ClientSecret = env.Get("ENTERPRISE_PORTAL_SAMS_CLIENT_SECRET", "",
|
||||
"Sourcegraph Accounts Management System client secret")
|
||||
}
|
||||
|
||||
@ -6,8 +6,19 @@ import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"connectrpc.com/grpcreflect"
|
||||
"github.com/sourcegraph/log"
|
||||
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
|
||||
"golang.org/x/net/http2"
|
||||
"golang.org/x/net/http2/h2c"
|
||||
|
||||
"github.com/sourcegraph/sourcegraph/cmd/enterprise-portal/internal/codyaccessservice"
|
||||
"github.com/sourcegraph/sourcegraph/cmd/enterprise-portal/internal/subscriptionsservice"
|
||||
|
||||
sams "github.com/sourcegraph/sourcegraph-accounts-sdk-go"
|
||||
"github.com/sourcegraph/sourcegraph-accounts-sdk-go/scopes"
|
||||
|
||||
"github.com/sourcegraph/sourcegraph/internal/debugserver"
|
||||
"github.com/sourcegraph/sourcegraph/internal/httpserver"
|
||||
"github.com/sourcegraph/sourcegraph/internal/trace/policy"
|
||||
"github.com/sourcegraph/sourcegraph/internal/version"
|
||||
@ -32,27 +43,94 @@ func (Service) Initialize(ctx context.Context, logger log.Logger, contract runti
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "newDotComDBConn")
|
||||
}
|
||||
defer dotcomDB.Close(context.Background())
|
||||
|
||||
// Validate connection on startup
|
||||
if err := dotcomDB.Ping(context.Background()); err != nil {
|
||||
if err := dotcomDB.Ping(ctx); err != nil {
|
||||
return nil, errors.Wrap(err, "dotcomDB.Ping")
|
||||
}
|
||||
logger.Debug("connected to dotcom database")
|
||||
|
||||
// Prepare SAMS client, so that we can enforce SAMS-based M2M authz/authn
|
||||
logger.Debug("using SAMS client",
|
||||
log.String("samsExternalURL", config.SAMS.ExternalURL),
|
||||
log.Stringp("samsAPIURL", config.SAMS.APIURL),
|
||||
log.String("clientID", config.SAMS.ClientID))
|
||||
samsClient, err := sams.NewClientV1(
|
||||
sams.ClientV1Config{
|
||||
ConnConfig: config.SAMS.ConnConfig,
|
||||
TokenSource: sams.ClientCredentialsTokenSource(
|
||||
config.SAMS.ConnConfig,
|
||||
config.SAMS.ClientID,
|
||||
config.SAMS.ClientSecret,
|
||||
[]scopes.Scope{scopes.OpenID, scopes.Profile, scopes.Email},
|
||||
),
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "create Sourcegraph Accounts client")
|
||||
}
|
||||
|
||||
httpServer := http.NewServeMux()
|
||||
|
||||
// Register MSP endpoints
|
||||
contract.Diagnostics.RegisterDiagnosticsHandlers(httpServer, serviceState{})
|
||||
|
||||
// Initialize server
|
||||
// Register connect endpoints
|
||||
codyaccessservice.RegisterV1(logger, httpServer, samsClient.Tokens(), dotcomDB)
|
||||
subscriptionsservice.RegisterV1(logger, httpServer)
|
||||
|
||||
listenAddr := fmt.Sprintf(":%d", contract.Port)
|
||||
if !contract.MSP && debugserver.GRPCWebUIEnabled {
|
||||
// Enable reflection for the web UI
|
||||
reflector := grpcreflect.NewStaticReflector(
|
||||
codyaccessservice.Name,
|
||||
subscriptionsservice.Name,
|
||||
)
|
||||
httpServer.Handle(grpcreflect.NewHandlerV1(reflector))
|
||||
httpServer.Handle(grpcreflect.NewHandlerV1Alpha(reflector)) // web UI still requires old API
|
||||
// Enable the web UI
|
||||
grpcUI := debugserver.NewGRPCWebUIEndpoint("enterprise-portal", listenAddr)
|
||||
httpServer.Handle(grpcUI.Path, grpcUI.Handler)
|
||||
logger.Warn("gRPC web UI enabled", log.String("url", fmt.Sprintf("%s%s", listenAddr, grpcUI.Path)))
|
||||
}
|
||||
|
||||
// Initialize server
|
||||
server := httpserver.NewFromAddr(
|
||||
listenAddr,
|
||||
&http.Server{
|
||||
ReadTimeout: 2 * time.Minute,
|
||||
WriteTimeout: 2 * time.Minute,
|
||||
Handler: httpServer,
|
||||
Addr: listenAddr,
|
||||
// Cloud Run only supports HTTP/2 if the service accepts HTTP/2 cleartext (h2c),
|
||||
// see https://cloud.google.com/run/docs/configuring/http2
|
||||
Handler: h2c.NewHandler(
|
||||
otelhttp.NewHandler(
|
||||
httpServer,
|
||||
"handler",
|
||||
// Don't trust incoming spans, start our own.
|
||||
otelhttp.WithPublicEndpoint(),
|
||||
// Generate custom span names from the request, the default is very vague.
|
||||
otelhttp.WithSpanNameFormatter(func(_ string, r *http.Request) string {
|
||||
// Prefix with 'handle' because outgoing HTTP requests can have similar-looking
|
||||
// spans.
|
||||
return fmt.Sprintf("handle.%s %s", r.Method, r.URL.Path)
|
||||
}),
|
||||
),
|
||||
&http2.Server{},
|
||||
),
|
||||
ReadTimeout: 30 * time.Second,
|
||||
WriteTimeout: time.Minute,
|
||||
},
|
||||
)
|
||||
return server, nil
|
||||
return background.LIFOStopRoutine{
|
||||
background.CallbackRoutine{
|
||||
StopFunc: func(ctx context.Context) error {
|
||||
start := time.Now()
|
||||
if err := dotcomDB.Close(ctx); err != nil {
|
||||
return errors.Wrap(err, "dotcomDB.Close")
|
||||
}
|
||||
logger.Info("database stopped", log.Duration("elapsed", time.Since(start)))
|
||||
return nil
|
||||
},
|
||||
},
|
||||
server, // stop server first
|
||||
}, nil
|
||||
}
|
||||
|
||||
@ -35,6 +35,10 @@ func (s *subscriptionsDB) UpdateCodyGatewayAccess(ctx context.Context, id string
|
||||
})
|
||||
}
|
||||
|
||||
func (s *subscriptionsDB) Archive(ctx context.Context, id string) error {
|
||||
return productsubscription.NewSubscriptionsDB(s.db).Archive(ctx, id)
|
||||
}
|
||||
|
||||
// NewSubscriptionsDB returns a new SubscriptionsDB backed by the given database.DB.
|
||||
// It requires testing.T to indicate that it should only be used in tests.
|
||||
//
|
||||
@ -57,6 +61,19 @@ func NewLicensesDB(t *testing.T, db database.DB) LicensesDB {
|
||||
return productsubscription.NewLicensesDB(db)
|
||||
}
|
||||
|
||||
type TokensDB interface {
|
||||
LookupProductSubscriptionIDByAccessToken(ctx context.Context, token string) (string, error)
|
||||
}
|
||||
|
||||
// NewTokensDB returns a new TokensDB backed by the given database.DB.
|
||||
// It requires testing.T to indicate that it should only be used in tests.
|
||||
//
|
||||
// See package docs for more details.
|
||||
func NewTokensDB(t *testing.T, db database.DB) TokensDB {
|
||||
t.Helper()
|
||||
return productsubscription.NewTokensDB(db)
|
||||
}
|
||||
|
||||
type mockAdminFetcher struct{}
|
||||
|
||||
func (mockAdminFetcher) GetByID(context.Context, int32) (*types.User, error) {
|
||||
|
||||
@ -61,7 +61,7 @@ func (r CodyGatewayDotcomUserResolver) CodyGatewayDotcomUserByToken(ctx context.
|
||||
return nil, err
|
||||
}
|
||||
|
||||
dbTokens := newDBTokens(r.DB)
|
||||
dbTokens := NewTokensDB(r.DB)
|
||||
userID, err := dbTokens.LookupDotcomUserIDByAccessToken(ctx, args.Token)
|
||||
if err != nil {
|
||||
if errcode.IsNotFound(err) {
|
||||
|
||||
@ -22,7 +22,9 @@ type dbTokens struct {
|
||||
store *basestore.Store
|
||||
}
|
||||
|
||||
func newDBTokens(db database.DB) dbTokens {
|
||||
// For package dotcomproductsubscriptiontest only; DO NOT USE from outside this
|
||||
// package.
|
||||
func NewTokensDB(db database.DB) dbTokens {
|
||||
return dbTokens{store: basestore.NewWithHandle(db.Handle())}
|
||||
}
|
||||
|
||||
|
||||
@ -43,7 +43,7 @@ func TestLookupProductSubscriptionIDByAccessToken(t *testing.T) {
|
||||
|
||||
accessToken := license.GenerateLicenseKeyBasedAccessToken(lc.LicenseKey)
|
||||
|
||||
gotPS, err := newDBTokens(db).LookupProductSubscriptionIDByAccessToken(ctx, accessToken)
|
||||
gotPS, err := NewTokensDB(db).LookupProductSubscriptionIDByAccessToken(ctx, accessToken)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, gotPS, ps)
|
||||
})
|
||||
@ -55,7 +55,7 @@ func TestLookupProductSubscriptionIDByAccessToken(t *testing.T) {
|
||||
accessToken := license.GenerateLicenseKeyBasedAccessToken(lc.LicenseKey)
|
||||
accessToken = productsubscription.AccessTokenPrefix + accessToken[len(license.LicenseKeyBasedAccessTokenPrefix):]
|
||||
|
||||
gotPS, err := newDBTokens(db).LookupProductSubscriptionIDByAccessToken(ctx, accessToken)
|
||||
gotPS, err := NewTokensDB(db).LookupProductSubscriptionIDByAccessToken(ctx, accessToken)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, gotPS, ps)
|
||||
})
|
||||
@ -95,7 +95,7 @@ func TestLookupProductSubscriptionIDByAccessToken(t *testing.T) {
|
||||
t.Fatal("last_used_at was not nil upon token creation")
|
||||
}
|
||||
|
||||
dbTokens := newDBTokens(db)
|
||||
dbTokens := NewTokensDB(db)
|
||||
|
||||
// Call LookupDotcomUserIDByAccessToken. This will have a side-effect of updating the
|
||||
// token's last_used_at column.
|
||||
|
||||
@ -32,7 +32,7 @@ func (r ProductSubscriptionLicensingResolver) ProductSubscriptionByAccessToken(c
|
||||
return nil, err
|
||||
}
|
||||
|
||||
subID, err := newDBTokens(r.DB).LookupProductSubscriptionIDByAccessToken(ctx, args.AccessToken)
|
||||
subID, err := NewTokensDB(r.DB).LookupProductSubscriptionIDByAccessToken(ctx, args.AccessToken)
|
||||
if err != nil {
|
||||
if errcode.IsNotFound(err) {
|
||||
return nil, ErrProductSubscriptionNotFound{err}
|
||||
|
||||
28
deps.bzl
28
deps.bzl
@ -44,6 +44,13 @@ def go_dependencies():
|
||||
sum = "h1:rOdrK/RTI/7TVnn3JsVxt3n028MlTRwmK5Q4heSpjis=",
|
||||
version = "v1.16.1",
|
||||
)
|
||||
go_repository(
|
||||
name = "com_connectrpc_grpcreflect",
|
||||
build_file_proto_mode = "disable_global",
|
||||
importpath = "connectrpc.com/grpcreflect",
|
||||
sum = "h1:Q6og1S7HinmtbEuBvARLNwYmTbhEGRpHDhqrPNlmK+U=",
|
||||
version = "v1.2.0",
|
||||
)
|
||||
go_repository(
|
||||
name = "com_connectrpc_otelconnect",
|
||||
build_file_proto_mode = "disable_global",
|
||||
@ -5696,11 +5703,12 @@ def go_dependencies():
|
||||
name = "com_github_sourcegraph_sourcegraph_accounts_sdk_go",
|
||||
build_directives = [
|
||||
"gazelle:resolve go github.com/sourcegraph/sourcegraph/lib/errors @//lib/errors",
|
||||
"gazelle:resolve go github.com/sourcegraph/sourcegraph/lib/background @//lib/background",
|
||||
],
|
||||
build_file_proto_mode = "disable_global",
|
||||
importpath = "github.com/sourcegraph/sourcegraph-accounts-sdk-go",
|
||||
sum = "h1:lOQJ+wDbQ5lSBuAv6GgCuoFKucte5k2bPf1a7navsd0=",
|
||||
version = "v0.0.0-20240426173441-db5b0a145ceb",
|
||||
sum = "h1:55o/Oo+gFRmE5tmFod6M/koth7RFtgRxfApjBxxtORI=",
|
||||
version = "v0.0.0-20240524154739-87189364d07f",
|
||||
)
|
||||
go_repository(
|
||||
name = "com_github_sourcegraph_zoekt",
|
||||
@ -6322,8 +6330,8 @@ def go_dependencies():
|
||||
name = "com_google_cloud_go",
|
||||
build_file_proto_mode = "disable_global",
|
||||
importpath = "cloud.google.com/go",
|
||||
sum = "h1:tpFCD7hpHFlQ8yPwT3x+QeXqc2T6+n6T+hmABHfDUSM=",
|
||||
version = "v0.112.0",
|
||||
sum = "h1:uJSeirPke5UNZHIb4SxfZklVSiWWVqW4oXlETwZziwM=",
|
||||
version = "v0.112.1",
|
||||
)
|
||||
go_repository(
|
||||
name = "com_google_cloud_go_accessapproval",
|
||||
@ -6924,8 +6932,8 @@ def go_dependencies():
|
||||
name = "com_google_cloud_go_pubsub",
|
||||
build_file_proto_mode = "disable_global",
|
||||
importpath = "cloud.google.com/go/pubsub",
|
||||
sum = "h1:dfEPuGCHGbWUhaMCTHUFjfroILEkx55iUmKBZTP5f+Y=",
|
||||
version = "v1.36.1",
|
||||
sum = "h1:0uEEfaB1VIJzabPpwpZf44zWAKAme3zwKKxHk7vJQxQ=",
|
||||
version = "v1.37.0",
|
||||
)
|
||||
go_repository(
|
||||
name = "com_google_cloud_go_pubsublite",
|
||||
@ -7050,8 +7058,8 @@ def go_dependencies():
|
||||
name = "com_google_cloud_go_storage",
|
||||
build_file_proto_mode = "disable_global",
|
||||
importpath = "cloud.google.com/go/storage",
|
||||
sum = "h1:WI8CsaFO8Q9KjPVtsZ5Cmi0dXV25zMoX0FklT7c3Jm4=",
|
||||
version = "v1.37.0",
|
||||
sum = "h1:Az68ZRGlnNTpIBbLjSMIV2BDcwwXYlRlQzis0llkpJg=",
|
||||
version = "v1.38.0",
|
||||
)
|
||||
go_repository(
|
||||
name = "com_google_cloud_go_storagetransfer",
|
||||
@ -8059,8 +8067,8 @@ def go_dependencies():
|
||||
name = "org_golang_google_protobuf",
|
||||
build_file_proto_mode = "disable_global",
|
||||
importpath = "google.golang.org/protobuf",
|
||||
sum = "h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=",
|
||||
version = "v1.33.0",
|
||||
sum = "h1:Qo/qEd2RZPCf2nKuorzksSknv0d3ERwp1vFG38gSmH4=",
|
||||
version = "v1.34.0",
|
||||
)
|
||||
go_repository(
|
||||
name = "org_golang_x_crypto",
|
||||
|
||||
13
go.mod
13
go.mod
@ -60,9 +60,9 @@ require (
|
||||
cloud.google.com/go/kms v1.15.7
|
||||
cloud.google.com/go/monitoring v1.18.0
|
||||
cloud.google.com/go/profiler v0.4.0
|
||||
cloud.google.com/go/pubsub v1.36.1
|
||||
cloud.google.com/go/pubsub v1.37.0
|
||||
cloud.google.com/go/secretmanager v1.11.5
|
||||
cloud.google.com/go/storage v1.37.0
|
||||
cloud.google.com/go/storage v1.38.0
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.45.0
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/trace v1.21.0
|
||||
github.com/Khan/genqlient v0.5.0
|
||||
@ -230,7 +230,7 @@ require (
|
||||
gonum.org/v1/gonum v0.14.0
|
||||
google.golang.org/api v0.169.0
|
||||
google.golang.org/genproto v0.0.0-20240213162025-012b6fc9bca9 // indirect
|
||||
google.golang.org/protobuf v1.33.0
|
||||
google.golang.org/protobuf v1.34.0
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1
|
||||
gopkg.in/yaml.v2 v2.4.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
@ -248,6 +248,7 @@ require (
|
||||
chainguard.dev/apko v0.14.0
|
||||
cloud.google.com/go/artifactregistry v1.14.8
|
||||
connectrpc.com/connect v1.16.1
|
||||
connectrpc.com/grpcreflect v1.2.0
|
||||
github.com/Azure/azure-sdk-for-go/sdk/ai/azopenai v0.5.0
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.9.2
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.4.0
|
||||
@ -299,8 +300,8 @@ require (
|
||||
github.com/sourcegraph/managed-services-platform-cdktf/gen/tfe v0.0.0-20240513203650-e2b1273f1c1a
|
||||
github.com/sourcegraph/notionreposync v0.0.0-20240510213306-87052870048d
|
||||
github.com/sourcegraph/scip v0.3.3
|
||||
github.com/sourcegraph/sourcegraph-accounts-sdk-go v0.0.0-20240426173441-db5b0a145ceb
|
||||
github.com/sourcegraph/sourcegraph/lib v0.0.0-20240422195121-52350cd2e507
|
||||
github.com/sourcegraph/sourcegraph-accounts-sdk-go v0.0.0-20240524154739-87189364d07f
|
||||
github.com/sourcegraph/sourcegraph/lib v0.0.0-20240524140455-2589fef13ea8
|
||||
github.com/sourcegraph/sourcegraph/lib/managedservicesplatform v0.0.0-00010101000000-000000000000
|
||||
github.com/sourcegraph/sourcegraph/monitoring v0.0.0-00010101000000-000000000000
|
||||
github.com/vektah/gqlparser/v2 v2.4.5
|
||||
@ -438,7 +439,7 @@ require (
|
||||
|
||||
require (
|
||||
bitbucket.org/creachadair/shell v0.0.7 // indirect
|
||||
cloud.google.com/go v0.112.0 // indirect
|
||||
cloud.google.com/go v0.112.1 // indirect
|
||||
cloud.google.com/go/compute v1.24.0 // indirect
|
||||
cloud.google.com/go/iam v1.1.6 // indirect
|
||||
cuelang.org/go v0.4.3
|
||||
|
||||
22
go.sum
22
go.sum
@ -25,8 +25,8 @@ cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmW
|
||||
cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg=
|
||||
cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8=
|
||||
cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0=
|
||||
cloud.google.com/go v0.112.0 h1:tpFCD7hpHFlQ8yPwT3x+QeXqc2T6+n6T+hmABHfDUSM=
|
||||
cloud.google.com/go v0.112.0/go.mod h1:3jEEVwZ/MHU4djK5t5RHuKOA/GbLddgTdVubX1qnPD4=
|
||||
cloud.google.com/go v0.112.1 h1:uJSeirPke5UNZHIb4SxfZklVSiWWVqW4oXlETwZziwM=
|
||||
cloud.google.com/go v0.112.1/go.mod h1:+Vbu+Y1UU+I1rjmzeMOb/8RfkKJK2Gyxi1X6jJCZLo4=
|
||||
cloud.google.com/go/artifactregistry v1.14.8 h1:icIyRzJ1Ag6EOafuDuFFJ/AdStcOFRVfSGURn27/7Pk=
|
||||
cloud.google.com/go/artifactregistry v1.14.8/go.mod h1:1UlSXh6sTXYrIT4kMO21AE1IDlMFemlZuX6QS+JXW7I=
|
||||
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
|
||||
@ -64,8 +64,8 @@ cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2k
|
||||
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
|
||||
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
|
||||
cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
|
||||
cloud.google.com/go/pubsub v1.36.1 h1:dfEPuGCHGbWUhaMCTHUFjfroILEkx55iUmKBZTP5f+Y=
|
||||
cloud.google.com/go/pubsub v1.36.1/go.mod h1:iYjCa9EzWOoBiTdd4ps7QoMtMln5NwaZQpK1hbRfBDE=
|
||||
cloud.google.com/go/pubsub v1.37.0 h1:0uEEfaB1VIJzabPpwpZf44zWAKAme3zwKKxHk7vJQxQ=
|
||||
cloud.google.com/go/pubsub v1.37.0/go.mod h1:YQOQr1uiUM092EXwKs56OPT650nwnawc+8/IjoUeGzQ=
|
||||
cloud.google.com/go/secretmanager v1.11.5 h1:82fpF5vBBvu9XW4qj0FU2C6qVMtj1RM/XHwKXUEAfYY=
|
||||
cloud.google.com/go/secretmanager v1.11.5/go.mod h1:eAGv+DaCHkeVyQi0BeXgAHOU0RdrMeZIASKc+S7VqH4=
|
||||
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
|
||||
@ -73,12 +73,14 @@ cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0Zeo
|
||||
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
|
||||
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
|
||||
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
|
||||
cloud.google.com/go/storage v1.37.0 h1:WI8CsaFO8Q9KjPVtsZ5Cmi0dXV25zMoX0FklT7c3Jm4=
|
||||
cloud.google.com/go/storage v1.37.0/go.mod h1:i34TiT2IhiNDmcj65PqwCjcoUX7Z5pLzS8DEmoiFq1k=
|
||||
cloud.google.com/go/storage v1.38.0 h1:Az68ZRGlnNTpIBbLjSMIV2BDcwwXYlRlQzis0llkpJg=
|
||||
cloud.google.com/go/storage v1.38.0/go.mod h1:tlUADB0mAb9BgYls9lq+8MGkfzOXuLrnHXlpHmvFJoY=
|
||||
cloud.google.com/go/trace v1.10.5 h1:0pr4lIKJ5XZFYD9GtxXEWr0KkVeigc3wlGpZco0X1oA=
|
||||
cloud.google.com/go/trace v1.10.5/go.mod h1:9hjCV1nGBCtXbAE4YK7OqJ8pmPYSxPA0I67JwRd5s3M=
|
||||
connectrpc.com/connect v1.16.1 h1:rOdrK/RTI/7TVnn3JsVxt3n028MlTRwmK5Q4heSpjis=
|
||||
connectrpc.com/connect v1.16.1/go.mod h1:XpZAduBQUySsb4/KO5JffORVkDI4B6/EYPi7N8xpNZw=
|
||||
connectrpc.com/grpcreflect v1.2.0 h1:Q6og1S7HinmtbEuBvARLNwYmTbhEGRpHDhqrPNlmK+U=
|
||||
connectrpc.com/grpcreflect v1.2.0/go.mod h1:nwSOKmE8nU5u/CidgHtPYk1PFI3U9ignz7iDMxOYkSY=
|
||||
connectrpc.com/otelconnect v0.7.0 h1:ZH55ZZtcJOTKWWLy3qmL4Pam4RzRWBJFOqTPyAqCXkY=
|
||||
connectrpc.com/otelconnect v0.7.0/go.mod h1:Bt2ivBymHZHqxvo4HkJ0EwHuUzQN6k2l0oH+mp/8nwc=
|
||||
contrib.go.opencensus.io/exporter/prometheus v0.4.2 h1:sqfsYl5GIY/L570iT+l93ehxaWJs2/OwXtiWwew3oAg=
|
||||
@ -1747,8 +1749,8 @@ github.com/sourcegraph/run v0.12.0 h1:3A8w5e8HIYPfafHekvmdmmh42RHKGVhmiTZAPJclg7
|
||||
github.com/sourcegraph/run v0.12.0/go.mod h1:PwaP936BTnAJC1cqR5rSbG5kOs/EWStTK3lqvMX5GUA=
|
||||
github.com/sourcegraph/scip v0.3.3 h1:3EOkChYOntwHl0pPSAju7rj0oRuujh8owC4vjGDEr0s=
|
||||
github.com/sourcegraph/scip v0.3.3/go.mod h1:Q67VaoTpftINIy/CLrkYQOMwlsx67h8ys+ligmdUcqM=
|
||||
github.com/sourcegraph/sourcegraph-accounts-sdk-go v0.0.0-20240426173441-db5b0a145ceb h1:lOQJ+wDbQ5lSBuAv6GgCuoFKucte5k2bPf1a7navsd0=
|
||||
github.com/sourcegraph/sourcegraph-accounts-sdk-go v0.0.0-20240426173441-db5b0a145ceb/go.mod h1:xul4Fiph3Pvdx/1qsmhCUL2GBeYjTcnga0LXZEbKdGo=
|
||||
github.com/sourcegraph/sourcegraph-accounts-sdk-go v0.0.0-20240524154739-87189364d07f h1:55o/Oo+gFRmE5tmFod6M/koth7RFtgRxfApjBxxtORI=
|
||||
github.com/sourcegraph/sourcegraph-accounts-sdk-go v0.0.0-20240524154739-87189364d07f/go.mod h1:+yFgPzr01Ks+pcvFlStxKtUp4Wq//BqQEJDnZczD3h0=
|
||||
github.com/sourcegraph/yaml v1.0.1-0.20200714132230-56936252f152 h1:z/MpntplPaW6QW95pzcAR/72Z5TWDyDnSo0EOcyij9o=
|
||||
github.com/sourcegraph/yaml v1.0.1-0.20200714132230-56936252f152/go.mod h1:GIjDIg/heH5DOkXY3YJ/wNhfHsQHoXGjl8G8amsYQ1I=
|
||||
github.com/sourcegraph/zoekt v0.0.0-20240514170004-fe8f2a3d9cab h1:9g0lbxps+Yr99nE2Y8RnhPwV6HUYUxEpHfB+RISFfdU=
|
||||
@ -2615,8 +2617,8 @@ google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQ
|
||||
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
|
||||
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||
google.golang.org/protobuf v1.34.0 h1:Qo/qEd2RZPCf2nKuorzksSknv0d3ERwp1vFG38gSmH4=
|
||||
google.golang.org/protobuf v1.34.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||
gopkg.in/alexcesaro/statsd.v2 v2.0.0 h1:FXkZSCZIH17vLCO5sO2UucTHsH9pc+17F6pl3JVCwMc=
|
||||
gopkg.in/alexcesaro/statsd.v2 v2.0.0/go.mod h1:i0ubccKGzBVNBpdGV5MocxyA/XlLUJzA7SLonnE4drU=
|
||||
|
||||
@ -6,14 +6,17 @@ import (
|
||||
|
||||
"github.com/fullstorydev/grpcui/standalone"
|
||||
"github.com/sourcegraph/log"
|
||||
"github.com/sourcegraph/sourcegraph/internal/env"
|
||||
"google.golang.org/grpc"
|
||||
|
||||
"github.com/sourcegraph/sourcegraph/internal/env"
|
||||
|
||||
"github.com/sourcegraph/sourcegraph/internal/grpc/defaults"
|
||||
"github.com/sourcegraph/sourcegraph/lib/errors"
|
||||
)
|
||||
|
||||
var envEnableGRPCWebUI = env.MustGetBool("GRPC_WEB_UI_ENABLED", false, "Enable the gRPC Web UI to debug and explore gRPC services")
|
||||
// GRPCWebUIEnabled is an additional environment variable that must be true to
|
||||
// enable the gRPC Web UI.
|
||||
var GRPCWebUIEnabled = env.MustGetBool("GRPC_WEB_UI_ENABLED", false, "Enable the gRPC Web UI to debug and explore gRPC services")
|
||||
|
||||
const gRPCWebUIPath = "/debug/grpcui"
|
||||
|
||||
@ -53,7 +56,7 @@ type grpcHandler struct {
|
||||
}
|
||||
|
||||
func (g *grpcHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
if !envEnableGRPCWebUI {
|
||||
if !GRPCWebUIEnabled {
|
||||
http.Error(w, "gRPC Web UI is disabled", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
@ -64,7 +64,8 @@ message CodyGatewayAccessToken {
|
||||
|
||||
message CodyGatewayAccess {
|
||||
// The external, prefixed UUID-format identifier for the Enterprise
|
||||
// subscription corresponding to this Cody Gateway access description.
|
||||
// subscription corresponding to this Cody Gateway access description
|
||||
// (e.g. "es_...").
|
||||
string subscription_id = 1;
|
||||
|
||||
// Whether or not a subscription has Cody Gateway access enabled.
|
||||
|
||||
@ -22,6 +22,7 @@ go_library(
|
||||
srcs = [
|
||||
"subscriptions.pb.go",
|
||||
"subscriptions_grpc.pb.go",
|
||||
"v1.go",
|
||||
],
|
||||
importpath = "github.com/sourcegraph/sourcegraph/lib/enterpriseportal/subscriptions/v1",
|
||||
visibility = ["//visibility:public"],
|
||||
|
||||
@ -56,7 +56,8 @@ message EnterpriseSubscriptionCondition {
|
||||
|
||||
// EnterpriseSubscription represents a Sourcegraph Enterprise subscription.
|
||||
message EnterpriseSubscription {
|
||||
// ID is the external, prefixed UUID-format identifier for this subscription.
|
||||
// ID is the external, prefixed UUID-format identifier for this subscription
|
||||
// (e.g. "es_...").
|
||||
string id = 1;
|
||||
// Timeline of key events corresponding to this subscription.
|
||||
repeated EnterpriseSubscriptionCondition conditions = 2;
|
||||
|
||||
7
lib/enterpriseportal/subscriptions/v1/v1.go
Normal file
7
lib/enterpriseportal/subscriptions/v1/v1.go
Normal file
@ -0,0 +1,7 @@
|
||||
package v1
|
||||
|
||||
const (
|
||||
// EnterpriseSubscriptionIDPrefix is the prefix for a subscription ID
|
||||
// ('es' for 'Enterprise Subscription').
|
||||
EnterpriseSubscriptionIDPrefix = "es_"
|
||||
)
|
||||
@ -386,6 +386,8 @@ commands:
|
||||
|
||||
enterprise-portal:
|
||||
cmd: |
|
||||
# Connect to local development database, with the assumption that it will
|
||||
# have dotcom database tables.
|
||||
export DOTCOM_PGDSN_OVERRIDE="postgres://$PGUSER:$PGPASSWORD@$PGHOST:$PGPORT/$PGDATABASE?sslmode=$PGSSLMODE"
|
||||
.bin/enterprise-portal
|
||||
install: |
|
||||
@ -398,6 +400,12 @@ commands:
|
||||
PORT: '6081'
|
||||
DIAGNOSTICS_SECRET: sekret
|
||||
SRC_LOG_LEVEL: debug
|
||||
GRPC_WEB_UI_ENABLED: 'true'
|
||||
# Used for authentication
|
||||
SAMS_URL: https://accounts.sgdev.org
|
||||
# Set real values in sg.config.yaml overrides
|
||||
ENTERPRISE_PORTAL_SAMS_CLIENT_ID: "sams_cid_put_a_real_value_in_sg.config.overwrite.yaml"
|
||||
ENTERPRISE_PORTAL_SAMS_CLIENT_SECRET: "sams_cs_put_a_real_value_in_sg.config.overwrite.yaml"
|
||||
watch:
|
||||
- lib
|
||||
- cmd/enterprise-portal
|
||||
|
||||
Loading…
Reference in New Issue
Block a user