Commit df741bc7 authored by Keith Smith's avatar Keith Smith
Browse files

Add support for dynamically registering a user with attributes



Signed-off-by: default avatarKeith Smith <bksmith@us.ibm.com>
Change-Id: I2d8883fbc19d99bb80815ec764d115af06e1be96
parent af6d3e82
......@@ -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)
......
......@@ -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)
}
......
......@@ -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);
......
......@@ -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
......
......@@ -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)
......
......@@ -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)
}
......@@ -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
......
......@@ -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)
......
......@@ -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)
......
......@@ -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()
......@@ -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 {
......@@ -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
}
......
......@@ -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"
......@@ -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
......@@ -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
......@@ -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
}
......@@ -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) {
......@@ -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
}
......@@ -48,6 +48,7 @@ var (
//
type ECA struct {
*CA
aca *ACA
obcKey []byte
obcPriv, obcPub []byte
gRPCServer *grpc.Server
......@@ -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")
{
......@@ -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])
}
}
......
......@@ -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
......@@ -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.")
}
......
......@@ -70,7 +70,7 @@ func setupTestConfig() {
func initPKI() {
CacheConfiguration() // Cache configuration
aca = NewACA()
eca = NewECA()
eca = NewECA(aca)
tca = NewTCA(eca)
}
......
......@@ -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")
......
......@@ -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
......
......@@ -19,6 +19,7 @@ It has these top-level messages:
Signature
Registrar
RegisterUserReq
Attribute
ReadUserSetReq
User
UserSet
......@@ -336,11 +337,12 @@ func (m *Registrar) GetId() *Identity {
}
type RegisterUserReq struct {
Id *Identity `protobuf:"bytes,1,opt,name=id" json:"id,omitempty"`
Role Role `protobuf:"varint,2,opt,name=role,enum=protos.Role" json:"role,omitempty"`
Affiliation string `protobuf:"bytes,4,opt,name=affiliation" json:"affiliation,omitempty"`
Registrar *Registrar `protobuf:"bytes,5,opt,name=registrar" json:"registrar,omitempty"`
Sig *Signature `protobuf:"bytes,6,opt,name=sig" json:"sig,omitempty"`
Id *Identity `protobuf:"bytes,1,opt,name=id" json:"id,omitempty"`
Role Role `protobuf:"varint,2,opt,name=role,enum=protos.Role" json:"role,omitempty"`
Attributes []*Attribute `protobuf:"bytes,3,rep,name=attributes" json:"attributes,omitempty"`
Affiliation string `protobuf:"bytes,4,opt,name=affiliation" json:"affiliation,omitempty"`
Registrar *Registrar `protobuf:"bytes,5,opt,name=registrar" json:"registrar,omitempty"`
Sig *Signature `protobuf:"bytes,6,opt,name=sig" json:"sig,omitempty"`
}
func (m *RegisterUserReq) Reset() { *m = RegisterUserReq{} }
......@@ -354,6 +356,13 @@ func (m *RegisterUserReq) GetId() *Identity {
return nil
}
func (m *RegisterUserReq) GetAttributes() []*Attribute {
if m != nil {
return m.Attributes
}
return nil
}
func (m *RegisterUserReq) GetRegistrar() *Registrar {
if m != nil {
return m.Registrar
......@@ -368,6 +377,17 @@ func (m *RegisterUserReq) GetSig() *Signature {
return nil
}
type Attribute struct {
Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"`
Value string `protobuf:"bytes,2,opt,name=value" json:"value,omitempty"`
NotBefore string `protobuf:"bytes,3,opt,name=notBefore" json:"notBefore,omitempty"`
NotAfter string `protobuf:"bytes,4,opt,name=notAfter" json:"notAfter,omitempty"`
}
func (m *Attribute) Reset() { *m = Attribute{} }
func (m *Attribute) String() string { return proto.CompactTextString(m) }
func (*Attribute) ProtoMessage() {}
type ReadUserSetReq struct {
Req *Identity `protobuf:"bytes,1,opt,name=req" json:"req,omitempty"`
Role Role `protobuf:"varint,2,opt,name=role,enum=protos.Role" json:"role,omitempty"`
......
......@@ -139,19 +139,27 @@ enum Role {
}
message Registrar {
Identity id = 1; // The identity of the registrar
repeated string roles = 2; // Roles that the registrar can register
repeated string delegateRoles = 3; // Roles that the registrar can give to another to register
Identity id = 1; // The identity of the registrar
repeated string roles = 2; // Roles that the registrar can register
repeated string delegateRoles = 3; // Roles that the registrar can give to another to register
}
message RegisterUserReq {
Identity id = 1;
Role role = 2;
string affiliation = 4; // Skipping field number 3 to maintain backward compatibility. It was used before for the primary account.
repeated Attribute attributes = 3;
string affiliation = 4;
Registrar registrar = 5;
Signature sig = 6;
}
message Attribute {
string name = 1;
string value = 2;
string notBefore = 3;
string notAfter = 4;
}
message ReadUserSetReq {
Identity req = 1;
Role role = 2; // bitmask
......
......@@ -73,7 +73,7 @@ func main() {
aca := ca.NewACA()
defer aca.Stop()
eca := ca.NewECA()
eca := ca.NewECA(aca)
defer eca.Stop()
tca := ca.NewTCA(eca)
......
......@@ -31,8 +31,16 @@ init() {
FABRIC=$GOPATH/src/github.com/hyperledger/fabric
LOGDIR=/tmp/node-sdk-unit-test
MSEXE=$FABRIC/build/bin/membersrvc
MSLOGFILE=$LOGDIR/membersrvc.log
MSEXE2=$FABRIC/build/image/membersrvc/bin/membersrvc
if [ ! -f $MSEXE -a -f $MSEXE2 ]; then
MSEXE=$MSEXE2
fi
PEEREXE=$FABRIC/build/bin/peer
PEEREXE2=$FABRIC/build/image/peer/bin/peer
if [ ! -f $PEEREXE -a -f $PEEREXE2 ]; then
PEEREXE=$PEEREXE2
fi
MSLOGFILE=$LOGDIR/membersrvc.log
PEERLOGFILE=$LOGDIR/peer.log
UNITTEST=$FABRIC/sdk/node/test/unit
EXAMPLES=$FABRIC/examples/chaincode/go
......@@ -73,6 +81,7 @@ runTests() {
runChainTests
runAssetMgmtTests
runAssetMgmtWithRolesTests
runAssetMgmtWithDynamicRolesTests
echo "End running tests in network mode"
}
......@@ -148,10 +157,8 @@ startExampleInDevMode() {
exit 1;
fi
EXE=$SRCDIR/$1
if [ ! -f $EXE ]; then
cd $SRCDIR
go build
fi
cd $SRCDIR
go build
export CORE_CHAINCODE_ID_NAME=$2
export CORE_PEER_ADDRESS=0.0.0.0:7051
startProcess "$EXE" "${EXE}.log" "$1"
......@@ -208,6 +215,18 @@ runAssetMgmtWithRolesTests() {
echo "END running asset management with roles tests"
}
runAssetMgmtWithDynamicRolesTests() {
echo "BEGIN running asset management with dynamic roles tests ..."
preExample asset_management_with_roles mycc3
node $UNITTEST/asset-mgmt-with-dynamic-roles.js
if [ $? -ne 0 ]; then
echo "ERROR running asset management with dynamic roles tests!"
NODE_ERR_CODE=1
fi
postExample asset_management_with_roles
echo "END running asset management with dynamic roles tests"
}
# start process
# $1 is executable path with any args
# $2 is the log file
......
......@@ -159,6 +159,8 @@ export interface RegistrationRequest {
roles?:string[];
// Affiliation for a user
affiliation:string;
// The attribute names and values to grant to this member
attributes?:Attribute[];
// 'registrar' enables this identity to register other members with types
// and can delegate the 'delegationRoles' roles
registrar?:{
......@@ -169,6 +171,15 @@ export interface RegistrationRequest {
};
}
// An attribute consisting of a name and value
export interface Attribute {
// The attribute name
name:string;
// The attribute value
value:string;
}
// An enrollment request
export interface EnrollmentRequest {
// The enrollment ID
enrollmentID:string;
......@@ -290,7 +301,7 @@ export class Certificate {
/**
* Enrollment certificate.
*/
export class ECert extends Certificate {
class ECert extends Certificate {
constructor(public cert:Buffer,
public privateKey:any) {
......@@ -2309,10 +2320,25 @@ class MemberServicesImpl implements MemberServices {
debug("MemberServicesImpl.register: req=%j", req);
if (!req.enrollmentID) return cb(new Error("missing req.enrollmentID"));
if (!registrar) return cb(new Error("chain registrar is not set"));
// Create proto request
let protoReq = new _caProto.RegisterUserReq();
protoReq.setId({id:req.enrollmentID});
protoReq.setRole(rolesToMask(req.roles));
protoReq.setAffiliation(req.affiliation);
let attrs = req.attributes;
if (Array.isArray(attrs)) {
let pattrs = [];
for (var i = 0; i < attrs.length; i++) {
var attr = attrs[i];
var pattr = new _caProto.Attribute();
if (attr.name) pattr.setName(attr.name);
if (attr.value) pattr.setValue(attr.value);
if (attr.notBefore) pattr.setNotBefore(attr.notBefore);
if (attr.notAfter) pattr.setNotAfter(attr.notAfter);
pattrs.push(pattr);
}
protoReq.setAttributes(pattrs);
}
// Create registrar info
let protoRegistrar = new _caProto.Registrar();
protoRegistrar.setId({id:registrar.getName()});
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment