forked from mystiq/dex
server: add an option to enable emails and passwords from the database
This commit is contained in:
parent
7ff3ce85a2
commit
cdf0b91690
2 changed files with 133 additions and 0 deletions
|
@ -3,12 +3,15 @@ package server
|
|||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
|
||||
"github.com/coreos/dex/connector"
|
||||
|
@ -44,6 +47,8 @@ type Config struct {
|
|||
// If specified, the server will use this function for determining time.
|
||||
Now func() time.Time
|
||||
|
||||
EnablePasswordDB bool
|
||||
|
||||
TemplateConfig TemplateConfig
|
||||
}
|
||||
|
||||
|
@ -91,6 +96,14 @@ func newServer(c Config, rotationStrategy rotationStrategy) (*Server, error) {
|
|||
if err != nil {
|
||||
return nil, fmt.Errorf("server: can't parse issuer URL")
|
||||
}
|
||||
if c.EnablePasswordDB {
|
||||
c.Connectors = append(c.Connectors, Connector{
|
||||
ID: "local",
|
||||
DisplayName: "Email",
|
||||
Connector: newPasswordDB(c.Storage),
|
||||
})
|
||||
}
|
||||
|
||||
if len(c.Connectors) == 0 {
|
||||
return nil, errors.New("server: no connectors specified")
|
||||
}
|
||||
|
@ -182,6 +195,38 @@ func (s *Server) absURL(pathItems ...string) string {
|
|||
return u.String()
|
||||
}
|
||||
|
||||
func newPasswordDB(s storage.Storage) interface {
|
||||
connector.Connector
|
||||
connector.PasswordConnector
|
||||
} {
|
||||
return passwordDB{s}
|
||||
}
|
||||
|
||||
type passwordDB struct {
|
||||
s storage.Storage
|
||||
}
|
||||
|
||||
func (db passwordDB) Close() error { return nil }
|
||||
|
||||
func (db passwordDB) Login(email, password string) (connector.Identity, bool, error) {
|
||||
p, err := db.s.GetPassword(email)
|
||||
if err != nil {
|
||||
if err != storage.ErrNotFound {
|
||||
log.Printf("get password: %v", err)
|
||||
}
|
||||
return connector.Identity{}, false, err
|
||||
}
|
||||
if err := bcrypt.CompareHashAndPassword(p.Hash, []byte(password)); err != nil {
|
||||
return connector.Identity{}, false, nil
|
||||
}
|
||||
return connector.Identity{
|
||||
UserID: p.UserID,
|
||||
Username: p.Username,
|
||||
Email: p.Email,
|
||||
EmailVerified: true,
|
||||
}, true, nil
|
||||
}
|
||||
|
||||
// newKeyCacher returns a storage which caches keys so long as the next
|
||||
func newKeyCacher(s storage.Storage, now func() time.Time) storage.Storage {
|
||||
if now == nil {
|
||||
|
|
|
@ -16,9 +16,12 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/ericchiang/oidc"
|
||||
"github.com/kylelemons/godebug/pretty"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
"golang.org/x/net/context"
|
||||
"golang.org/x/oauth2"
|
||||
|
||||
"github.com/coreos/dex/connector"
|
||||
"github.com/coreos/dex/connector/mock"
|
||||
"github.com/coreos/dex/storage"
|
||||
"github.com/coreos/dex/storage/memory"
|
||||
|
@ -381,6 +384,91 @@ func TestOAuth2ImplicitFlow(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestPasswordDB(t *testing.T) {
|
||||
s := memory.New()
|
||||
conn := newPasswordDB(s)
|
||||
defer conn.Close()
|
||||
|
||||
pw := "hi"
|
||||
|
||||
h, err := bcrypt.GenerateFromPassword([]byte(pw), bcrypt.MinCost)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
s.CreatePassword(storage.Password{
|
||||
Email: "jane@example.com",
|
||||
Username: "jane",
|
||||
UserID: "foobar",
|
||||
Hash: h,
|
||||
})
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
username string
|
||||
password string
|
||||
wantIdentity connector.Identity
|
||||
wantInvalid bool
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "valid password",
|
||||
username: "jane@example.com",
|
||||
password: pw,
|
||||
wantIdentity: connector.Identity{
|
||||
Email: "jane@example.com",
|
||||
Username: "jane",
|
||||
UserID: "foobar",
|
||||
EmailVerified: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "unknown user",
|
||||
username: "john@example.com",
|
||||
password: pw,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "invalid password",
|
||||
username: "jane@example.com",
|
||||
password: "not the correct password",
|
||||
wantInvalid: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
ident, valid, err := conn.Login(tc.username, tc.password)
|
||||
if err != nil {
|
||||
if !tc.wantErr {
|
||||
t.Errorf("%s: %v", tc.name, err)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if tc.wantErr {
|
||||
t.Errorf("%s: expected error", tc.name)
|
||||
continue
|
||||
}
|
||||
|
||||
if !valid {
|
||||
if !tc.wantInvalid {
|
||||
t.Errorf("%s: expected valid password", tc.name)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if tc.wantInvalid {
|
||||
t.Errorf("%s: expected invalid password", tc.name)
|
||||
continue
|
||||
}
|
||||
|
||||
if diff := pretty.Compare(tc.wantIdentity, ident); diff != "" {
|
||||
t.Errorf("%s: %s", tc.name, diff)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
type storageWithKeysTrigger struct {
|
||||
storage.Storage
|
||||
f func()
|
||||
|
|
Loading…
Reference in a new issue