ccm,ocm: normalize legacy fields into credentials at init, remove dual code path

This commit is contained in:
世界
2026-03-15 12:24:46 +08:00
parent bc6e72408d
commit 8e9c61e624
8 changed files with 265 additions and 321 deletions
+61 -89
View File
@@ -71,84 +71,68 @@ func resolveCredentialTags(tags []string, allCredentials map[string]Credential,
}
func validateCCMOptions(options option.CCMServiceOptions) error {
hasCredentials := len(options.Credentials) > 0
hasLegacyPath := options.CredentialPath != ""
hasLegacyUsages := options.UsagesPath != ""
hasLegacyDetour := options.Detour != ""
if hasCredentials && hasLegacyPath {
return E.New("credential_path and credentials are mutually exclusive")
}
if hasCredentials && hasLegacyUsages {
return E.New("usages_path and credentials are mutually exclusive; use usages_path on individual credentials")
}
if hasCredentials && hasLegacyDetour {
return E.New("detour and credentials are mutually exclusive; use detour on individual credentials")
}
if hasCredentials {
tags := make(map[string]bool)
credentialTypes := make(map[string]string)
for _, credential := range options.Credentials {
if tags[credential.Tag] {
return E.New("duplicate credential tag: ", credential.Tag)
tags := make(map[string]bool)
credentialTypes := make(map[string]string)
for _, credential := range options.Credentials {
if tags[credential.Tag] {
return E.New("duplicate credential tag: ", credential.Tag)
}
tags[credential.Tag] = true
credentialTypes[credential.Tag] = credential.Type
if credential.Type == "default" || credential.Type == "" {
if credential.DefaultOptions.Reserve5h > 99 {
return E.New("credential ", credential.Tag, ": reserve_5h must be at most 99")
}
tags[credential.Tag] = true
credentialTypes[credential.Tag] = credential.Type
if credential.Type == "default" || credential.Type == "" {
if credential.DefaultOptions.Reserve5h > 99 {
return E.New("credential ", credential.Tag, ": reserve_5h must be at most 99")
}
if credential.DefaultOptions.ReserveWeekly > 99 {
return E.New("credential ", credential.Tag, ": reserve_weekly must be at most 99")
}
if credential.DefaultOptions.Limit5h > 100 {
return E.New("credential ", credential.Tag, ": limit_5h must be at most 100")
}
if credential.DefaultOptions.LimitWeekly > 100 {
return E.New("credential ", credential.Tag, ": limit_weekly must be at most 100")
}
if credential.DefaultOptions.Reserve5h > 0 && credential.DefaultOptions.Limit5h > 0 {
return E.New("credential ", credential.Tag, ": reserve_5h and limit_5h are mutually exclusive")
}
if credential.DefaultOptions.ReserveWeekly > 0 && credential.DefaultOptions.LimitWeekly > 0 {
return E.New("credential ", credential.Tag, ": reserve_weekly and limit_weekly are mutually exclusive")
}
if credential.DefaultOptions.ReserveWeekly > 99 {
return E.New("credential ", credential.Tag, ": reserve_weekly must be at most 99")
}
if credential.Type == "external" {
if credential.ExternalOptions.Token == "" {
return E.New("credential ", credential.Tag, ": external credential requires token")
}
if credential.ExternalOptions.Reverse && credential.ExternalOptions.URL == "" {
return E.New("credential ", credential.Tag, ": reverse external credential requires url")
}
if credential.DefaultOptions.Limit5h > 100 {
return E.New("credential ", credential.Tag, ": limit_5h must be at most 100")
}
if credential.Type == "balancer" {
switch credential.BalancerOptions.Strategy {
case "", C.BalancerStrategyLeastUsed, C.BalancerStrategyRoundRobin, C.BalancerStrategyRandom, C.BalancerStrategyFallback:
default:
return E.New("credential ", credential.Tag, ": unknown balancer strategy: ", credential.BalancerOptions.Strategy)
}
if credential.BalancerOptions.RebalanceThreshold < 0 {
return E.New("credential ", credential.Tag, ": rebalance_threshold must not be negative")
}
if credential.DefaultOptions.LimitWeekly > 100 {
return E.New("credential ", credential.Tag, ": limit_weekly must be at most 100")
}
if credential.DefaultOptions.Reserve5h > 0 && credential.DefaultOptions.Limit5h > 0 {
return E.New("credential ", credential.Tag, ": reserve_5h and limit_5h are mutually exclusive")
}
if credential.DefaultOptions.ReserveWeekly > 0 && credential.DefaultOptions.LimitWeekly > 0 {
return E.New("credential ", credential.Tag, ": reserve_weekly and limit_weekly are mutually exclusive")
}
}
if credential.Type == "external" {
if credential.ExternalOptions.Token == "" {
return E.New("credential ", credential.Tag, ": external credential requires token")
}
if credential.ExternalOptions.Reverse && credential.ExternalOptions.URL == "" {
return E.New("credential ", credential.Tag, ": reverse external credential requires url")
}
}
if credential.Type == "balancer" {
switch credential.BalancerOptions.Strategy {
case "", C.BalancerStrategyLeastUsed, C.BalancerStrategyRoundRobin, C.BalancerStrategyRandom, C.BalancerStrategyFallback:
default:
return E.New("credential ", credential.Tag, ": unknown balancer strategy: ", credential.BalancerOptions.Strategy)
}
if credential.BalancerOptions.RebalanceThreshold < 0 {
return E.New("credential ", credential.Tag, ": rebalance_threshold must not be negative")
}
}
}
for _, user := range options.Users {
if user.Credential == "" {
return E.New("user ", user.Name, " must specify credential in multi-credential mode")
singleCredential := len(options.Credentials) == 1
for _, user := range options.Users {
if user.Credential == "" && !singleCredential {
return E.New("user ", user.Name, " must specify credential in multi-credential mode")
}
if user.Credential != "" && !tags[user.Credential] {
return E.New("user ", user.Name, " references unknown credential: ", user.Credential)
}
if user.ExternalCredential != "" {
if !tags[user.ExternalCredential] {
return E.New("user ", user.Name, " references unknown external_credential: ", user.ExternalCredential)
}
if !tags[user.Credential] {
return E.New("user ", user.Name, " references unknown credential: ", user.Credential)
}
if user.ExternalCredential != "" {
if !tags[user.ExternalCredential] {
return E.New("user ", user.Name, " references unknown external_credential: ", user.ExternalCredential)
}
if credentialTypes[user.ExternalCredential] != "external" {
return E.New("user ", user.Name, ": external_credential must reference an external type credential")
}
if credentialTypes[user.ExternalCredential] != "external" {
return E.New("user ", user.Name, ": external_credential must reference an external type credential")
}
}
}
@@ -159,16 +143,18 @@ func validateCCMOptions(options option.CCMServiceOptions) error {
func credentialForUser(
userConfigMap map[string]*option.CCMUser,
providers map[string]credentialProvider,
legacyProvider credentialProvider,
username string,
) (credentialProvider, error) {
if legacyProvider != nil {
return legacyProvider, nil
}
userConfig, exists := userConfigMap[username]
if !exists {
return nil, E.New("no credential mapping for user: ", username)
}
if userConfig.Credential == "" {
for _, provider := range providers {
return provider, nil
}
return nil, E.New("no credential available")
}
provider, exists := providers[userConfig.Credential]
if !exists {
return nil, E.New("unknown credential: ", userConfig.Credential)
@@ -176,17 +162,3 @@ func credentialForUser(
return provider, nil
}
func noUserCredentialProvider(
providers map[string]credentialProvider,
legacyProvider credentialProvider,
options option.CCMServiceOptions,
) credentialProvider {
if legacyProvider != nil {
return legacyProvider
}
if len(options.Credentials) > 0 {
tag := options.Credentials[0].Tag
return providers[tag]
}
return nil
}
+30 -30
View File
@@ -161,11 +161,6 @@ type Service struct {
trackingGroup sync.WaitGroup
shuttingDown bool
// Legacy mode (single credential)
legacyCredential *defaultCredential
legacyProvider credentialProvider
// Multi-credential mode
providers map[string]credentialProvider
allCredentials []Credential
userConfigMap map[string]*option.CCMUser
@@ -174,6 +169,25 @@ type Service struct {
func NewService(ctx context.Context, logger log.ContextLogger, tag string, options option.CCMServiceOptions) (adapter.Service, error) {
initCCMUserAgent(logger)
hasLegacy := options.CredentialPath != "" || options.UsagesPath != "" || options.Detour != ""
if hasLegacy && len(options.Credentials) > 0 {
return nil, E.New("credential_path/usages_path/detour and credentials are mutually exclusive")
}
if len(options.Credentials) == 0 {
options.Credentials = []option.CCMCredential{{
Type: "default",
Tag: "default",
DefaultOptions: option.CCMDefaultCredentialOptions{
CredentialPath: options.CredentialPath,
UsagesPath: options.UsagesPath,
Detour: options.Detour,
},
}}
options.CredentialPath = ""
options.UsagesPath = ""
options.Detour = ""
}
err := validateCCMOptions(options)
if err != nil {
return nil, E.Cause(err, "validate options")
@@ -198,32 +212,18 @@ func NewService(ctx context.Context, logger log.ContextLogger, tag string, optio
userManager: userManager,
}
if len(options.Credentials) > 0 {
providers, allCredentials, err := buildCredentialProviders(ctx, options, logger)
if err != nil {
return nil, E.Cause(err, "build credential providers")
}
service.providers = providers
service.allCredentials = allCredentials
userConfigMap := make(map[string]*option.CCMUser)
for i := range options.Users {
userConfigMap[options.Users[i].Name] = &options.Users[i]
}
service.userConfigMap = userConfigMap
} else {
credential, err := newDefaultCredential(ctx, "default", option.CCMDefaultCredentialOptions{
CredentialPath: options.CredentialPath,
UsagesPath: options.UsagesPath,
Detour: options.Detour,
}, logger)
if err != nil {
return nil, err
}
service.legacyCredential = credential
service.legacyProvider = &singleCredentialProvider{credential: credential}
service.allCredentials = []Credential{credential}
providers, allCredentials, err := buildCredentialProviders(ctx, options, logger)
if err != nil {
return nil, E.Cause(err, "build credential providers")
}
service.providers = providers
service.allCredentials = allCredentials
userConfigMap := make(map[string]*option.CCMUser)
for i := range options.Users {
userConfigMap[options.Users[i].Name] = &options.Users[i]
}
service.userConfigMap = userConfigMap
if options.TLS != nil {
tlsConfig, err := tls.NewServer(ctx, logger, common.PtrValueOrDefault(options.TLS))
+2 -2
View File
@@ -168,14 +168,14 @@ func (s *Service) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if len(s.options.Users) > 0 {
userConfig = s.userConfigMap[username]
var err error
provider, err = credentialForUser(s.userConfigMap, s.providers, s.legacyProvider, username)
provider, err = credentialForUser(s.userConfigMap, s.providers, username)
if err != nil {
s.logger.ErrorContext(ctx, "resolve credential: ", err)
writeJSONError(w, r, http.StatusInternalServerError, "api_error", err.Error())
return
}
} else {
provider = noUserCredentialProvider(s.providers, s.legacyProvider, s.options)
provider = s.providers[s.options.Credentials[0].Tag]
}
if provider == nil {
writeJSONError(w, r, http.StatusInternalServerError, "api_error", "no credential available")
+37 -36
View File
@@ -15,42 +15,43 @@ func (s *Service) handleStatusEndpoint(w http.ResponseWriter, r *http.Request) {
return
}
if len(s.options.Users) == 0 {
writeJSONError(w, r, http.StatusForbidden, "authentication_error", "status endpoint requires user authentication")
return
}
var provider credentialProvider
var userConfig *option.CCMUser
if len(s.options.Users) > 0 {
if r.Header.Get("X-Api-Key") != "" || r.Header.Get("Api-Key") != "" {
writeJSONError(w, r, http.StatusBadRequest, "invalid_request_error",
"API key authentication is not supported; use Authorization: Bearer with a CCM user token")
return
}
if r.Header.Get("X-Api-Key") != "" || r.Header.Get("Api-Key") != "" {
writeJSONError(w, r, http.StatusBadRequest, "invalid_request_error",
"API key authentication is not supported; use Authorization: Bearer with a CCM user token")
return
}
authHeader := r.Header.Get("Authorization")
if authHeader == "" {
writeJSONError(w, r, http.StatusUnauthorized, "authentication_error", "missing api key")
return
}
clientToken := strings.TrimPrefix(authHeader, "Bearer ")
if clientToken == authHeader {
writeJSONError(w, r, http.StatusUnauthorized, "authentication_error", "invalid api key format")
return
}
username, ok := s.userManager.Authenticate(clientToken)
if !ok {
writeJSONError(w, r, http.StatusUnauthorized, "authentication_error", "invalid api key")
return
}
authHeader := r.Header.Get("Authorization")
if authHeader == "" {
writeJSONError(w, r, http.StatusUnauthorized, "authentication_error", "missing api key")
return
userConfig = s.userConfigMap[username]
var err error
provider, err = credentialForUser(s.userConfigMap, s.providers, username)
if err != nil {
writeJSONError(w, r, http.StatusInternalServerError, "api_error", err.Error())
return
}
} else {
provider = s.providers[s.options.Credentials[0].Tag]
}
clientToken := strings.TrimPrefix(authHeader, "Bearer ")
if clientToken == authHeader {
writeJSONError(w, r, http.StatusUnauthorized, "authentication_error", "invalid api key format")
return
}
username, ok := s.userManager.Authenticate(clientToken)
if !ok {
writeJSONError(w, r, http.StatusUnauthorized, "authentication_error", "invalid api key")
return
}
userConfig := s.userConfigMap[username]
if userConfig == nil {
writeJSONError(w, r, http.StatusInternalServerError, "api_error", "user config not found")
return
}
provider, err := credentialForUser(s.userConfigMap, s.providers, s.legacyProvider, username)
if err != nil {
writeJSONError(w, r, http.StatusInternalServerError, "api_error", err.Error())
if provider == nil {
writeJSONError(w, r, http.StatusInternalServerError, "api_error", "no credential available")
return
}
@@ -72,10 +73,10 @@ func (s *Service) computeAggregatedUtilization(provider credentialProvider, user
if !credential.isAvailable() {
continue
}
if userConfig.ExternalCredential != "" && credential.tagName() == userConfig.ExternalCredential {
if userConfig != nil && userConfig.ExternalCredential != "" && credential.tagName() == userConfig.ExternalCredential {
continue
}
if !userConfig.AllowExternalUsage && credential.isExternal() {
if userConfig != nil && !userConfig.AllowExternalUsage && credential.isExternal() {
continue
}
weight := credential.planWeight()
@@ -100,7 +101,7 @@ func (s *Service) computeAggregatedUtilization(provider credentialProvider, user
}
func (s *Service) rewriteResponseHeadersForExternalUser(headers http.Header, userConfig *option.CCMUser) {
provider, err := credentialForUser(s.userConfigMap, s.providers, s.legacyProvider, userConfig.Name)
provider, err := credentialForUser(s.userConfigMap, s.providers, userConfig.Name)
if err != nil {
return
}
+61 -89
View File
@@ -71,84 +71,68 @@ func resolveCredentialTags(tags []string, allCredentials map[string]Credential,
}
func validateOCMOptions(options option.OCMServiceOptions) error {
hasCredentials := len(options.Credentials) > 0
hasLegacyPath := options.CredentialPath != ""
hasLegacyUsages := options.UsagesPath != ""
hasLegacyDetour := options.Detour != ""
if hasCredentials && hasLegacyPath {
return E.New("credential_path and credentials are mutually exclusive")
}
if hasCredentials && hasLegacyUsages {
return E.New("usages_path and credentials are mutually exclusive; use usages_path on individual credentials")
}
if hasCredentials && hasLegacyDetour {
return E.New("detour and credentials are mutually exclusive; use detour on individual credentials")
}
if hasCredentials {
tags := make(map[string]bool)
credentialTypes := make(map[string]string)
for _, credential := range options.Credentials {
if tags[credential.Tag] {
return E.New("duplicate credential tag: ", credential.Tag)
tags := make(map[string]bool)
credentialTypes := make(map[string]string)
for _, credential := range options.Credentials {
if tags[credential.Tag] {
return E.New("duplicate credential tag: ", credential.Tag)
}
tags[credential.Tag] = true
credentialTypes[credential.Tag] = credential.Type
if credential.Type == "default" || credential.Type == "" {
if credential.DefaultOptions.Reserve5h > 99 {
return E.New("credential ", credential.Tag, ": reserve_5h must be at most 99")
}
tags[credential.Tag] = true
credentialTypes[credential.Tag] = credential.Type
if credential.Type == "default" || credential.Type == "" {
if credential.DefaultOptions.Reserve5h > 99 {
return E.New("credential ", credential.Tag, ": reserve_5h must be at most 99")
}
if credential.DefaultOptions.ReserveWeekly > 99 {
return E.New("credential ", credential.Tag, ": reserve_weekly must be at most 99")
}
if credential.DefaultOptions.Limit5h > 100 {
return E.New("credential ", credential.Tag, ": limit_5h must be at most 100")
}
if credential.DefaultOptions.LimitWeekly > 100 {
return E.New("credential ", credential.Tag, ": limit_weekly must be at most 100")
}
if credential.DefaultOptions.Reserve5h > 0 && credential.DefaultOptions.Limit5h > 0 {
return E.New("credential ", credential.Tag, ": reserve_5h and limit_5h are mutually exclusive")
}
if credential.DefaultOptions.ReserveWeekly > 0 && credential.DefaultOptions.LimitWeekly > 0 {
return E.New("credential ", credential.Tag, ": reserve_weekly and limit_weekly are mutually exclusive")
}
if credential.DefaultOptions.ReserveWeekly > 99 {
return E.New("credential ", credential.Tag, ": reserve_weekly must be at most 99")
}
if credential.Type == "external" {
if credential.ExternalOptions.Token == "" {
return E.New("credential ", credential.Tag, ": external credential requires token")
}
if credential.ExternalOptions.Reverse && credential.ExternalOptions.URL == "" {
return E.New("credential ", credential.Tag, ": reverse external credential requires url")
}
if credential.DefaultOptions.Limit5h > 100 {
return E.New("credential ", credential.Tag, ": limit_5h must be at most 100")
}
if credential.Type == "balancer" {
switch credential.BalancerOptions.Strategy {
case "", C.BalancerStrategyLeastUsed, C.BalancerStrategyRoundRobin, C.BalancerStrategyRandom, C.BalancerStrategyFallback:
default:
return E.New("credential ", credential.Tag, ": unknown balancer strategy: ", credential.BalancerOptions.Strategy)
}
if credential.BalancerOptions.RebalanceThreshold < 0 {
return E.New("credential ", credential.Tag, ": rebalance_threshold must not be negative")
}
if credential.DefaultOptions.LimitWeekly > 100 {
return E.New("credential ", credential.Tag, ": limit_weekly must be at most 100")
}
if credential.DefaultOptions.Reserve5h > 0 && credential.DefaultOptions.Limit5h > 0 {
return E.New("credential ", credential.Tag, ": reserve_5h and limit_5h are mutually exclusive")
}
if credential.DefaultOptions.ReserveWeekly > 0 && credential.DefaultOptions.LimitWeekly > 0 {
return E.New("credential ", credential.Tag, ": reserve_weekly and limit_weekly are mutually exclusive")
}
}
if credential.Type == "external" {
if credential.ExternalOptions.Token == "" {
return E.New("credential ", credential.Tag, ": external credential requires token")
}
if credential.ExternalOptions.Reverse && credential.ExternalOptions.URL == "" {
return E.New("credential ", credential.Tag, ": reverse external credential requires url")
}
}
if credential.Type == "balancer" {
switch credential.BalancerOptions.Strategy {
case "", C.BalancerStrategyLeastUsed, C.BalancerStrategyRoundRobin, C.BalancerStrategyRandom, C.BalancerStrategyFallback:
default:
return E.New("credential ", credential.Tag, ": unknown balancer strategy: ", credential.BalancerOptions.Strategy)
}
if credential.BalancerOptions.RebalanceThreshold < 0 {
return E.New("credential ", credential.Tag, ": rebalance_threshold must not be negative")
}
}
}
for _, user := range options.Users {
if user.Credential == "" {
return E.New("user ", user.Name, " must specify credential in multi-credential mode")
singleCredential := len(options.Credentials) == 1
for _, user := range options.Users {
if user.Credential == "" && !singleCredential {
return E.New("user ", user.Name, " must specify credential in multi-credential mode")
}
if user.Credential != "" && !tags[user.Credential] {
return E.New("user ", user.Name, " references unknown credential: ", user.Credential)
}
if user.ExternalCredential != "" {
if !tags[user.ExternalCredential] {
return E.New("user ", user.Name, " references unknown external_credential: ", user.ExternalCredential)
}
if !tags[user.Credential] {
return E.New("user ", user.Name, " references unknown credential: ", user.Credential)
}
if user.ExternalCredential != "" {
if !tags[user.ExternalCredential] {
return E.New("user ", user.Name, " references unknown external_credential: ", user.ExternalCredential)
}
if credentialTypes[user.ExternalCredential] != "external" {
return E.New("user ", user.Name, ": external_credential must reference an external type credential")
}
if credentialTypes[user.ExternalCredential] != "external" {
return E.New("user ", user.Name, ": external_credential must reference an external type credential")
}
}
}
@@ -190,16 +174,18 @@ func validateOCMCompositeCredentialModes(
func credentialForUser(
userConfigMap map[string]*option.OCMUser,
providers map[string]credentialProvider,
legacyProvider credentialProvider,
username string,
) (credentialProvider, error) {
if legacyProvider != nil {
return legacyProvider, nil
}
userConfig, exists := userConfigMap[username]
if !exists {
return nil, E.New("no credential mapping for user: ", username)
}
if userConfig.Credential == "" {
for _, provider := range providers {
return provider, nil
}
return nil, E.New("no credential available")
}
provider, exists := providers[userConfig.Credential]
if !exists {
return nil, E.New("unknown credential: ", userConfig.Credential)
@@ -207,17 +193,3 @@ func credentialForUser(
return provider, nil
}
func noUserCredentialProvider(
providers map[string]credentialProvider,
legacyProvider credentialProvider,
options option.OCMServiceOptions,
) credentialProvider {
if legacyProvider != nil {
return legacyProvider
}
if len(options.Credentials) > 0 {
tag := options.Credentials[0].Tag
return providers[tag]
}
return nil
}
+33 -35
View File
@@ -176,17 +176,31 @@ type Service struct {
webSocketConns map[*webSocketSession]struct{}
shuttingDown bool
// Legacy mode
legacyCredential *defaultCredential
legacyProvider credentialProvider
// Multi-credential mode
providers map[string]credentialProvider
allCredentials []Credential
userConfigMap map[string]*option.OCMUser
}
func NewService(ctx context.Context, logger log.ContextLogger, tag string, options option.OCMServiceOptions) (adapter.Service, error) {
hasLegacy := options.CredentialPath != "" || options.UsagesPath != "" || options.Detour != ""
if hasLegacy && len(options.Credentials) > 0 {
return nil, E.New("credential_path/usages_path/detour and credentials are mutually exclusive")
}
if len(options.Credentials) == 0 {
options.Credentials = []option.OCMCredential{{
Type: "default",
Tag: "default",
DefaultOptions: option.OCMDefaultCredentialOptions{
CredentialPath: options.CredentialPath,
UsagesPath: options.UsagesPath,
Detour: options.Detour,
},
}}
options.CredentialPath = ""
options.UsagesPath = ""
options.Detour = ""
}
err := validateOCMOptions(options)
if err != nil {
return nil, E.Cause(err, "validate options")
@@ -212,32 +226,18 @@ func NewService(ctx context.Context, logger log.ContextLogger, tag string, optio
webSocketConns: make(map[*webSocketSession]struct{}),
}
if len(options.Credentials) > 0 {
providers, allCredentials, err := buildOCMCredentialProviders(ctx, options, logger)
if err != nil {
return nil, E.Cause(err, "build credential providers")
}
service.providers = providers
service.allCredentials = allCredentials
userConfigMap := make(map[string]*option.OCMUser)
for i := range options.Users {
userConfigMap[options.Users[i].Name] = &options.Users[i]
}
service.userConfigMap = userConfigMap
} else {
credential, err := newDefaultCredential(ctx, "default", option.OCMDefaultCredentialOptions{
CredentialPath: options.CredentialPath,
UsagesPath: options.UsagesPath,
Detour: options.Detour,
}, logger)
if err != nil {
return nil, err
}
service.legacyCredential = credential
service.legacyProvider = &singleCredentialProvider{credential: credential}
service.allCredentials = []Credential{credential}
providers, allCredentials, err := buildOCMCredentialProviders(ctx, options, logger)
if err != nil {
return nil, E.Cause(err, "build credential providers")
}
service.providers = providers
service.allCredentials = allCredentials
userConfigMap := make(map[string]*option.OCMUser)
for i := range options.Users {
userConfigMap[options.Users[i].Name] = &options.Users[i]
}
service.userConfigMap = userConfigMap
if options.TLS != nil {
tlsConfig, err := tls.NewServer(ctx, logger, common.PtrValueOrDefault(options.TLS))
@@ -270,11 +270,9 @@ func (s *Service) Start(stage adapter.StartStage) error {
s.interruptWebSocketSessionsForCredential(tag)
})
}
if len(s.options.Credentials) > 0 {
err := validateOCMCompositeCredentialModes(s.options, s.providers)
if err != nil {
return E.Cause(err, "validate loaded credentials")
}
err := validateOCMCompositeCredentialModes(s.options, s.providers)
if err != nil {
return E.Cause(err, "validate loaded credentials")
}
router := chi.NewRouter()
+4 -4
View File
@@ -53,9 +53,9 @@ func extractWeeklyCycleHint(headers http.Header) *WeeklyCycleHint {
func (s *Service) resolveCredentialProvider(username string) (credentialProvider, error) {
if len(s.options.Users) > 0 {
return credentialForUser(s.userConfigMap, s.providers, s.legacyProvider, username)
return credentialForUser(s.userConfigMap, s.providers, username)
}
provider := noUserCredentialProvider(s.providers, s.legacyProvider, s.options)
provider := s.providers[s.options.Credentials[0].Tag]
if provider == nil {
return nil, E.New("no credential available")
}
@@ -117,14 +117,14 @@ func (s *Service) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if len(s.options.Users) > 0 {
userConfig = s.userConfigMap[username]
var err error
provider, err = credentialForUser(s.userConfigMap, s.providers, s.legacyProvider, username)
provider, err = credentialForUser(s.userConfigMap, s.providers, username)
if err != nil {
s.logger.ErrorContext(ctx, "resolve credential: ", err)
writeJSONError(w, r, http.StatusInternalServerError, "api_error", err.Error())
return
}
} else {
provider = noUserCredentialProvider(s.providers, s.legacyProvider, s.options)
provider = s.providers[s.options.Credentials[0].Tag]
}
if provider == nil {
writeJSONError(w, r, http.StatusInternalServerError, "api_error", "no credential available")
+37 -36
View File
@@ -15,42 +15,43 @@ func (s *Service) handleStatusEndpoint(w http.ResponseWriter, r *http.Request) {
return
}
if len(s.options.Users) == 0 {
writeJSONError(w, r, http.StatusForbidden, "authentication_error", "status endpoint requires user authentication")
return
}
var provider credentialProvider
var userConfig *option.OCMUser
if len(s.options.Users) > 0 {
if r.Header.Get("X-Api-Key") != "" || r.Header.Get("Api-Key") != "" {
writeJSONError(w, r, http.StatusBadRequest, "invalid_request_error",
"API key authentication is not supported; use Authorization: Bearer with an OCM user token")
return
}
if r.Header.Get("X-Api-Key") != "" || r.Header.Get("Api-Key") != "" {
writeJSONError(w, r, http.StatusBadRequest, "invalid_request_error",
"API key authentication is not supported; use Authorization: Bearer with an OCM user token")
return
}
authHeader := r.Header.Get("Authorization")
if authHeader == "" {
writeJSONError(w, r, http.StatusUnauthorized, "authentication_error", "missing api key")
return
}
clientToken := strings.TrimPrefix(authHeader, "Bearer ")
if clientToken == authHeader {
writeJSONError(w, r, http.StatusUnauthorized, "authentication_error", "invalid api key format")
return
}
username, ok := s.userManager.Authenticate(clientToken)
if !ok {
writeJSONError(w, r, http.StatusUnauthorized, "authentication_error", "invalid api key")
return
}
authHeader := r.Header.Get("Authorization")
if authHeader == "" {
writeJSONError(w, r, http.StatusUnauthorized, "authentication_error", "missing api key")
return
userConfig = s.userConfigMap[username]
var err error
provider, err = credentialForUser(s.userConfigMap, s.providers, username)
if err != nil {
writeJSONError(w, r, http.StatusInternalServerError, "api_error", err.Error())
return
}
} else {
provider = s.providers[s.options.Credentials[0].Tag]
}
clientToken := strings.TrimPrefix(authHeader, "Bearer ")
if clientToken == authHeader {
writeJSONError(w, r, http.StatusUnauthorized, "authentication_error", "invalid api key format")
return
}
username, ok := s.userManager.Authenticate(clientToken)
if !ok {
writeJSONError(w, r, http.StatusUnauthorized, "authentication_error", "invalid api key")
return
}
userConfig := s.userConfigMap[username]
if userConfig == nil {
writeJSONError(w, r, http.StatusInternalServerError, "api_error", "user config not found")
return
}
provider, err := credentialForUser(s.userConfigMap, s.providers, s.legacyProvider, username)
if err != nil {
writeJSONError(w, r, http.StatusInternalServerError, "api_error", err.Error())
if provider == nil {
writeJSONError(w, r, http.StatusInternalServerError, "api_error", "no credential available")
return
}
@@ -72,10 +73,10 @@ func (s *Service) computeAggregatedUtilization(provider credentialProvider, user
if !credential.isAvailable() {
continue
}
if userConfig.ExternalCredential != "" && credential.tagName() == userConfig.ExternalCredential {
if userConfig != nil && userConfig.ExternalCredential != "" && credential.tagName() == userConfig.ExternalCredential {
continue
}
if !userConfig.AllowExternalUsage && credential.isExternal() {
if userConfig != nil && !userConfig.AllowExternalUsage && credential.isExternal() {
continue
}
weight := credential.planWeight()
@@ -100,7 +101,7 @@ func (s *Service) computeAggregatedUtilization(provider credentialProvider, user
}
func (s *Service) rewriteResponseHeadersForExternalUser(headers http.Header, userConfig *option.OCMUser) {
provider, err := credentialForUser(s.userConfigMap, s.providers, s.legacyProvider, userConfig.Name)
provider, err := credentialForUser(s.userConfigMap, s.providers, userConfig.Name)
if err != nil {
return
}