github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/api/usermanager/client.go (about)

     1  // Copyright 2014 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package usermanager
     5  
     6  import (
     7  	"fmt"
     8  	"strings"
     9  
    10  	"github.com/juju/errors"
    11  	"github.com/juju/loggo"
    12  	"gopkg.in/juju/names.v2"
    13  
    14  	"github.com/juju/juju/api/base"
    15  	"github.com/juju/juju/apiserver/params"
    16  )
    17  
    18  var logger = loggo.GetLogger("juju.api.usermanager")
    19  
    20  // Client provides methods that the Juju client command uses to interact
    21  // with users stored in the Juju Server.
    22  type Client struct {
    23  	base.ClientFacade
    24  	facade base.FacadeCaller
    25  }
    26  
    27  // NewClient creates a new `Client` based on an existing authenticated API
    28  // connection.
    29  func NewClient(st base.APICallCloser) *Client {
    30  	frontend, backend := base.NewClientFacade(st, "UserManager")
    31  	return &Client{ClientFacade: frontend, facade: backend}
    32  }
    33  
    34  // AddUser creates a new local user in the controller, sharing with that user any specified models.
    35  func (c *Client) AddUser(
    36  	username, displayName, password string,
    37  ) (_ names.UserTag, secretKey []byte, _ error) {
    38  	if !names.IsValidUser(username) {
    39  		return names.UserTag{}, nil, fmt.Errorf("invalid user name %q", username)
    40  	}
    41  
    42  	userArgs := params.AddUsers{
    43  		Users: []params.AddUser{{
    44  			Username:    username,
    45  			DisplayName: displayName,
    46  			Password:    password,
    47  		}},
    48  	}
    49  	var results params.AddUserResults
    50  	err := c.facade.FacadeCall("AddUser", userArgs, &results)
    51  	if err != nil {
    52  		return names.UserTag{}, nil, errors.Trace(err)
    53  	}
    54  	if count := len(results.Results); count != 1 {
    55  		logger.Errorf("expected 1 result, got %#v", results)
    56  		return names.UserTag{}, nil, errors.Errorf("expected 1 result, got %d", count)
    57  	}
    58  	result := results.Results[0]
    59  	if result.Error != nil {
    60  		return names.UserTag{}, nil, errors.Trace(result.Error)
    61  	}
    62  	tag, err := names.ParseUserTag(result.Tag)
    63  	if err != nil {
    64  		return names.UserTag{}, nil, errors.Trace(err)
    65  	}
    66  	return tag, result.SecretKey, nil
    67  }
    68  
    69  func (c *Client) userCall(username string, methodCall string) error {
    70  	if !names.IsValidUser(username) {
    71  		return errors.Errorf("%q is not a valid username", username)
    72  	}
    73  	tag := names.NewUserTag(username)
    74  
    75  	var results params.ErrorResults
    76  	args := params.Entities{
    77  		[]params.Entity{{tag.String()}},
    78  	}
    79  	err := c.facade.FacadeCall(methodCall, args, &results)
    80  	if err != nil {
    81  		return errors.Trace(err)
    82  	}
    83  	return results.OneError()
    84  }
    85  
    86  // DisableUser disables a user.  If the user is already disabled, the action
    87  // is considered a success.
    88  func (c *Client) DisableUser(username string) error {
    89  	return c.userCall(username, "DisableUser")
    90  }
    91  
    92  // EnableUser enables a users.  If the user is already enabled, the action is
    93  // considered a success.
    94  func (c *Client) EnableUser(username string) error {
    95  	return c.userCall(username, "EnableUser")
    96  }
    97  
    98  // RemoveUser deletes a user. That is it permanently removes the user, while
    99  // retaining the record of the user to maintain provenance.
   100  func (c *Client) RemoveUser(username string) error {
   101  	return c.userCall(username, "RemoveUser")
   102  }
   103  
   104  // IncludeDisabled is a type alias to avoid bare true/false values
   105  // in calls to the client method.
   106  type IncludeDisabled bool
   107  
   108  var (
   109  	// ActiveUsers indicates to only return active users.
   110  	ActiveUsers IncludeDisabled = false
   111  	// AllUsers indicates that both enabled and disabled users should be
   112  	// returned.
   113  	AllUsers IncludeDisabled = true
   114  )
   115  
   116  // UserInfo returns information about the specified users.  If no users are
   117  // specified, the call should return all users.  If includeDisabled is set to
   118  // ActiveUsers, only enabled users are returned.
   119  func (c *Client) UserInfo(usernames []string, all IncludeDisabled) ([]params.UserInfo, error) {
   120  	var results params.UserInfoResults
   121  	var entities []params.Entity
   122  	for _, username := range usernames {
   123  		if !names.IsValidUser(username) {
   124  			return nil, errors.Errorf("%q is not a valid username", username)
   125  		}
   126  		tag := names.NewUserTag(username)
   127  		entities = append(entities, params.Entity{Tag: tag.String()})
   128  	}
   129  	args := params.UserInfoRequest{
   130  		Entities:        entities,
   131  		IncludeDisabled: bool(all),
   132  	}
   133  	err := c.facade.FacadeCall("UserInfo", args, &results)
   134  	if err != nil {
   135  		return nil, errors.Trace(err)
   136  	}
   137  	// Only need to look for errors if users were explicitly specified, because
   138  	// if we didn't ask for any, we should get all, and we shouldn't get any
   139  	// errors for listing all.  We care here because we index into the users
   140  	// slice.
   141  	if len(results.Results) == len(usernames) {
   142  		var errorStrings []string
   143  		for i, result := range results.Results {
   144  			if result.Error != nil {
   145  				annotated := errors.Annotate(result.Error, usernames[i])
   146  				errorStrings = append(errorStrings, annotated.Error())
   147  			}
   148  		}
   149  		// TODO(wallyworld) - we should return these errors to the caller so that any
   150  		// users which are successfully found can be handled.
   151  		if len(errorStrings) > 0 {
   152  			return nil, errors.New(strings.Join(errorStrings, ", "))
   153  		}
   154  	}
   155  	info := []params.UserInfo{}
   156  	for i, result := range results.Results {
   157  		if result.Result == nil {
   158  			return nil, errors.Errorf("unexpected nil result at position %d", i)
   159  		}
   160  		info = append(info, *result.Result)
   161  	}
   162  	return info, nil
   163  }
   164  
   165  // SetPassword changes the password for the specified user.
   166  func (c *Client) SetPassword(username, password string) error {
   167  	if !names.IsValidUser(username) {
   168  		return errors.Errorf("%q is not a valid username", username)
   169  	}
   170  	tag := names.NewUserTag(username)
   171  	args := params.EntityPasswords{
   172  		Changes: []params.EntityPassword{{
   173  			Tag:      tag.String(),
   174  			Password: password}},
   175  	}
   176  	var results params.ErrorResults
   177  	err := c.facade.FacadeCall("SetPassword", args, &results)
   178  	if err != nil {
   179  		return err
   180  	}
   181  	return results.OneError()
   182  }
   183  
   184  // ResetPassword resets password for the specified user.
   185  func (c *Client) ResetPassword(username string) ([]byte, error) {
   186  	if !names.IsValidUser(username) {
   187  		return nil, fmt.Errorf("invalid user name %q", username)
   188  	}
   189  
   190  	in := params.Entities{
   191  		Entities: []params.Entity{{
   192  			Tag: names.NewUserTag(username).String(),
   193  		}},
   194  	}
   195  	var out params.AddUserResults
   196  	err := c.facade.FacadeCall("ResetPassword", in, &out)
   197  	if err != nil {
   198  		return nil, errors.Trace(err)
   199  	}
   200  	if count := len(out.Results); count != 1 {
   201  		logger.Errorf("expected 1 result, got %#v", out)
   202  		return nil, errors.Errorf("expected 1 result, got %d", count)
   203  	}
   204  	result := out.Results[0]
   205  	if result.Error != nil {
   206  		return nil, errors.Trace(result.Error)
   207  	}
   208  	return result.SecretKey, nil
   209  }