mirror of
https://github.com/Luzifer/nginx-sso.git
synced 2024-12-20 04:41:17 +00:00
Add plugin support (#38)
* Extract Authenticator and MFAProvider interfaces * Implement plugin loading * Add config example Signed-off-by: Knut Ahlers <knut@ahlers.me>
This commit is contained in:
parent
3988fa4f38
commit
97b284034f
16 changed files with 225 additions and 127 deletions
|
@ -7,6 +7,8 @@ import (
|
|||
crowd "github.com/jda/go-crowd"
|
||||
log "github.com/sirupsen/logrus"
|
||||
yaml "gopkg.in/yaml.v2"
|
||||
|
||||
"github.com/Luzifer/nginx-sso/plugins"
|
||||
)
|
||||
|
||||
func init() {
|
||||
|
@ -106,7 +108,7 @@ func (a authCrowd) DetectUser(res http.ResponseWriter, r *http.Request) (string,
|
|||
// in order to use DetectUser for the next login.
|
||||
// If the user did not login correctly the errNoValidUserFound
|
||||
// needs to be returned
|
||||
func (a authCrowd) Login(res http.ResponseWriter, r *http.Request) (string, []mfaConfig, error) {
|
||||
func (a authCrowd) Login(res http.ResponseWriter, r *http.Request) (string, []plugins.MFAConfig, error) {
|
||||
username := r.FormValue(strings.Join([]string{a.AuthenticatorID(), "username"}, "-"))
|
||||
password := r.FormValue(strings.Join([]string{a.AuthenticatorID(), "password"}, "-"))
|
||||
|
||||
|
@ -139,8 +141,8 @@ func (a authCrowd) Login(res http.ResponseWriter, r *http.Request) (string, []mf
|
|||
// LoginFields needs to return the fields required for this login
|
||||
// method. If no login using this method is possible the function
|
||||
// needs to return nil.
|
||||
func (a authCrowd) LoginFields() (fields []loginField) {
|
||||
return []loginField{
|
||||
func (a authCrowd) LoginFields() (fields []plugins.LoginField) {
|
||||
return []plugins.LoginField{
|
||||
{
|
||||
Label: "Username",
|
||||
Name: "username",
|
||||
|
|
|
@ -9,6 +9,8 @@ import (
|
|||
|
||||
ldap "gopkg.in/ldap.v2"
|
||||
yaml "gopkg.in/yaml.v2"
|
||||
|
||||
"github.com/Luzifer/nginx-sso/plugins"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -147,7 +149,7 @@ func (a authLDAP) DetectUser(res http.ResponseWriter, r *http.Request) (string,
|
|||
// in order to use DetectUser for the next login.
|
||||
// If the user did not login correctly the errNoValidUserFound
|
||||
// needs to be returned
|
||||
func (a authLDAP) Login(res http.ResponseWriter, r *http.Request) (string, []mfaConfig, error) {
|
||||
func (a authLDAP) Login(res http.ResponseWriter, r *http.Request) (string, []plugins.MFAConfig, error) {
|
||||
username := r.FormValue(strings.Join([]string{a.AuthenticatorID(), "username"}, "-"))
|
||||
password := r.FormValue(strings.Join([]string{a.AuthenticatorID(), "password"}, "-"))
|
||||
|
||||
|
@ -171,8 +173,8 @@ func (a authLDAP) Login(res http.ResponseWriter, r *http.Request) (string, []mfa
|
|||
// LoginFields needs to return the fields required for this login
|
||||
// method. If no login using this method is possible the function
|
||||
// needs to return nil.
|
||||
func (a authLDAP) LoginFields() (fields []loginField) {
|
||||
return []loginField{
|
||||
func (a authLDAP) LoginFields() (fields []plugins.LoginField) {
|
||||
return []plugins.LoginField{
|
||||
{
|
||||
Label: "Username",
|
||||
Name: "username",
|
||||
|
|
|
@ -8,6 +8,7 @@ import (
|
|||
yaml "gopkg.in/yaml.v2"
|
||||
|
||||
"github.com/Luzifer/go_helpers/str"
|
||||
"github.com/Luzifer/nginx-sso/plugins"
|
||||
)
|
||||
|
||||
func init() {
|
||||
|
@ -18,7 +19,7 @@ type authSimple struct {
|
|||
EnableBasicAuth bool `yaml:"enable_basic_auth"`
|
||||
Users map[string]string `yaml:"users"`
|
||||
Groups map[string][]string `yaml:"groups"`
|
||||
MFA map[string][]mfaConfig `yaml:"mfa"`
|
||||
MFA map[string][]plugins.MFAConfig `yaml:"mfa"`
|
||||
}
|
||||
|
||||
// AuthenticatorID needs to return an unique string to identify
|
||||
|
@ -109,7 +110,7 @@ func (a authSimple) DetectUser(res http.ResponseWriter, r *http.Request) (string
|
|||
// in order to use DetectUser for the next login.
|
||||
// If the user did not login correctly the errNoValidUserFound
|
||||
// needs to be returned
|
||||
func (a authSimple) Login(res http.ResponseWriter, r *http.Request) (string, []mfaConfig, error) {
|
||||
func (a authSimple) Login(res http.ResponseWriter, r *http.Request) (string, []plugins.MFAConfig, error) {
|
||||
username := r.FormValue(strings.Join([]string{a.AuthenticatorID(), "username"}, "-"))
|
||||
password := r.FormValue(strings.Join([]string{a.AuthenticatorID(), "password"}, "-"))
|
||||
|
||||
|
@ -133,8 +134,8 @@ func (a authSimple) Login(res http.ResponseWriter, r *http.Request) (string, []m
|
|||
// LoginFields needs to return the fields required for this login
|
||||
// method. If no login using this method is possible the function
|
||||
// needs to return nil.
|
||||
func (a authSimple) LoginFields() (fields []loginField) {
|
||||
return []loginField{
|
||||
func (a authSimple) LoginFields() (fields []plugins.LoginField) {
|
||||
return []plugins.LoginField{
|
||||
{
|
||||
Label: "Username",
|
||||
Name: "username",
|
||||
|
|
|
@ -5,6 +5,7 @@ import (
|
|||
"strings"
|
||||
|
||||
"github.com/Luzifer/go_helpers/str"
|
||||
"github.com/Luzifer/nginx-sso/plugins"
|
||||
|
||||
yaml "gopkg.in/yaml.v2"
|
||||
)
|
||||
|
@ -92,14 +93,14 @@ func (a authToken) DetectUser(res http.ResponseWriter, r *http.Request) (string,
|
|||
// in order to use DetectUser for the next login.
|
||||
// If the user did not login correctly the errNoValidUserFound
|
||||
// needs to be returned
|
||||
func (a authToken) Login(res http.ResponseWriter, r *http.Request) (string, []mfaConfig, error) {
|
||||
func (a authToken) Login(res http.ResponseWriter, r *http.Request) (string, []plugins.MFAConfig, error) {
|
||||
return "", nil, errNoValidUserFound
|
||||
}
|
||||
|
||||
// LoginFields needs to return the fields required for this login
|
||||
// method. If no login using this method is possible the function
|
||||
// needs to return nil.
|
||||
func (a authToken) LoginFields() []loginField { return nil }
|
||||
func (a authToken) LoginFields() []plugins.LoginField { return nil }
|
||||
|
||||
// Logout is called when the user visits the logout endpoint and
|
||||
// needs to destroy any persistent stored cookies
|
||||
|
|
|
@ -8,6 +8,7 @@ import (
|
|||
yaml "gopkg.in/yaml.v2"
|
||||
|
||||
"github.com/Luzifer/go_helpers/str"
|
||||
"github.com/Luzifer/nginx-sso/plugins"
|
||||
)
|
||||
|
||||
func init() {
|
||||
|
@ -89,7 +90,7 @@ func (a authYubikey) DetectUser(res http.ResponseWriter, r *http.Request) (strin
|
|||
// in order to use DetectUser for the next login.
|
||||
// If the user did not login correctly the errNoValidUserFound
|
||||
// needs to be returned
|
||||
func (a authYubikey) Login(res http.ResponseWriter, r *http.Request) (string, []mfaConfig, error) {
|
||||
func (a authYubikey) Login(res http.ResponseWriter, r *http.Request) (string, []plugins.MFAConfig, error) {
|
||||
keyInput := r.FormValue(strings.Join([]string{a.AuthenticatorID(), "key-input"}, "-"))
|
||||
|
||||
yubiAuth, err := yubigo.NewYubiAuth(a.ClientID, a.SecretKey)
|
||||
|
@ -122,8 +123,8 @@ func (a authYubikey) Login(res http.ResponseWriter, r *http.Request) (string, []
|
|||
// LoginFields needs to return the fields required for this login
|
||||
// method. If no login using this method is possible the function
|
||||
// needs to return nil.
|
||||
func (a authYubikey) LoginFields() (fields []loginField) {
|
||||
return []loginField{
|
||||
func (a authYubikey) LoginFields() (fields []plugins.LoginField) {
|
||||
return []plugins.LoginField{
|
||||
{
|
||||
Label: "Yubikey One-Time-Password",
|
||||
Name: "key-input",
|
||||
|
|
|
@ -50,6 +50,9 @@ mfa:
|
|||
host: "HOST"
|
||||
user_agent: "nginx-sso"
|
||||
|
||||
plugins:
|
||||
directory: ./plugins/
|
||||
|
||||
providers:
|
||||
# Authentication against an Atlassian Crowd directory server
|
||||
# Supports: Users, Groups
|
||||
|
|
10
main.go
10
main.go
|
@ -13,6 +13,7 @@ import (
|
|||
"github.com/flosch/pongo2"
|
||||
"github.com/gorilla/context"
|
||||
"github.com/gorilla/sessions"
|
||||
"github.com/pkg/errors"
|
||||
log "github.com/sirupsen/logrus"
|
||||
yaml "gopkg.in/yaml.v2"
|
||||
|
||||
|
@ -39,6 +40,9 @@ type mainConfig struct {
|
|||
HideMFAField bool `yaml:"hide_mfa_field"`
|
||||
Names map[string]string `yaml:"names"`
|
||||
} `yaml:"login"`
|
||||
Plugins struct {
|
||||
Directory string `yaml:"directory"`
|
||||
} `yaml:"plugins"`
|
||||
}
|
||||
|
||||
func (m *mainConfig) GetSessionOpts() *sessions.Options {
|
||||
|
@ -100,6 +104,12 @@ func loadConfiguration() error {
|
|||
return fmt.Errorf("Unable to load configuration file: %s", err)
|
||||
}
|
||||
|
||||
if mainCfg.Plugins.Directory != "" {
|
||||
if err = loadPlugins(mainCfg.Plugins.Directory); err != nil {
|
||||
return errors.Wrap(err, "Unable to load plugins")
|
||||
}
|
||||
}
|
||||
|
||||
if err = initializeAuthenticators(yamlSource); err != nil {
|
||||
return fmt.Errorf("Unable to configure authentication: %s", err)
|
||||
}
|
||||
|
|
53
mfa.go
53
mfa.go
|
@ -6,66 +6,27 @@ import (
|
|||
"sync"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/Luzifer/nginx-sso/plugins"
|
||||
)
|
||||
|
||||
const mfaLoginFieldName = "mfa-token"
|
||||
|
||||
var mfaLoginField = loginField{
|
||||
var mfaLoginField = plugins.LoginField{
|
||||
Label: "MFA Token",
|
||||
Name: mfaLoginFieldName,
|
||||
Placeholder: "(optional)",
|
||||
Type: "text",
|
||||
}
|
||||
|
||||
type mfaConfig struct {
|
||||
Provider string `yaml:"provider"`
|
||||
Attributes map[string]interface{} `yaml:"attributes"`
|
||||
}
|
||||
|
||||
func (m mfaConfig) AttributeInt(key string) int {
|
||||
if v, ok := m.Attributes[key]; ok && v != "" {
|
||||
if sv, ok := v.(int); ok {
|
||||
return sv
|
||||
}
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
func (m mfaConfig) AttributeString(key string) string {
|
||||
if v, ok := m.Attributes[key]; ok {
|
||||
if sv, ok := v.(string); ok {
|
||||
return sv
|
||||
}
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
type mfaProvider interface {
|
||||
// ProviderID needs to return an unique string to identify
|
||||
// this special MFA provider
|
||||
ProviderID() (id string)
|
||||
|
||||
// Configure loads the configuration for the Authenticator from the
|
||||
// global config.yaml file which is passed as a byte-slice.
|
||||
// If no configuration for the Authenticator is supplied the function
|
||||
// needs to return the errProviderUnconfigured
|
||||
Configure(yamlSource []byte) (err error)
|
||||
|
||||
// ValidateMFA takes the user from the login cookie and performs a
|
||||
// validation against the provided MFA configuration for this user
|
||||
ValidateMFA(res http.ResponseWriter, r *http.Request, user string, mfaCfgs []mfaConfig) error
|
||||
}
|
||||
|
||||
var (
|
||||
mfaRegistry = []mfaProvider{}
|
||||
mfaRegistry = []plugins.MFAProvider{}
|
||||
mfaRegistryMutex sync.RWMutex
|
||||
|
||||
activeMFAProviders = []mfaProvider{}
|
||||
activeMFAProviders = []plugins.MFAProvider{}
|
||||
)
|
||||
|
||||
func registerMFAProvider(m mfaProvider) {
|
||||
func registerMFAProvider(m plugins.MFAProvider) {
|
||||
mfaRegistryMutex.Lock()
|
||||
defer mfaRegistryMutex.Unlock()
|
||||
|
||||
|
@ -94,7 +55,7 @@ func initializeMFAProviders(yamlSource []byte) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func validateMFA(res http.ResponseWriter, r *http.Request, user string, mfaCfgs []mfaConfig) error {
|
||||
func validateMFA(res http.ResponseWriter, r *http.Request, user string, mfaCfgs []plugins.MFAConfig) error {
|
||||
if len(mfaCfgs) == 0 {
|
||||
// User has no configured MFA devices, their MFA is automatically valid
|
||||
return nil
|
||||
|
|
|
@ -10,6 +10,8 @@ import (
|
|||
"github.com/duosecurity/duo_api_golang/authapi"
|
||||
"github.com/pkg/errors"
|
||||
yaml "gopkg.in/yaml.v2"
|
||||
|
||||
"github.com/Luzifer/nginx-sso/plugins"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -62,7 +64,7 @@ func (m *mfaDuo) Configure(yamlSource []byte) (err error) {
|
|||
|
||||
// ValidateMFA takes the user from the login cookie and performs a
|
||||
// validation against the provided MFA configuration for this user
|
||||
func (m mfaDuo) ValidateMFA(res http.ResponseWriter, r *http.Request, user string, mfaCfgs []mfaConfig) error {
|
||||
func (m mfaDuo) ValidateMFA(res http.ResponseWriter, r *http.Request, user string, mfaCfgs []plugins.MFAConfig) error {
|
||||
var keyInput string
|
||||
|
||||
// Look for mfaConfigs with own provider name
|
||||
|
|
|
@ -8,6 +8,8 @@ import (
|
|||
"github.com/pkg/errors"
|
||||
"github.com/pquerna/otp"
|
||||
"github.com/pquerna/otp/totp"
|
||||
|
||||
"github.com/Luzifer/nginx-sso/plugins"
|
||||
)
|
||||
|
||||
func init() {
|
||||
|
@ -30,7 +32,7 @@ func (m mfaTOTP) Configure(yamlSource []byte) (err error) { return nil }
|
|||
|
||||
// ValidateMFA takes the user from the login cookie and performs a
|
||||
// validation against the provided MFA configuration for this user
|
||||
func (m mfaTOTP) ValidateMFA(res http.ResponseWriter, r *http.Request, user string, mfaCfgs []mfaConfig) error {
|
||||
func (m mfaTOTP) ValidateMFA(res http.ResponseWriter, r *http.Request, user string, mfaCfgs []plugins.MFAConfig) error {
|
||||
// Look for mfaConfigs with own provider name
|
||||
for _, c := range mfaCfgs {
|
||||
// Provider has been renamed, keep "google" for backwards compatibility
|
||||
|
@ -54,7 +56,7 @@ func (m mfaTOTP) ValidateMFA(res http.ResponseWriter, r *http.Request, user stri
|
|||
return errNoValidUserFound
|
||||
}
|
||||
|
||||
func (m mfaTOTP) exec(c mfaConfig) (string, error) {
|
||||
func (m mfaTOTP) exec(c plugins.MFAConfig) (string, error) {
|
||||
secret := c.AttributeString("secret")
|
||||
|
||||
// By default use Google Authenticator compatible settings
|
||||
|
|
|
@ -7,6 +7,8 @@ import (
|
|||
"github.com/GeertJohan/yubigo"
|
||||
"github.com/pkg/errors"
|
||||
yaml "gopkg.in/yaml.v2"
|
||||
|
||||
"github.com/Luzifer/nginx-sso/plugins"
|
||||
)
|
||||
|
||||
func init() {
|
||||
|
@ -49,7 +51,7 @@ func (m *mfaYubikey) Configure(yamlSource []byte) (err error) {
|
|||
|
||||
// ValidateMFA takes the user from the login cookie and performs a
|
||||
// validation against the provided MFA configuration for this user
|
||||
func (m mfaYubikey) ValidateMFA(res http.ResponseWriter, r *http.Request, user string, mfaCfgs []mfaConfig) error {
|
||||
func (m mfaYubikey) ValidateMFA(res http.ResponseWriter, r *http.Request, user string, mfaCfgs []plugins.MFAConfig) error {
|
||||
var keyInput string
|
||||
|
||||
yubiAuth, err := yubigo.NewYubiAuth(m.ClientID, m.SecretKey)
|
||||
|
|
58
plugins.go
Normal file
58
plugins.go
Normal file
|
@ -0,0 +1,58 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"plugin"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/Luzifer/nginx-sso/plugins"
|
||||
)
|
||||
|
||||
func loadPlugins(pluginDir string) error {
|
||||
logger := log.WithField("plugin_dir", pluginDir)
|
||||
|
||||
d, err := os.Stat(pluginDir)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
logger.Warn("Plugin directory not found, skipping")
|
||||
return nil
|
||||
}
|
||||
return errors.Wrap(err, "Could not stat plugin dir")
|
||||
}
|
||||
|
||||
if !d.IsDir() {
|
||||
return errors.New("Plugin directory is not a directory")
|
||||
}
|
||||
|
||||
return errors.Wrap(filepath.Walk(pluginDir, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !strings.HasSuffix(path, ".so") {
|
||||
// Ignore that file, is not a plugin
|
||||
return nil
|
||||
}
|
||||
|
||||
p, err := plugin.Open(path)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "Unable to load plugin %q", path)
|
||||
}
|
||||
|
||||
f, err := p.Lookup("Register")
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "Unable to find register function in %q", path)
|
||||
}
|
||||
|
||||
f.(func(plugins.RegisterAuthenticatorFunc, plugins.RegisterMFAProviderFunc))(
|
||||
registerAuthenticator,
|
||||
registerMFAProvider,
|
||||
)
|
||||
|
||||
return nil
|
||||
}), "Unable to load plugins")
|
||||
}
|
55
plugins/auth.go
Normal file
55
plugins/auth.go
Normal file
|
@ -0,0 +1,55 @@
|
|||
package plugins
|
||||
|
||||
import "net/http"
|
||||
|
||||
type Authenticator interface {
|
||||
// AuthenticatorID needs to return an unique string to identify
|
||||
// this special authenticator
|
||||
AuthenticatorID() (id string)
|
||||
|
||||
// Configure loads the configuration for the Authenticator from the
|
||||
// global config.yaml file which is passed as a byte-slice.
|
||||
// If no configuration for the Authenticator is supplied the function
|
||||
// needs to return the errProviderUnconfigured
|
||||
Configure(yamlSource []byte) (err error)
|
||||
|
||||
// DetectUser is used to detect a user without a login form from
|
||||
// a cookie, header or other methods
|
||||
// If no user was detected the errNoValidUserFound needs to be
|
||||
// returned
|
||||
DetectUser(res http.ResponseWriter, r *http.Request) (user string, groups []string, err error)
|
||||
|
||||
// Login is called when the user submits the login form and needs
|
||||
// to authenticate the user or throw an error. If the user has
|
||||
// successfully logged in the persistent cookie should be written
|
||||
// in order to use DetectUser for the next login.
|
||||
// With the login result an array of mfaConfig must be returned. In
|
||||
// case there is no MFA config or the provider does not support MFA
|
||||
// return nil.
|
||||
// If the user did not login correctly the errNoValidUserFound
|
||||
// needs to be returned
|
||||
Login(res http.ResponseWriter, r *http.Request) (user string, mfaConfigs []MFAConfig, err error)
|
||||
|
||||
// LoginFields needs to return the fields required for this login
|
||||
// method. If no login using this method is possible the function
|
||||
// needs to return nil.
|
||||
LoginFields() (fields []LoginField)
|
||||
|
||||
// Logout is called when the user visits the logout endpoint and
|
||||
// needs to destroy any persistent stored cookies
|
||||
Logout(res http.ResponseWriter, r *http.Request) (err error)
|
||||
|
||||
// SupportsMFA returns the MFA detection capabilities of the login
|
||||
// provider. If the provider can provide mfaConfig objects from its
|
||||
// configuration return true. If this is true the login interface
|
||||
// will display an additional field for this provider for the user
|
||||
// to fill in their MFA token.
|
||||
SupportsMFA() bool
|
||||
}
|
||||
|
||||
type LoginField struct {
|
||||
Label string
|
||||
Name string
|
||||
Placeholder string
|
||||
Type string
|
||||
}
|
44
plugins/mfa.go
Normal file
44
plugins/mfa.go
Normal file
|
@ -0,0 +1,44 @@
|
|||
package plugins
|
||||
|
||||
import "net/http"
|
||||
|
||||
type MFAProvider interface {
|
||||
// ProviderID needs to return an unique string to identify
|
||||
// this special MFA provider
|
||||
ProviderID() (id string)
|
||||
|
||||
// Configure loads the configuration for the Authenticator from the
|
||||
// global config.yaml file which is passed as a byte-slice.
|
||||
// If no configuration for the Authenticator is supplied the function
|
||||
// needs to return the errProviderUnconfigured
|
||||
Configure(yamlSource []byte) (err error)
|
||||
|
||||
// ValidateMFA takes the user from the login cookie and performs a
|
||||
// validation against the provided MFA configuration for this user
|
||||
ValidateMFA(res http.ResponseWriter, r *http.Request, user string, mfaCfgs []MFAConfig) error
|
||||
}
|
||||
|
||||
type MFAConfig struct {
|
||||
Provider string `yaml:"provider"`
|
||||
Attributes map[string]interface{} `yaml:"attributes"`
|
||||
}
|
||||
|
||||
func (m MFAConfig) AttributeInt(key string) int {
|
||||
if v, ok := m.Attributes[key]; ok && v != "" {
|
||||
if sv, ok := v.(int); ok {
|
||||
return sv
|
||||
}
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
func (m MFAConfig) AttributeString(key string) string {
|
||||
if v, ok := m.Attributes[key]; ok {
|
||||
if sv, ok := v.(string); ok {
|
||||
return sv
|
||||
}
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
4
plugins/register.go
Normal file
4
plugins/register.go
Normal file
|
@ -0,0 +1,4 @@
|
|||
package plugins
|
||||
|
||||
type RegisterAuthenticatorFunc func(Authenticator)
|
||||
type RegisterMFAProviderFunc func(MFAProvider)
|
68
registry.go
68
registry.go
|
@ -7,71 +7,21 @@ import (
|
|||
"sync"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/Luzifer/nginx-sso/plugins"
|
||||
)
|
||||
|
||||
type authenticator interface {
|
||||
// AuthenticatorID needs to return an unique string to identify
|
||||
// this special authenticator
|
||||
AuthenticatorID() (id string)
|
||||
|
||||
// Configure loads the configuration for the Authenticator from the
|
||||
// global config.yaml file which is passed as a byte-slice.
|
||||
// If no configuration for the Authenticator is supplied the function
|
||||
// needs to return the errProviderUnconfigured
|
||||
Configure(yamlSource []byte) (err error)
|
||||
|
||||
// DetectUser is used to detect a user without a login form from
|
||||
// a cookie, header or other methods
|
||||
// If no user was detected the errNoValidUserFound needs to be
|
||||
// returned
|
||||
DetectUser(res http.ResponseWriter, r *http.Request) (user string, groups []string, err error)
|
||||
|
||||
// Login is called when the user submits the login form and needs
|
||||
// to authenticate the user or throw an error. If the user has
|
||||
// successfully logged in the persistent cookie should be written
|
||||
// in order to use DetectUser for the next login.
|
||||
// With the login result an array of mfaConfig must be returned. In
|
||||
// case there is no MFA config or the provider does not support MFA
|
||||
// return nil.
|
||||
// If the user did not login correctly the errNoValidUserFound
|
||||
// needs to be returned
|
||||
Login(res http.ResponseWriter, r *http.Request) (user string, mfaConfigs []mfaConfig, err error)
|
||||
|
||||
// LoginFields needs to return the fields required for this login
|
||||
// method. If no login using this method is possible the function
|
||||
// needs to return nil.
|
||||
LoginFields() (fields []loginField)
|
||||
|
||||
// Logout is called when the user visits the logout endpoint and
|
||||
// needs to destroy any persistent stored cookies
|
||||
Logout(res http.ResponseWriter, r *http.Request) (err error)
|
||||
|
||||
// SupportsMFA returns the MFA detection capabilities of the login
|
||||
// provider. If the provider can provide mfaConfig objects from its
|
||||
// configuration return true. If this is true the login interface
|
||||
// will display an additional field for this provider for the user
|
||||
// to fill in their MFA token.
|
||||
SupportsMFA() bool
|
||||
}
|
||||
|
||||
type loginField struct {
|
||||
Label string
|
||||
Name string
|
||||
Placeholder string
|
||||
Type string
|
||||
}
|
||||
|
||||
var (
|
||||
errProviderUnconfigured = errors.New("No valid configuration found for this provider")
|
||||
errNoValidUserFound = errors.New("No valid users found")
|
||||
|
||||
authenticatorRegistry = []authenticator{}
|
||||
authenticatorRegistry = []plugins.Authenticator{}
|
||||
authenticatorRegistryMutex sync.RWMutex
|
||||
|
||||
activeAuthenticators = []authenticator{}
|
||||
activeAuthenticators = []plugins.Authenticator{}
|
||||
)
|
||||
|
||||
func registerAuthenticator(a authenticator) {
|
||||
func registerAuthenticator(a plugins.Authenticator) {
|
||||
authenticatorRegistryMutex.Lock()
|
||||
defer authenticatorRegistryMutex.Unlock()
|
||||
|
||||
|
@ -82,7 +32,7 @@ func initializeAuthenticators(yamlSource []byte) error {
|
|||
authenticatorRegistryMutex.Lock()
|
||||
defer authenticatorRegistryMutex.Unlock()
|
||||
|
||||
tmp := []authenticator{}
|
||||
tmp := []plugins.Authenticator{}
|
||||
for _, a := range authenticatorRegistry {
|
||||
err := a.Configure(yamlSource)
|
||||
|
||||
|
@ -126,7 +76,7 @@ func detectUser(res http.ResponseWriter, r *http.Request) (string, []string, err
|
|||
return "", nil, errNoValidUserFound
|
||||
}
|
||||
|
||||
func loginUser(res http.ResponseWriter, r *http.Request) (string, []mfaConfig, error) {
|
||||
func loginUser(res http.ResponseWriter, r *http.Request) (string, []plugins.MFAConfig, error) {
|
||||
authenticatorRegistryMutex.RLock()
|
||||
defer authenticatorRegistryMutex.RUnlock()
|
||||
|
||||
|
@ -158,11 +108,11 @@ func logoutUser(res http.ResponseWriter, r *http.Request) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func getFrontendAuthenticators() map[string][]loginField {
|
||||
func getFrontendAuthenticators() map[string][]plugins.LoginField {
|
||||
authenticatorRegistryMutex.RLock()
|
||||
defer authenticatorRegistryMutex.RUnlock()
|
||||
|
||||
output := map[string][]loginField{}
|
||||
output := map[string][]plugins.LoginField{}
|
||||
for _, a := range activeAuthenticators {
|
||||
if len(a.LoginFields()) == 0 {
|
||||
continue
|
||||
|
|
Loading…
Reference in a new issue