Skip to content

Commit e752be3

Browse files
committed
[FAB-8990] deliver uses generated mocks and ginkgo
Refactor deliver to use generated mocks and demonstrate unit testing via ginkgo. Change-Id: Ib00fec78bc0be9b22f977748185b8c4879d77f0c Signed-off-by: Matthew Sykes <sykesmat@us.ibm.com> Signed-off-by: Will Lahti <wtlahti@us.ibm.com>
1 parent 36785ff commit e752be3

21 files changed

+2106
-1243
lines changed

common/deliver/acl.go

+26-19
Original file line numberDiff line numberDiff line change
@@ -13,47 +13,55 @@ import (
1313
"github.com/pkg/errors"
1414
)
1515

16-
type expiresAtFunc func(identityBytes []byte) time.Time
16+
// ExpiresAtFunc is used to extract the time at which an identity expires.
17+
type ExpiresAtFunc func(identityBytes []byte) time.Time
1718

18-
// acSupport provides the backing resources needed to support access control validation
19-
type acSupport interface {
20-
// Sequence returns the current config sequence number, can be used to detect config changes
19+
// ConfigSequencer provides the sequence number of the current config block.
20+
type ConfigSequencer interface {
2121
Sequence() uint64
2222
}
2323

24-
func newSessionAC(sup acSupport, env *common.Envelope, poliyChecker PolicyChecker, channel string, expiresAt expiresAtFunc) (*sessionAC, error) {
24+
// NewSessionAC creates an instance of SessionAccessControl. This constructor will
25+
// return an error if a signature header cannot be extracted from the envelope.
26+
func NewSessionAC(chain ConfigSequencer, env *common.Envelope, policyChecker PolicyChecker, channelID string, expiresAt ExpiresAtFunc) (*SessionAccessControl, error) {
2527
signedData, err := env.AsSignedData()
2628
if err != nil {
2729
return nil, err
2830
}
2931

30-
return &sessionAC{
31-
env: env,
32-
channel: channel,
33-
acSupport: sup,
34-
checkPolicy: poliyChecker,
32+
return &SessionAccessControl{
33+
envelope: env,
34+
channelID: channelID,
35+
sequencer: chain,
36+
policyChecker: policyChecker,
3537
sessionEndTime: expiresAt(signedData[0].Identity),
3638
}, nil
3739
}
3840

39-
type sessionAC struct {
40-
acSupport
41-
checkPolicy PolicyChecker
42-
channel string
43-
env *common.Envelope
41+
// SessionAccessControl holds access control related data for a common Envelope
42+
// that is used to determine if a request is allowed for the identity
43+
// associated with the request envelope.
44+
type SessionAccessControl struct {
45+
sequencer ConfigSequencer
46+
policyChecker PolicyChecker
47+
channelID string
48+
envelope *common.Envelope
4449
lastConfigSequence uint64
4550
sessionEndTime time.Time
4651
usedAtLeastOnce bool
4752
}
4853

49-
func (ac *sessionAC) evaluate() error {
54+
// Evaluate uses the PolicyChecker to determine if a request should be allowed.
55+
// The decision is cached until the identity expires or the chain configuration
56+
// changes.
57+
func (ac *SessionAccessControl) Evaluate() error {
5058
if !ac.sessionEndTime.IsZero() && time.Now().After(ac.sessionEndTime) {
5159
return errors.Errorf("client identity expired %v before", time.Since(ac.sessionEndTime))
5260
}
5361

5462
policyCheckNeeded := !ac.usedAtLeastOnce
5563

56-
if currentConfigSequence := ac.Sequence(); currentConfigSequence > ac.lastConfigSequence {
64+
if currentConfigSequence := ac.sequencer.Sequence(); currentConfigSequence > ac.lastConfigSequence {
5765
ac.lastConfigSequence = currentConfigSequence
5866
policyCheckNeeded = true
5967
}
@@ -63,6 +71,5 @@ func (ac *sessionAC) evaluate() error {
6371
}
6472

6573
ac.usedAtLeastOnce = true
66-
return ac.checkPolicy(ac.env, ac.channel)
67-
74+
return ac.policyChecker.CheckPolicy(ac.envelope, ac.channelID)
6875
}

common/deliver/acl_test.go

+111-142
Original file line numberDiff line numberDiff line change
@@ -4,164 +4,133 @@ Copyright IBM Corp. All Rights Reserved.
44
SPDX-License-Identifier: Apache-2.0
55
*/
66

7-
package deliver
7+
package deliver_test
88

99
import (
10-
"testing"
1110
"time"
1211

13-
"github.com/hyperledger/fabric/protos/common"
12+
"github.com/hyperledger/fabric/common/deliver"
13+
"github.com/hyperledger/fabric/common/deliver/mock"
14+
cb "github.com/hyperledger/fabric/protos/common"
1415
"github.com/hyperledger/fabric/protos/utils"
16+
. "github.com/onsi/ginkgo"
17+
. "github.com/onsi/gomega"
1518
"github.com/pkg/errors"
16-
"github.com/stretchr/testify/assert"
17-
"github.com/stretchr/testify/mock"
1819
)
1920

20-
type mockACSupport struct {
21-
mock.Mock
22-
}
21+
var _ = Describe("SessionAccessControl", func() {
22+
var (
23+
fakeChain *mock.Chain
24+
envelope *cb.Envelope
25+
fakePolicyChecker *mock.PolicyChecker
26+
expiresAt deliver.ExpiresAtFunc
27+
)
28+
29+
BeforeEach(func() {
30+
envelope = &cb.Envelope{
31+
Payload: utils.MarshalOrPanic(&cb.Payload{
32+
Header: &cb.Header{},
33+
}),
34+
}
35+
36+
fakeChain = &mock.Chain{}
37+
fakePolicyChecker = &mock.PolicyChecker{}
38+
expiresAt = func([]byte) time.Time { return time.Time{} }
39+
})
2340

24-
func (s *mockACSupport) ExpiresAt(identityBytes []byte) time.Time {
25-
return s.Called().Get(0).(time.Time)
26-
}
41+
It("evaluates the policy", func() {
42+
sac, err := deliver.NewSessionAC(fakeChain, envelope, fakePolicyChecker, "chain-id", expiresAt)
43+
Expect(err).NotTo(HaveOccurred())
2744

28-
func (s *mockACSupport) Sequence() uint64 {
29-
return s.Called().Get(0).(uint64)
30-
}
45+
err = sac.Evaluate()
46+
Expect(err).NotTo(HaveOccurred())
3147

32-
func createEnvelope() *common.Envelope {
33-
chHdr := utils.MakeChannelHeader(common.HeaderType_DELIVER_SEEK_INFO, 0, "mychannel", 0)
34-
siHdr := utils.MakeSignatureHeader(nil, nil)
35-
paylBytes := utils.MarshalOrPanic(&common.Payload{
36-
Header: utils.MakePayloadHeader(chHdr, siHdr),
48+
Expect(fakePolicyChecker.CheckPolicyCallCount()).To(Equal(1))
49+
env, cid := fakePolicyChecker.CheckPolicyArgsForCall(0)
50+
Expect(env).To(Equal(envelope))
51+
Expect(cid).To(Equal("chain-id"))
3752
})
3853

39-
return &common.Envelope{Payload: paylBytes}
40-
}
54+
Context("when policy evaluation returns an error", func() {
55+
BeforeEach(func() {
56+
fakePolicyChecker.CheckPolicyReturns(errors.New("no-access-for-you"))
57+
})
58+
59+
It("returns the evaluation error", func() {
60+
sac, err := deliver.NewSessionAC(fakeChain, envelope, fakePolicyChecker, "chain-id", expiresAt)
61+
Expect(err).NotTo(HaveOccurred())
4162

42-
type oneTimeInvoke struct {
43-
f func(*common.Envelope, string) error
44-
invoked bool
45-
}
63+
err = sac.Evaluate()
64+
Expect(err).To(MatchError("no-access-for-you"))
65+
})
66+
})
67+
68+
It("caches positive policy evaluation", func() {
69+
sac, err := deliver.NewSessionAC(fakeChain, envelope, fakePolicyChecker, "chain-id", expiresAt)
70+
Expect(err).NotTo(HaveOccurred())
4671

47-
func (oti *oneTimeInvoke) invokeOnce() func(*common.Envelope, string) error {
48-
return func(env *common.Envelope, s string) error {
49-
if oti.invoked {
50-
panic("already invoked!")
72+
for i := 0; i < 5; i++ {
73+
err = sac.Evaluate()
74+
Expect(err).NotTo(HaveOccurred())
5175
}
52-
oti.invoked = true
53-
return oti.f(env, s)
54-
}
55-
}
56-
57-
func oneTimeFunction(f func(*common.Envelope, string) error) func(*common.Envelope, string) error {
58-
oti := &oneTimeInvoke{f: f}
59-
return oti.invokeOnce()
60-
}
61-
62-
func TestOneTimeFunction(t *testing.T) {
63-
acceptPolicyChecker := func(envelope *common.Envelope, channelID string) error {
64-
return nil
65-
}
66-
f := oneTimeFunction(acceptPolicyChecker)
67-
// First time no panic
68-
assert.NotPanics(t, func() {
69-
f(nil, "")
76+
Expect(fakePolicyChecker.CheckPolicyCallCount()).To(Equal(1))
7077
})
7178

72-
// Second time we panic
73-
assert.Panics(t, func() {
74-
f(nil, "")
79+
Context("when the config sequence changes", func() {
80+
BeforeEach(func() {
81+
fakePolicyChecker.CheckPolicyReturnsOnCall(2, errors.New("access-now-denied"))
82+
})
83+
84+
It("re-evaluates the policy", func() {
85+
sac, err := deliver.NewSessionAC(fakeChain, envelope, fakePolicyChecker, "chain-id", expiresAt)
86+
Expect(err).NotTo(HaveOccurred())
87+
88+
Expect(sac.Evaluate()).To(Succeed())
89+
Expect(fakePolicyChecker.CheckPolicyCallCount()).To(Equal(1))
90+
Expect(sac.Evaluate()).To(Succeed())
91+
Expect(fakePolicyChecker.CheckPolicyCallCount()).To(Equal(1))
92+
93+
fakeChain.SequenceReturns(2)
94+
Expect(sac.Evaluate()).To(Succeed())
95+
Expect(fakePolicyChecker.CheckPolicyCallCount()).To(Equal(2))
96+
Expect(sac.Evaluate()).To(Succeed())
97+
Expect(fakePolicyChecker.CheckPolicyCallCount()).To(Equal(2))
98+
99+
fakeChain.SequenceReturns(3)
100+
Expect(sac.Evaluate()).To(MatchError("access-now-denied"))
101+
Expect(fakePolicyChecker.CheckPolicyCallCount()).To(Equal(3))
102+
})
75103
})
76-
}
77-
78-
func TestAC(t *testing.T) {
79-
acceptPolicyChecker := func(envelope *common.Envelope, channelID string) error {
80-
return nil
81-
}
82-
83-
denyPolicyChecker := func(envelope *common.Envelope, channelID string) error {
84-
return errors.New("forbidden")
85-
}
86-
87-
sup := &mockACSupport{}
88-
// Scenario I: create empty header
89-
ac, err := newSessionAC(sup, &common.Envelope{}, nil, "mychannel", sup.ExpiresAt)
90-
assert.Nil(t, ac)
91-
assert.Contains(t, err.Error(), "Missing Header")
92-
93-
// Scenario II: Identity has expired.
94-
sup = &mockACSupport{}
95-
sup.On("ExpiresAt").Return(time.Now().Add(-1 * time.Second)).Once()
96-
ac, err = newSessionAC(sup, createEnvelope(), oneTimeFunction(acceptPolicyChecker), "mychannel", sup.ExpiresAt)
97-
assert.NotNil(t, ac)
98-
assert.NoError(t, err)
99-
err = ac.evaluate()
100-
assert.Contains(t, err.Error(), "expired")
101-
102-
// Scenario III: Identity hasn't expired, but is forbidden
103-
sup = &mockACSupport{}
104-
sup.On("ExpiresAt").Return(time.Now().Add(time.Second)).Once()
105-
sup.On("Sequence").Return(uint64(0)).Once()
106-
ac, err = newSessionAC(sup, createEnvelope(), oneTimeFunction(denyPolicyChecker), "mychannel", sup.ExpiresAt)
107-
assert.NoError(t, err)
108-
err = ac.evaluate()
109-
assert.Contains(t, err.Error(), "forbidden")
110-
111-
// Scenario IV: Identity hasn't expired, and is allowed
112-
// We actually check 2 sub-cases, the first one is if the identity can expire,
113-
// and the second one is if the identity can't expire (i.e an idemix identity currently can't expire)
114-
for _, expirationTime := range []time.Time{time.Now().Add(time.Second), {}} {
115-
sup = &mockACSupport{}
116-
sup.On("ExpiresAt").Return(expirationTime).Once()
117-
sup.On("Sequence").Return(uint64(0)).Once()
118-
ac, err = newSessionAC(sup, createEnvelope(), oneTimeFunction(acceptPolicyChecker), "mychannel", sup.ExpiresAt)
119-
assert.NoError(t, err)
120-
err = ac.evaluate()
121-
assert.NoError(t, err)
122-
// Execute again. We should not evaluate the policy again.
123-
// If we do, the test fails with panic because the function can be invoked only once
124-
sup.On("Sequence").Return(uint64(0)).Once()
125-
err = ac.evaluate()
126-
assert.NoError(t, err)
127-
}
128-
129-
// Scenario V: Identity hasn't expired, and is allowed at first, but afterwards there
130-
// is a config change and afterwards it isn't allowed
131-
sup = &mockACSupport{}
132-
sup.On("ExpiresAt").Return(time.Now().Add(time.Second)).Once()
133-
sup.On("Sequence").Return(uint64(0)).Once()
134-
sup.On("Sequence").Return(uint64(1)).Once()
135-
136-
firstInvoke := true
137-
policyChecker := func(envelope *common.Envelope, channelID string) error {
138-
if firstInvoke {
139-
firstInvoke = false
140-
return nil
141-
}
142-
return errors.New("forbidden")
143-
}
144-
145-
ac, err = newSessionAC(sup, createEnvelope(), policyChecker, "mychannel", sup.ExpiresAt)
146-
assert.NoError(t, err)
147-
err = ac.evaluate() // first time
148-
assert.NoError(t, err)
149-
err = ac.evaluate() // second time
150-
assert.Contains(t, err.Error(), "forbidden")
151-
152-
// Scenario VI: Identity hasn't expired at first, but expires at a later time,
153-
// and then it shouldn't be allowed to be serviced
154-
sup = &mockACSupport{}
155-
sup.On("ExpiresAt").Return(time.Now().Add(time.Millisecond * 500)).Once()
156-
sup.On("Sequence").Return(uint64(0)).Times(3)
157-
ac, err = newSessionAC(sup, createEnvelope(), oneTimeFunction(acceptPolicyChecker), "mychannel", sup.ExpiresAt)
158-
assert.NoError(t, err)
159-
err = ac.evaluate()
160-
assert.NoError(t, err)
161-
err = ac.evaluate()
162-
assert.NoError(t, err)
163-
time.Sleep(time.Second)
164-
err = ac.evaluate()
165-
assert.Error(t, err)
166-
assert.Contains(t, err.Error(), "expired")
167-
}
104+
105+
Context("when an identity expires", func() {
106+
BeforeEach(func() {
107+
expiresAt = func([]byte) time.Time {
108+
return time.Now().Add(250 * time.Millisecond)
109+
}
110+
})
111+
112+
It("returns an identity expired error", func() {
113+
sac, err := deliver.NewSessionAC(fakeChain, envelope, fakePolicyChecker, "chain-id", expiresAt)
114+
Expect(err).NotTo(HaveOccurred())
115+
116+
err = sac.Evaluate()
117+
Expect(err).NotTo(HaveOccurred())
118+
119+
Eventually(sac.Evaluate).Should(MatchError(ContainSubstring("client identity expired")))
120+
})
121+
})
122+
123+
Context("when the envelope cannot be represented as signed data", func() {
124+
BeforeEach(func() {
125+
envelope = &cb.Envelope{}
126+
})
127+
128+
It("returns an error", func() {
129+
_, expectedError := envelope.AsSignedData()
130+
Expect(expectedError).To(HaveOccurred())
131+
132+
_, err := deliver.NewSessionAC(fakeChain, envelope, fakePolicyChecker, "chain-id", expiresAt)
133+
Expect(err).To(Equal(expectedError))
134+
})
135+
})
136+
})

0 commit comments

Comments
 (0)