From 15c76354e4d5a28416c32fa3eb2a8ef3761b5187 Mon Sep 17 00:00:00 2001 From: Saad Karim Date: Tue, 17 Oct 2017 14:38:57 -0400 Subject: [PATCH] [FAB-6710] Remove GetUserInfo call GetUserInfo function removed from the UserRegistry and all calls to GetUserInfo now use GetUser instead. User interface enhanced to allow for this change. Change-Id: I44b0df95dcac867ed729b914827528f935c14ffe Signed-off-by: Saad Karim --- api/client.go | 13 ++- api/net.go | 3 +- cmd/fabric-ca-client/main_test.go | 56 ++++----- docs/source/users-guide.rst | 17 +-- lib/ca.go | 11 +- lib/client_test.go | 2 +- lib/dasqlite_test.go | 5 - lib/dbaccessor.go | 109 +++++++++++------- lib/ldap/client.go | 54 ++++++--- lib/server_test.go | 27 +++-- lib/serverenroll_test.go | 8 +- lib/servererror.go | 4 + lib/serverregister.go | 17 ++- lib/serverrequestcontext.go | 105 ++++++++++++++++- lib/serverrevoke.go | 61 +++------- lib/servertcert.go | 21 ++-- lib/spi/userregistry.go | 12 +- lib/tcert/api.go | 53 +-------- lib/tcert/tcert.go | 16 ++- lib/tcert/tcert_test.go | 24 ++-- lib/util.go | 7 ++ .../rootca/ca1/fabric-ca-server-config.yaml | 1 + 22 files changed, 379 insertions(+), 247 deletions(-) diff --git a/api/client.go b/api/client.go index f1cfaeaf4..63db0a1d2 100644 --- a/api/client.go +++ b/api/client.go @@ -17,10 +17,10 @@ limitations under the License. package api import ( + "math/big" "time" "github.com/cloudflare/cfssl/csr" - "github.com/hyperledger/fabric-ca/lib/tcert" "github.com/hyperledger/fabric-ca/util" ) @@ -161,7 +161,16 @@ type GetTCertBatchRequest struct { // GetTCertBatchResponse is the return value of identity.GetTCertBatch type GetTCertBatchResponse struct { - tcert.GetBatchResponse + ID *big.Int `json:"id"` + TS time.Time `json:"ts"` + Key []byte `json:"key"` + TCerts []TCert `json:"tcerts"` +} + +// TCert encapsulates a signed transaction certificate and optionally a map of keys +type TCert struct { + Cert []byte `json:"cert"` + Keys map[string][]byte `json:"keys,omitempty"` //base64 encoded string as value } // GetCAInfoRequest is request to get generic CA information diff --git a/api/net.go b/api/net.go index 6b9f7e3b0..88a0f7918 100644 --- a/api/net.go +++ b/api/net.go @@ -18,7 +18,6 @@ package api import ( "github.com/cloudflare/cfssl/signer" - "github.com/hyperledger/fabric-ca/lib/tcert" ) /* @@ -73,7 +72,7 @@ type GetTCertBatchRequestNet struct { // GetTCertBatchResponseNet is the network response for a batch of transaction certificates type GetTCertBatchResponseNet struct { - tcert.GetBatchResponse + GetTCertBatchResponse } // KeySig is a public key, signature, and signature algorithm tuple diff --git a/cmd/fabric-ca-client/main_test.go b/cmd/fabric-ca-client/main_test.go index cf0d74dff..dbd9ec9bd 100644 --- a/cmd/fabric-ca-client/main_test.go +++ b/cmd/fabric-ca-client/main_test.go @@ -779,7 +779,7 @@ func testRegisterEnvVar(t *testing.T) { os.Setenv("FABRIC_CA_CLIENT_HOME", "../../testdata/") os.Setenv("FABRIC_CA_CLIENT_ID_NAME", "testRegister2") - os.Setenv("FABRIC_CA_CLIENT_ID_AFFILIATION", "hyperledger") + os.Setenv("FABRIC_CA_CLIENT_ID_AFFILIATION", "hyperledger.org2") os.Setenv("FABRIC_CA_CLIENT_ID_TYPE", "client") defer func() { os.Unsetenv("FABRIC_CA_CLIENT_HOME") @@ -803,7 +803,7 @@ func testRegisterCommandLine(t *testing.T, srv *lib.Server) { fooName := "foo" fooVal := "a=b" roleName := "hf.Registrar.Roles" - roleVal := "peer,user,client" + roleVal := "peer,user" attributes := fmt.Sprintf("%s=%s,bar=c,\"%s=%s\"", fooName, fooVal, roleName, roleVal) err := RunMain([]string{cmdName, "register", "-d", "--id.name", "testRegister3", @@ -818,20 +818,21 @@ func testRegisterCommandLine(t *testing.T, srv *lib.Server) { db := lib.NewDBAccessor() db.SetDB(sqliteDB) - user, err := db.GetUserInfo("testRegister3") + user, err := db.GetUser("testRegister3", nil) assert.NoError(t, err) - val := lib.GetAttrValue(user.Attributes, fooName) + allAttrs, _ := user.GetAttributes(nil) + val := lib.GetAttrValue(allAttrs, fooName) if val != fooVal { t.Errorf("Incorrect value returned for attribute '%s', expected '%s' got '%s'", fooName, fooVal, val) } - val = lib.GetAttrValue(user.Attributes, roleName) + val = lib.GetAttrValue(allAttrs, roleName) if val != roleVal { t.Errorf("Incorrect value returned for attribute '%s', expected '%s' got '%s'", roleName, roleVal, val) } err = RunMain([]string{cmdName, "register", "-d", "--id.name", "testRegister4", - "--id.secret", "testRegister4", "--id.affiliation", "hyperledger.org2", "--id.type", "client"}) + "--id.secret", "testRegister4", "--id.affiliation", "hyperledger.org2", "--id.type", "user"}) if err != nil { t.Errorf("client register failed: %s", err) } @@ -842,9 +843,9 @@ func testRegisterCommandLine(t *testing.T, srv *lib.Server) { err = RunMain([]string{cmdName, "register", "-d", "--id.name", userName, "--id.secret", "testRegister5", "--id.affiliation", "hyperledger.org1"}) assert.NoError(t, err, "Failed to register identity "+userName) - user, err = db.GetUserInfo(userName) + user, err = db.GetUser(userName, nil) assert.NoError(t, err) - assert.Equal(t, "user", user.Type, "Identity type for '%s' should have been 'user'", userName) + assert.Equal(t, "user", user.GetType(), "Identity type for '%s' should have been 'user'", userName) os.Remove(defYaml) // Delete default config file @@ -911,28 +912,35 @@ func testRevoke(t *testing.T) { t.Errorf("The Serial and AKI are not associated with the enrollment ID: %s", err) } - // Enroll admin with root affiliation and test revoking with root - err = RunMain([]string{cmdName, "enroll", "-u", enrollURL}) - if err != nil { - t.Fatalf("client enroll -u failed: %s", err) - } - - // Enroll testRegister4, so the next revoke command will revoke atleast one - // ecert + // Enroll testRegister4 testRegister4Home := filepath.Join(os.TempDir(), "testregister4Home") defer os.RemoveAll(testRegister4Home) err = RunMain([]string{cmdName, "enroll", "-u", - fmt.Sprintf("http://testRegister4:testRegister4@localhost:%d", serverPort), "-H", testRegister4Home}) + fmt.Sprintf("http://testRegister4:testRegister4@localhost:%d", serverPort)}) if err != nil { t.Fatalf("Failed to enroll testRegister4 user: %s", err) } + // testRegister2's affiliation: hyperledger.org2, hyperledger.org2 + err = RunMain([]string{cmdName, "revoke", "-u", serverURL, "--revoke.name", + "testRegister2", "--revoke.serial", "", "--revoke.aki", ""}) + if err == nil { + t.Errorf("Revoker has different type than the identity being revoked, should have failed") + } + + // Enroll admin with root affiliation and test revoking with root + err = RunMain([]string{cmdName, "enroll", "-u", enrollURL}) + if err != nil { + t.Fatalf("client enroll -u failed: %s", err) + } + // testRegister4's affiliation: company2, revoker's affiliation: "" (root) err = RunMain([]string{cmdName, "revoke", "-u", serverURL, "--revoke.name", "testRegister4", "--revoke.serial", "", "--revoke.aki", "", "--gencrl"}) if err != nil { t.Errorf("User with root affiliation failed to revoke, error: %s", err) } + crlFile := filepath.Join(clientHome, "msp/crls/crl.pem") _, err = os.Stat(crlFile) assert.NoError(t, err, "CRL should be created when revoke is called with --gencrl parameter") @@ -962,13 +970,6 @@ func testRevoke(t *testing.T) { _, err = os.Stat(filepath.Join(clientHome, "msp/crls/crl.pem")) assert.Error(t, err, "CRL should not be created when revoke is called without --gencrl parameter") - // testRegister2's affiliation: hyperledger, revoker's affiliation: "" - err = RunMain([]string{cmdName, "revoke", "-u", serverURL, "--revoke.name", - "testRegister2", "--revoke.serial", "", "--revoke.aki", ""}) - if err != nil { - t.Errorf("Failed to revoke proper affiliation hierarchy, error: %s", err) - } - err = RunMain([]string{cmdName, "enroll", "-d", "-u", "http://admin3:adminpw3@localhost:7090"}) if err != nil { t.Errorf("client enroll -u failed: %s", err) @@ -1035,11 +1036,12 @@ func testAffiliation(t *testing.T) { db := lib.NewDBAccessor() db.SetDB(sqliteDB) - user, err := db.GetUserInfo("testRegister6") + user, err := db.GetUser("testRegister6", nil) assert.NoError(t, err) - if user.Affiliation != "hyperledger" { - t.Errorf("Incorrectly set affiliation for user being registered when no affiliation was specified, expected 'hyperledger' got %s", user.Affiliation) + userAff := lib.GetUserAffiliation(user) + if userAff != "hyperledger" { + t.Errorf("Incorrectly set affiliation for user being registered when no affiliation was specified, expected 'hyperledger' got %s", userAff) } os.RemoveAll(filepath.Dir(defYaml)) diff --git a/docs/source/users-guide.rst b/docs/source/users-guide.rst index 885344b14..c79839990 100644 --- a/docs/source/users-guide.rst +++ b/docs/source/users-guide.rst @@ -1185,13 +1185,16 @@ the certificates owned by the identity and will also prevent the identity from g any new certificates. Revoking a certificate will invalidate a single certificate. In order to revoke a certificate or an identity, the calling identity must have -the ``hf.Revoker`` attribute. The revoking identity can only revoke a certificate -or an identity that has an affiliation that is equal to or prefixed by the revoking -identity's affiliation. - -For example, a revoker with affiliation **orgs.org1** can revoke an identity -affiliated with **orgs.org1** or **orgs.org1.department1** but can't revoke an -identity affiliated with **orgs.org2**. +the ``hf.Revoker`` and ``hf.Registrar.Roles`` attribute. The revoking identity +can only revoke a certificate or an identity that has an affiliation that is +equal to or prefixed by the revoking identity's affiliation. Furthermore, the +revoker can only revoke identities with types that are listed in the revoker's +``hf.Registrar.Roles`` attribute. + +For example, a revoker with affiliation **orgs.org1** and 'hf.Registrar.Roles=peer,client' +attribute can revoke either a **peer** or **client** type identity affiliated with +**orgs.org1** or **orgs.org1.department1** but can't revoke an identity affiliated with +**orgs.org2** or of any other type. The following command disables an identity and revokes all of the certificates associated with the identity. All future requests received by the Fabric CA server diff --git a/lib/ca.go b/lib/ca.go index 42832a6a2..cfa120162 100644 --- a/lib/ca.go +++ b/lib/ca.go @@ -885,19 +885,22 @@ func (ca *CA) getUserAttrValue(username, attrname string) (string, error) { if err != nil { return "", err } - attrval := user.GetAttribute(attrname) + attrval, err := user.GetAttribute(attrname) + if err != nil { + return "", errors.Errorf("Failed to get attribute '%s': %s", attrname, err) + } log.Debugf("getUserAttrValue identity=%s, name=%s, value=%s", username, attrname, attrval) - return attrval, nil + return attrval.Value, nil } // getUserAffiliation returns a user's affiliation func (ca *CA) getUserAffiliation(username string) (string, error) { log.Debugf("getUserAffilliation identity=%s", username) - user, err := ca.registry.GetUserInfo(username) + user, err := ca.registry.GetUser(username, nil) if err != nil { return "", err } - aff := user.Affiliation + aff := GetUserAffiliation(user) log.Debugf("getUserAffiliation identity=%s, aff=%s", username, aff) return aff, nil } diff --git a/lib/client_test.go b/lib/client_test.go index 53a52d13b..da4c336be 100644 --- a/lib/client_test.go +++ b/lib/client_test.go @@ -1056,7 +1056,7 @@ func TestRevokedIdentity(t *testing.T) { // 'admin' registers 'TestUser' user registerReq := &api.RegistrationRequest{ Name: "TestUser", - Type: "Client", + Type: "user", Affiliation: "hyperledger", MaxEnrollments: 1, } diff --git a/lib/dasqlite_test.go b/lib/dasqlite_test.go index 209e0a6d3..00b4d1488 100644 --- a/lib/dasqlite_test.go +++ b/lib/dasqlite_test.go @@ -391,11 +391,6 @@ func TestDBErrorMessages(t *testing.T) { assert.Contains(t, err.Error(), fmt.Sprintf(expectedErr, "User")) } - _, err = ta.Accessor.GetUserInfo("testuser") - if assert.Error(t, err, "Should have errored, and not returned any results") { - assert.Contains(t, err.Error(), fmt.Sprintf(expectedErr, "User")) - } - newCertDBAcc := NewCertDBAccessor(db) _, err = newCertDBAcc.GetCertificateWithID("serial", "aki") if assert.Error(t, err, "Should have errored, and not returned any results") { diff --git a/lib/dbaccessor.go b/lib/dbaccessor.go index 9688aed58..1c0b1e04b 100644 --- a/lib/dbaccessor.go +++ b/lib/dbaccessor.go @@ -25,7 +25,6 @@ import ( "github.com/cloudflare/cfssl/log" "github.com/hyperledger/fabric-ca/api" "github.com/hyperledger/fabric-ca/lib/spi" - "github.com/hyperledger/fabric-ca/lib/tcert" "golang.org/x/crypto/bcrypt" "github.com/jmoiron/sqlx" @@ -240,36 +239,6 @@ func (d *Accessor) GetUser(id string, attrs []string) (spi.User, error) { return d.newDBUser(&userRec), nil } -// GetUserInfo gets user information from database -func (d *Accessor) GetUserInfo(id string) (spi.UserInfo, error) { - log.Debugf("DB: Getting information for identity %s", id) - - var userInfo spi.UserInfo - - err := d.checkDB() - if err != nil { - return userInfo, err - } - - var userRec UserRecord - err = d.db.Get(&userRec, d.db.Rebind(getUser), id) - if err != nil { - return userInfo, dbGetError(err, "User") - } - - var attributes []api.Attribute - json.Unmarshal([]byte(userRec.Attributes), &attributes) - - userInfo.Name = userRec.Name - userInfo.Type = userRec.Type - userInfo.Affiliation = userRec.Affiliation - userInfo.State = userRec.State - userInfo.MaxEnrollments = userRec.MaxEnrollments - userInfo.Attributes = attributes - - return userInfo, nil -} - // InsertAffiliation inserts affiliation into database func (d *Accessor) InsertAffiliation(name string, prekey string) error { log.Debugf("DB: Add affiliation %s", name) @@ -351,9 +320,13 @@ func (d *Accessor) newDBUser(userRec *UserRecord) *DBUser { json.Unmarshal([]byte(userRec.Attributes), &attrs) user.Attributes = attrs - user.attrs = make(map[string]string) + user.attrs = make(map[string]api.Attribute) for _, attr := range attrs { - user.attrs[attr.Name] = attr.Value + user.attrs[attr.Name] = api.Attribute{ + Name: attr.Name, + Value: attr.Value, + ECert: attr.ECert, + } } user.db = d.db @@ -364,7 +337,7 @@ func (d *Accessor) newDBUser(userRec *UserRecord) *DBUser { type DBUser struct { spi.UserInfo pass []byte - attrs map[string]string + attrs map[string]api.Attribute db *sqlx.DB } @@ -373,6 +346,16 @@ func (u *DBUser) GetName() string { return u.Name } +// GetType returns the type of the user +func (u *DBUser) GetType() string { + return u.Type +} + +// GetMaxEnrollments returns the max enrollments of the user +func (u *DBUser) GetMaxEnrollments() int { + return u.MaxEnrollments +} + // Login the user with a password func (u *DBUser) Login(pass string, caMaxEnrollments int) error { log.Debugf("DB: Login user %s with max enrollments of %d and state of %d", u.Name, u.MaxEnrollments, u.State) @@ -456,18 +439,60 @@ func (u *DBUser) GetAffiliationPath() []string { } // GetAttribute returns the value for an attribute name -func (u *DBUser) GetAttribute(name string) string { - return u.attrs[name] +func (u *DBUser) GetAttribute(name string) (*api.Attribute, error) { + value, hasAttr := u.attrs[name] + if !hasAttr { + return nil, errors.Errorf("User does not have attribute '%s'", name) + } + return &value, nil } -// GetAttributes returns the requested attributes -func (u *DBUser) GetAttributes(attrNames []string) []tcert.Attribute { - var attrs []tcert.Attribute +// GetAttributes returns the requested attributes. Return all the user's +// attributes if nil is passed in +func (u *DBUser) GetAttributes(attrNames []string) ([]api.Attribute, error) { + var attrs []api.Attribute + if attrNames == nil { + for _, value := range u.attrs { + attrs = append(attrs, value) + } + return attrs, nil + } + for _, name := range attrNames { - value := u.attrs[name] - attrs = append(attrs, tcert.Attribute{Name: name, Value: value}) + value, hasAttr := u.attrs[name] + if !hasAttr { + return nil, errors.Errorf("User does not have attribute '%s'", name) + } + attrs = append(attrs, value) + } + return attrs, nil +} + +// Revoke will revoke the user, setting the state of the user to be -1 +func (u *DBUser) Revoke() error { + stateUpdateSQL := "UPDATE users SET state = -1 WHERE (id = ?)" + + res, err := u.db.Exec(u.db.Rebind(stateUpdateSQL), u.GetName()) + if err != nil { + return errors.Wrapf(err, "Failed to update state of identity %s to -1", u.Name) + } + + numRowsAffected, err := res.RowsAffected() + if err != nil { + return errors.Wrap(err, "db.RowsAffected failed") } - return attrs + + if numRowsAffected == 0 { + return errors.Errorf("No rows were affected when updating the state of identity %s", u.Name) + } + + if numRowsAffected != 1 { + return errors.Errorf("%d rows were affected when updating the state of identity %s", numRowsAffected, u.Name) + } + + log.Debugf("Successfully incremented state for identity %s to -1", u.Name) + + return nil } func dbGetError(err error, prefix string) error { diff --git a/lib/ldap/client.go b/lib/ldap/client.go index 5d1bf96f6..f0d57dffc 100644 --- a/lib/ldap/client.go +++ b/lib/ldap/client.go @@ -27,8 +27,8 @@ import ( "github.com/pkg/errors" "github.com/cloudflare/cfssl/log" + "github.com/hyperledger/fabric-ca/api" "github.com/hyperledger/fabric-ca/lib/spi" - "github.com/hyperledger/fabric-ca/lib/tcert" ctls "github.com/hyperledger/fabric-ca/lib/tls" "github.com/hyperledger/fabric-ca/util" "github.com/hyperledger/fabric/bccsp" @@ -216,12 +216,6 @@ func (lc *Client) GetUser(username string, attrNames []string) (spi.User, error) return user, nil } -// GetUserInfo gets user information from database -func (lc *Client) GetUserInfo(id string) (spi.UserInfo, error) { - var userInfo spi.UserInfo - return userInfo, errNotSupported -} - // InsertUser inserts a user func (lc *Client) InsertUser(user spi.UserInfo) error { return errNotSupported @@ -304,6 +298,16 @@ func (u *User) GetName() string { return u.dn } +// GetType returns the type of the user +func (u *User) GetType() string { + return "" +} + +// GetMaxEnrollments returns the max enrollments of the user +func (u *User) GetMaxEnrollments() int { + return 0 +} + // Login logs a user in using password func (u *User) Login(password string, caMaxEnrollment int) error { @@ -335,18 +339,40 @@ func (u *User) GetAffiliationPath() []string { } // GetAttribute returns the value of an attribute, or "" if not found -func (u *User) GetAttribute(name string) string { - return u.attrs[name] +func (u *User) GetAttribute(name string) (*api.Attribute, error) { + value, hasAttr := u.attrs[name] + if !hasAttr { + return nil, errors.Errorf("User does not have attribute '%s'", name) + } + return &api.Attribute{ + Name: name, + Value: value, + }, nil } // GetAttributes returns the requested attributes -func (u *User) GetAttributes(attrNames []string) []tcert.Attribute { - var attrs []tcert.Attribute +func (u *User) GetAttributes(attrNames []string) ([]api.Attribute, error) { + var attrs []api.Attribute + if attrNames == nil { + for name, value := range u.attrs { + attrs = append(attrs, api.Attribute{Name: name, Value: value}) + } + return attrs, nil + } + for _, name := range attrNames { - value := u.attrs[name] - attrs = append(attrs, tcert.Attribute{Name: name, Value: value}) + value, hasAttr := u.attrs[name] + if !hasAttr { + return nil, errors.Errorf("User does not have attribute '%s'", name) + } + attrs = append(attrs, api.Attribute{Name: name, Value: value}) } - return attrs + return attrs, nil +} + +// Revoke requires not action on LDAP +func (u *User) Revoke() error { + return nil } // Returns a slice with the elements reversed diff --git a/lib/server_test.go b/lib/server_test.go index 9a7c43319..f56176a0c 100755 --- a/lib/server_test.go +++ b/lib/server_test.go @@ -1478,11 +1478,11 @@ func TestSRVMaxEnrollmentLimited(t *testing.T) { if err != nil { t.Errorf("Failed to register me_0_1, error: %s", err) } - user, err := srv.CA.DBAccessor().GetUserInfo("me_0_1") + user, err := srv.CA.DBAccessor().GetUser("me_0_1", nil) if err != nil { t.Errorf("Failed to find user 'me_0_1,' in database") } - if user.MaxEnrollments != 1 { + if user.GetMaxEnrollments() != 1 { t.Error("Failed to correctly set max enrollment value for a user registering with max enrollment of 0") } _, err = id.Identity.Register(&api.RegistrationRequest{ @@ -2144,11 +2144,12 @@ func TestRegisterationAffiliation(t *testing.T) { assert.NoError(t, err, "Client register failed") db := server.DBAccessor() - user, err := db.GetUserInfo("testuser") + user, err := db.GetUser("testuser", nil) assert.NoError(t, err) - if user.Affiliation != "" { - t.Errorf("Incorrect affiliation set for user being registered when no affiliation was specified, expected '' got %s", user.Affiliation) + userAff := GetUserAffiliation(user) + if userAff != "" { + t.Errorf("Incorrect affiliation set for user being registered when no affiliation was specified, expected '' got %s", userAff) } _, err = admin.Register(&api.RegistrationRequest{ @@ -2158,11 +2159,12 @@ func TestRegisterationAffiliation(t *testing.T) { }) assert.NoError(t, err, "Client register failed") - user, err = db.GetUserInfo("testuser2") + user, err = db.GetUser("testuser2", nil) assert.NoError(t, err) - if user.Affiliation != "" { - t.Errorf("Incorrect affiliation set for user being registered when no affiliation was specified, expected '' got %s", user.Affiliation) + userAff = GetUserAffiliation(user) + if userAff != "" { + t.Errorf("Incorrect affiliation set for user being registered when no affiliation was specified, expected '' got %s", userAff) } eresp, err = client.Enroll(&api.EnrollmentRequest{ @@ -2181,11 +2183,12 @@ func TestRegisterationAffiliation(t *testing.T) { assert.NoError(t, err, "Client register failed") db = server.DBAccessor() - user, err = db.GetUserInfo("testuser3") + user, err = db.GetUser("testuser3", nil) assert.NoError(t, err) - if user.Affiliation != "hyperledger" { - t.Errorf("Incorrect affiliation set for user being registered when no affiliation was specified, expected 'hyperledger' got %s", user.Affiliation) + userAff = GetUserAffiliation(user) + if userAff != "hyperledger" { + t.Errorf("Incorrect affiliation set for user being registered when no affiliation was specified, expected 'hyperledger' got %s", userAff) } _, err = admin2.Register(&api.RegistrationRequest{ @@ -2358,7 +2361,7 @@ func testRegistration(admin *Identity, t *testing.T) { Name: name, Type: "user", Affiliation: midAffiliation, - Attributes: makeAttrs(t, "hf.Registrar.Roles=user,peer", "hf.Registrar.DelegateRoles=user"), + Attributes: makeAttrs(t, "hf.Registrar.Roles=user,peer", "hf.Registrar.DelegateRoles=user", "hf.Registrar.Attributes=*"), }) if err != nil { t.Fatalf("%s", err) diff --git a/lib/serverenroll_test.go b/lib/serverenroll_test.go index f2826b14e..7f636e49c 100644 --- a/lib/serverenroll_test.go +++ b/lib/serverenroll_test.go @@ -45,10 +45,10 @@ func TestStateUpdate(t *testing.T) { assert.NoError(t, err, "Failed to enroll 'admin' user") registry := srv.CA.DBAccessor() - userInfo, err := registry.GetUserInfo("admin") + userInfo, err := registry.GetUser("admin", nil) assert.NoError(t, err, "Failed to get user 'admin' from database") // User state should have gotten updated to 1 after a successful enrollment - if userInfo.State != 1 { + if userInfo.(*DBUser).State != 1 { t.Error("Incorrect state set for user") } @@ -68,9 +68,9 @@ func TestStateUpdate(t *testing.T) { } // State should not have gotten updated because the enrollment failed - userInfo, err = registry.GetUserInfo("admin") + userInfo, err = registry.GetUser("admin", nil) assert.NoError(t, err, "Failed to get user 'admin' from database") - if userInfo.State != 1 { + if userInfo.(*DBUser).State != 1 { t.Error("Incorrect state set for user") } diff --git a/lib/servererror.go b/lib/servererror.go index f02fda1b9..bc0e495ca 100644 --- a/lib/servererror.go +++ b/lib/servererror.go @@ -117,6 +117,10 @@ const ( ErrRegAttrAuth = 42 // Registrar does not own 'hf.Registrar.Attributes' ErrMissingRegAttr = 43 + // Failed to get caller's affiliation + ErrCallerNotAffiliated = 44 + // Failed to verify if caller has appropriate + ErrGettingType = 33 ) // Construct a new HTTP error. diff --git a/lib/serverregister.go b/lib/serverregister.go index 9c029438d..35f74304e 100644 --- a/lib/serverregister.go +++ b/lib/serverregister.go @@ -85,7 +85,7 @@ func registerUser(req *api.RegistrationRequestNet, registrar string, ca *CA, ctx } // Check that the affiliation requested is of the appropriate level - registrarAff := strings.Join(registrarUser.GetAffiliationPath(), ".") + registrarAff := GetUserAffiliation(registrarUser) err = validateAffiliation(registrarAff, req) if err != nil { return "", fmt.Errorf("Registration of '%s' failed in affiliation validation: %s", req.Name, err) @@ -222,9 +222,12 @@ func canRegister(registrar string, req *api.RegistrationRequestNet, user spi.Use log.Debugf("canRegister - Check to see if user %s can register", registrar) var roles []string - rolesStr := user.GetAttribute("hf.Registrar.Roles") - if rolesStr != "" { - roles = strings.Split(rolesStr, ",") + rolesStr, err := user.GetAttribute("hf.Registrar.Roles") + if err != nil { + return errors.Errorf("Failed to get attribute 'hf.Registrar.Roles': %s", err) + } + if rolesStr.Value != "" { + roles = strings.Split(rolesStr.Value, ",") } else { roles = make([]string, 0) } @@ -239,7 +242,11 @@ func canRegister(registrar string, req *api.RegistrationRequestNet, user spi.Use // Validate that the registrar can register the requested attributes func validateRequestedAttributes(reqAttrs []api.Attribute, registrar spi.User) error { - registrarAttrs := registrar.GetAttribute(attrRegistrarAttr) + registrarCanRegisterAttrs, err := registrar.GetAttribute(attrRegistrarAttr) + if err != nil { + return newHTTPErr(401, ErrMissingRegAttr, "Registrar does not have attribute '%s' thus can't register any attributes", attrRegistrarAttr) + } + registrarAttrs := registrarCanRegisterAttrs.Value log.Debugf("Validating that registrar '%s' with the following value for hf.Registrar.Attributes '%s' is authorized to register the requested attributes '%+v'", registrar.GetName(), registrarAttrs, reqAttrs) if len(reqAttrs) == 0 { return nil diff --git a/lib/serverrequestcontext.go b/lib/serverrequestcontext.go index dab09e816..9037a2694 100644 --- a/lib/serverrequestcontext.go +++ b/lib/serverrequestcontext.go @@ -216,12 +216,13 @@ func (ctx *serverRequestContext) GetAttrExtension(attrReqs []*api.AttributeReque log.Debug("No attributes will be added to certificate with LDAP enabled") return nil, nil } - ui, err := ca.registry.GetUserInfo(ctx.enrollmentID) + ui, err := ca.registry.GetUser(ctx.enrollmentID, nil) if err != nil { return nil, err } + allAttrs, _ := ui.GetAttributes(nil) if attrReqs == nil { - attrReqs = getDefaultAttrReqs(ui.Attributes) + attrReqs = getDefaultAttrReqs(allAttrs) if attrReqs == nil { // No attributes are being requested, so we are done return nil, nil @@ -229,7 +230,7 @@ func (ctx *serverRequestContext) GetAttrExtension(attrReqs []*api.AttributeReque } attrs, err := ca.attrMgr.ProcessAttributeRequests( convertAttrReqs(attrReqs), - convertAttrs(ui.Attributes), + convertAttrs(allAttrs), ) if err != nil { return nil, err @@ -343,6 +344,104 @@ func (ctx *serverRequestContext) GetCaller() (spi.User, error) { return ctx.caller, nil } +// CanManageUser determines if the caller has the right type and affiliation to act on on a user +func (ctx *serverRequestContext) CanManageUser(user spi.User) error { + userAff := strings.Join(user.GetAffiliationPath(), ".") + validAffiliation, err := ctx.ContainsAffiliation(userAff) + if err != nil { + return newHTTPErr(500, ErrGettingAffiliation, "Failed to validate if caller has authority to get ID: %s", err) + } + if !validAffiliation { + return newAuthErr(ErrCallerNotAffiliated, "Caller does not have authority to act on affiliation '%s'", userAff) + } + + userType := user.GetType() + canAct, err := ctx.CanActOnType(userType) + if err != nil { + return newHTTPErr(500, ErrGettingType, "Failed to verify if user can act on type '%s': %s", userType, err) + } + if !canAct { + return newAuthErr(ErrCallerNotAffiliated, "Caller does not have authority to act on type '%s'", userType) + } + return nil +} + +// IsRegistrar returns back true if the caller is a registrar along with the types the registrar is allowed to register +func (ctx *serverRequestContext) IsRegistrar() (string, bool, error) { + caller, err := ctx.GetCaller() + if err != nil { + return "", false, err + } + + log.Debugf("Checking to see if caller '%s' is a registrar", caller.GetName()) + + rolesStr, err := caller.GetAttribute("hf.Registrar.Roles") + if err != nil { + return "", false, newAuthErr(ErrRegAttrAuth, "'%s' is not a registrar", caller.GetName()) + } + + // Has some value for attribute 'hf.Registrar.Roles' then user is a registrar + if rolesStr.Value != "" { + return rolesStr.Value, true, nil + } + + return "", false, nil +} + +// CanActOnType returns true if the caller has the proper authority to take action on specific type +func (ctx *serverRequestContext) CanActOnType(typ string) (bool, error) { + caller, err := ctx.GetCaller() + if err != nil { + return false, err + } + + log.Debugf("Checking to see if caller '%s' with type '%s' can act on type '%s'", caller.GetName(), typ) + + typesStr, isRegistrar, err := ctx.IsRegistrar() + if err != nil { + return false, err + } + if !isRegistrar { + return false, newAuthErr(ErrRegAttrAuth, "'%s' is not allowed to manage users", caller.GetName()) + } + + var types []string + if typesStr != "" { + types = strings.Split(typesStr, ",") + } else { + types = make([]string, 0) + } + + if !util.StrContained(typ, types) { + log.Debug("Caller with types '%s' is not authorized to act on '%s'", types, typ) + return false, nil + } + + return true, nil +} + +// ContainsAffiliation returns true if the caller the requested affiliation contains the caller's affiliation +func (ctx *serverRequestContext) ContainsAffiliation(affiliation string) (bool, error) { + caller, err := ctx.GetCaller() + if err != nil { + return false, err + } + + callerAffiliationPath := strings.Join(caller.GetAffiliationPath(), ".") + log.Debugf("Checking to see if affiliation '%s' contains caller's affiliation '%s'", affiliation, callerAffiliationPath) + + // If the caller has root affiliation return "true" + if callerAffiliationPath == "" { + return true, nil + } + + if strings.HasPrefix(affiliation, callerAffiliationPath) { + return true, nil + } + + return false, nil +} + func convertAttrReqs(attrReqs []*api.AttributeRequest) []attrmgr.AttributeRequest { rtn := make([]attrmgr.AttributeRequest, len(attrReqs)) for i := range attrReqs { diff --git a/lib/serverrevoke.go b/lib/serverrevoke.go index da0ae44cd..cf2a7eef7 100644 --- a/lib/serverrevoke.go +++ b/lib/serverrevoke.go @@ -17,12 +17,12 @@ limitations under the License. package lib import ( + "encoding/hex" "strings" "github.com/cloudflare/cfssl/log" "github.com/hyperledger/fabric-ca/api" - "github.com/hyperledger/fabric-ca/lib/spi" "github.com/hyperledger/fabric-ca/util" ) @@ -73,6 +73,9 @@ func revokeHandler(ctx *serverRequestContext) (interface{}, error) { result := &revocationResponseNet{} if req.Serial != "" && req.AKI != "" { + calleraki := strings.ToLower(strings.TrimLeft(hex.EncodeToString(ctx.enrollmentCert.AuthorityKeyId), "0")) + callerserial := strings.ToLower(strings.TrimLeft(util.GetSerialAsHex(ctx.enrollmentCert.SerialNumber), "0")) + certificate, err := certDBAccessor.GetCertificateWithID(req.Serial, req.AKI) if err != nil { return nil, newHTTPErr(404, ErrRevCertNotFound, "Certificate with serial %s and AKI %s was not found: %s", @@ -84,14 +87,16 @@ func revokeHandler(ctx *serverRequestContext) (interface{}, error) { req.Serial, req.AKI, req.Name) } - userInfo, err := registry.GetUserInfo(certificate.ID) + userInfo, err := registry.GetUser(certificate.ID, nil) if err != nil { return nil, newHTTPErr(404, ErrRevokeIDNotFound, "Identity %s was not found: %s", certificate.ID, err) } - err = checkAffiliations(id, userInfo, ca) - if err != nil { - return nil, err + if !((req.AKI == calleraki) && (req.Serial == callerserial)) { + err = ctx.CanManageUser(userInfo) + if err != nil { + return nil, err + } } err = certDBAccessor.RevokeCertificate(req.Serial, req.AKI, reason) @@ -108,22 +113,21 @@ func revokeHandler(ctx *serverRequestContext) (interface{}, error) { // Set user state to -1 for revoked user if user != nil { - var userInfo spi.UserInfo - userInfo, err = registry.GetUserInfo(req.Name) - if err != nil { - return nil, newHTTPErr(500, ErrRevokeUserInfoNotFound, "Failed getting info for identity %s: %s", req.Name, err) - } - - err = checkAffiliations(id, userInfo, ca) + caller, err := ctx.GetCaller() if err != nil { return nil, err } - userInfo.State = -1 + if caller.GetName() != user.GetName() { + err = ctx.CanManageUser(user) + if err != nil { + return nil, err + } + } - err = registry.UpdateUser(userInfo) + err = user.Revoke() if err != nil { - return nil, newHTTPErr(500, ErrRevokeUpdateUser, "Failed to update identity info: %s", err) + return nil, newHTTPErr(500, ErrRevokeUpdateUser, "Failed to revoke user: %s", err) } } @@ -159,30 +163,3 @@ func revokeHandler(ctx *serverRequestContext) (interface{}, error) { return result, nil } - -func checkAffiliations(revoker string, revoking spi.UserInfo, ca *CA) error { - log.Debugf("Check to see if revoker %s has affiliations to revoke: %s", revoker, revoking.Name) - userAffiliation, err := ca.getUserAffiliation(revoker) - if err != nil { - return newHTTPErr(500, ErrGettingAffiliation, "Failed to get affiliation of %s: %s", revoker, err) - } - - log.Debugf("Affiliation of revoker: %s, affiliation of identity being revoked: %s", userAffiliation, revoking.Affiliation) - - // Revoking user has root affiliation thus has ability to revoke - if userAffiliation == "" { - log.Debug("Identity with root affiliation revoking") - return nil - } - - revokingAffiliation := strings.Split(revoking.Affiliation, ".") - revokerAffiliation := strings.Split(userAffiliation, ".") - for i := range revokerAffiliation { - if revokerAffiliation[i] != revokingAffiliation[i] { - return newHTTPErr(401, ErrRevokerNotAffiliated, - "Revoker %s does not have proper affiliation to revoke identity %s", revoker, revoking.Name) - } - } - - return nil -} diff --git a/lib/servertcert.go b/lib/servertcert.go index a942a2a99..a6f931f3f 100644 --- a/lib/servertcert.go +++ b/lib/servertcert.go @@ -18,8 +18,9 @@ package lib import ( "github.com/hyperledger/fabric-ca/api" - "github.com/hyperledger/fabric-ca/lib/tcert" + tcert "github.com/hyperledger/fabric-ca/lib/tcert" "github.com/hyperledger/fabric/bccsp" + "github.com/pkg/errors" ) func newTCertEndpoint(s *Server) *serverEndpoint { @@ -53,7 +54,10 @@ func tcertHandler(ctx *serverRequestContext) (interface{}, error) { if err != nil { return nil, err } - attrs := caller.GetAttributes(req.AttrNames) + attrs, err := caller.GetAttributes(req.AttrNames) + if err != nil { + return nil, errors.Errorf("Failed to get attributes '%s': %s", req.AttrNames, err) + } affiliationPath := caller.GetAffiliationPath() // Get the prekey associated with the affiliation path prekey, err := ca.keyTree.GetKey(affiliationPath) @@ -65,13 +69,12 @@ func tcertHandler(ctx *serverRequestContext) (interface{}, error) { // which isn't correct. prekeyStr := string(prekey.SKI()) // Call the tcert library to get the batch of tcerts - tcertReq := &tcert.GetBatchRequest{ - Count: req.Count, - Attrs: attrs, - EncryptAttrs: req.EncryptAttrs, - ValidityPeriod: req.ValidityPeriod, - PreKey: prekeyStr, - } + tcertReq := &tcert.GetTCertBatchRequest{} + tcertReq.Count = req.Count + tcertReq.Attrs = attrs + tcertReq.EncryptAttrs = req.EncryptAttrs + tcertReq.ValidityPeriod = req.ValidityPeriod + tcertReq.PreKey = prekeyStr resp, err := ca.tcertMgr.GetBatch(tcertReq, ctx.GetECert()) if err != nil { return nil, err diff --git a/lib/spi/userregistry.go b/lib/spi/userregistry.go index 5a28ffb29..a3e885d98 100644 --- a/lib/spi/userregistry.go +++ b/lib/spi/userregistry.go @@ -22,7 +22,6 @@ package spi import ( "github.com/hyperledger/fabric-ca/api" - "github.com/hyperledger/fabric-ca/lib/tcert" ) // UserInfo contains information about a user @@ -40,22 +39,27 @@ type UserInfo struct { type User interface { // Returns the enrollment ID of the user GetName() string + // Return the type of the user + GetType() string + // Return the max enrollments of the user + GetMaxEnrollments() int // Login the user with a password Login(password string, caMaxEnrollment int) error // Get the complete path for the user's affiliation. GetAffiliationPath() []string // GetAttribute returns the value for an attribute name - GetAttribute(name string) string + GetAttribute(name string) (*api.Attribute, error) // GetAttributes returns the requested attributes - GetAttributes(attrNames []string) []tcert.Attribute + GetAttributes(attrNames []string) ([]api.Attribute, error) // LoginComplete completes the login process by incrementing the state of the user LoginComplete() error + // Revoke will revoke the user, setting the state of the user to be -1 + Revoke() error } // UserRegistry is the API for retreiving users and groups type UserRegistry interface { GetUser(id string, attrs []string) (User, error) - GetUserInfo(id string) (UserInfo, error) InsertUser(user UserInfo) error UpdateUser(user UserInfo) error DeleteUser(id string) error diff --git a/lib/tcert/api.go b/lib/tcert/api.go index a30069ca6..c31656f78 100644 --- a/lib/tcert/api.go +++ b/lib/tcert/api.go @@ -16,53 +16,10 @@ limitations under the License. package tcert -import ( - "math/big" - "time" -) +import "github.com/hyperledger/fabric-ca/api" -/* - * This file contains definitions of the input and output to the TCert - * library APIs. - */ - -// GetBatchRequest defines input to the GetBatch API -type GetBatchRequest struct { - // Number of TCerts in the batch. - Count int `json:"count"` - // If PublicKeys is non nil, generates a TCert for each public key; - // in this case, the 'Count' field is ignored and the number of TCerts - // generated matches the number of public keys in the array. - PublicKeys [][]byte `json:"public_keys,omitempty"` - // The attribute name and values that are to be inserted in the issued TCerts. - Attrs []Attribute `json:"attrs,omitempty"` - // EncryptAttrs denotes whether to encrypt attribute values or not. - // When set to true, each issued TCert in the batch will contain encrypted attribute values. - EncryptAttrs bool `json:"encrypt_attrs,omitempty"` - // Certificate Validity Period. If specified, the value used - // is the minimum of this value and the configured validity period - // of the TCert manager. - ValidityPeriod time.Duration `json:"validity_period,omitempty"` - // The pre-key to be used for key derivation. - PreKey string `json:"prekey"` -} - -// GetBatchResponse is the response from the GetBatch API -type GetBatchResponse struct { - ID *big.Int `json:"id"` - TS time.Time `json:"ts"` - Key []byte `json:"key"` - TCerts []TCert `json:"tcerts"` -} - -// TCert encapsulates a signed transaction certificate and optionally a map of keys -type TCert struct { - Cert []byte `json:"cert"` - Keys map[string][]byte `json:"keys,omitempty"` //base64 encoded string as value -} - -// Attribute is a single attribute name and value -type Attribute struct { - Name string `json:"name"` - Value string `json:"value"` +// GetTCertBatchRequest defines input to the GetBatch API +type GetTCertBatchRequest struct { + api.GetTCertBatchRequest + Attrs []api.Attribute } diff --git a/lib/tcert/tcert.go b/lib/tcert/tcert.go index 5feaf7196..a2c39b70e 100644 --- a/lib/tcert/tcert.go +++ b/lib/tcert/tcert.go @@ -32,6 +32,7 @@ import ( "strconv" "github.com/cloudflare/cfssl/log" + "github.com/hyperledger/fabric-ca/api" "github.com/hyperledger/fabric-ca/util" "github.com/hyperledger/fabric/bccsp" cspsigner "github.com/hyperledger/fabric/bccsp/signer" @@ -109,7 +110,7 @@ type Mgr struct { // GetBatch gets a batch of TCerts // @parameter req Is the TCert batch request // @parameter ecert Is the enrollment certificate of the caller -func (tm *Mgr) GetBatch(req *GetBatchRequest, ecert *x509.Certificate) (*GetBatchResponse, error) { +func (tm *Mgr) GetBatch(req *GetTCertBatchRequest, ecert *x509.Certificate) (*api.GetTCertBatchResponse, error) { log.Debugf("GetBatch req=%+v", req) @@ -154,7 +155,7 @@ func (tm *Mgr) GetBatch(req *GetBatchRequest, ecert *x509.Certificate) (*GetBatc mac.Write(raw) kdfKey := mac.Sum(nil) - var set []TCert + var set []api.TCert for i := 0; i < numTCertsInBatch; i++ { tcertid, uuidError := GenerateIntUUID() @@ -208,7 +209,7 @@ func (tm *Mgr) GetBatch(req *GetBatchRequest, ecert *x509.Certificate) (*GetBatc pem := ConvertDERToPEM(raw, "CERTIFICATE") - set = append(set, TCert{pem, ks}) + set = append(set, api.TCert{Cert: pem, Keys: ks}) } tcertID, randNumErr := GenNumber(big.NewInt(20)) @@ -216,7 +217,12 @@ func (tm *Mgr) GetBatch(req *GetBatchRequest, ecert *x509.Certificate) (*GetBatc return nil, randNumErr } - tcertResponse := &GetBatchResponse{tcertID, time.Now(), kdfKey, set} + tcertResponse := &api.GetTCertBatchResponse{ + ID: tcertID, + TS: time.Now(), + Key: kdfKey, + TCerts: set, + } return tcertResponse, nil @@ -234,7 +240,7 @@ func createHMACKey() string { } // Generate encrypted extensions to be included into the TCert (TCertIndex, EnrollmentID and attributes). -func generateExtensions(tcertid *big.Int, tidx []byte, enrollmentCert *x509.Certificate, batchRequest *GetBatchRequest) ([]pkix.Extension, map[string][]byte, error) { +func generateExtensions(tcertid *big.Int, tidx []byte, enrollmentCert *x509.Certificate, batchRequest *GetTCertBatchRequest) ([]pkix.Extension, map[string][]byte, error) { // For each TCert we need to store and retrieve to the user the list of Ks used to encrypt the EnrollmentID and the attributes. ks := make(map[string][]byte) attrs := batchRequest.Attrs diff --git a/lib/tcert/tcert_test.go b/lib/tcert/tcert_test.go index 55537b264..a64a7ab8d 100644 --- a/lib/tcert/tcert_test.go +++ b/lib/tcert/tcert_test.go @@ -20,6 +20,7 @@ import ( "testing" "github.com/cloudflare/cfssl/log" + "github.com/hyperledger/fabric-ca/api" "github.com/hyperledger/fabric-ca/util" ) @@ -43,10 +44,11 @@ func TestTCertWithoutAttribute(t *testing.T) { t.Errorf("LoadCert unable to load ec.pem %v", err) } - resp, err := mgr.GetBatch(&GetBatchRequest{ - Count: 1, - PreKey: "anyroot", - }, ecert) + batchReq := &GetTCertBatchRequest{} + batchReq.Count = 1 + batchReq.PreKey = "anyroot" + + resp, err := mgr.GetBatch(batchReq, ecert) if err != nil { t.Errorf("Error from GetBatch: %s", err) return @@ -71,7 +73,7 @@ func TestTCertWitAttributes(t *testing.T) { if err != nil { return } - var Attrs = []Attribute{ + var Attrs = []api.Attribute{ { Name: "SSN", Value: "123-456-789", @@ -82,12 +84,12 @@ func TestTCertWitAttributes(t *testing.T) { Value: "USD", }, } - resp, err := mgr.GetBatch(&GetBatchRequest{ - Count: 2, - EncryptAttrs: true, - Attrs: Attrs, - PreKey: "anotherprekey", - }, ecert) + batchReq := &GetTCertBatchRequest{} + batchReq.Count = 2 + batchReq.EncryptAttrs = true + batchReq.Attrs = Attrs + batchReq.PreKey = "anotherprekey" + resp, err := mgr.GetBatch(batchReq, ecert) if err != nil { t.Errorf("Error from GetBatch: %s", err) return diff --git a/lib/util.go b/lib/util.go index a541988fa..1bd60f3a8 100644 --- a/lib/util.go +++ b/lib/util.go @@ -23,11 +23,13 @@ import ( "encoding/pem" "fmt" "io/ioutil" + "strings" "github.com/pkg/errors" "github.com/cloudflare/cfssl/log" "github.com/hyperledger/fabric-ca/api" + "github.com/hyperledger/fabric-ca/lib/spi" "github.com/hyperledger/fabric-ca/util" "github.com/spf13/viper" ) @@ -180,3 +182,8 @@ func getMaxEnrollments(userMaxEnrollments int, caMaxEnrollments int) (int, error } } } + +// GetUserAffiliation return a joined version version of the affiliation path with '.' as the seperator +func GetUserAffiliation(user spi.User) string { + return strings.Join(user.GetAffiliationPath(), ".") +} diff --git a/testdata/ca/rootca/ca1/fabric-ca-server-config.yaml b/testdata/ca/rootca/ca1/fabric-ca-server-config.yaml index 2b248bb30..fe612f5f0 100644 --- a/testdata/ca/rootca/ca1/fabric-ca-server-config.yaml +++ b/testdata/ca/rootca/ca1/fabric-ca-server-config.yaml @@ -56,3 +56,4 @@ registry: hf.Registrar.DelegateRoles: "client,user,validator,auditor" hf.Revoker: true hf.IntermediateCA: true + hf.Registrar.Attributes: "*"