package crowd import ( "net/http" "strings" crowd "github.com/jda/go-crowd" log "github.com/sirupsen/logrus" yaml "gopkg.in/yaml.v3" "github.com/Luzifer/nginx-sso/plugins" ) type AuthCrowd struct { URL string `yaml:"url"` AppName string `yaml:"app_name"` AppPassword string `yaml:"app_pass"` crowd crowd.Crowd } func New() *AuthCrowd { return &AuthCrowd{} } // AuthenticatorID needs to return an unique string to identify // this special authenticator func (a AuthCrowd) AuthenticatorID() string { return "crowd" } // 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 plugins.ErrProviderUnconfigured func (a *AuthCrowd) Configure(yamlSource []byte) error { envelope := struct { Providers struct { Crowd *AuthCrowd `yaml:"crowd"` } `yaml:"providers"` }{} if err := yaml.Unmarshal(yamlSource, &envelope); err != nil { return err } if envelope.Providers.Crowd == nil { return plugins.ErrProviderUnconfigured } a.URL = envelope.Providers.Crowd.URL a.AppName = envelope.Providers.Crowd.AppName a.AppPassword = envelope.Providers.Crowd.AppPassword if a.AppName == "" || a.AppPassword == "" { return plugins.ErrProviderUnconfigured } var err error a.crowd, err = crowd.New(a.AppName, a.AppPassword, a.URL) return err } // DetectUser is used to detect a user without a login form from // a cookie, header or other methods // If no user was detected the plugins.ErrNoValidUserFound needs to be // returned func (a AuthCrowd) DetectUser(res http.ResponseWriter, r *http.Request) (string, []string, error) { cc, err := a.crowd.GetCookieConfig() if err != nil { return "", nil, err } cookie, err := r.Cookie(cc.Name) switch err { case nil: // Fine, we do have a cookie case http.ErrNoCookie: // Also fine, there is no cookie return "", nil, plugins.ErrNoValidUserFound default: return "", nil, err } ssoToken := cookie.Value sess, err := a.crowd.GetSession(ssoToken) if err != nil { log.WithError(err).Debug("Getting crowd session failed") return "", nil, plugins.ErrNoValidUserFound } user := sess.User.UserName cGroups, err := a.crowd.GetDirectGroups(user) if err != nil { return "", nil, err } groups := []string{} for _, g := range cGroups { groups = append(groups, g.Name) } return user, groups, nil } // 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. // If the user did not login correctly the plugins.ErrNoValidUserFound // needs to be returned 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"}, "-")) cc, err := a.crowd.GetCookieConfig() if err != nil { return "", nil, err } sess, err := a.crowd.NewSession(username, password, r.RemoteAddr) if err != nil { log.WithFields(log.Fields{ "username": username, }).WithError(err).Debug("Crowd authentication failed") return "", nil, plugins.ErrNoValidUserFound } http.SetCookie(res, &http.Cookie{ Name: cc.Name, Value: sess.Token, Path: "/", Domain: cc.Domain, Expires: sess.Expires, Secure: cc.Secure, HttpOnly: true, }) return username, nil, nil } // 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 []plugins.LoginField) { return []plugins.LoginField{ { Label: "Username", Name: "username", Placeholder: "Username", Type: "text", }, { Label: "Password", Name: "password", Placeholder: "****", Type: "password", }, } } // Logout is called when the user visits the logout endpoint and // needs to destroy any persistent stored cookies func (a AuthCrowd) Logout(res http.ResponseWriter, r *http.Request) (err error) { cc, err := a.crowd.GetCookieConfig() if err != nil { return err } http.SetCookie(res, &http.Cookie{ Name: cc.Name, Value: "", Path: "/", Domain: cc.Domain, MaxAge: -1, Secure: cc.Secure, HttpOnly: true, }) return nil } // 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. func (a AuthCrowd) SupportsMFA() bool { return false }