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")
+ }
+
+}