Skip to content

Commit

Permalink
[FAB-5726] 2. Dynamic Cfg - identities: CLI2
Browse files Browse the repository at this point in the history
This change sets create the appropriate HTTP calls
on the client side for all the various config actions,
such as add, modify, and remove. Also, client side logic
for process the CLI inputs is completed.

Next change set will create the 'identities' handler on
the server side and layout the framework for supporting
adding, modifying, and removing identities on the server.

Change-Id: I0cb5a83dddffebe808aff2c28ed79d0da00f09d9
Signed-off-by: Saad Karim <skarim@us.ibm.com>
  • Loading branch information
Saad Karim committed Nov 21, 2017
1 parent 3fad051 commit 7a17a94
Show file tree
Hide file tree
Showing 9 changed files with 383 additions and 20 deletions.
15 changes: 12 additions & 3 deletions api/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,7 @@ type AddIdentityRequest struct {
// a random secret is generated. In both cases, the secret
// is returned in the RegistrationResponse.
Secret string `json:"secret,omitempty" mask:"password" help:"The enrollment secret for the identity being registered"`
CAName string `json:"caname,omitempty" skip:"true"`
}

// ModifyIdentityRequest represents the request to modify an existing identity on the
Expand All @@ -211,25 +212,33 @@ type ModifyIdentityRequest struct {
// a random secret is generated. In both cases, the secret
// is returned in the RegistrationResponse.a
Secret string `json:"secret,omitempty" mask:"password"`
CAName string `json:"caname,omitempty" skip:"true"`
}

// RemoveIdentityRequest represents the request to remove an existing identity from the
// fabric-ca-server
type RemoveIdentityRequest struct {
ID string `json:"id" skip:"true"`
CAName string `json:"caname" skip:"true"`
CAName string `json:"caname,omitempty" skip:"true"`
}

// GetIDResponse is the response from the GetIdentity call
type GetIDResponse struct {
IdentityInfo `mapstructure:",squash"`
CAName string `json:"caname"`
CAName string `json:"caname,omitempty"`
}

// GetAllIDsResponse is the response from the GetAllIdentities call
type GetAllIDsResponse struct {
Identities []IdentityInfo
CAName string `json:"caname"`
CAName string `json:"caname,omitempty"`
}

// IdentityResponse is the response from the any add/modify/remove identity call
type IdentityResponse struct {
IdentityInfo `mapstructure:",squash"`
Secret string `json:"secret,omitempty"`
CAName string `json:"caname,omitempty"`
}

// IdentityInfo contains information about an identity
Expand Down
110 changes: 101 additions & 9 deletions cmd/fabric-ca-client/identity.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,10 @@ func (c *ClientCmd) newAddIdentityCommand() *cobra.Command {
return nil
},
RunE: func(cmd *cobra.Command, args []string) error {
if c.dynamicIdentity.json != "" && checkOtherFlags(cmd) {
return errors.Errorf("Can't use 'json' flag in conjunction with other flags")
}

err := c.runAddIdentity(args)
if err != nil {
return err
Expand Down Expand Up @@ -140,6 +144,10 @@ func (c *ClientCmd) newModifyIdentityCommand() *cobra.Command {
return nil
},
RunE: func(cmd *cobra.Command, args []string) error {
if c.dynamicIdentity.json != "" && checkOtherFlags(cmd) {
return errors.Errorf("Can't use 'json' flag in conjunction with other flags")
}

err := c.runModifyIdentity(args)
if err != nil {
return err
Expand Down Expand Up @@ -229,29 +237,113 @@ func (c *ClientCmd) runListIdentity() error {

// The client side logic for adding an identity
func (c *ClientCmd) runAddIdentity(args []string) error {
log.Debug("Entered runAddIdentity")
log.Debugf("Entered runAddIdentity: %+v", c.dynamicIdentity)

// TODO
id, err := c.loadMyIdentity()
if err != nil {
return err
}

req := &api.AddIdentityRequest{}

if c.dynamicIdentity.json != "" {
newIdentity := api.IdentityInfo{}
err := util.Unmarshal([]byte(c.dynamicIdentity.json), &newIdentity, "addIdentity")
if err != nil {
return errors.Wrap(err, "Invalid value for --json option")
}
req.IdentityInfo = newIdentity
} else {
req.IdentityInfo = c.dynamicIdentity.add.IdentityInfo
req.IdentityInfo.Attributes = c.clientCfg.ID.Attributes
}

return errors.Errorf("Not Implemented")
req.ID = args[0]
req.CAName = c.clientCfg.CAName
resp, err := id.AddIdentity(req)
if err != nil {
return err
}

fmt.Printf("Successfully added identity: %+v\n", resp)

return nil
}

// The client side logic for modifying an identity
func (c *ClientCmd) runModifyIdentity(args []string) error {
log.Debug("Entered runModifyIdentity")
log.Debugf("Entered runModifyIdentity: %+v", c.dynamicIdentity)

req := &api.ModifyIdentityRequest{}

id, err := c.loadMyIdentity()
if err != nil {
return err
}

// TODO
if c.dynamicIdentity.json != "" {
modifyIdentity := &api.IdentityInfo{}
err := util.Unmarshal([]byte(c.dynamicIdentity.json), modifyIdentity, "modifyIdentity")
if err != nil {
return errors.Wrap(err, "Invalid value for --json option")
}
req.IdentityInfo = *modifyIdentity
} else {
req.IdentityInfo = c.dynamicIdentity.modify.IdentityInfo
req.IdentityInfo.Attributes = c.clientCfg.ID.Attributes
}

req.ID = args[0]
req.CAName = c.clientCfg.CAName
resp, err := id.ModifyIdentity(req)
if err != nil {
return err
}

return errors.Errorf("Not Implemented")
fmt.Printf("Successfully modified identity: %+v\n", resp)

return nil
}

// The client side logic for removing an identity
func (c *ClientCmd) runRemoveIdentity(args []string) error {
log.Debug("Entered runRemoveIdentity")
log.Debugf("Entered runRemoveIdentity: %+v", c.dynamicIdentity)

// TODO
id, err := c.loadMyIdentity()
if err != nil {
return err
}

req := &c.dynamicIdentity.remove
req.ID = args[0]
req.CAName = c.clientCfg.CAName
resp, err := id.RemoveIdentity(req)
if err != nil {
return err
}

fmt.Printf("Successfully removed identity: %+v\n", resp)

return nil
}

// checkOtherFlags returs true if other flags besides '--json' are set
// Viper.IsSet does not work correctly if there are defaults defined for
// flags. This is a workaround until this bug is addressed in Viper.
// Viper Bug: https://github.com/spf13/viper/issues/276
func checkOtherFlags(cmd *cobra.Command) bool {
checkFlags := []string{"id", "type", "affiliation", "secret", "maxenrollments", "attrs"}
flags := cmd.Flags()
for _, checkFlag := range checkFlags {
flag := flags.Lookup(checkFlag)
if flag != nil {
if flag.Changed {
return true
}
}
}

return errors.Errorf("Not Implemented")
return false
}

func argsCheck(args []string) error {
Expand Down
24 changes: 24 additions & 0 deletions cmd/fabric-ca-client/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -527,6 +527,30 @@ func TestIdentityCmd(t *testing.T) {
err = RunMain([]string{
cmdName, "identity", "remove", "testuser"})
assert.Error(t, err, "Should have failed, not yet implemented")

err = RunMain([]string{
cmdName, "identity", "add", "testuser", "--json", `{"type": "peer"}`, "--type", "peer"})
if assert.Error(t, err, "Should have failed") {
assert.Contains(t, err.Error(), "Can't use 'json' flag in conjunction with other flags")
}

err = RunMain([]string{
cmdName, "identity", "add", "testuser", "--json", `{"type": "peer"}`, "--attrs", "hf.Revoker=true"})
if assert.Error(t, err, "Should have failed") {
assert.Contains(t, err.Error(), "Can't use 'json' flag in conjunction with other flags")
}

err = RunMain([]string{
cmdName, "identity", "modify", "testuser", "--json", `{"type": "peer"}`, "--type", "peer"})
if assert.Error(t, err, "Should have failed") {
assert.Contains(t, err.Error(), "Can't use 'json' flag in conjunction with other flags")
}

err = RunMain([]string{
cmdName, "identity", "modify", "testuser", "--json", `{"type": "peer"}`, "--affiliation", "org1"})
if assert.Error(t, err, "Should have failed") {
assert.Contains(t, err.Error(), "Can't use 'json' flag in conjunction with other flags")
}
}

// Verify the certificate has attribute 'name' with a value of 'val'
Expand Down
32 changes: 29 additions & 3 deletions lib/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -332,7 +332,7 @@ func (c *Client) StoreMyIdentity(cert []byte) error {

// LoadIdentity loads an identity from disk
func (c *Client) LoadIdentity(keyFile, certFile string) (*Identity, error) {
log.Debug("Loading identity: keyFile=%s, certFile=%s", keyFile, certFile)
log.Debugf("Loading identity: keyFile=%s, certFile=%s", keyFile, certFile)
err := c.Init()
if err != nil {
return nil, err
Expand Down Expand Up @@ -383,7 +383,7 @@ func (c *Client) GetCertFilePath() string {
return c.certFile
}

// NewGet create a new GET request
// newGet create a new GET request
func (c *Client) newGet(endpoint string) (*http.Request, error) {
curl, err := c.getURL(endpoint)
if err != nil {
Expand All @@ -396,6 +396,32 @@ func (c *Client) newGet(endpoint string) (*http.Request, error) {
return req, nil
}

// newPut create a new PUT request
func (c *Client) newPut(endpoint string, reqBody []byte) (*http.Request, error) {
curl, err := c.getURL(endpoint)
if err != nil {
return nil, err
}
req, err := http.NewRequest("PUT", curl, bytes.NewReader(reqBody))
if err != nil {
return nil, errors.Wrapf(err, "Failed creating PUT request for %s", curl)
}
return req, nil
}

// newDelete create a new DELETE request
func (c *Client) newDelete(endpoint string) (*http.Request, error) {
curl, err := c.getURL(endpoint)
if err != nil {
return nil, err
}
req, err := http.NewRequest("DELETE", curl, bytes.NewReader([]byte{}))
if err != nil {
return nil, errors.Wrapf(err, "Failed creating DELETE request for %s", curl)
}
return req, nil
}

// NewPost create a new post request
func (c *Client) newPost(endpoint string, reqBody []byte) (*http.Request, error) {
curl, err := c.getURL(endpoint)
Expand All @@ -422,7 +448,7 @@ func (c *Client) SendReq(req *http.Request, result interface{}) (err error) {

resp, err := c.httpClient.Do(req)
if err != nil {
return errors.Wrapf(err, "POST failure of request: %s", reqStr)
return errors.Wrapf(err, "%s failure of request: %s", req.Method, reqStr)
}
var respBody []byte
if resp.Body != nil {
Expand Down
105 changes: 102 additions & 3 deletions lib/identity.go
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,78 @@ func (i *Identity) GetAllIdentities(caname string) (*api.GetAllIDsResponse, erro
return result, nil
}

// IdentityResponse is the response from the any add/modify/remove identity call
type IdentityResponse struct {
api.IdentityInfo `mapstructure:",squash"`
Secret string `json:"secret"`
CAName string `json:"caname"`
}

// AddIdentity adds a new identity to the server
func (i *Identity) AddIdentity(req *api.AddIdentityRequest) (*IdentityResponse, error) {
log.Debugf("Entering identity.AddIdentity with request: %+v", req)
if req.ID == "" {
return nil, errors.New("Adding identity with no 'ID' set")
}

reqBody, err := util.Marshal(req, "addIdentity")
if err != nil {
return nil, err
}

// Send a post to the "identities" endpoint with req as body
result := &IdentityResponse{}
err = i.Post("identities", reqBody, result)
if err != nil {
return nil, err
}

log.Debug("Successfully added new identity")
return result, nil
}

// ModifyIdentity modifies an existing identity on the server
func (i *Identity) ModifyIdentity(req *api.ModifyIdentityRequest) (*IdentityResponse, error) {
log.Debugf("Entering identity.ModifyIdentity with request: %+v", req)
if req.ID == "" {
return nil, errors.New("Name of identity to be modified not specified")
}

reqBody, err := util.Marshal(req, "modifyIdentity")
if err != nil {
return nil, err
}

// Send a put to the "identities" endpoint with req as body
result := &IdentityResponse{}
err = i.Put(fmt.Sprintf("identities/%s", req.ID), reqBody, result)
if err != nil {
return nil, err
}

log.Debug("Successfully modified identity")
return result, nil
}

// RemoveIdentity removes a new identity from the server
func (i *Identity) RemoveIdentity(req *api.RemoveIdentityRequest) (*IdentityResponse, error) {
log.Debugf("Entering identity.RemoveIdentity with request: %+v", req)
id := req.ID
if id == "" {
return nil, errors.New("Name of the identity to removed is required")
}

// Send a delete to the "identities" endpoint id as a path parameter
result := &IdentityResponse{}
err := i.Delete(fmt.Sprintf("identities/%s", id), req.CAName, result)
if err != nil {
return nil, err
}

log.Debugf("Successfully removed identity: %s", id)
return result, nil
}

// Store writes my identity info to disk
func (i *Identity) Store() error {
if i.client == nil {
Expand All @@ -240,9 +312,36 @@ func (i *Identity) Get(endpoint, caname string, result interface{}) error {
return err
}
if caname != "" {
url := req.URL.Query()
url.Add("ca", caname)
req.URL.RawQuery = url.Encode()
addQueryParm(req, "ca", caname)
}
err = i.addTokenAuthHdr(req, nil)
if err != nil {
return err
}
return i.client.SendReq(req, result)
}

// Put sends a put request to an endpoint
func (i *Identity) Put(endpoint string, reqBody []byte, result interface{}) error {
req, err := i.client.newPut(endpoint, reqBody)
if err != nil {
return err
}
err = i.addTokenAuthHdr(req, reqBody)
if err != nil {
return err
}
return i.client.SendReq(req, result)
}

// Delete sends a delete request to an endpoint
func (i *Identity) Delete(endpoint, caname string, result interface{}) error {
req, err := i.client.newDelete(endpoint)
if err != nil {
return err
}
if caname != "" {
addQueryParm(req, "ca", caname)
}
err = i.addTokenAuthHdr(req, nil)
if err != nil {
Expand Down
Loading

0 comments on commit 7a17a94

Please sign in to comment.