Skip to content

Commit

Permalink
Add support for dynamically registering a user with attributes
Browse files Browse the repository at this point in the history
Signed-off-by: Keith Smith <bksmith@us.ibm.com>
Change-Id: I2d8883fbc19d99bb80815ec764d115af06e1be96
  • Loading branch information
Keith Smith committed Sep 7, 2016
1 parent af6d3e8 commit df741bc
Show file tree
Hide file tree
Showing 22 changed files with 508 additions and 87 deletions.
2 changes: 1 addition & 1 deletion core/chaincode/exectransaction_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ func initMemSrvc() (net.Listener, error) {
ca.CacheConfiguration() // Cache configuration

aca := ca.NewACA()
eca := ca.NewECA()
eca := ca.NewECA(aca)
tca := ca.NewTCA(eca)
tlsca := ca.NewTLSCA(eca)

Expand Down
2 changes: 1 addition & 1 deletion core/crypto/crypto_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1543,7 +1543,7 @@ func setup() {
func initPKI() {
ca.CacheConfiguration() // Need cache the configuration first
aca = ca.NewACA()
eca = ca.NewECA()
eca = ca.NewECA(aca)
tca = ca.NewTCA(eca)
tlsca = ca.NewTLSCA(eca)
}
Expand Down
7 changes: 6 additions & 1 deletion docs/Setup/NodeSDK-setup.md
Original file line number Diff line number Diff line change
Expand Up @@ -122,11 +122,16 @@ function handleUserRequest(userName, chaincodeID, fcn, args) {
// If this user has already been registered and/or enrolled, this will
// still succeed because the state is kept in the KeyValStore
// (i.e. in '/tmp/keyValStore' in this sample).
// The attributes field is optional but can be used for role-based access control.
// See fabric/sdk/node/test/unit/asset-mgmt-with-dynamic-roles.js as an example.
var registrationRequest = {
enrollmentID: userName,
// Customize account & affiliation
account: "bank_a",
affiliation: "00001"
affiliation: "00001",
attributes: [
{ name: "bankAccountId", value: "12345-67890" }
]
};
chain.registerAndEnroll( registrationRequest, function(err, user) {
if (err) return console.log("ERROR: %s",err);
Expand Down
2 changes: 1 addition & 1 deletion examples/chaincode/go/asset_management/asset_management.go
Original file line number Diff line number Diff line change
Expand Up @@ -264,7 +264,7 @@ func (t *AssetManagementChaincode) Query(stub shim.ChaincodeStubInterface, funct
myLogger.Debugf("Query [%s]", function)

if function != "query" {
return nil, errors.New("Invalid query function name. Expecting \"query\"")
return nil, errors.New("Invalid query function name. Expecting 'query' but found '" + function + "'")
}

var err error
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -531,7 +531,7 @@ func initMembershipSrvc() {
//ca.LogInit(ioutil.Discard, os.Stdout, os.Stdout, os.Stderr, os.Stdout)
ca.CacheConfiguration() // Cache configuration
aca = ca.NewACA()
eca = ca.NewECA()
eca = ca.NewECA(aca)
tca = ca.NewTCA(eca)
tlsca = ca.NewTLSCA(eca)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ func (t *AssetManagementChaincode) assign(stub shim.ChaincodeStubInterface, args

callerRole, err := stub.ReadCertAttribute("role")
if err != nil {
fmt.Printf("Error reading attribute [%v] \n", err)
fmt.Printf("Error reading attribute 'role' [%v] \n", err)
return nil, fmt.Errorf("Failed fetching caller role. Error was [%v]", err)
}

Expand Down Expand Up @@ -235,7 +235,7 @@ func (t *AssetManagementChaincode) Invoke(stub shim.ChaincodeStubInterface, func
// Query callback representing the query of a chaincode
func (t *AssetManagementChaincode) Query(stub shim.ChaincodeStubInterface, function string, args []string) ([]byte, error) {
if function != "query" {
return nil, errors.New("Invalid query function name. Expecting \"query\"")
return nil, errors.New("Invalid query function name. Expecting 'query' but found '" + function + "'")
}

var err error
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -366,7 +366,7 @@ func setup() {
func initMembershipSrvc() {
ca.CacheConfiguration() // Cache configuration
aca = ca.NewACA()
eca = ca.NewECA()
eca = ca.NewECA(aca)
tca = ca.NewTCA(eca)
tlsca = ca.NewTLSCA(eca)

Expand Down
2 changes: 1 addition & 1 deletion examples/chaincode/go/rbac_tcerts_no_attrs/rbac_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -441,7 +441,7 @@ func setup() {

func initMemershipServices() {
ca.CacheConfiguration() // Cache configuration
eca = ca.NewECA()
eca = ca.NewECA(nil)
tca = ca.NewTCA(eca)
tlsca = ca.NewTLSCA(eca)

Expand Down
8 changes: 6 additions & 2 deletions membersrvc/ca/aca.go
Original file line number Diff line number Diff line change
Expand Up @@ -276,7 +276,10 @@ func (aca *ACA) fetchAttributes(id, affiliation string) ([]*AttributePair, error
return attributes, nil
}

func (aca *ACA) populateAttributes(attrs []*AttributePair) error {
func (aca *ACA) PopulateAttributes(attrs []*AttributePair) error {

acaLogger.Debugf("PopulateAttributes: %+v", attrs)

mutex.Lock()
defer mutex.Unlock()

Expand All @@ -285,6 +288,7 @@ func (aca *ACA) populateAttributes(attrs []*AttributePair) error {
return dberr
}
for _, attr := range attrs {
acaLogger.Debugf("attr: %+v", attr)
if err := aca.populateAttribute(tx, attr); err != nil {
dberr = tx.Rollback()
if dberr != nil {
Expand Down Expand Up @@ -331,7 +335,7 @@ func (aca *ACA) fetchAndPopulateAttributes(id, affiliation string) error {
if err != nil {
return err
}
err = aca.populateAttributes(attrs)
err = aca.PopulateAttributes(attrs)
if err != nil {
return err
}
Expand Down
80 changes: 70 additions & 10 deletions membersrvc/ca/ca.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ import (
"sync"
"time"

gp "google/protobuf"

"github.com/hyperledger/fabric/core/crypto/primitives"
"github.com/hyperledger/fabric/flogging"
pb "github.com/hyperledger/fabric/membersrvc/protos"
Expand Down Expand Up @@ -578,11 +580,11 @@ func (ca *CA) validateAndGenerateEnrollID(id, affiliation string, role pb.Role)

// registerUser registers a new member with the CA
//
func (ca *CA) registerUser(id, affiliation string, role pb.Role, registrar, memberMetadata string, opt ...string) (string, error) {
func (ca *CA) registerUser(id, affiliation string, role pb.Role, attrs []*pb.Attribute, aca *ACA, registrar, memberMetadata string, opt ...string) (string, error) {
memberMetadata = removeQuotes(memberMetadata)
roleStr, _ := MemberRoleToString(role)
caLogger.Debugf("Received request to register user with id: %s, affiliation: %s, role: %s, registrar: %s, memberMetadata: %s\n",
id, affiliation, roleStr, registrar, memberMetadata)
caLogger.Debugf("Received request to register user with id: %s, affiliation: %s, role: %s, attrs: %+v, registrar: %s, memberMetadata: %s\n",
id, affiliation, roleStr, attrs, registrar, memberMetadata)

var enrollID, tok string
var err error
Expand All @@ -606,11 +608,21 @@ func (ca *CA) registerUser(id, affiliation string, role pb.Role, registrar, memb
if err != nil {
return "", err
}

tok, err = ca.registerUserWithEnrollID(id, enrollID, role, memberMetadata, opt...)
if err != nil {
return "", err
}
return tok, nil

if attrs != nil && aca != nil {
var pairs []*AttributePair
pairs, err = toAttributePairs(id, affiliation, attrs)
if err == nil {
err = aca.PopulateAttributes(pairs)
}
}

return tok, err
}

// registerUserWithEnrollID registers a new user and its enrollmentID, role and state
Expand Down Expand Up @@ -870,25 +882,36 @@ func (mm *MemberMetadata) canRegister(registrar string, newRole string, newMembe
caLogger.Debugf("MM.canRegister: role %s can't be registered by %s\n", newRole, registrar)
return errors.New("member " + registrar + " may not register member of type " + newRole)
}

// The registrar privileges that are being registered must not be larger than the registrar's
if newMemberMetadata == nil {
// Not requesting registrar privileges for this member, so we are OK
caLogger.Debug("MM.canRegister: not requesting registrar privileges")
return nil
}
return strsContained(newMemberMetadata.Registrar.Roles, mm.Registrar.DelegateRoles, registrar, "delegateRoles")

// Make sure this registrar is not delegating an invalid role
err := checkDelegateRoles(newMemberMetadata.Registrar.Roles, mm.Registrar.DelegateRoles, registrar)
if err != nil {
caLogger.Debug("MM.canRegister: checkDelegateRoles failure")
return err
}

// Can register OK
caLogger.Debug("MM.canRegister: OK")
return nil
}

// Return an error if all strings in 'strs1' are not contained in 'strs2'
func strsContained(strs1 []string, strs2 []string, registrar string, field string) error {
caLogger.Debugf("CA.strsContained: registrar=%s, field=%s, strs1=%+v, strs2=%+v\n", registrar, field, strs1, strs2)
func checkDelegateRoles(strs1 []string, strs2 []string, registrar string) error {
caLogger.Debugf("CA.checkDelegateRoles: registrar=%s, strs1=%+v, strs2=%+v\n", registrar, strs1, strs2)
for _, s := range strs1 {
if !strContained(s, strs2) {
caLogger.Debugf("CA.strsContained: no: %s not in %+v\n", s, strs2)
return errors.New("user " + registrar + " may not register " + field + " " + s)
caLogger.Debugf("CA.checkDelegateRoles: no: %s not in %+v\n", s, strs2)
return errors.New("user " + registrar + " may not register delegateRoles " + s)
}
}
caLogger.Debug("CA.strsContained: ok")
caLogger.Debug("CA.checkDelegateRoles: ok")
return nil
}

Expand All @@ -902,6 +925,16 @@ func strContained(str string, strs []string) bool {
return false
}

// Return true if 'str' is prefixed by any string in 'strs'; otherwise return false
func isPrefixed(str string, strs []string) bool {
for _, s := range strs {
if strings.HasPrefix(str, s) {
return true
}
}
return false
}

// convert a role to a string
func role2String(role int) string {
if role == int(pb.Role_CLIENT) {
Expand All @@ -928,3 +961,30 @@ func removeQuotes(str string) string {
caLogger.Debugf("removeQuotes: %s\n", str)
return str
}

// Convert the protobuf array of attributes to the AttributePair array format
// as required by the ACA code to populate the table
func toAttributePairs(id, affiliation string, attrs []*pb.Attribute) ([]*AttributePair, error) {
var pairs = make([]*AttributePair, 0)
for _, attr := range attrs {
vals := []string{id, affiliation, attr.Name, attr.Value, attr.NotBefore, attr.NotAfter}
pair, err := NewAttributePair(vals, nil)
if err != nil {
return nil, err
}
pairs = append(pairs, pair)
}
caLogger.Debugf("toAttributePairs: id=%s, affiliation=%s, attrs=%v, pairs=%v\n",
id, affiliation, attrs, pairs)
return pairs, nil
}

func convertTime(ts *gp.Timestamp) time.Time {
var t time.Time
if ts == nil {
t = time.Unix(0, 0).UTC()
} else {
t = time.Unix(ts.Seconds, int64(ts.Nanos)).UTC()
}
return t
}
7 changes: 4 additions & 3 deletions membersrvc/ca/eca.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ var (
//
type ECA struct {
*CA
aca *ACA
obcKey []byte
obcPriv, obcPub []byte
gRPCServer *grpc.Server
Expand All @@ -59,8 +60,8 @@ func initializeECATables(db *sql.DB) error {

// NewECA sets up a new ECA.
//
func NewECA() *ECA {
eca := &ECA{CA: NewCA("eca", initializeECATables)}
func NewECA(aca *ACA) *ECA {
eca := &ECA{CA: NewCA("eca", initializeECATables), aca: aca}
flogging.LoggingInit("eca")

{
Expand Down Expand Up @@ -152,7 +153,7 @@ func (eca *ECA) populateUsersTable() {
}
}
}
eca.registerUser(id, affiliation, pb.Role(role), registrar, memberMetadata, vals[1])
eca.registerUser(id, affiliation, pb.Role(role), nil, eca.aca, registrar, memberMetadata, vals[1])
}
}

Expand Down
4 changes: 2 additions & 2 deletions membersrvc/ca/ecaa.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ func (ecaa *ECAA) RegisterUser(ctx context.Context, in *pb.RegisterUserReq) (*pb
}
jsonStr := string(json)
ecaaLogger.Debugf("gRPC ECAA:RegisterUser: json=%s", jsonStr)
tok, err := ecaa.eca.registerUser(in.Id.Id, in.Affiliation, in.Role, registrarID, jsonStr)
tok, err := ecaa.eca.registerUser(in.Id.Id, in.Affiliation, in.Role, in.Attributes, ecaa.eca.aca, registrarID, jsonStr)

// Return the one-time password
return &pb.Token{Tok: []byte(tok)}, err
Expand Down Expand Up @@ -105,7 +105,7 @@ func (ecaa *ECAA) checkRegistrarSignature(in *pb.RegisterUserReq) error {
// Check the signature
if ecdsa.Verify(cert.PublicKey.(*ecdsa.PublicKey), hash.Sum(nil), r, s) == false {
// Signature verification failure
ecaaLogger.Debugf("ECAA.checkRegistrarSignature: failure for %s", registrar)
ecaaLogger.Debugf("ECAA.checkRegistrarSignature: failure for %s (len=%d): %+v", registrar, len(raw), in)
return errors.New("Signature verification failed.")
}

Expand Down
2 changes: 1 addition & 1 deletion membersrvc/ca/membersrvc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ func setupTestConfig() {
func initPKI() {
CacheConfiguration() // Cache configuration
aca = NewACA()
eca = NewECA()
eca = NewECA(aca)
tca = NewTCA(eca)
}

Expand Down
9 changes: 5 additions & 4 deletions membersrvc/ca/tca_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -155,16 +155,17 @@ func initTCA() (*TCA, error) {
}

CacheConfiguration() // Cache configuration
eca := NewECA()
if eca == nil {
return nil, fmt.Errorf("Could not create a new ECA")
}

aca := NewACA()
if aca == nil {
return nil, fmt.Errorf("Could not create a new ACA")
}

eca := NewECA(aca)
if eca == nil {
return nil, fmt.Errorf("Could not create a new ECA")
}

tca := NewTCA(eca)
if tca == nil {
return nil, fmt.Errorf("Could not create a new TCA")
Expand Down
2 changes: 1 addition & 1 deletion membersrvc/ca/tlsca_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ func TestTLS(t *testing.T) {

func startTLSCA(t *testing.T) {
CacheConfiguration() // Cache configuration
ecaS = NewECA()
ecaS = NewECA(nil)
tlscaS = NewTLSCA(ecaS)

var opts []grpc.ServerOption
Expand Down
Loading

0 comments on commit df741bc

Please sign in to comment.