Skip to content

Commit

Permalink
per_api field logic changed, unit tests for ApplyPolicies added
Browse files Browse the repository at this point in the history
  • Loading branch information
dencoded committed Sep 4, 2018
1 parent 8fc04a8 commit 77e3f2d
Show file tree
Hide file tree
Showing 2 changed files with 252 additions and 57 deletions.
129 changes: 72 additions & 57 deletions middleware.go
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,9 @@ func (t BaseMiddleware) UpdateRequestSession(r *http.Request) bool {
// will overwrite the session state to use the policy values.
func (t BaseMiddleware) ApplyPolicies(key string, session *user.SessionState) error {
rights := session.AccessRights
if rights == nil {
rights = make(map[string]user.AccessDefinition)
}
tags := make(map[string]bool)
didQuota, didRateLimit, didACL := false, false, false
didPerAPI := make(map[string]bool)
Expand All @@ -225,69 +228,82 @@ func (t BaseMiddleware) ApplyPolicies(key string, session *user.SessionState) er
return err
}

if policy.Partitions.Quota || policy.Partitions.RateLimit || policy.Partitions.Acl {
// This is a partitioned policy, only apply what is active
if policy.Partitions.PerAPI {
// new logic when you can specify quota or rate in more than one policy but for different APIs
// this new option will be applied to session ONLY when policy.Partitions.Acl == true
// as this is where we merging access rights from all policies into session
for apiID, accessRights := range policy.AccessRights {
if didPerAPI[apiID] {
err := fmt.Errorf("cannot apply multiple policies for API: %s", apiID)
log.Error(err)
return err
}
didPerAPI[apiID] = true
if policy.Partitions.PerAPI &&
(policy.Partitions.Quota || policy.Partitions.RateLimit || policy.Partitions.Acl) {
err := fmt.Errorf("cannot apply policy %s which has per_api and any of partitions set", policy.ID)
log.Error(err)
return err
}

// check if we already have limit on API level specified when policy was created
if accessRights.Limit != nil {
continue
}
if policy.Partitions.PerAPI {
// new logic when you can specify quota or rate in more than one policy but for different APIs
if didQuota || didRateLimit || didACL { // no other partitions allowed
err := fmt.Errorf("cannot apply multiple policies when some have per_api set and some are partitioned")
log.Error(err)
return err
}
for apiID, accessRights := range policy.AccessRights {
// check if limit was already set for this API by other policy assigned to key
if didPerAPI[apiID] {
err := fmt.Errorf("cannot apply multiple policies for API: %s", apiID)
log.Error(err)
return err
}

// check if we already have limit on API level specified when policy was created
if accessRights.Limit == nil {
// limit was not specified on API level so we will populate it from policy
apiLimitFromPolicy := &user.APILimit{
QuotaMax: -1,
SetByPolicy: true,
accessRights.Limit = &user.APILimit{
QuotaMax: policy.QuotaMax,
QuotaRenewalRate: policy.QuotaRenewalRate,
Rate: policy.Rate,
Per: policy.Per,
SetByPolicy: true,
}
if policy.Partitions.Quota {
apiLimitFromPolicy.QuotaMax = policy.QuotaMax
apiLimitFromPolicy.QuotaRenewalRate = policy.QuotaRenewalRate
}
if policy.Partitions.RateLimit {
apiLimitFromPolicy.Rate = policy.Rate
apiLimitFromPolicy.Per = policy.Per
}
accessRights.Limit = apiLimitFromPolicy
policy.AccessRights[apiID] = accessRights
}
} else {
// legacy logic when you can specify quota or rate only in no more than one policy
if policy.Partitions.Quota {
if didQuota {
err := fmt.Errorf("cannot apply multiple quota policies")
log.Error(err)
return err
}
didQuota = true
// Quotas
session.QuotaMax = policy.QuotaMax
session.QuotaRenewalRate = policy.QuotaRenewalRate

// adjust policy access right with limit on API level
policy.AccessRights[apiID] = accessRights

// overwrite session access right for this API
rights[apiID] = accessRights

// identify that limit for that API is set (to allow set it only once)
didPerAPI[apiID] = true
}
} else if policy.Partitions.Quota || policy.Partitions.RateLimit || policy.Partitions.Acl {
// This is a partitioned policy, only apply what is active
// legacy logic when you can specify quota or rate only in no more than one policy
if len(didPerAPI) > 0 { // no policies with per_api set allowed
err := fmt.Errorf("cannot apply multiple policies when some are partitioned and some have per_api set")
log.Error(err)
return err
}
if policy.Partitions.Quota {
if didQuota {
err := fmt.Errorf("cannot apply multiple quota policies")
log.Error(err)
return err
}
didQuota = true
// Quotas
session.QuotaMax = policy.QuotaMax
session.QuotaRenewalRate = policy.QuotaRenewalRate
}

if policy.Partitions.RateLimit {
if didRateLimit {
err := fmt.Errorf("cannot apply multiple rate limit policies")
log.Error(err)
return err
}
didRateLimit = true
// Rate limiting
session.Allowance = policy.Rate // This is a legacy thing, merely to make sure output is consistent. Needs to be purged
session.Rate = policy.Rate
session.Per = policy.Per
if policy.LastUpdated != "" {
session.LastUpdated = policy.LastUpdated
}
if policy.Partitions.RateLimit {
if didRateLimit {
err := fmt.Errorf("cannot apply multiple rate limit policies")
log.Error(err)
return err
}
didRateLimit = true
// Rate limiting
session.Allowance = policy.Rate // This is a legacy thing, merely to make sure output is consistent. Needs to be purged
session.Rate = policy.Rate
session.Per = policy.Per
if policy.LastUpdated != "" {
session.LastUpdated = policy.LastUpdated
}
}

Expand All @@ -303,7 +319,6 @@ func (t BaseMiddleware) ApplyPolicies(key string, session *user.SessionState) er
}
session.HMACEnabled = policy.HMACEnabled
}

} else {
if len(policies) > 1 {
err := fmt.Errorf("cannot apply multiple policies if any are non-partitioned")
Expand Down
180 changes: 180 additions & 0 deletions policy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,108 @@ func testPrepareApplyPolicies() (*BaseMiddleware, []testApplyPoliciesData) {
"acl3": {
AccessRights: map[string]user.AccessDefinition{"c": {}},
},
"per_api_and_partitions": {
ID: "per_api_and_partitions",
Partitions: user.PolicyPartitions{
PerAPI: true,
Quota: true,
RateLimit: true,
Acl: true,
},
AccessRights: map[string]user.AccessDefinition{"d": {
Limit: &user.APILimit{
QuotaMax: 1000,
QuotaRenewalRate: 3600,
Rate: 20,
Per: 1,
},
}},
},
"per_api_and_some_partitions": {
ID: "per_api_and_some_partitions",
Partitions: user.PolicyPartitions{
PerAPI: true,
Quota: false,
RateLimit: true,
Acl: false,
},
AccessRights: map[string]user.AccessDefinition{"d": {
Limit: &user.APILimit{
QuotaMax: 1000,
QuotaRenewalRate: 3600,
Rate: 20,
Per: 1,
},
}},
},
"per_api_and_no_other_partitions": {
ID: "per_api_and_no_other_partitions",
Partitions: user.PolicyPartitions{
PerAPI: true,
Quota: false,
RateLimit: false,
Acl: false,
},
AccessRights: map[string]user.AccessDefinition{
"d": {
Limit: &user.APILimit{
QuotaMax: 1000,
QuotaRenewalRate: 3600,
Rate: 20,
Per: 1,
},
},
"c": {
Limit: &user.APILimit{
QuotaMax: -1,
Rate: 2000,
Per: 60,
},
},
},
},
"per_api_with_the_same_api": {
ID: "per_api_with_the_same_api",
Partitions: user.PolicyPartitions{
PerAPI: true,
Quota: false,
RateLimit: false,
Acl: false,
},
AccessRights: map[string]user.AccessDefinition{
"d": {
Limit: &user.APILimit{
QuotaMax: 5000,
QuotaRenewalRate: 3600,
Rate: 200,
Per: 10,
},
},
},
},
"per_api_with_limit_set_from_policy": {
ID: "per_api_with_limit_set_from_policy",
QuotaMax: -1,
Rate: 300,
Per: 1,
Partitions: user.PolicyPartitions{
PerAPI: true,
Quota: false,
RateLimit: false,
Acl: false,
},
AccessRights: map[string]user.AccessDefinition{
"d": {
Limit: &user.APILimit{
QuotaMax: 5000,
QuotaRenewalRate: 3600,
Rate: 200,
Per: 10,
},
},
"e": {},
},
},
}
policiesMu.RUnlock()
bmid := &BaseMiddleware{Spec: &APISpec{
Expand Down Expand Up @@ -215,6 +317,84 @@ func testPrepareApplyPolicies() (*BaseMiddleware, []testApplyPoliciesData) {
}
},
},
{
name: "Per API is set with other partitions to true",
policies: []string{"per_api_and_partitions"},
errMatch: "cannot apply policy per_api_and_partitions which has per_api and any of partitions set",
},
{
name: "Per API is set to true with some partitions set to true",
policies: []string{"per_api_and_some_partitions"},
errMatch: "cannot apply policy per_api_and_some_partitions which has per_api and any of partitions set",
},
{
name: "Per API is set to true with no other partitions set to true",
policies: []string{"per_api_and_no_other_partitions"},
sessMatch: func(t *testing.T, s *user.SessionState) {
want := map[string]user.AccessDefinition{
"d": {
Limit: &user.APILimit{
QuotaMax: 1000,
QuotaRenewalRate: 3600,
Rate: 20,
Per: 1,
},
},
"c": {
Limit: &user.APILimit{
QuotaMax: -1,
Rate: 2000,
Per: 60,
},
},
}
if !reflect.DeepEqual(want, s.AccessRights) {
t.Fatalf("want %v got %v", want, s.AccessRights)
}
},
},
{
name: "several policies with Per API set to true but specifying limit for the same API",
policies: []string{"per_api_and_no_other_partitions", "per_api_with_the_same_api"},
errMatch: "cannot apply multiple policies for API: d",
},
{
name: "several policies, mixed the one which has Per API set to true and partitioned ones",
policies: []string{"per_api_and_no_other_partitions", "quota1"},
errMatch: "cannot apply multiple policies when some are partitioned and some have per_api set",
},
{
name: "several policies, mixed the one which has Per API set to true and partitioned ones (different order)",
policies: []string{"rate1", "per_api_and_no_other_partitions"},
errMatch: "cannot apply multiple policies when some have per_api set and some are partitioned",
},
{
name: "Per API is set to true and some API gets limit set from policy's fields",
policies: []string{"per_api_with_limit_set_from_policy"},
sessMatch: func(t *testing.T, s *user.SessionState) {
want := map[string]user.AccessDefinition{
"d": {
Limit: &user.APILimit{
QuotaMax: 5000,
QuotaRenewalRate: 3600,
Rate: 200,
Per: 10,
},
},
"e": {
Limit: &user.APILimit{
QuotaMax: -1,
Rate: 300,
Per: 1,
SetByPolicy: true,
},
},
}
if !reflect.DeepEqual(want, s.AccessRights) {
t.Fatalf("want %v got %v", want, s.AccessRights)
}
},
},
}

return bmid, tests
Expand Down

0 comments on commit 77e3f2d

Please sign in to comment.