feat: implement OAuth Client ID Metadata Document (#1525) (#1526)

Co-authored-by: Elias Schneider <login@eliasschneider.com>
This commit is contained in:
Jean-François Roy
2026-08-02 15:05:39 +00:00
committed by GitHub
co-authored by Elias Schneider
parent 7c55bdf115
commit 1934efa84c
67 changed files with 2311 additions and 217 deletions
+4
View File
@@ -61,6 +61,8 @@ type AppConfigModel struct {
LdapAttributeGroupName AppConfigValue `json:"ldapAttributeGroupName"`
LdapAdminGroupName AppConfigValue `json:"ldapAdminGroupName"`
LdapSoftDeleteUsers AppConfigValue `json:"ldapSoftDeleteUsers" type:"bool"`
// OIDC
CIMDURLAllowlist AppConfigValue `json:"cimdUrlAllowlist"` // JSON-encoded array of strings
}
// Clone returns a deep copy of the AppConfigModel.
@@ -145,6 +147,8 @@ func getDefaultConfig() *AppConfigModel {
LdapAttributeGroupName: "",
LdapAdminGroupName: "",
LdapSoftDeleteUsers: "true",
// OIDC
CIMDURLAllowlist: "[]",
}
}
+32
View File
@@ -2,6 +2,7 @@ package appconfig
import (
"context"
"encoding/json"
"errors"
"fmt"
"os"
@@ -94,6 +95,24 @@ func (s *AppConfigService) GetConfig(parentCtx context.Context) (*AppConfigModel
return &cfg, nil
}
// GetCIMDURLAllowlist returns the configured CIMD metadata-document URL
// allowlist. Returns an empty slice if unset or malformed (which denies all).
func (s *AppConfigService) GetCIMDURLAllowlist() []string {
cfg, err := s.GetConfig(context.Background())
if err != nil {
return nil
}
raw := string(cfg.CIMDURLAllowlist)
if raw == "" {
return nil
}
var patterns []string
if err := json.Unmarshal([]byte(raw), &patterns); err != nil {
return nil
}
return patterns
}
// UpdateAppConfig replaces the entire application configuration with the values from the input DTO.
func (s *AppConfigService) UpdateAppConfig(ctx context.Context, input dto.AppConfigUpdateDto) ([]AppConfigVariable, error) {
// If the UI config is disabled, we cannot continue
@@ -101,6 +120,19 @@ func (s *AppConfigService) UpdateAppConfig(ctx context.Context, input dto.AppCon
return nil, &common.UiConfigDisabledError{}
}
// Validate the CIMD URL allowlist patterns, if provided
if input.CIMDURLAllowlist != "" {
var patterns []string
if err := json.Unmarshal([]byte(input.CIMDURLAllowlist), &patterns); err != nil {
return nil, &common.InvalidCIMDURLPatternError{Pattern: input.CIMDURLAllowlist}
}
for _, p := range patterns {
if err := utils.ValidateCallbackURLPattern(p); err != nil {
return nil, &common.InvalidCIMDURLPatternError{Pattern: p}
}
}
}
// Replace the entire config by invoking the actor
cfg, err := s.invokeConfigActor(ctx, "replace", input)
if err != nil {
@@ -342,3 +342,53 @@ func TestService_ListAppConfig(t *testing.T) {
assert.Equal(t, "XXXXXXXXXX", got)
})
}
func TestService_CIMDURLAllowlist(t *testing.T) {
t.Run("defaults to empty", func(t *testing.T) {
setUIConfigDisabled(t, false)
db := testutils.NewDatabaseForTest(t)
svc := newActorBackedService(t, db)
assert.Empty(t, svc.GetCIMDURLAllowlist())
})
t.Run("round-trips a valid allowlist", func(t *testing.T) {
setUIConfigDisabled(t, false)
db := testutils.NewDatabaseForTest(t)
svc := newActorBackedService(t, db)
_, err := svc.UpdateAppConfig(t.Context(), dto.AppConfigUpdateDto{
AppName: "App",
SessionDuration: "60",
CIMDURLAllowlist: `["https://app.example.com/**","https://*.trusted.com/oauth"]`,
})
require.NoError(t, err)
assert.Equal(t,
[]string{"https://app.example.com/**", "https://*.trusted.com/oauth"},
svc.GetCIMDURLAllowlist(),
)
})
t.Run("rejects an invalid pattern", func(t *testing.T) {
setUIConfigDisabled(t, false)
db := testutils.NewDatabaseForTest(t)
svc := newActorBackedService(t, db)
_, err := svc.UpdateAppConfig(t.Context(), dto.AppConfigUpdateDto{
AppName: "App",
SessionDuration: "60",
CIMDURLAllowlist: `["javascript:alert(1)"]`,
})
require.Error(t, err)
})
t.Run("returns empty on malformed value", func(t *testing.T) {
setUIConfigDisabled(t, false)
db := testutils.NewDatabaseForTest(t)
svc := newActorBackedService(t, db)
require.NoError(t, svc.UpdateAppConfigValues(t.Context(), "cimdUrlAllowlist", "not-json"))
assert.Empty(t, svc.GetCIMDURLAllowlist())
})
}