Skip to content

Commit

Permalink
[FAB-8609] User refactoring
Browse files Browse the repository at this point in the history
User interface has been moved to pkg/core.

Also, both User interface and implementation are cleaned up:
- Setters and non-utilized methods are removed from the interface
- Implementation is hidden behind IdentityManager. Only
  IdentityManager can create a user instance.

A simple usage pattern of a User is:
- Register
- Enroll
- Obtain a user by calling IdentityManager.GetUser(username)

Change-Id: Ib50ff33df039310f9c2b314ba862e085fb427b97
Signed-off-by: Aleksandar Likic <aleksandar.likic@securekey.com>
  • Loading branch information
Aleksandar Likic committed Mar 2, 2018
1 parent 4845e91 commit 77daffe
Show file tree
Hide file tree
Showing 37 changed files with 477 additions and 1,116 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -82,14 +82,14 @@ func TestBadClient(t *testing.T) {
// Non-existent user
ccPolicyProvider, err := newCCPolicyProvider(sdk, "mychannel", "Invalid", "Org1")
_, err = ccPolicyProvider.GetChaincodePolicy("mychannel")
if !strings.Contains(err.Error(), "Unable to load identity") {
if !strings.Contains(err.Error(), "user not found") {
t.Fatalf("Should have failed for invalid user name: %v", err)
}

// Invalid org
ccPolicyProvider, err = newCCPolicyProvider(sdk, "mychannel", "User1", "Invalid")
_, err = ccPolicyProvider.GetChaincodePolicy("mychannel")
if !strings.Contains(err.Error(), "Unable to load identity") {
if !strings.Contains(err.Error(), "invalid org name") {
t.Fatalf("Should have failed for invalid org name")
}
}
1 change: 1 addition & 0 deletions pkg/context/api/identitymgr.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ type SigningIdentity struct {
// IdentityManager provides management of identities in a Fabric network
type IdentityManager interface {
GetSigningIdentity(name string) (*SigningIdentity, error)
GetUser(name string) (User, error)
Enroll(enrollmentID string, enrollmentSecret string) error
Reenroll(user User) error
Register(request *RegistrationRequest) (string, error)
Expand Down
13 changes: 13 additions & 0 deletions pkg/context/api/mocks/mockidmgr.gen.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 1 addition & 14 deletions pkg/context/api/userstore.go → pkg/context/api/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,21 +32,8 @@ var (
// have access to the Peer identity’s private key.
type User interface {
MspID() string
Name() string
Identity() ([]byte, error)
PrivateKey() core.Key
Name() string
EnrollmentCertificate() []byte
Roles() []string
}

// UserKey is a lookup key in UserStore
type UserKey struct {
MspID string
Name string
}

// UserStore is responsible for User persistence
type UserStore interface {
Store(User) error
Load(UserKey) (User, error)
}
86 changes: 86 additions & 0 deletions pkg/core/identitymgr/certfileuserstore.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
/*
Copyright SecureKey Technologies Inc. All Rights Reserved.
SPDX-License-Identifier: Apache-2.0
*/

package identitymgr

import (
"github.com/hyperledger/fabric-sdk-go/pkg/fab/keyvaluestore"

"github.com/hyperledger/fabric-sdk-go/pkg/context/api"
"github.com/pkg/errors"
)

// CertFileUserStore stores each user in a separate file.
// Only user's enrollment cert is stored, in pem format.
// File naming is <user>@<org>-cert.pem
type CertFileUserStore struct {
store api.KVStore
}

func userIdentifierFromUser(user UserData) UserIdentifier {
return UserIdentifier{
MspID: user.MspID,
Name: user.Name,
}
}

func storeKeyFromUserIdentifier(key UserIdentifier) string {
return key.Name + "@" + key.MspID + "-cert.pem"
}

// NewCertFileUserStore1 creates a new instance of CertFileUserStore
func NewCertFileUserStore1(store api.KVStore) (*CertFileUserStore, error) {
return &CertFileUserStore{
store: store,
}, nil
}

// NewCertFileUserStore creates a new instance of CertFileUserStore
func NewCertFileUserStore(path string) (*CertFileUserStore, error) {
if path == "" {
return nil, errors.New("path is empty")
}
store, err := keyvaluestore.New(&keyvaluestore.FileKeyValueStoreOptions{
Path: path,
})
if err != nil {
return nil, errors.WithMessage(err, "user store creation failed")
}
return NewCertFileUserStore1(store)
}

// Load returns the User stored in the store for a key.
func (s *CertFileUserStore) Load(key UserIdentifier) (UserData, error) {
var userData UserData
cert, err := s.store.Load(storeKeyFromUserIdentifier(key))
if err != nil {
if err == api.ErrNotFound {
return userData, api.ErrUserNotFound
}
return userData, err
}
certBytes, ok := cert.([]byte)
if !ok {
return userData, errors.New("user is not of proper type")
}
userData = UserData{
MspID: key.MspID,
Name: key.Name,
EnrollmentCertificate: certBytes,
}
return userData, nil
}

// Store stores a User into store
func (s *CertFileUserStore) Store(user UserData) error {
key := storeKeyFromUserIdentifier(UserIdentifier{MspID: user.MspID, Name: user.Name})
return s.store.Store(key, user.EnrollmentCertificate)
}

// Delete deletes a User from store
func (s *CertFileUserStore) Delete(key UserIdentifier) error {
return s.store.Delete(storeKeyFromUserIdentifier(key))
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ Copyright SecureKey Technologies Inc. All Rights Reserved.
SPDX-License-Identifier: Apache-2.0
*/

package identity
package identitymgr

import (
"bytes"
Expand All @@ -14,25 +14,13 @@ import (
"path"
"testing"

"github.com/golang/mock/gomock"

fabricCaUtil "github.com/hyperledger/fabric-sdk-go/internal/github.com/hyperledger/fabric-ca/util"
"github.com/hyperledger/fabric-sdk-go/pkg/context/api"
"github.com/hyperledger/fabric-sdk-go/pkg/context/api/core"
"github.com/hyperledger/fabric-sdk-go/pkg/context/api/core/mocks"
"github.com/hyperledger/fabric-sdk-go/pkg/core/cryptosuite/bccsp/sw"
"github.com/pkg/errors"
)

var storePathRoot = "/tmp/testcertfileuserstore"
var storePath = path.Join(storePathRoot, "-certs")

var testPrivKey1 = `-----BEGIN PRIVATE KEY-----
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgp4qKKB0WCEfx7XiB
5Ul+GpjM1P5rqc6RhjD5OkTgl5OhRANCAATyFT0voXX7cA4PPtNstWleaTpwjvbS
J3+tMGTG67f+TdCfDxWYMpQYxLlE8VkbEzKWDwCYvDZRMKCQfv2ErNvb
-----END PRIVATE KEY-----`

var testCert1 = `-----BEGIN CERTIFICATE-----
MIICGTCCAcCgAwIBAgIRALR/1GXtEud5GQL2CZykkOkwCgYIKoZIzj0EAwIwczEL
MAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFjAUBgNVBAcTDVNhbiBG
Expand All @@ -48,12 +36,6 @@ AiaiI2BjxnL3/TetJ8iFJYZyWvK//an13WV/AiARBJd/pI5A7KZgQxJhXmmR8bie
XdsmTcdRvJ3TS/6HCA==
-----END CERTIFICATE-----`

var testPrivKey2 = `-----BEGIN PRIVATE KEY-----
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQg5Ahcehypz6IpAYy6
DtIf5zZsRjP4PtsmDhLbBJsXmD6hRANCAAR+YRAn8dFpDQDyvDA7JKPl5PoZenj3
m1KOnMry/mOZcnXnTIh2ASV4ss8VluzBcyHGAv7BCmxXxDkjcV9eybv8
-----END PRIVATE KEY-----`

var testCert2 = `-----BEGIN CERTIFICATE-----
MIICGjCCAcCgAwIBAgIRAIQkbh9nsGnLmDalAVlj8sUwCgYIKoZIzj0EAwIwczEL
MAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFjAUBgNVBAcTDVNhbiBG
Expand All @@ -69,95 +51,62 @@ NrfToiPzJpEFPGF+/8CpzOkl91oz+XJsvdgf5wIgI/e8mpvpplUQbU52+LejA36D
CsbWERvZPjR/GFEDEvc=
-----END CERTIFICATE-----`

func crypto(t *testing.T) core.CryptoSuite {
mockCtrl := gomock.NewController(t)
defer mockCtrl.Finish()

mockConfig := mock_core.NewMockConfig(mockCtrl)
mockConfig.EXPECT().SecurityProvider().Return("SW")
mockConfig.EXPECT().SecurityAlgorithm().Return("SHA2")
mockConfig.EXPECT().SecurityLevel().Return(256)
mockConfig.EXPECT().KeyStorePath().Return(path.Join(storePathRoot, "-keys"))
mockConfig.EXPECT().Ephemeral().Return(false)

//Get cryptosuite using config
c, err := sw.GetSuiteByConfig(mockConfig)
if err != nil {
t.Fatalf("Not supposed to get error, but got: %v", err)
}
return c
}
func TestStore(t *testing.T) {

cleanup(t, storePathRoot)
defer cleanup(t, storePathRoot)

crypto := crypto(t)

_, err := fabricCaUtil.ImportBCCSPKeyFromPEMBytes([]byte(testPrivKey1), crypto, false)
if err != nil {
t.Fatalf("ImportBCCSPKeyFromPEMBytes failed [%s]", err)
}

_, err = fabricCaUtil.ImportBCCSPKeyFromPEMBytes([]byte(testPrivKey2), crypto, false)
if err != nil {
t.Fatalf("ImportBCCSPKeyFromPEMBytes failed [%s]", err)
}
cleanupTestPath(t, storePathRoot)
defer cleanupTestPath(t, storePathRoot)

store, err := NewCertFileUserStore(storePath, crypto)
store, err := NewCertFileUserStore(storePath)
if err != nil {
t.Fatalf("NewFileKeyValueStore failed [%s]", err)
}
cleanup(t, storePath)
err = store.Store(nil)
if err == nil {
t.Fatal("Store(nil) should throw error")
}
cleanupTestPath(t, storePath)

user1 := &User{
mspID: "Org1",
name: "user1",
enrollmentCertificate: []byte(testCert1),
user1 := UserData{
MspID: "Org1",
Name: "user1",
EnrollmentCertificate: []byte(testCert1),
}
user2 := &User{
mspID: "Org2",
name: "user2",
enrollmentCertificate: []byte(testCert2),
user2 := UserData{
MspID: "Org2",
Name: "user2",
EnrollmentCertificate: []byte(testCert2),
}

if err := store.Store(user1); err != nil {
t.Fatalf("Store %s failed [%s]", user1.Name(), err)
t.Fatalf("Store %s failed [%s]", user1.Name, err)
}
if err := store.Store(user2); err != nil {
t.Fatalf("Store %s failed [%s]", user2.Name(), err)
t.Fatalf("Store %s failed [%s]", user2.Name, err)
}

// Check key1, value1
if err := checkStoreValue(store, user1, user1.EnrollmentCertificate()); err != nil {
t.Fatalf("checkStoreValue %s failed [%s]", user1.Name(), err)
if err := checkStoreValue(store, user1, user1.EnrollmentCertificate); err != nil {
t.Fatalf("checkStoreValue %s failed [%s]", user1.Name, err)
}
if err := store.Delete(user1); err != nil {
t.Fatalf("Delete %s failed [%s]", user1.Name(), err)
if err := store.Delete(userIdentifier(user1)); err != nil {
t.Fatalf("Delete %s failed [%s]", user1.Name, err)
}
if err := checkStoreValue(store, user2, user2.EnrollmentCertificate()); err != nil {
t.Fatalf("checkStoreValue %s failed [%s]", user2.Name(), err)
if err := checkStoreValue(store, user2, user2.EnrollmentCertificate); err != nil {
t.Fatalf("checkStoreValue %s failed [%s]", user2.Name, err)
}
if err := checkStoreValue(store, user1, nil); err != api.ErrUserNotFound {
t.Fatalf("checkStoreValue %s failed, expected api.ErrUserNotFound, got: %v", user1.Name(), err)
t.Fatalf("checkStoreValue %s failed, expected api.ErrUserNotFound, got: %v", user1.Name, err)
}

// Check ke2, value2
if err := checkStoreValue(store, user2, user2.EnrollmentCertificate()); err != nil {
t.Fatalf("checkStoreValue %s failed [%s]", user2.Name(), err)
if err := checkStoreValue(store, user2, user2.EnrollmentCertificate); err != nil {
t.Fatalf("checkStoreValue %s failed [%s]", user2.Name, err)
}
if err := store.Delete(user2); err != nil {
t.Fatalf("Delete %s failed [%s]", user2.Name(), err)
if err := store.Delete(userIdentifier(user2)); err != nil {
t.Fatalf("Delete %s failed [%s]", user2.Name, err)
}
if err := checkStoreValue(store, user2, nil); err != api.ErrUserNotFound {
t.Fatalf("checkStoreValue %s failed, expected api.ErrUserNotFound, got: %v", user2.Name(), err)
t.Fatalf("checkStoreValue %s failed, expected api.ErrUserNotFound, got: %v", user2.Name, err)
}

// Check non-existing key
nonExistingKey := api.UserKey{
nonExistingKey := UserIdentifier{
MspID: "Orgx",
Name: "userx",
}
Expand All @@ -169,34 +118,20 @@ func TestStore(t *testing.T) {

func TestCreateNewStore(t *testing.T) {

crypto := crypto(t)

_, err := NewCertFileUserStore("", crypto)
_, err := NewCertFileUserStore("")
if err == nil {
t.Fatal("should return error for empty path")
}

_, err = NewCertFileUserStore("mypath", nil)
if err == nil {
t.Fatal("should return error for nil cryptosuite")
}
}

func cleanup(t *testing.T, storePath string) {
err := os.RemoveAll(storePath)
if err != nil {
t.Fatalf("Cleaning up directory '%s' failed: %v", storePath, err)
}
}

func checkStoreValue(store *CertFileUserStore, user api.User, expected []byte) error {
userKey := userKeyFromUser(user)
storeKey := storeKeyFromUserKey(userKeyFromUser(user))
v, err := store.Load(userKey)
func checkStoreValue(store *CertFileUserStore, user UserData, expected []byte) error {
userIdentifier := userIdentifier(user)
storeKey := storeKeyFromUserIdentifier(userIdentifier)
v, err := store.Load(userIdentifier)
if err != nil {
return err
}
if err = compare(v.EnrollmentCertificate(), expected); err != nil {
if err = compare(v.EnrollmentCertificate, expected); err != nil {
return err
}
file := path.Join(storePath, storeKey)
Expand Down
Loading

0 comments on commit 77daffe

Please sign in to comment.