cmd/dex: accept raw bcrypt'd hash as well as base64'd version of hash
This commit is contained in:
		| @@ -1,9 +1,12 @@ | |||||||
| package main | package main | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
|  | 	"encoding/base64" | ||||||
| 	"encoding/json" | 	"encoding/json" | ||||||
| 	"fmt" | 	"fmt" | ||||||
|  |  | ||||||
|  | 	"golang.org/x/crypto/bcrypt" | ||||||
|  |  | ||||||
| 	"github.com/coreos/dex/connector" | 	"github.com/coreos/dex/connector" | ||||||
| 	"github.com/coreos/dex/connector/github" | 	"github.com/coreos/dex/connector/github" | ||||||
| 	"github.com/coreos/dex/connector/ldap" | 	"github.com/coreos/dex/connector/ldap" | ||||||
| @@ -38,7 +41,47 @@ type Config struct { | |||||||
| 	// StaticPasswords cause the server use this list of passwords rather than | 	// StaticPasswords cause the server use this list of passwords rather than | ||||||
| 	// querying the storage. Cannot be specified without enabling a passwords | 	// querying the storage. Cannot be specified without enabling a passwords | ||||||
| 	// database. | 	// database. | ||||||
| 	StaticPasswords []storage.Password `json:"staticPasswords"` | 	StaticPasswords []password `json:"staticPasswords"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type password storage.Password | ||||||
|  |  | ||||||
|  | func (p *password) UnmarshalJSON(b []byte) error { | ||||||
|  | 	var data struct { | ||||||
|  | 		Email    string `json:"email"` | ||||||
|  | 		Username string `json:"username"` | ||||||
|  | 		UserID   string `json:"userID"` | ||||||
|  | 		Hash     string `json:"hash"` | ||||||
|  | 	} | ||||||
|  | 	if err := json.Unmarshal(b, &data); err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	*p = password(storage.Password{ | ||||||
|  | 		Email:    data.Email, | ||||||
|  | 		Username: data.Username, | ||||||
|  | 		UserID:   data.UserID, | ||||||
|  | 	}) | ||||||
|  | 	if len(data.Hash) == 0 { | ||||||
|  | 		return fmt.Errorf("no password hash provided") | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// If this value is a valid bcrypt, use it. | ||||||
|  | 	_, bcryptErr := bcrypt.Cost([]byte(data.Hash)) | ||||||
|  | 	if bcryptErr == nil { | ||||||
|  | 		p.Hash = []byte(data.Hash) | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// For backwards compatibility try to base64 decode this value. | ||||||
|  | 	hashBytes, err := base64.StdEncoding.DecodeString(data.Hash) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return fmt.Errorf("malformed bcrypt hash: %v", bcryptErr) | ||||||
|  | 	} | ||||||
|  | 	if _, err := bcrypt.Cost(hashBytes); err != nil { | ||||||
|  | 		return fmt.Errorf("malformed bcrypt hash: %v", err) | ||||||
|  | 	} | ||||||
|  | 	p.Hash = hashBytes | ||||||
|  | 	return nil | ||||||
| } | } | ||||||
|  |  | ||||||
| // OAuth2 describes enabled OAuth2 extensions. | // OAuth2 describes enabled OAuth2 extensions. | ||||||
| @@ -161,7 +204,7 @@ func (c *Connector) UnmarshalJSON(b []byte) error { | |||||||
| 	} | 	} | ||||||
| 	*c = Connector{ | 	*c = Connector{ | ||||||
| 		Type:   conn.Type, | 		Type:   conn.Type, | ||||||
| 		Name:   conn.Type, | 		Name:   conn.Name, | ||||||
| 		ID:     conn.ID, | 		ID:     conn.ID, | ||||||
| 		Config: connConfig, | 		Config: connConfig, | ||||||
| 	} | 	} | ||||||
|   | |||||||
| @@ -3,37 +3,124 @@ package main | |||||||
| import ( | import ( | ||||||
| 	"testing" | 	"testing" | ||||||
|  |  | ||||||
|  | 	"github.com/coreos/dex/connector/mock" | ||||||
|  | 	"github.com/coreos/dex/connector/oidc" | ||||||
| 	"github.com/coreos/dex/storage" | 	"github.com/coreos/dex/storage" | ||||||
|  | 	"github.com/coreos/dex/storage/sql" | ||||||
|  | 	"github.com/ghodss/yaml" | ||||||
| 	"github.com/kylelemons/godebug/pretty" | 	"github.com/kylelemons/godebug/pretty" | ||||||
|  |  | ||||||
| 	yaml "gopkg.in/yaml.v2" |  | ||||||
| ) | ) | ||||||
|  |  | ||||||
| func TestUnmarshalClients(t *testing.T) { | var _ = yaml.YAMLToJSON | ||||||
| 	data := `staticClients: |  | ||||||
|  | func TestUnmarshalConfig(t *testing.T) { | ||||||
|  | 	rawConfig := []byte(` | ||||||
|  | issuer: http://127.0.0.1:5556/dex | ||||||
|  | storage: | ||||||
|  |   type: sqlite3 | ||||||
|  |   config: | ||||||
|  |     file: examples/dex.db | ||||||
|  |  | ||||||
|  | web: | ||||||
|  |   http: 127.0.0.1:5556 | ||||||
|  | staticClients: | ||||||
| - id: example-app | - id: example-app | ||||||
|   redirectURIs: |   redirectURIs: | ||||||
|   - 'http://127.0.0.1:5555/callback' |   - 'http://127.0.0.1:5555/callback' | ||||||
|   name: 'Example App' |   name: 'Example App' | ||||||
|   secret: ZXhhbXBsZS1hcHAtc2VjcmV0 |   secret: ZXhhbXBsZS1hcHAtc2VjcmV0 | ||||||
| ` |  | ||||||
| 	var c Config |  | ||||||
| 	if err := yaml.Unmarshal([]byte(data), &c); err != nil { |  | ||||||
| 		t.Fatal(err) |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	wantClients := []storage.Client{ | connectors: | ||||||
| 		{ | - type: mockCallback | ||||||
| 			ID:     "example-app", |   id: mock | ||||||
| 			Name:   "Example App", |   name: Example | ||||||
| 			Secret: "ZXhhbXBsZS1hcHAtc2VjcmV0", | - type: oidc | ||||||
| 			RedirectURIs: []string{ |   id: google | ||||||
| 				"http://127.0.0.1:5555/callback", |   name: Google | ||||||
|  |   config: | ||||||
|  |     issuer: https://accounts.google.com | ||||||
|  |     #  Config values starting with a "$" will read from the environment. | ||||||
|  |     clientID: $GOOGLE_CLIENT_ID | ||||||
|  |     clientSecret: $GOOGLE_CLIENT_SECRET | ||||||
|  |     redirectURI: http://127.0.0.1:5556/dex/callback/google | ||||||
|  |  | ||||||
|  | enablePasswordDB: true | ||||||
|  | staticPasswords: | ||||||
|  | - email: "admin@example.com" | ||||||
|  |   # bcrypt hash of the string "password" | ||||||
|  |   hash: "$2a$10$33EMT0cVYVlPy6WAMCLsceLYjWhuHpbz5yuZxu/GAFj03J9Lytjuy" | ||||||
|  |   username: "admin" | ||||||
|  |   userID: "08a8684b-db88-4b73-90a9-3cd1661f5466" | ||||||
|  | - email: "foo@example.com"   | ||||||
|  |   # base64'd value of the same bcrypt hash above. We want to be able to parse both of these | ||||||
|  |   hash: "JDJhJDEwJDMzRU1UMGNWWVZsUHk2V0FNQ0xzY2VMWWpXaHVIcGJ6NXl1Wnh1L0dBRmowM0o5THl0anV5" | ||||||
|  |   username: "foo" | ||||||
|  |   userID: "41331323-6f44-45e6-b3b9-2c4b60c02be5" | ||||||
|  | `) | ||||||
|  |  | ||||||
|  | 	want := Config{ | ||||||
|  | 		Issuer: "http://127.0.0.1:5556/dex", | ||||||
|  | 		Storage: Storage{ | ||||||
|  | 			Type: "sqlite3", | ||||||
|  | 			Config: &sql.SQLite3{ | ||||||
|  | 				File: "examples/dex.db", | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
|  | 		Web: Web{ | ||||||
|  | 			HTTP: "127.0.0.1:5556", | ||||||
|  | 		}, | ||||||
|  | 		StaticClients: []storage.Client{ | ||||||
|  | 			{ | ||||||
|  | 				ID:     "example-app", | ||||||
|  | 				Secret: "ZXhhbXBsZS1hcHAtc2VjcmV0", | ||||||
|  | 				Name:   "Example App", | ||||||
|  | 				RedirectURIs: []string{ | ||||||
|  | 					"http://127.0.0.1:5555/callback", | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
|  | 		Connectors: []Connector{ | ||||||
|  | 			{ | ||||||
|  | 				Type:   "mockCallback", | ||||||
|  | 				ID:     "mock", | ||||||
|  | 				Name:   "Example", | ||||||
|  | 				Config: &mock.CallbackConfig{}, | ||||||
|  | 			}, | ||||||
|  | 			{ | ||||||
|  | 				Type: "oidc", | ||||||
|  | 				ID:   "google", | ||||||
|  | 				Name: "Google", | ||||||
|  | 				Config: &oidc.Config{ | ||||||
|  | 					Issuer:       "https://accounts.google.com", | ||||||
|  | 					ClientID:     "$GOOGLE_CLIENT_ID", | ||||||
|  | 					ClientSecret: "$GOOGLE_CLIENT_SECRET", | ||||||
|  | 					RedirectURI:  "http://127.0.0.1:5556/dex/callback/google", | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
|  | 		EnablePasswordDB: true, | ||||||
|  | 		StaticPasswords: []password{ | ||||||
|  | 			{ | ||||||
|  | 				Email:    "admin@example.com", | ||||||
|  | 				Hash:     []byte("$2a$10$33EMT0cVYVlPy6WAMCLsceLYjWhuHpbz5yuZxu/GAFj03J9Lytjuy"), | ||||||
|  | 				Username: "admin", | ||||||
|  | 				UserID:   "08a8684b-db88-4b73-90a9-3cd1661f5466", | ||||||
|  | 			}, | ||||||
|  | 			{ | ||||||
|  | 				Email:    "foo@example.com", | ||||||
|  | 				Hash:     []byte("$2a$10$33EMT0cVYVlPy6WAMCLsceLYjWhuHpbz5yuZxu/GAFj03J9Lytjuy"), | ||||||
|  | 				Username: "foo", | ||||||
|  | 				UserID:   "41331323-6f44-45e6-b3b9-2c4b60c02be5", | ||||||
| 			}, | 			}, | ||||||
| 		}, | 		}, | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if diff := pretty.Compare(wantClients, c.StaticClients); diff != "" { | 	var c Config | ||||||
| 		t.Errorf("did not get expected clients: %s", diff) | 	if err := yaml.Unmarshal(rawConfig, &c); err != nil { | ||||||
|  | 		t.Fatalf("failed to decode config: %v", err) | ||||||
| 	} | 	} | ||||||
|  | 	if diff := pretty.Compare(c, want); diff != "" { | ||||||
|  | 		t.Errorf("got!=want: %s", diff) | ||||||
|  | 	} | ||||||
|  |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -136,7 +136,11 @@ func serve(cmd *cobra.Command, args []string) error { | |||||||
| 		s = storage.WithStaticClients(s, c.StaticClients) | 		s = storage.WithStaticClients(s, c.StaticClients) | ||||||
| 	} | 	} | ||||||
| 	if len(c.StaticPasswords) > 0 { | 	if len(c.StaticPasswords) > 0 { | ||||||
| 		s = storage.WithStaticPasswords(s, c.StaticPasswords) | 		passwords := make([]storage.Password, len(c.StaticPasswords)) | ||||||
|  | 		for i, p := range c.StaticPasswords { | ||||||
|  | 			passwords[i] = storage.Password(p) | ||||||
|  | 		} | ||||||
|  | 		s = storage.WithStaticPasswords(s, passwords) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	serverConfig := server.Config{ | 	serverConfig := server.Config{ | ||||||
|   | |||||||
| @@ -58,7 +58,7 @@ enablePasswordDB: true | |||||||
| staticPasswords: | staticPasswords: | ||||||
| - email: "admin@example.com" | - email: "admin@example.com" | ||||||
|   # bcrypt hash of the string "password" |   # bcrypt hash of the string "password" | ||||||
|   hash: "JDJhJDE0JDh4TnlVZ3pzSmVuQm4ySlRPT2QvbmVGcUlnQzF4TEFVRFA3VlpTVzhDNWlkLnFPcmNlYUJX" |   hash: "$2a$10$33EMT0cVYVlPy6WAMCLsceLYjWhuHpbz5yuZxu/GAFj03J9Lytjuy" | ||||||
|   username: "admin" |   username: "admin" | ||||||
|   userID: "08a8684b-db88-4b73-90a9-3cd1661f5466" |   userID: "08a8684b-db88-4b73-90a9-3cd1661f5466" | ||||||
|  |  | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user