package connector import ( "fmt" "html/template" "net/http" "net/url" "path" phttp "github.com/coreos/dex/pkg/http" "github.com/coreos/dex/pkg/log" "github.com/coreos/dex/user" "github.com/coreos/go-oidc/oauth2" "github.com/coreos/go-oidc/oidc" ) const ( LocalConnectorType = "local" LoginPageTemplateName = "local-login.html" ) func init() { RegisterConnectorConfigType(LocalConnectorType, func() ConnectorConfig { return &LocalConnectorConfig{} }) } type LocalConnectorConfig struct { ID string `json:"id"` PasswordInfos []user.PasswordInfo `json:"passwordInfos"` } func (cfg *LocalConnectorConfig) ConnectorID() string { return cfg.ID } func (cfg *LocalConnectorConfig) ConnectorType() string { return LocalConnectorType } func (cfg *LocalConnectorConfig) Connector(ns url.URL, lf oidc.LoginFunc, tpls *template.Template) (Connector, error) { tpl := tpls.Lookup(LoginPageTemplateName) if tpl == nil { return nil, fmt.Errorf("unable to find necessary HTML template") } idpc := &LocalConnector{ id: cfg.ID, namespace: ns, loginFunc: lf, loginTpl: tpl, } return idpc, nil } type LocalConnector struct { id string idp *LocalIdentityProvider namespace url.URL loginFunc oidc.LoginFunc loginTpl *template.Template } type Page struct { PostURL string Name string Error bool Message string SessionKey string } func (c *LocalConnector) ID() string { return c.id } func (c *LocalConnector) Healthy() error { return nil } func (c *LocalConnector) SetLocalIdentityProvider(idp *LocalIdentityProvider) { c.idp = idp } func (c *LocalConnector) LoginURL(sessionKey, prompt string) (string, error) { q := url.Values{} q.Set("session_key", sessionKey) q.Set("prompt", prompt) enc := q.Encode() return path.Join(c.namespace.Path, "login") + "?" + enc, nil } func (c *LocalConnector) Register(mux *http.ServeMux, errorURL url.URL) { route := c.namespace.Path + "/login" mux.Handle(route, handleLoginFunc(c.loginFunc, c.loginTpl, c.idp, route, errorURL)) } func (c *LocalConnector) Sync() chan struct{} { return make(chan struct{}) } func (c *LocalConnector) TrustedEmailProvider() bool { return false } func redirectPostError(w http.ResponseWriter, errorURL url.URL, q url.Values) { redirectURL := phttp.MergeQuery(errorURL, q) w.Header().Set("Location", redirectURL.String()) w.WriteHeader(http.StatusSeeOther) } func handleLoginFunc(lf oidc.LoginFunc, tpl *template.Template, idp *LocalIdentityProvider, localErrorPath string, errorURL url.URL) http.HandlerFunc { handleGET := func(w http.ResponseWriter, r *http.Request, errMsg string) { q := r.URL.Query() sessionKey := q.Get("session_key") p := &Page{PostURL: r.URL.String(), Name: "Local", SessionKey: sessionKey} if errMsg != "" { p.Error = true p.Message = errMsg } if err := tpl.Execute(w, p); err != nil { phttp.WriteError(w, http.StatusInternalServerError, err.Error()) } } handlePOST := func(w http.ResponseWriter, r *http.Request) { if err := r.ParseForm(); err != nil { msg := fmt.Sprintf("unable to parse form from body: %v", err) phttp.WriteError(w, http.StatusBadRequest, msg) return } userid := r.PostForm.Get("userid") if userid == "" { handleGET(w, r, "missing email address") return } password := r.PostForm.Get("password") if password == "" { handleGET(w, r, "missing password") return } ident, err := idp.Identity(userid, password) log.Errorf("IDENTITY: err: %v", err) if ident == nil || err != nil { handleGET(w, r, "invalid login") return } q := r.URL.Query() sessionKey := r.FormValue("session_key") if sessionKey == "" { q.Set("error", oauth2.ErrorInvalidRequest) q.Set("error_description", "missing session_key") redirectPostError(w, errorURL, q) return } redirectURL, err := lf(*ident, sessionKey) if err != nil { log.Errorf("Unable to log in %#v: %v", *ident, err) q.Set("error", oauth2.ErrorAccessDenied) q.Set("error_description", "login failed") redirectPostError(w, errorURL, q) return } w.Header().Set("Location", redirectURL) w.WriteHeader(http.StatusTemporaryRedirect) } return func(w http.ResponseWriter, r *http.Request) { switch r.Method { case "POST": handlePOST(w, r) case "GET": handleGET(w, r, "") default: w.Header().Set("Allow", "GET, POST") phttp.WriteError(w, http.StatusMethodNotAllowed, "GET and POST only acceptable methods") } } } type LocalIdentityProvider struct { PasswordInfoRepo user.PasswordInfoRepo UserRepo user.UserRepo } func (m *LocalIdentityProvider) Identity(email, password string) (*oidc.Identity, error) { user, err := m.UserRepo.GetByEmail(nil, email) if err != nil { return nil, err } id := user.ID pi, err := m.PasswordInfoRepo.Get(nil, id) if err != nil { return nil, err } return pi.Authenticate(password) }