-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
8 changed files
with
303 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
/****************************************************************************** | ||
* | ||
* Copyright 2023 SAP SE | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
* | ||
******************************************************************************/ | ||
|
||
package mock | ||
|
||
import ( | ||
"time" | ||
) | ||
|
||
// Clock is a deterministic clock for unit tests. It starts at the Unix epoch | ||
// and only advances when Clock.StepBy() is called. | ||
type Clock struct { | ||
currentTime int64 | ||
listeners []func(time.Time) | ||
} | ||
|
||
// NewClock starts a new Clock at the Unix epoch. | ||
func NewClock() *Clock { | ||
return &Clock{currentTime: 0} | ||
} | ||
|
||
// AddListener registers a callback that will be called whenever the clock is | ||
// advanced. It will also be called once immediately. | ||
func (c *Clock) AddListener(callback func(time.Time)) { | ||
c.listeners = append(c.listeners, callback) | ||
callback(c.Now()) | ||
} | ||
|
||
// Now reads the clock. This function can be used as a test double for time.Now(). | ||
func (c *Clock) Now() time.Time { | ||
return time.Unix(c.currentTime, 0).UTC() | ||
} | ||
|
||
// StepBy advances the clock by the given duration. | ||
func (c *Clock) StepBy(d time.Duration) { | ||
c.currentTime += int64(d / time.Second) | ||
for _, callback := range c.listeners { | ||
callback(c.Now()) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
/****************************************************************************** | ||
* | ||
* Copyright 2023 SAP SE | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
* | ||
******************************************************************************/ | ||
|
||
package mock | ||
|
||
import ( | ||
"testing" | ||
"time" | ||
|
||
"github.com/sapcc/go-bits/assert" | ||
) | ||
|
||
func TestClock(t *testing.T) { | ||
//clock should start at zero | ||
c := NewClock() | ||
assert.DeepEqual(t, "Clock.Now as Unix timestamp", c.Now().Unix(), int64(0)) | ||
|
||
//clock should not advance on its own | ||
assert.DeepEqual(t, "Clock.Now as Unix timestamp", c.Now().Unix(), int64(0)) | ||
|
||
//clock should advance when asked to | ||
c.StepBy(5 * time.Minute) | ||
assert.DeepEqual(t, "Clock.Now as Unix timestamp", c.Now().Unix(), int64(300)) | ||
|
||
//adding a listener should invoke the callback immediately | ||
currentTime := int64(-1) | ||
c.AddListener(func(t time.Time) { | ||
currentTime = t.Unix() | ||
}) | ||
assert.DeepEqual(t, "Unix timestamp from callback", currentTime, int64(300)) | ||
|
||
//advancing the clock should invoke the callback | ||
c.StepBy(time.Second) | ||
assert.DeepEqual(t, "Unix timestamp from callback", currentTime, int64(301)) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
/****************************************************************************** | ||
* | ||
* Copyright 2023 SAP SE | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
* | ||
******************************************************************************/ | ||
|
||
package mock | ||
|
||
import policy "github.com/databus23/goslo.policy" | ||
|
||
// Enforcer implements the gopherpolicy.Enforcer interface. During enforcement, | ||
// all accesses are allowed by default. More restrictive policies can be | ||
// configured with Forbid() and Allow(). Request attributes cannot be checked. | ||
type Enforcer struct { | ||
forbiddenRules map[string]bool | ||
} | ||
|
||
// NewEnforcer initializes an Enforcer instance. | ||
func NewEnforcer() *Enforcer { | ||
return &Enforcer{make(map[string]bool)} | ||
} | ||
|
||
// Forbid will cause all subsequent calls to Enforce() to return false when | ||
// called for this rule. | ||
func (e *Enforcer) Forbid(rule string) { | ||
e.forbiddenRules[rule] = true | ||
} | ||
|
||
// Allow reverses a previous Forbid call and allows the given policy rule. | ||
func (e *Enforcer) Allow(rule string) { | ||
e.forbiddenRules[rule] = false | ||
} | ||
|
||
// Enforce implements the gopherpolicy.Enforcer interface. | ||
func (e *Enforcer) Enforce(rule string, ctx policy.Context) bool { | ||
return !e.forbiddenRules[rule] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
/****************************************************************************** | ||
* | ||
* Copyright 2023 SAP SE | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
* | ||
******************************************************************************/ | ||
|
||
// Package mock contains basic mocks and test doubles. | ||
package mock |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
/****************************************************************************** | ||
* | ||
* Copyright 2023 SAP SE | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
* | ||
******************************************************************************/ | ||
|
||
package mock | ||
|
||
import ( | ||
"net/http" | ||
|
||
policy "github.com/databus23/goslo.policy" | ||
|
||
"github.com/sapcc/go-bits/gopherpolicy" | ||
) | ||
|
||
// Validator implements the gopherpolicy.Validator and gopherpolicy.Enforcer | ||
// interfaces. | ||
// | ||
// During validation, the X-Auth-Token header on the request is not inspected | ||
// at all. Instead, auth success is always assumed and a token is built from | ||
// the Auth parameters provided during New(), using the mock itself as Enforcer. | ||
// | ||
// During enforcement, all accesses are allowed by default. More restrictive | ||
// policies can be configured with Forbid() and Allow(). | ||
type Validator[E gopherpolicy.Enforcer] struct { | ||
Enforcer E | ||
Auth map[string]string | ||
} | ||
|
||
// NewValidator initializes a new Validator. The provided auth variables will | ||
// be mirrored into all gopherpolicy.Token instances returned by this Validator. | ||
func NewValidator[E gopherpolicy.Enforcer](enforcer E, auth map[string]string) *Validator[E] { | ||
return &Validator[E]{enforcer, auth} | ||
} | ||
|
||
// CheckToken implements the gopherpolicy.Validator interface. | ||
func (v *Validator[E]) CheckToken(r *http.Request) *gopherpolicy.Token { | ||
return &gopherpolicy.Token{ | ||
Enforcer: v.Enforcer, | ||
Context: policy.Context{ | ||
Auth: v.Auth, | ||
Request: map[string]string{}, | ||
}, | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
/****************************************************************************** | ||
* | ||
* Copyright 2023 SAP SE | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
* | ||
******************************************************************************/ | ||
|
||
package mock | ||
|
||
import ( | ||
"net/http" | ||
"testing" | ||
|
||
"github.com/sapcc/go-bits/assert" | ||
) | ||
|
||
func TestValidator(t *testing.T) { | ||
v := NewValidator(NewEnforcer(), nil) | ||
|
||
//setup a simple HTTP handler that just outputs status 204, 401 or 403 depending on auth result | ||
h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | ||
if !v.CheckToken(r).Require(w, "api:access") { | ||
return | ||
} | ||
w.WriteHeader(http.StatusNoContent) | ||
}) | ||
|
||
//the default behavior is permissive | ||
assert.HTTPRequest{ | ||
Method: http.MethodGet, | ||
Path: "/", | ||
ExpectStatus: http.StatusNoContent, | ||
}.Check(t, h) | ||
|
||
//Forbid() on an unrelated rule does not affect the result | ||
v.Enforcer.Forbid("api:details") | ||
assert.HTTPRequest{ | ||
Method: http.MethodGet, | ||
Path: "/", | ||
ExpectStatus: http.StatusNoContent, | ||
}.Check(t, h) | ||
|
||
//Forbid() on the relevant rule causes 403 error | ||
v.Enforcer.Forbid("api:access") | ||
assert.HTTPRequest{ | ||
Method: http.MethodGet, | ||
Path: "/", | ||
ExpectStatus: http.StatusForbidden, | ||
}.Check(t, h) | ||
|
||
//explicit Allow() reverses an earlier Forbid | ||
v.Enforcer.Allow("api:access") | ||
assert.HTTPRequest{ | ||
Method: http.MethodGet, | ||
Path: "/", | ||
ExpectStatus: http.StatusNoContent, | ||
}.Check(t, h) | ||
} |