Merge "Add a retry loop to StartCall.  If a deadline is not set, use defaultCallTimeout."
diff --git a/services/identity/googleoauth/handler.go b/services/identity/googleoauth/handler.go
index 915db37..596f96a 100644
--- a/services/identity/googleoauth/handler.go
+++ b/services/identity/googleoauth/handler.go
@@ -64,8 +64,13 @@
 // identity and bless it with the Google email address.
 func NewHandler(args HandlerArgs) http.Handler {
 	config := NewOAuthConfig(args.ClientID, args.ClientSecret, args.redirectURL())
-	tokenMap := make(map[tokenCaveatIDKey]bool)
-	return &handler{config, util.NewCSRFCop(), args.Auditor, args.RevocationManager, tokenMap}
+	return &handler{
+		config:            config,
+		csrfCop:           util.NewCSRFCop(),
+		auditor:           args.Auditor,
+		revocationManager: args.RevocationManager,
+		tokenMap:          newTokenRevocationCaveatMap(time.Hour),
+	}
 }
 
 // NewOAuthConfig returns the oauth.Config required for obtaining just the email address from Google using OAuth 2.0.
@@ -80,16 +85,12 @@
 	}
 }
 
-type tokenCaveatIDKey struct {
-	token, caveatID string
-}
-
 type handler struct {
-	config                   *oauth.Config
-	csrfCop                  *util.CSRFCop
-	auditor                  string
-	revocationManager        *revocation.RevocationManager
-	tokenRevocationCaveatMap map[tokenCaveatIDKey]bool
+	config            *oauth.Config
+	csrfCop           *util.CSRFCop
+	auditor           string
+	revocationManager *revocation.RevocationManager
+	tokenMap          *tokenRevocationCaveatMap
 }
 
 func (h *handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
@@ -175,10 +176,7 @@
 					if revocationTime := h.revocationManager.GetRevocationTime(blessEntry.RevocationCaveat.ID()); revocationTime != nil {
 						tmplentry.RevocationTime = *revocationTime
 					}
-					// TODO(suharshs): Add a timeout that removes old entries to reduce storage space.
-					// TODO(suharshs): Make this map from CSRFToken to Email address, have
-					// revocation manager have map from caveatID to Email address in DirectoryStore.
-					h.tokenRevocationCaveatMap[tokenCaveatIDKey{csrf, tmplentry.RevocationCaveatID}] = true
+					h.tokenMap.Insert(csrf, tmplentry.RevocationCaveatID)
 				}
 				ch <- tmplentry
 			}
@@ -247,7 +245,7 @@
 	if err := h.csrfCop.ValidateToken(CSRFToken, r, "VeyronHTTPIdentityRevocationID"); err != nil {
 		return fmt.Errorf("invalid CSRF token: %v in request: %#v", err, r)
 	}
-	if _, exists := h.tokenRevocationCaveatMap[tokenCaveatIDKey{CSRFToken, revocationCaveatID}]; exists {
+	if h.tokenMap.Exists(CSRFToken, revocationCaveatID) {
 		return nil
 	}
 	return fmt.Errorf("this token has no matching entry for the provided caveat ID")
diff --git a/services/identity/googleoauth/utils.go b/services/identity/googleoauth/utils.go
index 6f5230a..a123f25 100644
--- a/services/identity/googleoauth/utils.go
+++ b/services/identity/googleoauth/utils.go
@@ -4,6 +4,8 @@
 	"encoding/json"
 	"fmt"
 	"io"
+	"sync"
+	"time"
 )
 
 // ClientIDFromJSON parses JSON-encoded API access information in 'r' and returns
@@ -63,3 +65,54 @@
 	}
 	return
 }
+
+// Map that maintains storage of tokens and caveatIDs and removal after specified timeout.
+// This is need to ensure the correct identities have revoke access from the identity server.
+type tokenRevocationCaveatMap struct {
+	mapIndex  int // The index of the current map being. This will be the index of the map that should be inserted into.
+	tokenMaps [2]map[tokenCaveatIDKey]bool
+	mu        sync.RWMutex // guards mapIndex and tokenMaps
+}
+
+type tokenCaveatIDKey struct {
+	token, caveatID string
+}
+
+// newTokenRevocationCaveatMap returns a map from tokens to CaveatIDs such that every Insert-ed entry will Exist at least
+// until 'timeout' and at most until 2*'timeout'
+func newTokenRevocationCaveatMap(timeout time.Duration) *tokenRevocationCaveatMap {
+	var tokenMaps [2]map[tokenCaveatIDKey]bool
+	for i := 0; i < 2; i++ {
+		tokenMaps[i] = make(map[tokenCaveatIDKey]bool)
+	}
+	m := tokenRevocationCaveatMap{mapIndex: 0, tokenMaps: tokenMaps}
+	go m.timeoutLoop(timeout)
+	return &m
+}
+
+func (m *tokenRevocationCaveatMap) Insert(token, caveatID string) {
+	m.mu.Lock()
+	defer m.mu.Unlock()
+	m.tokenMaps[m.mapIndex][tokenCaveatIDKey{token, caveatID}] = true
+}
+
+func (m *tokenRevocationCaveatMap) Exists(token, caveatID string) bool {
+	m.mu.RLock()
+	defer m.mu.RUnlock()
+	for i := 0; i < 2; i++ {
+		if _, exists := m.tokenMaps[i][tokenCaveatIDKey{token, caveatID}]; exists {
+			return true
+		}
+	}
+	return false
+}
+
+func (m *tokenRevocationCaveatMap) timeoutLoop(timeout time.Duration) {
+	for {
+		time.Sleep(timeout)
+		m.mu.Lock()
+		m.mapIndex = (m.mapIndex + 1) % 2
+		m.tokenMaps[m.mapIndex] = make(map[tokenCaveatIDKey]bool)
+		m.mu.Unlock()
+	}
+}
diff --git a/services/identity/googleoauth/utils_test.go b/services/identity/googleoauth/utils_test.go
index ab2e84e..342b1b2 100644
--- a/services/identity/googleoauth/utils_test.go
+++ b/services/identity/googleoauth/utils_test.go
@@ -3,6 +3,7 @@
 import (
 	"strings"
 	"testing"
+	"time"
 )
 
 func TestClientIDAndSecretFromJSON(t *testing.T) {
@@ -18,3 +19,33 @@
 		t.Errorf("Got %q want %q", secret, "SECRET")
 	}
 }
+
+func TestTokenRevocationCaveatMap(t *testing.T) {
+	d := time.Millisecond * 10
+	tokenMap := newTokenRevocationCaveatMap(d)
+	// Test non-existent token and non-existent caveatID.
+	if tokenMap.Exists("NEToken", "NECaveatID") {
+		t.Errorf("found non-existent token with non-existent caveatID")
+	}
+	tokenMap.Insert("Token", "CaveatID")
+	// Test token and caveatID when both exist.
+	if !tokenMap.Exists("Token", "CaveatID") {
+		t.Errorf("did not find existing token and caveatID")
+	}
+	// Test that after timeout they don't exist.
+	time.Sleep(3 * d)
+	if tokenMap.Exists("Token", "CaveatID") {
+		t.Errorf("token and caveatID should have been removed")
+	}
+
+	tokenMap.Insert("Token", "CaveatID")
+	// Test non-existent token and existent caveatID.
+	if tokenMap.Exists("NEToken", "CaveatID") {
+		t.Errorf("found non-existent token with existing caveatID")
+	}
+	// Test existent token and non-existent caveatID.
+	if tokenMap.Exists("Token", "NECaveatID") {
+		t.Errorf("found existing token with non-existent caveatID")
+	}
+
+}