setting_service_oidc_config_test.go 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251
  1. //go:build unit
  2. package service
  3. import (
  4. "context"
  5. "fmt"
  6. "net/http"
  7. "net/http/httptest"
  8. "testing"
  9. "github.com/Wei-Shaw/sub2api/internal/config"
  10. "github.com/stretchr/testify/require"
  11. )
  12. type settingOIDCRepoStub struct {
  13. values map[string]string
  14. }
  15. func (s *settingOIDCRepoStub) Get(ctx context.Context, key string) (*Setting, error) {
  16. panic("unexpected Get call")
  17. }
  18. func (s *settingOIDCRepoStub) GetValue(ctx context.Context, key string) (string, error) {
  19. panic("unexpected GetValue call")
  20. }
  21. func (s *settingOIDCRepoStub) Set(ctx context.Context, key, value string) error {
  22. panic("unexpected Set call")
  23. }
  24. func (s *settingOIDCRepoStub) GetMultiple(ctx context.Context, keys []string) (map[string]string, error) {
  25. out := make(map[string]string, len(keys))
  26. for _, key := range keys {
  27. if value, ok := s.values[key]; ok {
  28. out[key] = value
  29. }
  30. }
  31. return out, nil
  32. }
  33. func (s *settingOIDCRepoStub) SetMultiple(ctx context.Context, settings map[string]string) error {
  34. panic("unexpected SetMultiple call")
  35. }
  36. func (s *settingOIDCRepoStub) GetAll(ctx context.Context) (map[string]string, error) {
  37. panic("unexpected GetAll call")
  38. }
  39. func (s *settingOIDCRepoStub) Delete(ctx context.Context, key string) error {
  40. panic("unexpected Delete call")
  41. }
  42. func TestGetOIDCConnectOAuthConfig_ResolvesEndpointsFromIssuerDiscovery(t *testing.T) {
  43. var discoveryHits int
  44. var baseURL string
  45. srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  46. if r.URL.Path != "/issuer/.well-known/openid-configuration" {
  47. http.NotFound(w, r)
  48. return
  49. }
  50. discoveryHits++
  51. w.Header().Set("Content-Type", "application/json")
  52. _, _ = w.Write([]byte(fmt.Sprintf(`{
  53. "authorization_endpoint":"%s/issuer/protocol/openid-connect/auth",
  54. "token_endpoint":"%s/issuer/protocol/openid-connect/token",
  55. "userinfo_endpoint":"%s/issuer/protocol/openid-connect/userinfo",
  56. "jwks_uri":"%s/issuer/protocol/openid-connect/certs"
  57. }`, baseURL, baseURL, baseURL, baseURL)))
  58. }))
  59. defer srv.Close()
  60. baseURL = srv.URL
  61. cfg := &config.Config{
  62. OIDC: config.OIDCConnectConfig{
  63. Enabled: true,
  64. ProviderName: "OIDC",
  65. ClientID: "oidc-client",
  66. ClientSecret: "oidc-secret",
  67. IssuerURL: srv.URL + "/issuer",
  68. RedirectURL: "https://example.com/api/v1/auth/oauth/oidc/callback",
  69. FrontendRedirectURL: "/auth/oidc/callback",
  70. Scopes: "openid email profile",
  71. TokenAuthMethod: "client_secret_post",
  72. ValidateIDToken: true,
  73. AllowedSigningAlgs: "RS256",
  74. ClockSkewSeconds: 120,
  75. },
  76. }
  77. repo := &settingOIDCRepoStub{values: map[string]string{}}
  78. svc := NewSettingService(repo, cfg)
  79. got, err := svc.GetOIDCConnectOAuthConfig(context.Background())
  80. require.NoError(t, err)
  81. require.Equal(t, 1, discoveryHits)
  82. require.Equal(t, srv.URL+"/issuer/.well-known/openid-configuration", got.DiscoveryURL)
  83. require.Equal(t, srv.URL+"/issuer/protocol/openid-connect/auth", got.AuthorizeURL)
  84. require.Equal(t, srv.URL+"/issuer/protocol/openid-connect/token", got.TokenURL)
  85. require.Equal(t, srv.URL+"/issuer/protocol/openid-connect/userinfo", got.UserInfoURL)
  86. require.Equal(t, srv.URL+"/issuer/protocol/openid-connect/certs", got.JWKSURL)
  87. }
  88. func TestSettingService_ParseSettings_PreservesOptionalOIDCCompatibilityFlags(t *testing.T) {
  89. svc := NewSettingService(&settingOIDCRepoStub{values: map[string]string{}}, &config.Config{})
  90. got := svc.parseSettings(map[string]string{
  91. SettingKeyOIDCConnectEnabled: "true",
  92. SettingKeyOIDCConnectUsePKCE: "false",
  93. SettingKeyOIDCConnectValidateIDToken: "false",
  94. })
  95. require.False(t, got.OIDCConnectUsePKCE)
  96. require.False(t, got.OIDCConnectValidateIDToken)
  97. }
  98. func TestSettingService_ParseSettings_DefaultsOIDCSecurityFlagsToSafeConfigValues(t *testing.T) {
  99. svc := NewSettingService(&settingOIDCRepoStub{values: map[string]string{}}, &config.Config{
  100. OIDC: config.OIDCConnectConfig{
  101. UsePKCE: true,
  102. UsePKCEExplicit: true,
  103. ValidateIDToken: true,
  104. ValidateIDTokenExplicit: true,
  105. },
  106. })
  107. got := svc.parseSettings(map[string]string{
  108. SettingKeyOIDCConnectEnabled: "true",
  109. })
  110. require.True(t, got.OIDCConnectUsePKCE)
  111. require.True(t, got.OIDCConnectValidateIDToken)
  112. }
  113. func TestSettingService_ParseSettings_DefaultsOIDCCompatibilityFlagsToSafeDefaultsWhenSettingsMissing(t *testing.T) {
  114. svc := NewSettingService(&settingOIDCRepoStub{values: map[string]string{}}, &config.Config{
  115. OIDC: config.OIDCConnectConfig{
  116. UsePKCE: true,
  117. ValidateIDToken: true,
  118. },
  119. })
  120. got := svc.parseSettings(map[string]string{
  121. SettingKeyOIDCConnectEnabled: "true",
  122. })
  123. require.True(t, got.OIDCConnectUsePKCE)
  124. require.True(t, got.OIDCConnectValidateIDToken)
  125. }
  126. func TestGetOIDCConnectOAuthConfig_AllowsCompatibilityFlagsToDisablePKCEAndIDTokenValidation(t *testing.T) {
  127. cfg := &config.Config{
  128. OIDC: config.OIDCConnectConfig{
  129. Enabled: true,
  130. ProviderName: "OIDC",
  131. ClientID: "oidc-client",
  132. ClientSecret: "oidc-secret",
  133. IssuerURL: "https://issuer.example.com",
  134. AuthorizeURL: "https://issuer.example.com/auth",
  135. TokenURL: "https://issuer.example.com/token",
  136. UserInfoURL: "https://issuer.example.com/userinfo",
  137. RedirectURL: "https://example.com/api/v1/auth/oauth/oidc/callback",
  138. FrontendRedirectURL: "/auth/oidc/callback",
  139. Scopes: "openid email profile",
  140. TokenAuthMethod: "client_secret_post",
  141. },
  142. }
  143. repo := &settingOIDCRepoStub{values: map[string]string{
  144. SettingKeyOIDCConnectEnabled: "true",
  145. SettingKeyOIDCConnectUsePKCE: "false",
  146. SettingKeyOIDCConnectValidateIDToken: "false",
  147. }}
  148. svc := NewSettingService(repo, cfg)
  149. got, err := svc.GetOIDCConnectOAuthConfig(context.Background())
  150. require.NoError(t, err)
  151. require.False(t, got.UsePKCE)
  152. require.False(t, got.ValidateIDToken)
  153. }
  154. func TestGetOIDCConnectOAuthConfig_DefaultsToSecureFlagsWhenSettingsMissing(t *testing.T) {
  155. cfg := &config.Config{
  156. OIDC: config.OIDCConnectConfig{
  157. Enabled: true,
  158. ProviderName: "OIDC",
  159. ClientID: "oidc-client",
  160. ClientSecret: "oidc-secret",
  161. IssuerURL: "https://issuer.example.com",
  162. AuthorizeURL: "https://issuer.example.com/auth",
  163. TokenURL: "https://issuer.example.com/token",
  164. UserInfoURL: "https://issuer.example.com/userinfo",
  165. JWKSURL: "https://issuer.example.com/jwks",
  166. RedirectURL: "https://example.com/api/v1/auth/oauth/oidc/callback",
  167. FrontendRedirectURL: "/auth/oidc/callback",
  168. Scopes: "openid email profile",
  169. TokenAuthMethod: "client_secret_post",
  170. UsePKCE: true,
  171. UsePKCEExplicit: true,
  172. ValidateIDToken: true,
  173. ValidateIDTokenExplicit: true,
  174. AllowedSigningAlgs: "RS256",
  175. ClockSkewSeconds: 120,
  176. },
  177. }
  178. repo := &settingOIDCRepoStub{values: map[string]string{
  179. SettingKeyOIDCConnectEnabled: "true",
  180. }}
  181. svc := NewSettingService(repo, cfg)
  182. got, err := svc.GetOIDCConnectOAuthConfig(context.Background())
  183. require.NoError(t, err)
  184. require.True(t, got.UsePKCE)
  185. require.True(t, got.ValidateIDToken)
  186. }
  187. func TestGetOIDCConnectOAuthConfig_DefaultsCompatibilityFlagsToSafeValuesWhenSettingsMissing(t *testing.T) {
  188. cfg := &config.Config{
  189. OIDC: config.OIDCConnectConfig{
  190. Enabled: true,
  191. ProviderName: "OIDC",
  192. ClientID: "oidc-client",
  193. ClientSecret: "oidc-secret",
  194. IssuerURL: "https://issuer.example.com",
  195. AuthorizeURL: "https://issuer.example.com/auth",
  196. TokenURL: "https://issuer.example.com/token",
  197. UserInfoURL: "https://issuer.example.com/userinfo",
  198. JWKSURL: "https://issuer.example.com/jwks",
  199. RedirectURL: "https://example.com/api/v1/auth/oauth/oidc/callback",
  200. FrontendRedirectURL: "/auth/oidc/callback",
  201. Scopes: "openid email profile",
  202. TokenAuthMethod: "client_secret_post",
  203. UsePKCE: true,
  204. ValidateIDToken: true,
  205. AllowedSigningAlgs: "RS256",
  206. ClockSkewSeconds: 120,
  207. },
  208. }
  209. repo := &settingOIDCRepoStub{values: map[string]string{
  210. SettingKeyOIDCConnectEnabled: "true",
  211. }}
  212. svc := NewSettingService(repo, cfg)
  213. got, err := svc.GetOIDCConnectOAuthConfig(context.Background())
  214. require.NoError(t, err)
  215. require.True(t, got.UsePKCE)
  216. require.True(t, got.ValidateIDToken)
  217. }