Repository: coreos/dex
Pull request #671 (2016-11-04 04:53:10)

server: use seconds instead of nano seconds for expires_in and expiry


Patch:
diff --git a/cmd/example-app/main.go b/cmd/example-app/main.go
index d2ada840..9f46f9ed 100644
--- a/cmd/example-app/main.go
+++ b/cmd/example-app/main.go
@@ -11,7 +11,6 @@ import (
 	"log"
 	"net"
 	"net/http"
-	"net/http/httputil"
 	"net/url"
 	"os"
 	"strings"
@@ -63,31 +62,6 @@ func httpClientForRootCAs(rootCAs string) (*http.Client, error) {
 	}, nil
 }
 
-type debugTransport struct {
-	t http.RoundTripper
-}
-
-func (d debugTransport) RoundTrip(req *http.Request) (*http.Response, error) {
-	reqDump, err := httputil.DumpRequest(req, true)
-	if err != nil {
-		return nil, err
-	}
-	log.Printf("%s", reqDump)
-
-	resp, err := d.t.RoundTrip(req)
-	if err != nil {
-		return nil, err
-	}
-
-	respDump, err := httputil.DumpResponse(resp, true)
-	if err != nil {
-		resp.Body.Close()
-		return nil, err
-	}
-	log.Printf("%s", respDump)
-	return resp, nil
-}
-
 func cmd() *cobra.Command {
 	var (
 		a         app
@@ -96,7 +70,6 @@ func cmd() *cobra.Command {
 		tlsCert   string
 		tlsKey    string
 		rootCAs   string
-		debug     bool
 	)
 	c := cobra.Command{
 		Use:   "example-app",
@@ -128,17 +101,6 @@ func cmd() *cobra.Command {
 				a.ctx = context.WithValue(a.ctx, oauth2.HTTPClient, client)
 			}
 
-			if debug {
-				client, ok := a.ctx.Value(oauth2.HTTPClient).(*http.Client)
-				if ok {
-					client.Transport = debugTransport{client.Transport}
-				} else {
-					a.ctx = context.WithValue(a.ctx, oauth2.HTTPClient, &http.Client{
-						Transport: debugTransport{http.DefaultTransport},
-					})
-				}
-			}
-
 			// TODO(ericchiang): Retry with backoff
 			provider, err := oidc.NewProvider(a.ctx, issuerURL)
 			if err != nil {
@@ -199,7 +161,6 @@ func cmd() *cobra.Command {
 	c.Flags().StringVar(&tlsCert, "tls-cert", "", "X509 cert file to present when serving HTTPS.")
 	c.Flags().StringVar(&tlsKey, "tls-key", "", "Private key for the HTTPS cert.")
 	c.Flags().StringVar(&rootCAs, "issuer-root-ca", "", "Root certificate authorities for the issuer. Defaults to host certs.")
-	c.Flags().BoolVar(&debug, "debug", false, "Print all request and responses from the OpenID Connect issuer.")
 	return &c
 }
 
diff --git a/server/handlers.go b/server/handlers.go
index 3bc49e7c..5bb5e904 100644
--- a/server/handlers.go
+++ b/server/handlers.go
@@ -439,7 +439,7 @@ func (s *Server) sendCodeResponse(w http.ResponseWriter, r *http.Request, authRe
 			v.Set("token_type", "bearer")
 			v.Set("id_token", idToken)
 			v.Set("state", authReq.State)
-			v.Set("expires_in", strconv.Itoa(int(expiry.Sub(s.now()))))
+			v.Set("expires_in", strconv.Itoa(int(expiry.Sub(s.now()).Seconds())))
 			u.Fragment = v.Encode()
 		}
 	}
@@ -637,7 +637,7 @@ func (s *Server) writeAccessToken(w http.ResponseWriter, idToken, refreshToken s
 	}{
 		storage.NewID(),
 		"bearer",
-		int(expiry.Sub(s.now())),
+		int(expiry.Sub(s.now()).Seconds()),
 		refreshToken,
 		idToken,
 	}


Test patch:
diff --git a/server/server_test.go b/server/server_test.go
index d3ba1e3e..1f49a2bd 100644
--- a/server/server_test.go
+++ b/server/server_test.go
@@ -137,6 +137,18 @@ func TestOAuth2CodeFlow(t *testing.T) {
 	clientSecret := "testclientsecret"
 	requestedScopes := []string{oidc.ScopeOpenID, "email", "offline_access"}
 
+	t0 := time.Now().Round(time.Second)
+
+	// Always have the time function used by the server return the same time so
+	// we can predict expected values of "expires_in" fields exactly.
+	now := func() time.Time { return t0 }
+
+	// Used later when configuring test servers to set how long id_tokens will be valid for.
+	//
+	// The actual value of 30s is completely arbitrary. We just need to set a value
+	// so tests can compute the expected "expires_in" field.
+	idTokensValidFor := time.Second * 30
+
 	tests := []struct {
 		name        string
 		handleToken func(context.Context, *oidc.Provider, *oauth2.Config, *oauth2.Token) error
@@ -154,6 +166,29 @@ func TestOAuth2CodeFlow(t *testing.T) {
 				return nil
 			},
 		},
+		{
+			name: "verify id token and oauth2 token expiry",
+			handleToken: func(ctx context.Context, p *oidc.Provider, config *oauth2.Config, token *oauth2.Token) error {
+				expectedExpiry := now().Add(idTokensValidFor)
+
+				if !token.Expiry.Round(time.Second).Equal(expectedExpiry) {
+					return fmt.Errorf("expected expired_in to be %s, got %s", expectedExpiry, token.Expiry)
+				}
+
+				rawIDToken, ok := token.Extra("id_token").(string)
+				if !ok {
+					return fmt.Errorf("no id token found")
+				}
+				idToken, err := p.NewVerifier(ctx).Verify(rawIDToken)
+				if err != nil {
+					return fmt.Errorf("failed to verify id token: %v", err)
+				}
+				if !idToken.Expiry.Round(time.Second).Equal(expectedExpiry) {
+					return fmt.Errorf("expected id token expiry to be %s, got %s", expectedExpiry, token.Expiry)
+				}
+				return nil
+			},
+		},
 		{
 			name: "refresh token",
 			handleToken: func(ctx context.Context, p *oidc.Provider, config *oauth2.Config, token *oauth2.Token) error {
@@ -259,6 +294,8 @@ func TestOAuth2CodeFlow(t *testing.T) {
 
 			httpServer, s := newTestServer(ctx, t, func(c *Config) {
 				c.Issuer = c.Issuer + "/non-root-path"
+				c.Now = now
+				c.IDTokensValidFor = idTokensValidFor
 			})
 			defer httpServer.Close()