Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion backend/internal/application/service_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3327,7 +3327,7 @@ func TestAcrValidationTestSuite(t *testing.T) {
func (s *AcrValidationTestSuite) initRegistry(mapping engineconfig.AuthClassConfig) {
config.ResetServerRuntime()
s.Require().NoError(config.InitializeServerRuntime("", &config.Config{
OAuth: engineconfig.OAuthConfig{
OAuth: config.OAuthConfig{
AuthClass: mapping,
},
}))
Expand Down
9 changes: 3 additions & 6 deletions backend/internal/application/tools.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import (

"github.com/thunder-id/thunderid/internal/application/model"
oauthconfig "github.com/thunder-id/thunderid/internal/oauth/config"
oauth2const "github.com/thunder-id/thunderid/internal/oauth/oauth2/constants"
"github.com/thunder-id/thunderid/internal/system/mcp/tool"
)

Expand Down Expand Up @@ -332,11 +331,9 @@ func (t *applicationTools) getApplicationTemplates(
func getCommonSchemaModifiers() []func(*jsonschema.Schema) {
oauthCfg := oauthconfig.FromServerRuntime()
return []func(*jsonschema.Schema){
tool.WithEnum("inbound_auth_config.config", "grant_types", oauth2const.GetSupportedGrantTypes(oauthCfg)),
tool.WithEnum("inbound_auth_config.config", "response_types",
oauth2const.GetSupportedResponseTypes(oauthCfg)),
tool.WithEnum("inbound_auth_config.config", "token_endpoint_auth_method",
oauth2const.GetSupportedTokenEndpointAuthMethods(oauthCfg)),
tool.WithEnum("inbound_auth_config.config", "grant_types", oauthCfg.OAuth.AllowedGrantTypes),
tool.WithEnum("inbound_auth_config.config", "response_types", oauthCfg.OAuth.AllowedResponseTypes),
tool.WithEnum("inbound_auth_config.config", "token_endpoint_auth_method", oauthCfg.OAuth.AllowedAuthMethods),
tool.WithEnum("inbound_auth_config", "type", []string{string(providers.OAuthInboundAuthType)}),
}
}
Expand Down
82 changes: 80 additions & 2 deletions backend/internal/oauth/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@
package oauthconfig

import (
"github.com/thunder-id/thunderid/internal/oauth/oauth2/constants"
"github.com/thunder-id/thunderid/internal/system/config"
engineconfig "github.com/thunder-id/thunderid/pkg/thunderidengine/config"
"github.com/thunder-id/thunderid/pkg/thunderidengine/providers"
)

// Config holds configuration values required by OAuth services.
Expand All @@ -19,15 +21,91 @@ type Config struct {
GateClient engineconfig.GateClientConfig
}

// FromServerRuntime builds OAuth configuration from the global server runtime.
// FromServerRuntime builds OAuth configuration from the global server runtime, seeding the
// additional OIDC fields with the server defaults.
func FromServerRuntime() Config {
runtime := config.GetServerRuntime()
oauth := runtime.Config.OAuth.ToEngineConfig()
applyOIDCDefaults(&oauth)

return Config{
DeploymentID: runtime.Config.Server.Identifier,
RuntimeTransientDBType: runtime.Config.Database.RuntimeTransient.Type,
BaseURL: config.GetServerURL(&runtime.Config.Server),
JWT: runtime.Config.JWT,
OAuth: runtime.Config.OAuth,
OAuth: oauth,
GateClient: runtime.Config.GateClient,
}
}

// applyOIDCDefaults seeds the default values for the additional OIDC fields on the given OAuthConfig.
func applyOIDCDefaults(oauth *engineconfig.OAuthConfig) {
mapping := make(map[string][]string, len(constants.StandardOIDCScopes))
scopes := make([]string, 0, len(constants.StandardOIDCScopes))

claimSet := make(map[string]struct{})
for _, c := range constants.GetStandardClaims() {
claimSet[c] = struct{}{}
}
for scope, def := range constants.StandardOIDCScopes {
claims := make([]string, len(def.Claims))
copy(claims, def.Claims)
mapping[scope] = claims
scopes = append(scopes, scope)
for _, c := range def.Claims {
claimSet[c] = struct{}{}
}
}

claims := make([]string, 0, len(claimSet))
for c := range claimSet {
claims = append(claims, c)
}

oauth.DefaultScopeClaimsMapping = mapping
oauth.AllowedScopes = scopes
oauth.AllowedClaims = claims

oauth.AllowedSubjectTypes = defaultAllowedSubjectTypes()
if len(oauth.AllowedGrantTypes) == 0 {
oauth.AllowedGrantTypes = defaultAllowedGrantTypes()
}
if len(oauth.AllowedResponseTypes) == 0 {
oauth.AllowedResponseTypes = defaultAllowedResponseTypes()
}
if len(oauth.AllowedAuthMethods) == 0 {
oauth.AllowedAuthMethods = defaultAllowedAuthMethods()
}
}

// defaultAllowedSubjectTypes returns the default allowed OIDC subject types for the server.
func defaultAllowedSubjectTypes() []string {
return []string{constants.SubjectTypePublic}
}

// defaultAllowedGrantTypes returns the default allowed grant types for the server.
func defaultAllowedGrantTypes() []string {
result := make([]string, len(providers.SupportedGrantTypes))
for i, v := range providers.SupportedGrantTypes {
result[i] = string(v)
}
return result
}

// defaultAllowedResponseTypes returns the default allowed response types for the server.
func defaultAllowedResponseTypes() []string {
result := make([]string, len(providers.SupportedResponseTypes))
for i, v := range providers.SupportedResponseTypes {
result[i] = string(v)
}
return result
}

// defaultAllowedAuthMethods returns the default allowed token endpoint authentication methods for the server.
func defaultAllowedAuthMethods() []string {
result := make([]string, len(providers.SupportedTokenEndpointAuthMethods))
for i, v := range providers.SupportedTokenEndpointAuthMethods {
result[i] = string(v)
}
return result
}
60 changes: 59 additions & 1 deletion backend/internal/oauth/config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (

"github.com/stretchr/testify/suite"

"github.com/thunder-id/thunderid/internal/oauth/oauth2/constants"
"github.com/thunder-id/thunderid/internal/system/config"
)

Expand Down Expand Up @@ -44,7 +45,7 @@ func (s *OAuthConfigTestSuite) TestFromServerRuntime() {
Issuer: "https://thunder.io",
ValidityPeriod: 3600,
},
OAuth: engineconfig.OAuthConfig{
OAuth: config.OAuthConfig{
PAR: engineconfig.PARConfig{ExpiresIn: 600},
},
GateClient: engineconfig.GateClientConfig{
Expand All @@ -64,4 +65,61 @@ func (s *OAuthConfigTestSuite) TestFromServerRuntime() {
s.Equal("https://thunder.io", result.JWT.Issuer)
s.Equal(int64(600), result.OAuth.PAR.ExpiresIn)
s.Equal("localhost", result.GateClient.Hostname)

s.NotEmpty(result.OAuth.DefaultScopeClaimsMapping, "default mapping should be seeded")
s.Len(result.OAuth.DefaultScopeClaimsMapping, len(constants.StandardOIDCScopes))
for scope, def := range constants.StandardOIDCScopes {
s.ElementsMatch(def.Claims, result.OAuth.DefaultScopeClaimsMapping[scope], "scope %q claims mismatch", scope)
}
standardScopeNames := make([]string, 0, len(constants.StandardOIDCScopes))
for scope := range constants.StandardOIDCScopes {
standardScopeNames = append(standardScopeNames, scope)
}
s.ElementsMatch(standardScopeNames, result.OAuth.AllowedScopes)
s.ElementsMatch([]string{constants.SubjectTypePublic}, result.OAuth.AllowedSubjectTypes)
for _, c := range constants.GetStandardClaims() {
s.Contains(result.OAuth.AllowedClaims, c, "allowed_claims must include standard JWT claim %q", c)
}
for _, def := range constants.StandardOIDCScopes {
for _, c := range def.Claims {
s.Contains(result.OAuth.AllowedClaims, c, "allowed_claims must include mapped claim %q", c)
}
}
}

func (s *OAuthConfigTestSuite) TestApplyOIDCDefaults_Idempotent() {
var oauth engineconfig.OAuthConfig
applyOIDCDefaults(&oauth)
first := oauth
applyOIDCDefaults(&oauth)
s.ElementsMatch(first.AllowedScopes, oauth.AllowedScopes)
s.ElementsMatch(first.AllowedClaims, oauth.AllowedClaims)
s.ElementsMatch(first.AllowedSubjectTypes, oauth.AllowedSubjectTypes)
s.Equal(first.DefaultScopeClaimsMapping, oauth.DefaultScopeClaimsMapping)
s.ElementsMatch(first.AllowedGrantTypes, oauth.AllowedGrantTypes)
s.ElementsMatch(first.AllowedResponseTypes, oauth.AllowedResponseTypes)
s.ElementsMatch(first.AllowedAuthMethods, oauth.AllowedAuthMethods)
}

func (s *OAuthConfigTestSuite) TestApplyOIDCDefaults_SeedsAllowedListsWhenEmpty() {
var oauth engineconfig.OAuthConfig
applyOIDCDefaults(&oauth)
s.NotEmpty(oauth.AllowedGrantTypes)
s.NotEmpty(oauth.AllowedResponseTypes)
s.NotEmpty(oauth.AllowedAuthMethods)
s.Contains(oauth.AllowedGrantTypes, "authorization_code")
s.Contains(oauth.AllowedResponseTypes, "code")
s.Contains(oauth.AllowedAuthMethods, "client_secret_basic")
}

func (s *OAuthConfigTestSuite) TestApplyOIDCDefaults_PreservesConfiguredAllowedLists() {
oauth := engineconfig.OAuthConfig{
AllowedGrantTypes: []string{"client_credentials"},
AllowedResponseTypes: []string{"code"},
AllowedAuthMethods: []string{"client_secret_post"},
}
applyOIDCDefaults(&oauth)
s.Equal([]string{"client_credentials"}, oauth.AllowedGrantTypes)
s.Equal([]string{"code"}, oauth.AllowedResponseTypes)
s.Equal([]string{"client_secret_post"}, oauth.AllowedAuthMethods)
}
2 changes: 1 addition & 1 deletion backend/internal/oauth/oauth2/authz/handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ func (suite *AuthorizeHandlerTestSuite) SetupTest() {
JWT: engineconfig.JWTConfig{
Issuer: "https://localhost:8090",
},
OAuth: engineconfig.OAuthConfig{
OAuth: config.OAuthConfig{
AuthorizationCode: engineconfig.AuthorizationCodeConfig{
ValidityPeriod: 600,
},
Expand Down
16 changes: 8 additions & 8 deletions backend/internal/oauth/oauth2/authz/service_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ func authorizeServiceCfgFromRuntime() oauthconfig.Config {
runtime := config.GetServerRuntime()
return oauthconfig.Config{
JWT: runtime.Config.JWT,
OAuth: runtime.Config.OAuth,
OAuth: runtime.Config.OAuth.ToEngineConfig(),
GateClient: runtime.Config.GateClient,
}
}
Expand Down Expand Up @@ -120,7 +120,7 @@ func (suite *AuthorizeServiceTestSuite) BeforeTest(suiteName, testName string) {
JWT: engineconfig.JWTConfig{
Issuer: "https://localhost:8090",
},
OAuth: engineconfig.OAuthConfig{
OAuth: config.OAuthConfig{
AuthorizationCode: engineconfig.AuthorizationCodeConfig{ValidityPeriod: 600},
},
}
Expand Down Expand Up @@ -1822,7 +1822,7 @@ func (suite *AuthorizeServiceTestSuite) TestResolveAttrCacheTTL_RefreshAllowed_U
config.ResetServerRuntime()
_ = config.InitializeServerRuntime("test", &config.Config{
JWT: engineconfig.JWTConfig{ValidityPeriod: 900},
OAuth: engineconfig.OAuthConfig{
OAuth: config.OAuthConfig{
RefreshToken: engineconfig.RefreshTokenConfig{ValidityPeriod: 7200},
AuthorizationCode: engineconfig.AuthorizationCodeConfig{ValidityPeriod: 600},
},
Expand All @@ -1848,7 +1848,7 @@ func (suite *AuthorizeServiceTestSuite) TestResolveAttrCacheTTL_RefreshTokenAllo
config.ResetServerRuntime()
_ = config.InitializeServerRuntime("test", &config.Config{
JWT: engineconfig.JWTConfig{ValidityPeriod: 900},
OAuth: engineconfig.OAuthConfig{
OAuth: config.OAuthConfig{
RefreshToken: engineconfig.RefreshTokenConfig{ValidityPeriod: 1800},
AuthorizationCode: engineconfig.AuthorizationCodeConfig{ValidityPeriod: 600},
},
Expand All @@ -1874,7 +1874,7 @@ func (suite *AuthorizeServiceTestSuite) TestResolveUserAttributesCacheTTL_Refres
config.ResetServerRuntime()
_ = config.InitializeServerRuntime("test", &config.Config{
JWT: engineconfig.JWTConfig{ValidityPeriod: 900},
OAuth: engineconfig.OAuthConfig{
OAuth: config.OAuthConfig{
// RefreshToken.ValidityPeriod is 0 → ResolveTokenConfig falls back to global JWT validity.
RefreshToken: engineconfig.RefreshTokenConfig{ValidityPeriod: 0},
AuthorizationCode: engineconfig.AuthorizationCodeConfig{ValidityPeriod: 600},
Expand Down Expand Up @@ -1910,7 +1910,7 @@ func (suite *AuthorizeServiceTestSuite) TestResolveAttrCacheTTL_NoRefreshToken_Z
config.ResetServerRuntime()
_ = config.InitializeServerRuntime("test", &config.Config{
JWT: engineconfig.JWTConfig{ValidityPeriod: 900},
OAuth: engineconfig.OAuthConfig{
OAuth: config.OAuthConfig{
AuthorizationCode: engineconfig.AuthorizationCodeConfig{ValidityPeriod: 600},
},
})
Expand All @@ -1933,7 +1933,7 @@ func (suite *AuthorizeServiceTestSuite) TestResolveAttrCacheTTL_NoRefreshToken_N
config.ResetServerRuntime()
_ = config.InitializeServerRuntime("test", &config.Config{
JWT: engineconfig.JWTConfig{ValidityPeriod: 900},
OAuth: engineconfig.OAuthConfig{
OAuth: config.OAuthConfig{
AuthorizationCode: engineconfig.AuthorizationCodeConfig{ValidityPeriod: 600},
},
})
Expand All @@ -1951,7 +1951,7 @@ func (suite *AuthorizeServiceTestSuite) TestResolveAttrCacheTTL_NoRefreshToken_N
config.ResetServerRuntime()
_ = config.InitializeServerRuntime("test", &config.Config{
JWT: engineconfig.JWTConfig{ValidityPeriod: 900},
OAuth: engineconfig.OAuthConfig{
OAuth: config.OAuthConfig{
AuthorizationCode: engineconfig.AuthorizationCodeConfig{ValidityPeriod: 600},
},
})
Expand Down
3 changes: 1 addition & 2 deletions backend/internal/oauth/oauth2/authz/validator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import (
"net/url"
"testing"

engineconfig "github.com/thunder-id/thunderid/pkg/thunderidengine/config"
"github.com/thunder-id/thunderid/pkg/thunderidengine/providers"

"github.com/stretchr/testify/assert"
Expand All @@ -31,7 +30,7 @@ func TestAuthorizationValidatorTestSuite(t *testing.T) {
func (suite *AuthorizationValidatorTestSuite) SetupTest() {
sysconfig.ResetServerRuntime()
err := sysconfig.InitializeServerRuntime("/tmp/test", &sysconfig.Config{
OAuth: engineconfig.OAuthConfig{AllowWildcardRedirectURI: true},
OAuth: sysconfig.OAuthConfig{AllowWildcardRedirectURI: true},
})
suite.Require().NoError(err)

Expand Down
46 changes: 0 additions & 46 deletions backend/internal/oauth/oauth2/constants/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,7 @@ package constants
import (
"errors"

oauthconfig "github.com/thunder-id/thunderid/internal/oauth/config"
"github.com/thunder-id/thunderid/internal/oauth/oauth2/model"
"github.com/thunder-id/thunderid/pkg/thunderidengine/providers"
)

// OAuth2 request parameters.
Expand Down Expand Up @@ -341,50 +339,6 @@ const (
SupportedAuthorizationGrantProfileIDJAG = "urn:ietf:params:oauth:grant-profile:id-jag"
)

// GetSupportedResponseTypes returns all supported OAuth2 response types.
func GetSupportedResponseTypes(oauthConfig oauthconfig.Config) []string {
allowedResponseTypes := oauthConfig.OAuth.AllowedResponseTypes
if len(allowedResponseTypes) > 0 {
return allowedResponseTypes
}
result := make([]string, len(providers.SupportedResponseTypes))
for i, rt := range providers.SupportedResponseTypes {
result[i] = string(rt)
}
return result
}

// GetSupportedGrantTypes returns all supported OAuth2 grant types.
func GetSupportedGrantTypes(oauthConfig oauthconfig.Config) []string {
allowedGrantTypes := oauthConfig.OAuth.AllowedGrantTypes
if len(allowedGrantTypes) > 0 {
return allowedGrantTypes
}
result := make([]string, len(providers.SupportedGrantTypes))
for i, gt := range providers.SupportedGrantTypes {
result[i] = string(gt)
}
return result
}

// GetSupportedTokenEndpointAuthMethods returns all supported token endpoint authentication methods.
func GetSupportedTokenEndpointAuthMethods(oauthConfig oauthconfig.Config) []string {
allowedAuthMethods := oauthConfig.OAuth.AllowedAuthMethods
if len(allowedAuthMethods) > 0 {
return allowedAuthMethods
}
result := make([]string, len(providers.SupportedTokenEndpointAuthMethods))
for i, tam := range providers.SupportedTokenEndpointAuthMethods {
result[i] = string(tam)
}
return result
}

// GetSupportedSubjectTypes returns all supported OIDC subject types.
func GetSupportedSubjectTypes() []string {
return []string{SubjectTypePublic}
}

// GetStandardClaims returns all standard JWT claims that are always included in tokens.
func GetStandardClaims() []string {
return []string{
Expand Down
2 changes: 1 addition & 1 deletion backend/internal/oauth/oauth2/dcr/handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ func TestDCRHandlerTestSuite(t *testing.T) {
func (s *DCRHandlerTestSuite) SetupTest() {
s.mockService = NewDCRServiceInterfaceMock(s.T())
_ = config.InitializeServerRuntime("test", &config.Config{
OAuth: engineconfig.OAuthConfig{DCR: engineconfig.DCRConfig{Insecure: true}},
OAuth: config.OAuthConfig{DCR: engineconfig.DCRConfig{Insecure: true}},
})
cfg := testhelpers.OAuthConfig()
cfg.OAuth.DCR.Insecure = true
Expand Down
Loading
Loading