diff --git a/system/autosetup.go b/system/autosetup.go index fcff004df..52be6f681 100644 --- a/system/autosetup.go +++ b/system/autosetup.go @@ -2,12 +2,16 @@ package system import ( "context" + "errors" + "strings" "github.com/spf13/cobra" "github.com/titpetric/factory" "go.uber.org/zap" "github.com/cortezaproject/corteza-server/pkg/cli" + "github.com/cortezaproject/corteza-server/pkg/cli/options" + "github.com/cortezaproject/corteza-server/system/internal/auth/external" "github.com/cortezaproject/corteza-server/system/internal/repository" "github.com/cortezaproject/corteza-server/system/internal/service" "github.com/cortezaproject/corteza-server/system/types" @@ -77,3 +81,49 @@ func makeDefaultApplications(ctx context.Context, cmd *cobra.Command, c *cli.Con func discoverSettings(ctx context.Context, cmd *cobra.Command, c *cli.Config) error { return service.DefaultSettings.With(ctx).AutoDiscovery() } + +func oidcAutoDiscovery(ctx context.Context, cmd *cobra.Command, c *cli.Config) (err error) { + var provider = strings.TrimSpace(options.EnvString("", "PROVISION_OIDC_PROVIDER", "")) + + c.Log.Debug("OIDC auto discovery provision", zap.String("providers", provider)) + + if len(provider) == 0 { + return + } + + var ( + providers = strings.Split(provider, " ") + plen = len(providers) + name, purl string + eap *service.AuthSettingsExternalAuthProvider + ) + + if plen%2 == 1 { + return errors.New("expecting even number of providers") + } + + for p := 0; p < plen; p = p + 2 { + name, purl = providers[p], providers[p+1] + + eap, err = external.RegisterOidcProvider(ctx, name, purl, false, true, true) + + if err != nil { + c.Log.Error( + "could not register oidc provider", + zap.String("url", purl), + zap.String("name", name), + zap.Error(err)) + return + } else if eap == nil { + c.Log.Info("provider already exists", + zap.String("name", name)) + } else { + c.Log.Info("provider successfuly registered", + zap.String("url", purl), + zap.String("key", eap.Key), + zap.String("name", name)) + } + } + + return +} diff --git a/system/commands/auth.go b/system/commands/auth.go index cd8bc1beb..908d9366c 100644 --- a/system/commands/auth.go +++ b/system/commands/auth.go @@ -2,16 +2,13 @@ package commands import ( "context" - "net/url" "regexp" "strconv" - "strings" "github.com/spf13/cobra" "github.com/titpetric/factory" "github.com/cortezaproject/corteza-server/internal/auth" - "github.com/cortezaproject/corteza-server/internal/settings" "github.com/cortezaproject/corteza-server/pkg/cli" "github.com/cortezaproject/corteza-server/system/internal/auth/external" "github.com/cortezaproject/corteza-server/system/internal/repository" @@ -38,45 +35,17 @@ func Auth(ctx context.Context, c *cli.Config) *cobra.Command { Run: func(cmd *cobra.Command, args []string) { c.InitServices(ctx, c) - var ( - name, providerUrl = args[0], args[1] - es = service.DefaultAuthSettings + _, err := external.RegisterOidcProvider( + ctx, + args[0], + args[1], + true, + !skipValidationOnAutoDiscoveredProvider, + enableDiscoveredProvider, ) - if !skipValidationOnAutoDiscoveredProvider { - // Do basic validation of external auth settings - // will fail if secret or url are not set - cli.HandleError(es.StaticValidateExternal()) - - // Do full rediredct-URL check - cli.HandleError(es.ValidateExternalRedirectURL()) - } - - p, err := parseExternalProviderUrl(providerUrl) cli.HandleError(err) - eap, err := external.RegisterNewOpenIdClient(ctx, es, name, p.String()) - cli.HandleError(err) - - vv, err := eap.MakeValueSet("openid-connect." + name) - cli.HandleError(err) - - if enableDiscoveredProvider { - cli.HandleError(vv.Walk(func(value *settings.Value) error { - if strings.HasSuffix(value.Name, ".enabled") { - return value.SetValue(true) - } - - return nil - })) - - v := &settings.Value{Name: "auth.external.enabled"} - cli.HandleError(v.SetValue(true)) - vv = append(vv, v) - } - - cli.HandleError(service.DefaultIntSettings.BulkSet(vv)) - if enableDiscoveredProvider { cmd.Println("OIDC provider successfully added and enabled.") } else { @@ -171,20 +140,3 @@ func Auth(ctx context.Context, c *cli.Config) *cobra.Command { return cmd } - -func parseExternalProviderUrl(in string) (p *url.URL, err error) { - if i := strings.Index(in, "://"); i == -1 { - // Add schema if missing - in = "https://" + in - } - - if p, err = url.Parse(in); err != nil { - // Try to parse it - return - } else if i := strings.Index(p.Path, external.WellKnown); i > -1 { - // Cut off well-known-path - p.Path = p.Path[:i] - } - - return -} diff --git a/system/internal/auth/external/external.go b/system/internal/auth/external/external.go index 2a359b410..59abe8307 100644 --- a/system/internal/auth/external/external.go +++ b/system/internal/auth/external/external.go @@ -7,6 +7,10 @@ import ( "github.com/cortezaproject/corteza-server/system/internal/service" ) +const ( + OIDC_PROVIDER_PREFIX = "openid-connect." +) + func Init() { setupGoth(service.DefaultAuthSettings) } diff --git a/system/commands/auth_test.go b/system/internal/auth/external/external_test.go similarity index 87% rename from system/commands/auth_test.go rename to system/internal/auth/external/external_test.go index 7047cd313..83cded383 100644 --- a/system/commands/auth_test.go +++ b/system/internal/auth/external/external_test.go @@ -1,11 +1,9 @@ -package commands +package external import ( "net/url" "reflect" "testing" - - "github.com/cortezaproject/corteza-server/system/internal/auth/external" ) func Test_parseExternalProviderUrl(t *testing.T) { @@ -48,7 +46,7 @@ func Test_parseExternalProviderUrl(t *testing.T) { }, { "add schema and remove well-known", - args{"cortezaproject.org/some-subdir/" + external.WellKnown}, + args{"cortezaproject.org/some-subdir/" + WellKnown}, mustParseURL("https://cortezaproject.org/some-subdir/"), false, }, diff --git a/system/internal/auth/external/goth.go b/system/internal/auth/external/goth.go index 2b9ddd694..0d2538413 100644 --- a/system/internal/auth/external/goth.go +++ b/system/internal/auth/external/goth.go @@ -77,7 +77,7 @@ func setupGothProviders(as service.AuthSettings) { continue } - if strings.Index(name, "openid-connect.") == 0 { + if strings.Index(name, OIDC_PROVIDER_PREFIX) == 0 { if pc.IssuerUrl == "" { log.Error("failed to discover OIDC provider, URL empty") continue diff --git a/system/internal/auth/external/register.go b/system/internal/auth/external/register.go index 6fa2e1d3c..e6525fa05 100644 --- a/system/internal/auth/external/register.go +++ b/system/internal/auth/external/register.go @@ -3,19 +3,22 @@ package external import ( "context" "fmt" + "net/url" + "strings" "github.com/crusttech/go-oidc" + "github.com/cortezaproject/corteza-server/internal/settings" "github.com/cortezaproject/corteza-server/system/internal/service" ) // @todo remove dependency on github.com/crusttech/go-oidc (and github.com/coreos/go-oidc) // and move client registration to corteza codebase -func RegisterNewOpenIdClient(ctx context.Context, eas service.AuthSettings, name, url string) (eap *service.AuthSettingsExternalAuthProvider, err error) { +func DiscoverOidcProvider(ctx context.Context, eas service.AuthSettings, name, url string) (eap *service.AuthSettingsExternalAuthProvider, err error) { var ( provider *oidc.Provider client *oidc.Client - redirectUrl = fmt.Sprintf(eas.ExternalRedirectUrl, "openid-connect."+name) + redirectUrl = fmt.Sprintf(eas.ExternalRedirectUrl, OIDC_PROVIDER_PREFIX+name) ) if provider, err = oidc.NewProvider(ctx, url); err != nil { @@ -41,3 +44,88 @@ func RegisterNewOpenIdClient(ctx context.Context, eas service.AuthSettings, name return } + +func RegisterOidcProvider(ctx context.Context, name, providerUrl string, force, validate, enable bool) (eap *service.AuthSettingsExternalAuthProvider, err error) { + var ( + as = service.DefaultAuthSettings + ) + + if !force { + if _, exists := as.ExternalProviders[OIDC_PROVIDER_PREFIX+name]; exists { + return + } + } + + if validate { + // Do basic validation of external auth settings + // will fail if secret or url are not set + if err = as.StaticValidateExternal(); err != nil { + return + } + + // Do full rediredct-URL check + if err = as.ValidateExternalRedirectURL(); err != nil { + return + } + } + + p, err := parseExternalProviderUrl(providerUrl) + if err != nil { + return + } + + eap, err = DiscoverOidcProvider(ctx, as, name, p.String()) + if err != nil { + return + } + + vv, err := eap.MakeValueSet(OIDC_PROVIDER_PREFIX + name) + if err != nil { + return + } + + if enable { + err = vv.Walk(func(value *settings.Value) error { + if strings.HasSuffix(value.Name, ".enabled") { + return value.SetValue(true) + } + + return nil + }) + + if err != nil { + return + } + + v := &settings.Value{Name: "auth.external.enabled"} + err = v.SetValue(true) + if err != nil { + return + } + vv = append(vv, v) + } + + err = service.DefaultIntSettings.BulkSet(vv) + if err != nil { + return + } + + return +} + +func parseExternalProviderUrl(in string) (p *url.URL, err error) { + if i := strings.Index(in, "://"); i == -1 { + // Add schema if missing + in = "https://" + in + } + + if p, err = url.Parse(in); err != nil { + // Try to parse it + return + } else if i := strings.Index(p.Path, WellKnown); i > -1 { + // Cut off well-known-path + p.Path = p.Path[:i] + } + + return +} diff --git a/system/system.go b/system/system.go index 2e2cc06ef..9ad420d95 100644 --- a/system/system.go +++ b/system/system.go @@ -56,6 +56,7 @@ func Configure() *cli.Config { cli.HandleError(accessControlSetup(ctx, cmd, c)) cli.HandleError(makeDefaultApplications(ctx, cmd, c)) cli.HandleError(discoverSettings(ctx, cmd, c)) + cli.HandleError(oidcAutoDiscovery(ctx, cmd, c)) // Run auto configuration commands.SettingsAutoConfigure(cmd)