Skip to content

Commit

Permalink
TT-6162 on setting session lifetime, consider the sessionlifetime set…
Browse files Browse the repository at this point in the history
… at api level (#4217)

* on setting session lifetime, consider the sessionlifetime set at api level

* normalize behaviour of session lifetime. It will always use the bigger lifetime found

* simplify handleAddKey
  • Loading branch information
sredxny committed Aug 5, 2022
1 parent 1869f4e commit fce0346
Show file tree
Hide file tree
Showing 3 changed files with 133 additions and 15 deletions.
54 changes: 43 additions & 11 deletions gateway/api.go
Expand Up @@ -245,14 +245,51 @@ func (gw *Gateway) applyPoliciesAndSave(keyName string, session *user.SessionSta
return err
}

lifetime := session.Lifetime(spec.GetSessionLifetimeRespectsKeyExpiration(), spec.SessionLifetime, gw.GetConfig().ForceGlobalSessionLifetime, gw.GetConfig().GlobalSessionLifetime)
// calculate lifetime considering access rights
lifetime := gw.ApplyLifetime(session, spec)
if err := gw.GlobalSessionManager.UpdateSession(keyName, session, lifetime, isHashed); err != nil {
return err
}

return nil
}

// GetApiSpecsFromAccessRights from the session.AccessRights returns the collection of api specs
func (gw *Gateway) GetApiSpecsFromAccessRights(sess *user.SessionState) []*APISpec {
var apis []*APISpec
if sess != nil && len(sess.AccessRights) > 0 {
for apiID := range sess.AccessRights {
spec := gw.getApiSpec(apiID)
if spec != nil {
apis = append(apis, spec)
}
}
}

return apis
}

// ApplyLifetime calculates the lifetime for the key. It considers the access rights and the bigger lifetime will be used
func (gw *Gateway) ApplyLifetime(sess *user.SessionState, specs ...*APISpec) int64 {
var lifetime int64

if len(sess.AccessRights) > 0 {
specs = gw.GetApiSpecsFromAccessRights(sess)
}

for _, spec := range specs {
if spec != nil {
sessionLifeTime := sess.Lifetime(spec.GetSessionLifetimeRespectsKeyExpiration(), spec.SessionLifetime, gw.GetConfig().ForceGlobalSessionLifetime, gw.GetConfig().GlobalSessionLifetime)
// uses the greater lifetime
if sessionLifeTime > lifetime {
lifetime = sessionLifeTime
}
}
}

return lifetime
}

func resetAPILimits(accessRights map[string]user.AccessDefinition) {
for apiID := range accessRights {
// reset API-level limit to nil if it has a zero-value
Expand All @@ -274,7 +311,6 @@ func (gw *Gateway) doAddOrUpdate(keyName string, newSession *user.SessionState,
// reset API-level limit to empty APILimit if any has a zero-value
resetAPILimits(newSession.AccessRights)
// We have a specific list of access rules, only add / update those

for apiId := range newSession.AccessRights {
apiSpec := gw.getApiSpec(apiId)
if apiSpec == nil {
Expand Down Expand Up @@ -315,13 +351,13 @@ func (gw *Gateway) doAddOrUpdate(keyName string, newSession *user.SessionState,
log.Warning("No API Access Rights set, adding key to ALL.")
gw.apisMu.RLock()
defer gw.apisMu.RUnlock()

for _, spec := range gw.apisByID {
if !dontReset {
gw.GlobalSessionManager.ResetQuota(keyName, newSession, isHashed)
newSession.QuotaRenews = time.Now().Unix() + newSession.QuotaRenewalRate
}
gw.checkAndApplyTrialPeriod(keyName, newSession, isHashed)

// apply polices (if any) and save key
if err := gw.applyPoliciesAndSave(keyName, newSession, spec, isHashed); err != nil {
return err
Expand Down Expand Up @@ -672,7 +708,7 @@ func (gw *Gateway) handleGetAllKeys(filter string) (interface{}, int) {
return sessionsObj, http.StatusOK
}

func (gw *Gateway) handleAddKey(keyName, hashedName, sessionString, apiID string, orgId string) {
func (gw *Gateway) handleAddKey(keyName, sessionString, orgId string) {
sess := &user.SessionState{}
json.Unmarshal([]byte(sessionString), sess)
sess.LastUpdated = strconv.Itoa(int(time.Now().Unix()))
Expand All @@ -681,12 +717,8 @@ func (gw *Gateway) handleAddKey(keyName, hashedName, sessionString, apiID string
return
}

var err error
if gw.GetConfig().HashKeys {
err = gw.GlobalSessionManager.UpdateSession(hashedName, sess, 0, true)
} else {
err = gw.GlobalSessionManager.UpdateSession(keyName, sess, 0, false)
}
lifetime := gw.ApplyLifetime(sess, nil)
err := gw.GlobalSessionManager.UpdateSession(keyName, sess, lifetime, gw.GetConfig().HashKeys)
if err != nil {
log.WithFields(logrus.Fields{
"prefix": "api",
Expand Down Expand Up @@ -1841,6 +1873,7 @@ func (gw *Gateway) createKeyHandler(w http.ResponseWriter, r *http.Request) {
sessionManager := gw.GlobalSessionManager
newSession.QuotaRenews = time.Now().Unix() + newSession.QuotaRenewalRate
sessionManager.ResetQuota(newKey, newSession, false)
// apply polices (if any) and save key
err := sessionManager.UpdateSession(newKey, newSession, -1, false)
if err != nil {
doJSONWrite(w, http.StatusInternalServerError, apiError("Failed to create key - "+err.Error()))
Expand Down Expand Up @@ -1871,7 +1904,6 @@ func (gw *Gateway) createKeyHandler(w http.ResponseWriter, r *http.Request) {
gw.GlobalSessionManager.ResetQuota(newKey, newSession, false)
newSession.QuotaRenews = time.Now().Unix() + newSession.QuotaRenewalRate
}
// apply polices (if any) and save key
if err := gw.applyPoliciesAndSave(newKey, newSession, spec, false); err != nil {
doJSONWrite(w, http.StatusInternalServerError, apiError("Failed to create key - "+err.Error()))
return
Expand Down
86 changes: 86 additions & 0 deletions gateway/api_test.go
Expand Up @@ -3093,3 +3093,89 @@ func testImportOAS(t *testing.T, ts *Test, testCase test.TestCase) string {

return importResp.Key
}

func TestApplyLifetime(t *testing.T) {

ts := StartTest(nil)
defer ts.Close()

ts.Gw.BuildAndLoadAPI(
func(spec *APISpec) {
spec.APIID = "api1"
},
func(spec *APISpec) {
spec.APIID = "api2"
spec.SessionLifetime = 1000
},
func(spec *APISpec) {
spec.APIID = "api3"
spec.SessionLifetime = 999
},
)

testCases := []struct {
name string
expectedLifetime int64
getTestSession func() user.SessionState
}{
{
name: "single api without session lifetime set",
expectedLifetime: 0,
getTestSession: func() user.SessionState {
return user.SessionState{
AccessRights: map[string]user.AccessDefinition{
"api1": {
APIID: "api1", Versions: []string{"v1"},
},
},
}
},
},
{
name: "many apis, one of them with session lifetime set",
expectedLifetime: 1000,
getTestSession: func() user.SessionState {
return user.SessionState{
AccessRights: map[string]user.AccessDefinition{
"api1": {
APIID: "api1", Versions: []string{"v1"},
},
"api2": {
APIID: "api2", Versions: []string{"v1"},
},
},
}
},
},
{
name: "many apis with session lifetime set, greater should be used",
expectedLifetime: 1000,
getTestSession: func() user.SessionState {
return user.SessionState{
AccessRights: map[string]user.AccessDefinition{
"api2": {
APIID: "api2", Versions: []string{"v1"},
},
"api3": {
APIID: "api3", Versions: []string{"v1"},
},
},
}
},
},
{
name: "Session without access rights",
expectedLifetime: 0,
getTestSession: func() user.SessionState {
return user.SessionState{}
},
},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
session := tc.getTestSession()
assert.Equal(t, tc.expectedLifetime, ts.Gw.ApplyLifetime(&session, nil))
})
}
}
8 changes: 4 additions & 4 deletions gateway/rpc_storage_handler.go
Expand Up @@ -831,17 +831,17 @@ func (r *RPCStorageHandler) CheckForKeyspaceChanges(orgId string) {

func (gw *Gateway) getSessionAndCreate(keyName string, r *RPCStorageHandler, isHashed bool, orgId string) {

hashedKeyName := keyName
key := keyName
// avoid double hashing
if !isHashed {
hashedKeyName = storage.HashKey(keyName, gw.GetConfig().HashKeys)
key = storage.HashKey(keyName, gw.GetConfig().HashKeys)
}

sessionString, err := r.GetRawKey("apikey-" + hashedKeyName)
sessionString, err := r.GetRawKey("apikey-" + key)
if err != nil {
log.Error("Key not found in master - skipping")
} else {
gw.handleAddKey(keyName, hashedKeyName, sessionString, "-1", orgId)
gw.handleAddKey(key, sessionString, orgId)
}
}

Expand Down

0 comments on commit fce0346

Please sign in to comment.