Refactor & merge external auth settings
This commit is contained in:
+17
-10
@@ -22,7 +22,8 @@ import (
|
||||
// Will perform OpenID connect auto-configuration
|
||||
func Auth(ctx context.Context, c *cli.Config) *cobra.Command {
|
||||
var (
|
||||
enableDiscoveredProvider bool
|
||||
enableDiscoveredProvider bool
|
||||
skipValidationOnAutoDiscoveredProvider bool
|
||||
)
|
||||
|
||||
cmd := &cobra.Command{
|
||||
@@ -39,22 +40,22 @@ func Auth(ctx context.Context, c *cli.Config) *cobra.Command {
|
||||
|
||||
var (
|
||||
name, providerUrl = args[0], args[1]
|
||||
es = service.DefaultAuthSettings
|
||||
)
|
||||
|
||||
eas, err := external.ExternalAuthSettings(service.DefaultIntSettings)
|
||||
cli.HandleError(err)
|
||||
if !skipValidationOnAutoDiscoveredProvider {
|
||||
// Do basic validation of external auth settings
|
||||
// will fail if secret or url are not set
|
||||
cli.HandleError(es.StaticValidateExternal())
|
||||
|
||||
// Do basic validation of external auth settings
|
||||
// will fail if secret or url are not set
|
||||
cli.HandleError(eas.ValidateStatic())
|
||||
|
||||
// Do full rediredct-URL check
|
||||
cli.HandleError(eas.ValidateRedirectURL())
|
||||
// Do full rediredct-URL check
|
||||
cli.HandleError(es.ValidateExternalRedirectURL())
|
||||
}
|
||||
|
||||
p, err := parseExternalProviderUrl(providerUrl)
|
||||
cli.HandleError(err)
|
||||
|
||||
eap, err := external.RegisterNewOpenIdClient(ctx, eas, name, p.String())
|
||||
eap, err := external.RegisterNewOpenIdClient(ctx, es, name, p.String())
|
||||
cli.HandleError(err)
|
||||
|
||||
vv, err := eap.MakeValueSet("openid-connect." + name)
|
||||
@@ -90,6 +91,12 @@ func Auth(ctx context.Context, c *cli.Config) *cobra.Command {
|
||||
false,
|
||||
"Enable this provider and external auth")
|
||||
|
||||
autoDiscoverCmd.Flags().BoolVar(
|
||||
&skipValidationOnAutoDiscoveredProvider,
|
||||
"skip-validation",
|
||||
false,
|
||||
"Skip validation")
|
||||
|
||||
jwtCmd := &cobra.Command{
|
||||
Use: "jwt [email-or-id]",
|
||||
Short: "Generates new JWT for a user",
|
||||
|
||||
+3
-7
@@ -3,16 +3,12 @@ package external
|
||||
import (
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/cortezaproject/corteza-server/internal/settings"
|
||||
"github.com/cortezaproject/corteza-server/pkg/logger"
|
||||
"github.com/cortezaproject/corteza-server/system/internal/service"
|
||||
)
|
||||
|
||||
func Init(settingsService settings.Service) {
|
||||
if eas, err := ExternalAuthSettings(settingsService); err != nil {
|
||||
log().Error("failed load external authentication settings", zap.Error(err))
|
||||
} else {
|
||||
setupGoth(eas)
|
||||
}
|
||||
func Init() {
|
||||
setupGoth(service.DefaultAuthSettings)
|
||||
}
|
||||
|
||||
func log() *zap.Logger {
|
||||
|
||||
+19
-17
@@ -12,6 +12,8 @@ import (
|
||||
"github.com/markbates/goth/providers/linkedin"
|
||||
"github.com/markbates/goth/providers/openidConnect"
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/cortezaproject/corteza-server/system/internal/service"
|
||||
)
|
||||
|
||||
// We're expecting that our users will be able to complete
|
||||
@@ -22,16 +24,16 @@ const (
|
||||
WellKnown = "/.well-known/openid-configuration"
|
||||
)
|
||||
|
||||
func setupGoth(eas *externalAuthSettings) {
|
||||
if eas == nil || !eas.enabled {
|
||||
func setupGoth(as service.AuthSettings) {
|
||||
if !as.ExternalEnabled {
|
||||
log().Info("external authentication disabled")
|
||||
return
|
||||
}
|
||||
|
||||
store := sessions.NewCookieStore([]byte(eas.sessionStoreSecret))
|
||||
store := sessions.NewCookieStore([]byte(as.ExternalSessionStoreSecret))
|
||||
store.MaxAge(gothMaxSessionStoreAge)
|
||||
store.Options.HttpOnly = true
|
||||
store.Options.Secure = eas.sessionStoreSecure
|
||||
store.Options.Secure = as.ExternalSessionStoreSecure
|
||||
gothic.Store = store
|
||||
|
||||
log().Debug("registering cookie session store")
|
||||
@@ -41,11 +43,11 @@ func setupGoth(eas *externalAuthSettings) {
|
||||
|
||||
}
|
||||
|
||||
setupGothProviders(eas)
|
||||
setupGothProviders(as)
|
||||
|
||||
}
|
||||
|
||||
func setupGothProviders(eas *externalAuthSettings) {
|
||||
func setupGothProviders(as service.AuthSettings) {
|
||||
var (
|
||||
err error
|
||||
scopes = []string{"email"}
|
||||
@@ -58,32 +60,32 @@ func setupGothProviders(eas *externalAuthSettings) {
|
||||
}
|
||||
|
||||
var enabled = 0
|
||||
for _, pc := range eas.providers {
|
||||
if pc.enabled {
|
||||
for _, pc := range as.ExternalProviders {
|
||||
if pc.Enabled {
|
||||
enabled++
|
||||
}
|
||||
}
|
||||
|
||||
log().Debug("initializing enabled external authentication providers", zap.Int("count", enabled))
|
||||
|
||||
for name, pc := range eas.providers {
|
||||
for name, pc := range as.ExternalProviders {
|
||||
var provider goth.Provider
|
||||
|
||||
log := log().With(zap.String("provider", name))
|
||||
|
||||
if !pc.enabled {
|
||||
if !pc.Enabled {
|
||||
continue
|
||||
}
|
||||
|
||||
if strings.Index(name, "openid-connect.") == 0 {
|
||||
if pc.issuerUrl == "" {
|
||||
if pc.IssuerUrl == "" {
|
||||
log.Error("failed to discover OIDC provider, URL empty")
|
||||
continue
|
||||
}
|
||||
|
||||
wellKnown := strings.TrimSuffix(pc.issuerUrl, "/") + WellKnown
|
||||
wellKnown := strings.TrimSuffix(pc.IssuerUrl, "/") + WellKnown
|
||||
|
||||
if provider, err = openidConnect.New(pc.key, pc.secret, pc.redirectUrl, wellKnown, scopes...); err != nil {
|
||||
if provider, err = openidConnect.New(pc.Key, pc.Secret, pc.RedirectUrl, wellKnown, scopes...); err != nil {
|
||||
log.Error("failed to discover OIDC provider", zap.Error(err), zap.String("well-known", wellKnown))
|
||||
continue
|
||||
} else {
|
||||
@@ -92,13 +94,13 @@ func setupGothProviders(eas *externalAuthSettings) {
|
||||
} else {
|
||||
switch name {
|
||||
case "github":
|
||||
provider = github.New(pc.key, pc.secret, pc.redirectUrl, scopes...)
|
||||
provider = github.New(pc.Key, pc.Secret, pc.RedirectUrl, scopes...)
|
||||
case "facebook":
|
||||
provider = facebook.New(pc.key, pc.secret, pc.redirectUrl, scopes...)
|
||||
provider = facebook.New(pc.Key, pc.Secret, pc.RedirectUrl, scopes...)
|
||||
case "gplus":
|
||||
provider = gplus.New(pc.key, pc.secret, pc.redirectUrl, scopes...)
|
||||
provider = gplus.New(pc.Key, pc.Secret, pc.RedirectUrl, scopes...)
|
||||
case "linkedin":
|
||||
provider = linkedin.New(pc.key, pc.secret, pc.redirectUrl, scopes...)
|
||||
provider = linkedin.New(pc.Key, pc.Secret, pc.RedirectUrl, scopes...)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+9
-7
@@ -5,15 +5,17 @@ import (
|
||||
"fmt"
|
||||
|
||||
"github.com/crusttech/go-oidc"
|
||||
|
||||
"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 *externalAuthSettings, name, url string) (eap *externalAuthProvider, err error) {
|
||||
func RegisterNewOpenIdClient(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.redirectUrl, "openid-connect."+name)
|
||||
redirectUrl = fmt.Sprintf(eas.ExternalRedirectUrl, "openid-connect."+name)
|
||||
)
|
||||
|
||||
if provider, err = oidc.NewProvider(ctx, url); err != nil {
|
||||
@@ -30,11 +32,11 @@ func RegisterNewOpenIdClient(ctx context.Context, eas *externalAuthSettings, nam
|
||||
return
|
||||
}
|
||||
|
||||
eap = &externalAuthProvider{
|
||||
redirectUrl: redirectUrl,
|
||||
key: client.ID,
|
||||
secret: client.Secret,
|
||||
issuerUrl: url,
|
||||
eap = &service.AuthSettingsExternalAuthProvider{
|
||||
RedirectUrl: redirectUrl,
|
||||
Key: client.ID,
|
||||
Secret: client.Secret,
|
||||
IssuerUrl: url,
|
||||
}
|
||||
|
||||
return
|
||||
|
||||
-225
@@ -1,225 +0,0 @@
|
||||
package external
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
intset "github.com/cortezaproject/corteza-server/internal/settings"
|
||||
)
|
||||
|
||||
const (
|
||||
settingsKeyBase = "auth.external."
|
||||
|
||||
settingsKeyProviders = settingsKeyBase + "providers."
|
||||
settingsKeyRedirectUrl = settingsKeyBase + "redirect-url"
|
||||
settingsKeySessionStoreSecret = settingsKeyBase + "session-store-secret"
|
||||
settingsKeySessionStoreSecure = settingsKeyBase + "session-store-secure"
|
||||
)
|
||||
|
||||
type (
|
||||
externalAuthSettings struct {
|
||||
enabled bool
|
||||
redirectUrl string
|
||||
sessionStoreSecret string
|
||||
sessionStoreSecure bool
|
||||
providers map[string]externalAuthProvider
|
||||
}
|
||||
|
||||
externalAuthProvider struct {
|
||||
enabled bool
|
||||
key string
|
||||
secret string
|
||||
redirectUrl string
|
||||
issuerUrl string
|
||||
}
|
||||
)
|
||||
|
||||
func ExternalAuthProvider(kv intset.KV) (eap externalAuthProvider, err error) {
|
||||
for k, v := range kv {
|
||||
ld := strings.LastIndex(k, ".")
|
||||
|
||||
switch k[ld+1:] {
|
||||
case "enabled":
|
||||
err = v.Unmarshal(&eap.enabled)
|
||||
case "key":
|
||||
err = v.Unmarshal(&eap.key)
|
||||
case "secret":
|
||||
err = v.Unmarshal(&eap.secret)
|
||||
case "issuer":
|
||||
err = v.Unmarshal(&eap.issuerUrl)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (eas externalAuthSettings) Enabled() bool {
|
||||
return eas.enabled
|
||||
}
|
||||
|
||||
func (eas externalAuthSettings) ValidateStatic() error {
|
||||
if eas.redirectUrl == "" {
|
||||
return errors.New("redirect URL is empty")
|
||||
}
|
||||
|
||||
const (
|
||||
tpt = "test-provider-test"
|
||||
)
|
||||
p, err := url.Parse(fmt.Sprintf(eas.redirectUrl, tpt))
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "invalid redirect URL")
|
||||
}
|
||||
|
||||
if !strings.Contains(p.Path, tpt+"/callback") {
|
||||
return errors.Wrap(err, "could find injected provider in the URL, make sure you use '%s' as a placeholder")
|
||||
}
|
||||
|
||||
if eas.sessionStoreSecret == "" {
|
||||
return errors.New("session store secret is empty")
|
||||
}
|
||||
|
||||
if eas.sessionStoreSecure && p.Scheme != "https" {
|
||||
return errors.New("session store is secure, redirect URL should have HTTPS")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (eas externalAuthSettings) ValidateRedirectURL() error {
|
||||
const tpt = "test-provider-test"
|
||||
const cb = "/callback"
|
||||
|
||||
// Replace placeholders & remove /callback
|
||||
var url = fmt.Sprintf(eas.redirectUrl, tpt)
|
||||
url = url[0 : len(url)-len(cb)]
|
||||
|
||||
rsp, err := http.DefaultClient.Get(url)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not get response from redirect URL")
|
||||
}
|
||||
|
||||
defer rsp.Body.Close()
|
||||
body, err := ioutil.ReadAll(rsp.Body)
|
||||
|
||||
if strings.Contains(string(body), tpt) {
|
||||
return nil
|
||||
}
|
||||
|
||||
return errors.New("could not validate external auth redirection URL")
|
||||
}
|
||||
|
||||
func (eap externalAuthProvider) MakeValueSet(name string) (vv intset.ValueSet, err error) {
|
||||
set := func(name string, value interface{}) error {
|
||||
v := &intset.Value{Name: name}
|
||||
if v.Value, err = json.Marshal(value); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
vv = append(vv, v)
|
||||
return nil
|
||||
}
|
||||
|
||||
prefix := settingsKeyProviders + name
|
||||
|
||||
if err = set(prefix+".enabled", eap.enabled); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = set(prefix+".key", eap.key); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = set(prefix+".secret", eap.secret); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = set(prefix+".issuer", eap.issuerUrl); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return vv, err
|
||||
}
|
||||
|
||||
// ExternalAuthSettings maps from plain values to externalAuthSettings struct
|
||||
//
|
||||
// see settings.Initialize() func
|
||||
func ExternalAuthSettings(s intset.Service) (eas *externalAuthSettings, err error) {
|
||||
// Read all settings and populate struct
|
||||
settings, err := s.FindByPrefix(settingsKeyBase)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not load settings for external auth provider")
|
||||
}
|
||||
|
||||
kv := settings.KV()
|
||||
|
||||
eas = &externalAuthSettings{
|
||||
enabled: kv.Bool(settingsKeyBase + "enabled"),
|
||||
redirectUrl: kv.String(settingsKeyRedirectUrl),
|
||||
sessionStoreSecret: kv.String(settingsKeySessionStoreSecret),
|
||||
}
|
||||
|
||||
if !kv.Has(settingsKeySessionStoreSecure) {
|
||||
// If auth.external.session-store-secure is not explicitly set;
|
||||
// check if redirectUrl uses HTTPS schema and assume we want secure session store
|
||||
eas.sessionStoreSecure = strings.Index(eas.redirectUrl, "https://") == 0
|
||||
} else {
|
||||
eas.sessionStoreSecure = kv.Bool(settingsKeySessionStoreSecure)
|
||||
}
|
||||
|
||||
if eas.providers, err = extractProviders(eas.redirectUrl, kv); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func extractProviders(redirectUrl string, kv intset.KV) (providers map[string]externalAuthProvider, err error) {
|
||||
// Standard providers:
|
||||
providers = map[string]externalAuthProvider{
|
||||
"github": {},
|
||||
"facebook": {},
|
||||
"gplus": {},
|
||||
"linkedin": {},
|
||||
}
|
||||
|
||||
oidcKeyBase := settingsKeyProviders + "openid-connect."
|
||||
for k := range kv.Filter(oidcKeyBase) {
|
||||
if len(k) < len(oidcKeyBase)+2 {
|
||||
// skip invalid keys
|
||||
continue
|
||||
}
|
||||
|
||||
// find next dot:
|
||||
name := k[len(oidcKeyBase):]
|
||||
dotPos := strings.Index(name, ".")
|
||||
if dotPos > 0 {
|
||||
name = name[:dotPos]
|
||||
}
|
||||
|
||||
providers["openid-connect."+name] = externalAuthProvider{}
|
||||
}
|
||||
|
||||
for provider := range providers {
|
||||
if eap, err := ExternalAuthProvider(kv.Filter(settingsKeyProviders + provider)); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
if eap.enabled {
|
||||
eap.redirectUrl = fmt.Sprintf(redirectUrl, provider)
|
||||
}
|
||||
|
||||
providers[provider] = eap
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
-142
@@ -1,142 +0,0 @@
|
||||
package external
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
intset "github.com/cortezaproject/corteza-server/internal/settings"
|
||||
"github.com/jmoiron/sqlx/types"
|
||||
)
|
||||
|
||||
func Test_extractProviders(t *testing.T) {
|
||||
type args struct {
|
||||
redirectUrl string
|
||||
kv intset.KV
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
wantProviders map[string]externalAuthProvider
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "Empty KV",
|
||||
args: args{},
|
||||
wantProviders: map[string]externalAuthProvider{
|
||||
"github": externalAuthProvider{},
|
||||
"linkedin": externalAuthProvider{},
|
||||
"gplus": externalAuthProvider{},
|
||||
"facebook": externalAuthProvider{},
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
name: "Random config",
|
||||
args: args{
|
||||
redirectUrl: "http://%s",
|
||||
kv: intset.KV{
|
||||
"auth.external.providers.openid-connect.foo.enabled": types.JSONText("true"),
|
||||
"auth.external.providers.openid-connect.foo.issuer": types.JSONText(`"url"`),
|
||||
"auth.external.providers.openid-connect.foo.key": types.JSONText(`"key"`),
|
||||
"auth.external.providers.openid-connect.foo.secret": types.JSONText(`"secret"`),
|
||||
"auth.external.providers.openid-connect.bar.enabled": types.JSONText("true"),
|
||||
"auth.external.providers.openid-connect.bar.issuer": types.JSONText(`"url"`),
|
||||
"auth.external.providers.openid-connect.bar.key": types.JSONText(`"key"`),
|
||||
"auth.external.providers.openid-connect.bar.secret": types.JSONText(`"secret"`),
|
||||
"auth.external.providers.openid-connect.baz.enabled": types.JSONText("false"),
|
||||
"auth.external.providers.github.enabled": types.JSONText(`false`),
|
||||
"auth.external.providers.facebook.enabled": types.JSONText(`true`),
|
||||
"auth.external.providers.facebook.secret": types.JSONText(`"fb-secret"`),
|
||||
},
|
||||
},
|
||||
|
||||
wantProviders: map[string]externalAuthProvider{
|
||||
"openid-connect.foo": externalAuthProvider{
|
||||
enabled: true,
|
||||
key: "key",
|
||||
secret: "secret",
|
||||
redirectUrl: "http://openid-connect.foo",
|
||||
issuerUrl: "url",
|
||||
},
|
||||
"openid-connect.bar": externalAuthProvider{
|
||||
enabled: true,
|
||||
key: "key",
|
||||
secret: "secret",
|
||||
redirectUrl: "http://openid-connect.bar",
|
||||
issuerUrl: "url",
|
||||
},
|
||||
"openid-connect.baz": externalAuthProvider{
|
||||
enabled: false,
|
||||
},
|
||||
"github": externalAuthProvider{
|
||||
enabled: false,
|
||||
},
|
||||
"linkedin": externalAuthProvider{
|
||||
enabled: false,
|
||||
},
|
||||
"gplus": externalAuthProvider{
|
||||
enabled: false,
|
||||
},
|
||||
"facebook": externalAuthProvider{
|
||||
enabled: true,
|
||||
secret: "fb-secret",
|
||||
redirectUrl: "http://facebook",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
gotProviders, err := extractProviders(tt.args.redirectUrl, tt.args.kv)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("extractProviders() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if !reflect.DeepEqual(gotProviders, tt.wantProviders) {
|
||||
t.Errorf("extractProviders() = %v, want %v", gotProviders, tt.wantProviders)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestExternalAuthProvider(t *testing.T) {
|
||||
type args struct {
|
||||
kv intset.KV
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
wantEap externalAuthProvider
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
args: args{
|
||||
kv: intset.KV{
|
||||
"foo.enabled": types.JSONText("true"),
|
||||
"foo.issuer": types.JSONText(`"example.tld"`),
|
||||
"foo.key": types.JSONText(`"key"`),
|
||||
"foo.secret": types.JSONText(`"secret"`),
|
||||
},
|
||||
},
|
||||
|
||||
wantEap: externalAuthProvider{
|
||||
enabled: true,
|
||||
key: "key",
|
||||
secret: "secret",
|
||||
issuerUrl: "example.tld",
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
gotEap, err := ExternalAuthProvider(tt.args.kv)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("ExternalAuthProvider() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if !reflect.DeepEqual(gotEap, tt.wantEap) {
|
||||
t.Errorf("ExternalAuthProvider() = %v, want %v", gotEap, tt.wantEap)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -29,7 +29,7 @@ type (
|
||||
credentials repository.CredentialsRepository
|
||||
users repository.UserRepository
|
||||
roles repository.RoleRepository
|
||||
settings authSettings
|
||||
settings AuthSettings
|
||||
notifications AuthNotificationService
|
||||
|
||||
providerValidator func(string) error
|
||||
@@ -130,7 +130,7 @@ func (svc auth) log(fields ...zapcore.Field) *zap.Logger {
|
||||
// 2.3. create credentials for that social login
|
||||
//
|
||||
func (svc auth) External(profile goth.User) (u *types.User, err error) {
|
||||
if !svc.settings.externalEnabled {
|
||||
if !svc.settings.ExternalEnabled {
|
||||
return nil, errors.New("external authentication disabled")
|
||||
}
|
||||
|
||||
@@ -250,7 +250,7 @@ func (svc auth) External(profile goth.User) (u *types.User, err error) {
|
||||
|
||||
// FrontendRedirectURL - a proxy to frontend redirect url setting
|
||||
func (svc auth) FrontendRedirectURL() string {
|
||||
return svc.settings.frontendUrlRedirect
|
||||
return svc.settings.FrontendUrlRedirect
|
||||
}
|
||||
|
||||
// InternalSignUp protocol
|
||||
@@ -259,11 +259,11 @@ func (svc auth) FrontendRedirectURL() string {
|
||||
//
|
||||
// We're accepting the whole user object here and copy all we need to the new user
|
||||
func (svc auth) InternalSignUp(input *types.User, password string) (u *types.User, err error) {
|
||||
if !svc.settings.internalEnabled {
|
||||
if !svc.settings.InternalEnabled {
|
||||
return nil, errors.New("internal authentication disabled")
|
||||
}
|
||||
|
||||
if !svc.settings.internalSignUpEnabled {
|
||||
if !svc.settings.InternalSignUpEnabled {
|
||||
return nil, errors.New("internal signup disabled")
|
||||
}
|
||||
|
||||
@@ -324,7 +324,7 @@ func (svc auth) InternalSignUp(input *types.User, password string) (u *types.Use
|
||||
Handle: input.Handle,
|
||||
|
||||
// Do we need confirmed email?
|
||||
EmailConfirmed: !svc.settings.internalSignUpEmailConfirmationRequired,
|
||||
EmailConfirmed: !svc.settings.InternalSignUpEmailConfirmationRequired,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
@@ -367,7 +367,7 @@ func (svc auth) validateInternalSignUp(email string) (err error) {
|
||||
// Expects plain text password as an input
|
||||
func (svc auth) InternalLogin(email string, password string) (u *types.User, err error) {
|
||||
|
||||
if !svc.settings.internalEnabled {
|
||||
if !svc.settings.InternalEnabled {
|
||||
return nil, errors.New("internal authentication disabled")
|
||||
}
|
||||
|
||||
@@ -468,7 +468,7 @@ func (svc auth) checkPassword(password string, cc types.CredentialsSet) (err err
|
||||
func (svc auth) SetPassword(userID uint64, newPassword string) (err error) {
|
||||
log := svc.log(zap.Uint64("userID", userID))
|
||||
|
||||
if !svc.settings.internalEnabled {
|
||||
if !svc.settings.InternalEnabled {
|
||||
return errors.New("internal authentication disabled")
|
||||
}
|
||||
|
||||
@@ -490,7 +490,7 @@ func (svc auth) SetPassword(userID uint64, newPassword string) (err error) {
|
||||
func (svc auth) ChangePassword(userID uint64, oldPassword, newPassword string) (err error) {
|
||||
log := svc.log(zap.Uint64("userID", userID))
|
||||
|
||||
if !svc.settings.internalEnabled {
|
||||
if !svc.settings.InternalEnabled {
|
||||
return errors.New("internal authentication disabled")
|
||||
}
|
||||
|
||||
@@ -577,7 +577,7 @@ func (svc auth) ValidateAuthRequestToken(token string) (user *types.User, err er
|
||||
}
|
||||
|
||||
func (svc auth) ValidateEmailConfirmationToken(token string) (user *types.User, err error) {
|
||||
if !svc.settings.internalEnabled {
|
||||
if !svc.settings.InternalEnabled {
|
||||
return nil, errors.New("internal authentication disabled")
|
||||
}
|
||||
|
||||
@@ -595,11 +595,11 @@ func (svc auth) ValidateEmailConfirmationToken(token string) (user *types.User,
|
||||
}
|
||||
|
||||
func (svc auth) ValidatePasswordResetToken(token string) (user *types.User, err error) {
|
||||
if !svc.settings.internalEnabled {
|
||||
if !svc.settings.InternalEnabled {
|
||||
return nil, errors.New("internal authentication disabled")
|
||||
}
|
||||
|
||||
if !svc.settings.internalPasswordResetEnabled {
|
||||
if !svc.settings.InternalPasswordResetEnabled {
|
||||
return nil, errors.New("password reset disabled")
|
||||
}
|
||||
|
||||
@@ -619,12 +619,12 @@ func (svc auth) ValidatePasswordResetToken(token string) (user *types.User, err
|
||||
|
||||
// ExchangePasswordResetToken exchanges reset password token for a new one and returns it with user info
|
||||
func (svc auth) ExchangePasswordResetToken(token string) (user *types.User, exchangedToken string, err error) {
|
||||
if !svc.settings.internalEnabled {
|
||||
if !svc.settings.InternalEnabled {
|
||||
err = errors.New("internal authentication disabled")
|
||||
return
|
||||
}
|
||||
|
||||
if !svc.settings.internalPasswordResetEnabled {
|
||||
if !svc.settings.InternalPasswordResetEnabled {
|
||||
err = errors.New("password reset disabled")
|
||||
return
|
||||
}
|
||||
@@ -646,7 +646,7 @@ func (svc auth) ExchangePasswordResetToken(token string) (user *types.User, exch
|
||||
}
|
||||
|
||||
func (svc auth) SendEmailAddressConfirmationToken(email string) error {
|
||||
if !svc.settings.internalEnabled {
|
||||
if !svc.settings.InternalEnabled {
|
||||
return errors.New("internal authentication disabled")
|
||||
}
|
||||
|
||||
@@ -683,11 +683,11 @@ func (svc auth) sendEmailAddressConfirmationToken(u *types.User) (err error) {
|
||||
|
||||
func (svc auth) SendPasswordResetToken(email string) error {
|
||||
|
||||
if !svc.settings.internalEnabled {
|
||||
if !svc.settings.InternalEnabled {
|
||||
return errors.New("internal authentication disabled")
|
||||
}
|
||||
|
||||
if !svc.settings.internalPasswordResetEnabled {
|
||||
if !svc.settings.InternalPasswordResetEnabled {
|
||||
return errors.New("password reset disabled")
|
||||
}
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ type (
|
||||
ctx context.Context
|
||||
logger *zap.Logger
|
||||
|
||||
settings authSettings
|
||||
settings AuthSettings
|
||||
}
|
||||
|
||||
AuthNotificationService interface {
|
||||
@@ -127,20 +127,20 @@ func (svc authNotification) log(fields ...zapcore.Field) *zap.Logger {
|
||||
func (svc authNotification) EmailConfirmation(lang string, emailAddress string, token string) error {
|
||||
return svc.send("email-confirmation", lang, authNotificationPayload{
|
||||
EmailAddress: emailAddress,
|
||||
URL: svc.settings.frontendUrlEmailConfirmation + token,
|
||||
URL: svc.settings.FrontendUrlEmailConfirmation + token,
|
||||
})
|
||||
}
|
||||
|
||||
func (svc authNotification) PasswordReset(lang string, emailAddress string, token string) error {
|
||||
return svc.send("password-reset", lang, authNotificationPayload{
|
||||
EmailAddress: emailAddress,
|
||||
URL: svc.settings.frontendUrlPasswordReset + token,
|
||||
URL: svc.settings.FrontendUrlPasswordReset + token,
|
||||
})
|
||||
}
|
||||
|
||||
func (svc authNotification) newMail() *gomail.Message {
|
||||
m := gomail.NewMessage()
|
||||
m.SetAddressHeader("From", svc.settings.mailFromAddress, svc.settings.mailFromName)
|
||||
m.SetAddressHeader("From", svc.settings.MailFromAddress, svc.settings.MailFromName)
|
||||
return m
|
||||
}
|
||||
|
||||
@@ -148,9 +148,9 @@ func (svc authNotification) send(name, lang string, payload authNotificationPayl
|
||||
ntf := svc.newMail()
|
||||
|
||||
payload.Logo = template.URL(defaultLogo)
|
||||
payload.BaseURL = svc.settings.frontendUrlBase
|
||||
payload.SignatureName = svc.settings.mailFromName
|
||||
payload.SignatureEmail = svc.settings.mailFromAddress
|
||||
payload.BaseURL = svc.settings.FrontendUrlBase
|
||||
payload.SignatureName = svc.settings.MailFromName
|
||||
payload.SignatureEmail = svc.settings.MailFromAddress
|
||||
|
||||
ntf.SetAddressHeader("To", payload.EmailAddress, "")
|
||||
ntf.SetHeader("Subject", svc.render(emailTemplates[name+"."+lang+".subject"], payload))
|
||||
|
||||
@@ -1,74 +1,182 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/markbates/goth"
|
||||
"github.com/pkg/errors"
|
||||
|
||||
intset "github.com/cortezaproject/corteza-server/internal/settings"
|
||||
)
|
||||
|
||||
type (
|
||||
authSettings struct {
|
||||
AuthSettings struct {
|
||||
// Password reset path (<frontend password reset url> "?token=" + <token>)
|
||||
frontendUrlPasswordReset string
|
||||
FrontendUrlPasswordReset string
|
||||
|
||||
// EmailAddress confirmation path (<frontend email confirmation url> "?token=" + <token>)
|
||||
frontendUrlEmailConfirmation string
|
||||
FrontendUrlEmailConfirmation string
|
||||
|
||||
// Where to redirect user after external auth flow
|
||||
frontendUrlRedirect string
|
||||
FrontendUrlRedirect string
|
||||
|
||||
// Webapp Base URL
|
||||
frontendUrlBase string
|
||||
FrontendUrlBase string
|
||||
|
||||
mailFromAddress string
|
||||
mailFromName string
|
||||
|
||||
// Is external authentication
|
||||
externalEnabled bool
|
||||
MailFromAddress string
|
||||
MailFromName string
|
||||
|
||||
// Is internal authentication (username + password) enabled
|
||||
internalEnabled bool
|
||||
InternalEnabled bool
|
||||
|
||||
// Can users register
|
||||
internalSignUpEnabled bool
|
||||
InternalSignUpEnabled bool
|
||||
|
||||
// Users should confirm their emails when signing-up
|
||||
internalSignUpEmailConfirmationRequired bool
|
||||
InternalSignUpEmailConfirmationRequired bool
|
||||
|
||||
// Can users reset their passwords
|
||||
internalPasswordResetEnabled bool
|
||||
InternalPasswordResetEnabled bool
|
||||
|
||||
// Is external authentication
|
||||
ExternalEnabled bool
|
||||
|
||||
// Where to redirect (url used for registration)
|
||||
ExternalRedirectUrl string
|
||||
|
||||
// session secret to use
|
||||
ExternalSessionStoreSecret string
|
||||
|
||||
// session store should be secure
|
||||
ExternalSessionStoreSecure bool
|
||||
|
||||
// all external providers we know
|
||||
ExternalProviders map[string]AuthSettingsExternalAuthProvider
|
||||
}
|
||||
|
||||
authSettingsStore interface {
|
||||
Bool(string) bool
|
||||
String(string) string
|
||||
AuthSettingsExternalAuthProvider struct {
|
||||
Enabled bool
|
||||
Key string
|
||||
Secret string
|
||||
RedirectUrl string
|
||||
IssuerUrl string
|
||||
}
|
||||
)
|
||||
|
||||
// AuthSettings maps from plain values to authSettings struct
|
||||
// ParseAuthSettings maps from plain values to AuthSettings struct
|
||||
//
|
||||
// see settings.Initialize() func
|
||||
func AuthSettings(kv authSettingsStore) authSettings {
|
||||
return authSettings{
|
||||
frontendUrlPasswordReset: kv.String("auth.frontend.url.password-reset"),
|
||||
frontendUrlEmailConfirmation: kv.String("auth.frontend.url.email-confirmation"),
|
||||
frontendUrlRedirect: kv.String("auth.frontend.url.redirect"),
|
||||
frontendUrlBase: kv.String("auth.frontend.url.base"),
|
||||
func ParseAuthSettings(kv intset.KV) (as AuthSettings, err error) {
|
||||
as = AuthSettings{
|
||||
FrontendUrlPasswordReset: kv.String("auth.frontend.url.password-reset"),
|
||||
FrontendUrlEmailConfirmation: kv.String("auth.frontend.url.email-confirmation"),
|
||||
FrontendUrlRedirect: kv.String("auth.frontend.url.redirect"),
|
||||
FrontendUrlBase: kv.String("auth.frontend.url.base"),
|
||||
|
||||
mailFromAddress: kv.String("auth.mail.from-address"),
|
||||
mailFromName: kv.String("auth.mail.from-name"),
|
||||
MailFromAddress: kv.String("auth.mail.from-address"),
|
||||
MailFromName: kv.String("auth.mail.from-name"),
|
||||
|
||||
externalEnabled: kv.Bool("auth.external.enabled"),
|
||||
internalEnabled: kv.Bool("auth.internal.enabled"),
|
||||
InternalEnabled: kv.Bool("auth.internal.enabled"),
|
||||
|
||||
internalSignUpEnabled: kv.Bool("auth.internal.signup.enabled"),
|
||||
internalSignUpEmailConfirmationRequired: kv.Bool("auth.internal.signup-email-confirmation-required"),
|
||||
InternalSignUpEnabled: kv.Bool("auth.internal.signup.enabled"),
|
||||
InternalSignUpEmailConfirmationRequired: kv.Bool("auth.internal.signup-email-confirmation-required"),
|
||||
|
||||
internalPasswordResetEnabled: kv.Bool("auth.internal.password-reset.enabled"),
|
||||
InternalPasswordResetEnabled: kv.Bool("auth.internal.password-reset.enabled"),
|
||||
|
||||
ExternalEnabled: kv.Bool("auth.external.enabled"),
|
||||
|
||||
ExternalRedirectUrl: kv.String("auth.external.redirect-url"),
|
||||
ExternalSessionStoreSecret: kv.String("auth.external.session-store-secret"),
|
||||
ExternalSessionStoreSecure: kv.Bool("auth.external.session-store-secure"),
|
||||
}
|
||||
|
||||
as.ExternalProviders, err = as.parseExternalProviders(kv)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (s authSettings) Format() map[string]interface{} {
|
||||
func (as *AuthSettings) parseExternalProviders(kv intset.KV) (map[string]AuthSettingsExternalAuthProvider, error) {
|
||||
// Standard providers:
|
||||
var (
|
||||
ep = map[string]AuthSettingsExternalAuthProvider{
|
||||
"github": {},
|
||||
"facebook": {},
|
||||
"gplus": {},
|
||||
"linkedin": {},
|
||||
}
|
||||
|
||||
// Add all oidc providers we find
|
||||
extKeyBase = "auth.external.providers."
|
||||
oidcKeyBase = extKeyBase + "openid-connect."
|
||||
)
|
||||
|
||||
for k := range kv.Filter(oidcKeyBase) {
|
||||
if len(k) < len(oidcKeyBase)+2 {
|
||||
// skip invalid keys
|
||||
continue
|
||||
}
|
||||
|
||||
// find next dot:
|
||||
name := k[len(oidcKeyBase):]
|
||||
dotPos := strings.Index(name, ".")
|
||||
if dotPos > 0 {
|
||||
name = name[:dotPos]
|
||||
}
|
||||
|
||||
ep["openid-connect."+name] = AuthSettingsExternalAuthProvider{}
|
||||
}
|
||||
|
||||
for provider := range ep {
|
||||
if p, err := as.parseExternalProvider(kv.Filter(extKeyBase + provider)); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
if as.ExternalRedirectUrl != "" && p.Enabled {
|
||||
p.RedirectUrl = fmt.Sprintf(as.ExternalRedirectUrl, provider)
|
||||
}
|
||||
|
||||
ep[provider] = *p
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return ep, nil
|
||||
}
|
||||
|
||||
// Parses external provider out of KV set
|
||||
//
|
||||
// Function only looks at the end of key string (after last dot)
|
||||
// so passing multiple providers will result in overriding values
|
||||
func (as *AuthSettings) parseExternalProvider(kv intset.KV) (p *AuthSettingsExternalAuthProvider, err error) {
|
||||
p = &AuthSettingsExternalAuthProvider{}
|
||||
|
||||
for k, v := range kv {
|
||||
ld := strings.LastIndex(k, ".")
|
||||
|
||||
switch k[ld+1:] {
|
||||
case "enabled":
|
||||
err = v.Unmarshal(&p.Enabled)
|
||||
case "key":
|
||||
err = v.Unmarshal(&p.Key)
|
||||
case "secret":
|
||||
err = v.Unmarshal(&p.Secret)
|
||||
case "issuer":
|
||||
err = v.Unmarshal(&p.IssuerUrl)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (as AuthSettings) Format() map[string]interface{} {
|
||||
type (
|
||||
externalProvider struct {
|
||||
Label string `json:"label"`
|
||||
@@ -109,12 +217,101 @@ func (s authSettings) Format() map[string]interface{} {
|
||||
}
|
||||
|
||||
return map[string]interface{}{
|
||||
"internalEnabled": s.internalEnabled,
|
||||
"internalPasswordResetEnabled": s.internalPasswordResetEnabled,
|
||||
"internalSignUpEmailConfirmationRequired": s.internalSignUpEmailConfirmationRequired,
|
||||
"internalSignUpEnabled": s.internalSignUpEnabled,
|
||||
"internalEnabled": as.InternalEnabled,
|
||||
"internalPasswordResetEnabled": as.InternalPasswordResetEnabled,
|
||||
"internalSignUpEmailConfirmationRequired": as.InternalSignUpEmailConfirmationRequired,
|
||||
"internalSignUpEnabled": as.InternalSignUpEnabled,
|
||||
|
||||
"externalEnabled": s.externalEnabled,
|
||||
"externalEnabled": as.ExternalEnabled,
|
||||
"externalProviders": providers,
|
||||
}
|
||||
}
|
||||
|
||||
// StaticValidateExternal
|
||||
//
|
||||
// Simple checks of external auth settings
|
||||
func (as AuthSettings) StaticValidateExternal() error {
|
||||
if as.ExternalRedirectUrl == "" {
|
||||
return errors.New("redirect URL is empty")
|
||||
}
|
||||
|
||||
const (
|
||||
tpt = "test-provider-test"
|
||||
)
|
||||
p, err := url.Parse(fmt.Sprintf(as.ExternalRedirectUrl, tpt))
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "invalid redirect URL")
|
||||
}
|
||||
|
||||
if !strings.Contains(p.Path, tpt+"/callback") {
|
||||
return errors.Wrap(err, "could find injected provider in the URL, make sure you use '%s' as a placeholder")
|
||||
}
|
||||
|
||||
if as.ExternalSessionStoreSecret == "" {
|
||||
return errors.New("session store secret is empty")
|
||||
}
|
||||
|
||||
if as.ExternalSessionStoreSecure && p.Scheme != "https" {
|
||||
return errors.New("session store is secure, redirect URL should have HTTPS")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ValidateExternalRedirectURL
|
||||
//
|
||||
// Validates external redirect URL
|
||||
func (as AuthSettings) ValidateExternalRedirectURL() error {
|
||||
const tpt = "test-provider-test"
|
||||
const cb = "/callback"
|
||||
|
||||
// Replace placeholders & remove /callback
|
||||
var url = fmt.Sprintf(as.ExternalRedirectUrl, tpt)
|
||||
url = url[0 : len(url)-len(cb)]
|
||||
|
||||
rsp, err := http.DefaultClient.Get(url)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not get response from redirect URL")
|
||||
}
|
||||
|
||||
defer rsp.Body.Close()
|
||||
body, err := ioutil.ReadAll(rsp.Body)
|
||||
|
||||
if strings.Contains(string(body), tpt) {
|
||||
return nil
|
||||
}
|
||||
|
||||
return errors.New("could not validate external auth redirection URL")
|
||||
}
|
||||
|
||||
func (p AuthSettingsExternalAuthProvider) MakeValueSet(name string) (vv intset.ValueSet, err error) {
|
||||
set := func(name string, value interface{}) error {
|
||||
v := &intset.Value{Name: name}
|
||||
if v.Value, err = json.Marshal(value); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
vv = append(vv, v)
|
||||
return nil
|
||||
}
|
||||
|
||||
prefix := "auth.external.providers." + name
|
||||
|
||||
if err = set(prefix+".enabled", p.Enabled); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = set(prefix+".key", p.Key); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = set(prefix+".secret", p.Secret); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = set(prefix+".issuer", p.IssuerUrl); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return vv, err
|
||||
}
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/jmoiron/sqlx/types"
|
||||
|
||||
intset "github.com/cortezaproject/corteza-server/internal/settings"
|
||||
)
|
||||
|
||||
func Test_extractProviders(t *testing.T) {
|
||||
type args struct {
|
||||
redirectUrl string
|
||||
kv intset.KV
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
wantProviders map[string]AuthSettingsExternalAuthProvider
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "Empty KV",
|
||||
args: args{},
|
||||
wantProviders: map[string]AuthSettingsExternalAuthProvider{
|
||||
"github": AuthSettingsExternalAuthProvider{},
|
||||
"linkedin": AuthSettingsExternalAuthProvider{},
|
||||
"gplus": AuthSettingsExternalAuthProvider{},
|
||||
"facebook": AuthSettingsExternalAuthProvider{},
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
name: "Random config",
|
||||
args: args{
|
||||
kv: intset.KV{
|
||||
"auth.external.redirect-url": types.JSONText(`"http://%s"`),
|
||||
"auth.external.providers.openid-connect.foo.enabled": types.JSONText("true"),
|
||||
"auth.external.providers.openid-connect.foo.issuer": types.JSONText(`"url"`),
|
||||
"auth.external.providers.openid-connect.foo.key": types.JSONText(`"key"`),
|
||||
"auth.external.providers.openid-connect.foo.secret": types.JSONText(`"secret"`),
|
||||
"auth.external.providers.openid-connect.bar.enabled": types.JSONText("true"),
|
||||
"auth.external.providers.openid-connect.bar.issuer": types.JSONText(`"url"`),
|
||||
"auth.external.providers.openid-connect.bar.key": types.JSONText(`"key"`),
|
||||
"auth.external.providers.openid-connect.bar.secret": types.JSONText(`"secret"`),
|
||||
"auth.external.providers.openid-connect.baz.enabled": types.JSONText("false"),
|
||||
"auth.external.providers.github.enabled": types.JSONText(`false`),
|
||||
"auth.external.providers.facebook.enabled": types.JSONText(`true`),
|
||||
"auth.external.providers.facebook.secret": types.JSONText(`"fb-secret"`),
|
||||
},
|
||||
},
|
||||
|
||||
wantProviders: map[string]AuthSettingsExternalAuthProvider{
|
||||
"openid-connect.foo": AuthSettingsExternalAuthProvider{
|
||||
Enabled: true,
|
||||
Key: "key",
|
||||
Secret: "secret",
|
||||
RedirectUrl: "http://openid-connect.foo",
|
||||
IssuerUrl: "url",
|
||||
},
|
||||
"openid-connect.bar": AuthSettingsExternalAuthProvider{
|
||||
Enabled: true,
|
||||
Key: "key",
|
||||
Secret: "secret",
|
||||
RedirectUrl: "http://openid-connect.bar",
|
||||
IssuerUrl: "url",
|
||||
},
|
||||
"openid-connect.baz": AuthSettingsExternalAuthProvider{},
|
||||
"github": AuthSettingsExternalAuthProvider{},
|
||||
"linkedin": AuthSettingsExternalAuthProvider{},
|
||||
"gplus": AuthSettingsExternalAuthProvider{},
|
||||
"facebook": AuthSettingsExternalAuthProvider{
|
||||
Enabled: true,
|
||||
Secret: "fb-secret",
|
||||
RedirectUrl: "http://facebook",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
as, err := ParseAuthSettings(tt.args.kv)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("extractProviders() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(as.ExternalProviders, tt.wantProviders) {
|
||||
t.Errorf("extractProviders()\ngot: %v\nwant: %v\n", as.ExternalProviders, tt.wantProviders)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -65,7 +65,7 @@ func TestAuth_External_Existing(t *testing.T) {
|
||||
|
||||
svc := makeMockAuthService(usrRpoMock, crdRpoMock)
|
||||
svc.logger = zap.NewNop()
|
||||
svc.settings.externalEnabled = true
|
||||
svc.settings.ExternalEnabled = true
|
||||
|
||||
{
|
||||
auser, err := svc.External(p)
|
||||
@@ -111,7 +111,7 @@ func TestAuth_External_NonExisting(t *testing.T) {
|
||||
|
||||
svc := makeMockAuthService(usrRpoMock, crdRpoMock)
|
||||
svc.logger = zap.NewNop()
|
||||
svc.settings.externalEnabled = true
|
||||
svc.settings.ExternalEnabled = true
|
||||
|
||||
{
|
||||
auser, err := svc.External(p)
|
||||
@@ -137,7 +137,7 @@ func Test_auth_validateInternalLogin(t *testing.T) {
|
||||
}
|
||||
svc := auth{}
|
||||
svc.logger = zap.NewNop()
|
||||
svc.settings.internalEnabled = true
|
||||
svc.settings.InternalEnabled = true
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
|
||||
@@ -30,7 +30,7 @@ var (
|
||||
|
||||
DefaultSettings SettingsService
|
||||
DefaultAuthNotification AuthNotificationService
|
||||
DefaultAuthSettings authSettings
|
||||
DefaultAuthSettings AuthSettings
|
||||
|
||||
DefaultAuth AuthService
|
||||
DefaultUser UserService
|
||||
|
||||
@@ -35,7 +35,7 @@ type (
|
||||
BulkSet(vv internalSettings.ValueSet) (err error)
|
||||
Get(name string, ownedBy uint64) (out *internalSettings.Value, err error)
|
||||
|
||||
LoadAuthSettings() (authSettings, error)
|
||||
LoadAuthSettings() (AuthSettings, error)
|
||||
AutoDiscovery() error
|
||||
}
|
||||
)
|
||||
@@ -99,12 +99,12 @@ func (svc settings) Get(name string, ownedBy uint64) (out *internalSettings.Valu
|
||||
}
|
||||
|
||||
// Loads auth.% settings, initializes & fills auth settings struct
|
||||
func (svc settings) LoadAuthSettings() (authSettings, error) {
|
||||
func (svc settings) LoadAuthSettings() (AuthSettings, error) {
|
||||
vv, err := svc.internalSettings.FindByPrefix("auth.")
|
||||
if err != nil {
|
||||
return authSettings{}, err
|
||||
return AuthSettings{}, err
|
||||
}
|
||||
return AuthSettings(vv.KV()), nil
|
||||
return ParseAuthSettings(vv.KV())
|
||||
}
|
||||
|
||||
// AutoDiscovery orchestrates settings auto discovery
|
||||
|
||||
@@ -26,7 +26,7 @@ type (
|
||||
ctx context.Context
|
||||
logger *zap.Logger
|
||||
|
||||
settings authSettings
|
||||
settings AuthSettings
|
||||
|
||||
auth userAuth
|
||||
|
||||
@@ -245,7 +245,7 @@ func (svc user) Unsuspend(ID uint64) (err error) {
|
||||
func (svc user) SetPassword(userID uint64, newPassword string) (err error) {
|
||||
log := svc.log(zap.Uint64("userID", userID))
|
||||
|
||||
if !svc.settings.internalEnabled {
|
||||
if !svc.settings.InternalEnabled {
|
||||
return errors.New("internal authentication disabled")
|
||||
}
|
||||
|
||||
|
||||
+2
-1
@@ -64,7 +64,8 @@ func Configure() *cli.Config {
|
||||
service.DefaultAuthSettings, _ = service.DefaultSettings.LoadAuthSettings()
|
||||
}
|
||||
|
||||
external.Init(service.DefaultIntSettings)
|
||||
// Initialize external authentication (from default settings)
|
||||
external.Init()
|
||||
go service.Watchers(ctx)
|
||||
return nil
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user