1
// Copyright 2014 Canonical Ltd.
2
// Licensed under the AGPLv3, see LICENCE file for details.
10
"gopkg.in/macaroon.v1"
12
"github.com/juju/errors"
13
"github.com/juju/loggo"
14
"gopkg.in/juju/names.v2"
16
"github.com/juju/juju/api/base"
17
"github.com/juju/juju/api/modelmanager"
18
"github.com/juju/juju/apiserver/params"
21
var logger = loggo.GetLogger("juju.api.usermanager")
23
// Client provides methods that the Juju client command uses to interact
24
// with users stored in the Juju Server.
27
facade base.FacadeCaller
30
// NewClient creates a new `Client` based on an existing authenticated API
32
func NewClient(st base.APICallCloser) *Client {
33
frontend, backend := base.NewClientFacade(st, "UserManager")
34
return &Client{ClientFacade: frontend, facade: backend}
37
// AddUser creates a new local user in the controller, sharing with that user any specified models.
38
func (c *Client) AddUser(
39
username, displayName, password, access string, modelUUIDs ...string,
40
) (_ names.UserTag, secretKey []byte, _ error) {
41
if !names.IsValidUser(username) {
42
return names.UserTag{}, nil, fmt.Errorf("invalid user name %q", username)
44
modelTags := make([]string, len(modelUUIDs))
45
for i, uuid := range modelUUIDs {
46
modelTags[i] = names.NewModelTag(uuid).String()
49
var accessPermission params.UserAccessPermission
51
if len(modelTags) > 0 {
52
accessPermission, err = modelmanager.ParseModelAccess(access)
54
return names.UserTag{}, nil, errors.Trace(err)
58
userArgs := params.AddUsers{
59
Users: []params.AddUser{{
61
DisplayName: displayName,
63
SharedModelTags: modelTags,
64
ModelAccess: accessPermission}},
66
var results params.AddUserResults
67
err = c.facade.FacadeCall("AddUser", userArgs, &results)
69
return names.UserTag{}, nil, errors.Trace(err)
71
if count := len(results.Results); count != 1 {
72
logger.Errorf("expected 1 result, got %#v", results)
73
return names.UserTag{}, nil, errors.Errorf("expected 1 result, got %d", count)
75
result := results.Results[0]
76
if result.Error != nil {
77
return names.UserTag{}, nil, errors.Trace(result.Error)
79
tag, err := names.ParseUserTag(result.Tag)
81
return names.UserTag{}, nil, errors.Trace(err)
83
return tag, result.SecretKey, nil
86
func (c *Client) userCall(username string, methodCall string) error {
87
if !names.IsValidUser(username) {
88
return errors.Errorf("%q is not a valid username", username)
90
tag := names.NewUserTag(username)
92
var results params.ErrorResults
93
args := params.Entities{
94
[]params.Entity{{tag.String()}},
96
err := c.facade.FacadeCall(methodCall, args, &results)
98
return errors.Trace(err)
100
return results.OneError()
103
// DisableUser disables a user. If the user is already disabled, the action
104
// is considered a success.
105
func (c *Client) DisableUser(username string) error {
106
return c.userCall(username, "DisableUser")
109
// EnableUser enables a users. If the user is already enabled, the action is
110
// considered a success.
111
func (c *Client) EnableUser(username string) error {
112
return c.userCall(username, "EnableUser")
115
// RemoveUser deletes a user. That is it permanently removes the user, while
116
// retaining the record of the user to maintain provenance.
117
func (c *Client) RemoveUser(username string) error {
118
return c.userCall(username, "RemoveUser")
121
// IncludeDisabled is a type alias to avoid bare true/false values
122
// in calls to the client method.
123
type IncludeDisabled bool
126
// ActiveUsers indicates to only return active users.
127
ActiveUsers IncludeDisabled = false
128
// AllUsers indicates that both enabled and disabled users should be
130
AllUsers IncludeDisabled = true
133
// UserInfo returns information about the specified users. If no users are
134
// specified, the call should return all users. If includeDisabled is set to
135
// ActiveUsers, only enabled users are returned.
136
func (c *Client) UserInfo(usernames []string, all IncludeDisabled) ([]params.UserInfo, error) {
137
var results params.UserInfoResults
138
var entities []params.Entity
139
for _, username := range usernames {
140
if !names.IsValidUser(username) {
141
return nil, errors.Errorf("%q is not a valid username", username)
143
tag := names.NewUserTag(username)
144
entities = append(entities, params.Entity{Tag: tag.String()})
146
args := params.UserInfoRequest{
148
IncludeDisabled: bool(all),
150
err := c.facade.FacadeCall("UserInfo", args, &results)
152
return nil, errors.Trace(err)
154
// Only need to look for errors if users were explicitly specified, because
155
// if we didn't ask for any, we should get all, and we shouldn't get any
156
// errors for listing all. We care here because we index into the users
158
if len(results.Results) == len(usernames) {
159
var errorStrings []string
160
for i, result := range results.Results {
161
if result.Error != nil {
162
annotated := errors.Annotate(result.Error, usernames[i])
163
errorStrings = append(errorStrings, annotated.Error())
166
if len(errorStrings) > 0 {
167
return nil, errors.New(strings.Join(errorStrings, ", "))
170
info := []params.UserInfo{}
171
for i, result := range results.Results {
172
if result.Result == nil {
173
return nil, errors.Errorf("unexpected nil result at position %d", i)
175
info = append(info, *result.Result)
180
// SetPassword changes the password for the specified user.
181
func (c *Client) SetPassword(username, password string) error {
182
if !names.IsValidUser(username) {
183
return errors.Errorf("%q is not a valid username", username)
185
tag := names.NewUserTag(username)
186
args := params.EntityPasswords{
187
Changes: []params.EntityPassword{{
189
Password: password}},
191
var results params.ErrorResults
192
err := c.facade.FacadeCall("SetPassword", args, &results)
196
return results.OneError()
199
// CreateLocalLoginMacaroon creates a local login macaroon for the
200
// authenticated user.
201
func (c *Client) CreateLocalLoginMacaroon(tag names.UserTag) (*macaroon.Macaroon, error) {
202
args := params.Entities{Entities: []params.Entity{{tag.String()}}}
203
var results params.MacaroonResults
204
if err := c.facade.FacadeCall("CreateLocalLoginMacaroon", args, &results); err != nil {
205
return nil, errors.Trace(err)
207
if n := len(results.Results); n != 1 {
208
logger.Errorf("expected 1 result, got %#v", results)
209
return nil, errors.Errorf("expected 1 result, got %d", n)
211
result := results.Results[0]
212
if result.Error != nil {
213
return nil, errors.Trace(result.Error)
215
return result.Result, nil