From 53d1be4a87e8ef4913325ef53204e6d3de6dd9db Mon Sep 17 00:00:00 2001
From: Eric Chiang <eric.chiang.m@gmail.com>
Date: Fri, 5 Aug 2016 09:50:22 -0700
Subject: [PATCH] *: load static clients from config file

---
 cmd/poke/config.go       |  6 ++++++
 cmd/poke/config_test.go  | 39 +++++++++++++++++++++++++++++++++++++++
 cmd/poke/serve.go        |  6 +++++-
 example/config-dev.yaml  | 27 +++++++++++++++++++++++++++
 server/handlers.go       |  6 +++++-
 storage/memory/memory.go | 12 ++++++++++++
 storage/storage.go       | 14 +++++++-------
 7 files changed, 101 insertions(+), 9 deletions(-)
 create mode 100644 cmd/poke/config_test.go
 create mode 100644 example/config-dev.yaml

diff --git a/cmd/poke/config.go b/cmd/poke/config.go
index 3d840a40..fc06b208 100644
--- a/cmd/poke/config.go
+++ b/cmd/poke/config.go
@@ -9,6 +9,7 @@ import (
 	"github.com/coreos/poke/connector/mock"
 	"github.com/coreos/poke/storage"
 	"github.com/coreos/poke/storage/kubernetes"
+	"github.com/coreos/poke/storage/memory"
 )
 
 // Config is the config format for the main application.
@@ -17,6 +18,8 @@ type Config struct {
 	Storage    Storage     `yaml:"storage"`
 	Connectors []Connector `yaml:"connectors"`
 	Web        Web         `yaml:"web"`
+
+	StaticClients []storage.Client `yaml:"staticClients"`
 }
 
 // Web is the config format for the HTTP server.
@@ -46,9 +49,12 @@ func (s *Storage) UnmarshalYAML(unmarshal func(interface{}) error) error {
 	var c struct {
 		Config StorageConfig `yaml:"config"`
 	}
+	// TODO(ericchiang): replace this with a registration process.
 	switch storageMeta.Type {
 	case "kubernetes":
 		c.Config = &kubernetes.Config{}
+	case "memory":
+		c.Config = &memory.Config{}
 	default:
 		return fmt.Errorf("unknown storage type %q", storageMeta.Type)
 	}
diff --git a/cmd/poke/config_test.go b/cmd/poke/config_test.go
new file mode 100644
index 00000000..f4158bc3
--- /dev/null
+++ b/cmd/poke/config_test.go
@@ -0,0 +1,39 @@
+package main
+
+import (
+	"testing"
+
+	"github.com/coreos/poke/storage"
+	"github.com/kylelemons/godebug/pretty"
+
+	yaml "gopkg.in/yaml.v2"
+)
+
+func TestUnmarshalClients(t *testing.T) {
+	data := `staticClients:
+- id: example-app
+  redirectURIs:
+  - 'http://127.0.0.1:5555/callback'
+  name: 'Example App'
+  secret: ZXhhbXBsZS1hcHAtc2VjcmV0
+`
+	var c Config
+	if err := yaml.Unmarshal([]byte(data), &c); err != nil {
+		t.Fatal(err)
+	}
+
+	wantClients := []storage.Client{
+		{
+			ID:     "example-app",
+			Name:   "Example App",
+			Secret: "ZXhhbXBsZS1hcHAtc2VjcmV0",
+			RedirectURIs: []string{
+				"http://127.0.0.1:5555/callback",
+			},
+		},
+	}
+
+	if diff := pretty.Compare(wantClients, c.StaticClients); diff != "" {
+		t.Errorf("did not get expected clients: %s", diff)
+	}
+}
diff --git a/cmd/poke/serve.go b/cmd/poke/serve.go
index 28611f50..24bb4917 100644
--- a/cmd/poke/serve.go
+++ b/cmd/poke/serve.go
@@ -7,10 +7,11 @@ import (
 	"log"
 	"net/http"
 
+	"github.com/spf13/cobra"
 	yaml "gopkg.in/yaml.v2"
 
 	"github.com/coreos/poke/server"
-	"github.com/spf13/cobra"
+	"github.com/coreos/poke/storage"
 )
 
 func commandServe() *cobra.Command {
@@ -83,6 +84,9 @@ func serve(cmd *cobra.Command, args []string) error {
 	if err != nil {
 		return fmt.Errorf("initializing storage: %v", err)
 	}
+	if len(c.StaticClients) > 0 {
+		s = storage.WithStaticClients(s, c.StaticClients)
+	}
 
 	serverConfig := server.Config{
 		Issuer:     c.Issuer,
diff --git a/example/config-dev.yaml b/example/config-dev.yaml
new file mode 100644
index 00000000..d3d01afb
--- /dev/null
+++ b/example/config-dev.yaml
@@ -0,0 +1,27 @@
+issuer: http://127.0.0.1:5556
+storage:
+  # NOTE(ericchiang): This will be replaced by sqlite3 in the future.
+  type: memory
+
+web:
+  http: 127.0.0.1:5556
+
+connectors:
+- type: mock
+  id: mock
+  name: Mock
+- type: github
+  id: github
+  name: GitHub
+  config:
+    clientID: "$GITHUB_CLIENT_ID"
+    clientSecret: "$GITHUB_CLIENT_SECRET"
+    redirectURI: http://127.0.0.1:5556/callback/github
+    org: kubernetes
+
+staticClients:
+- id: example-app
+  redirectURIs:
+  - 'http://127.0.0.1:5555/callback'
+  name: 'Example App'
+  secret: ZXhhbXBsZS1hcHAtc2VjcmV0
diff --git a/server/handlers.go b/server/handlers.go
index 85214a41..76bdbc5f 100644
--- a/server/handlers.go
+++ b/server/handlers.go
@@ -2,6 +2,7 @@ package server
 
 import (
 	"encoding/json"
+	"errors"
 	"fmt"
 	"log"
 	"net/http"
@@ -125,7 +126,7 @@ func (s *Server) handleAuthorization(w http.ResponseWriter, r *http.Request) {
 	for id := range s.connectors {
 		connectorInfos[i] = connectorInfo{
 			DisplayName: id,
-			URL:         s.absPath("/auth", id) + "?state=" + state,
+			URL:         s.absPath("/auth", id),
 		}
 		i++
 	}
@@ -224,6 +225,9 @@ func (s *Server) handleConnectorCallback(w http.ResponseWriter, r *http.Request)
 }
 
 func (s *Server) finalizeLogin(identity connector.Identity, authReqID, connectorID string, conn connector.Connector) (string, error) {
+	if authReqID == "" {
+		return "", errors.New("no auth request ID passed")
+	}
 	claims := storage.Claims{
 		UserID:        identity.UserID,
 		Username:      identity.Username,
diff --git a/storage/memory/memory.go b/storage/memory/memory.go
index 748e9528..d4bb1bcd 100644
--- a/storage/memory/memory.go
+++ b/storage/memory/memory.go
@@ -18,6 +18,18 @@ func New() storage.Storage {
 	}
 }
 
+// Config is an implementation of a storage configuration.
+//
+// TODO(ericchiang): Actually define a storage config interface and have registration.
+type Config struct {
+	// The in memory implementation has no config.
+}
+
+// Open always returns a new in memory storage.
+func (c *Config) Open() (storage.Storage, error) {
+	return New(), nil
+}
+
 type memStorage struct {
 	mu sync.Mutex
 
diff --git a/storage/storage.go b/storage/storage.go
index 88b11542..399b35cd 100644
--- a/storage/storage.go
+++ b/storage/storage.go
@@ -81,19 +81,19 @@ type Storage interface {
 //   * Trusted peers: https://developers.google.com/identity/protocols/CrossClientAuth
 //   * Public clients: https://developers.google.com/api-client-library/python/auth/installed-app
 type Client struct {
-	ID           string
-	Secret       string
-	RedirectURIs []string
+	ID           string   `json:"id" yaml:"id"`
+	Secret       string   `json:"secret" yaml:"secret"`
+	RedirectURIs []string `json:"redirectURIs" yaml:"redirectURIs"`
 
 	// TrustedPeers are a list of peers which can issue tokens on this client's behalf.
 	// Clients inherently trust themselves.
-	TrustedPeers []string
+	TrustedPeers []string `json:"trustedPeers" yaml:"trustedPeers"`
 
 	// Public clients must use either use a redirectURL 127.0.0.1:X or "urn:ietf:wg:oauth:2.0:oob"
-	Public bool
+	Public bool `json:"public" yaml:"public"`
 
-	Name    string
-	LogoURL string
+	Name    string `json:"name" yaml:"name"`
+	LogoURL string `json:"logoURL" yaml:"logoURL"`
 }
 
 // Claims represents the ID Token claims supported by the server.
-- 
GitLab