Merge lp:~thumper/juju-core/user-display-name into lp:~go-bot/juju-core/trunk
- user-display-name
- Merge into trunk
Status: | Needs review |
---|---|
Proposed branch: | lp:~thumper/juju-core/user-display-name |
Merge into: | lp:~go-bot/juju-core/trunk |
Diff against target: |
1164 lines (+351/-240) 22 files modified
agent/bootstrap.go (+1/-1) cmd/juju/authorizedkeys_test.go (+4/-8) cmd/juju/user_add.go (+43/-28) cmd/juju/user_add_test.go (+148/-76) juju/conn_test.go (+1/-1) juju/testing/conn.go (+6/-0) provider/dummy/environs.go (+1/-1) state/api/params/params.go (+12/-2) state/api/usermanager/client.go (+8/-7) state/api/usermanager/client_test.go (+18/-5) state/apiserver/charms_test.go (+2/-5) state/apiserver/client/api_test.go (+1/-2) state/apiserver/client/client_test.go (+2/-5) state/apiserver/login_test.go (+1/-2) state/apiserver/usermanager/usermanager.go (+9/-4) state/apiserver/usermanager/usermanager_test.go (+24/-19) state/compat_test.go (+1/-1) state/conn_test.go (+1/-1) state/megawatcher_internal_test.go (+1/-1) state/state_test.go (+2/-2) state/user.go (+16/-9) state/user_test.go (+49/-60) |
To merge this branch: | bzr merge lp:~thumper/juju-core/user-display-name |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Juju Engineering | Pending | ||
Review via email: mp+221823@code.launchpad.net |
Commit message
Description of the change
Add a display name option for new users
This ended up being more work than I hoped as I refactored the apiserver
parts, many tests, and made sure there was backwards compatability with
older client apis.
Also changed how the CLI command was tested so it didn't need a
JujuConnSuite.
Menno Finlay-Smits (menno.smits) wrote : | # |
LGTM - only minor comments and praise added in-line. The user add tests
look much better.
One thing: can this actually land without schema migration support?
https:/
File cmd/juju/
https:/
cmd/juju/
Sorry for missing this when I implemented --password
https:/
File cmd/juju/
https:/
cmd/juju/
errors.Trace() here too?
https:/
File state/api/
https:/
state/api/
[]params.
These names were terrible! Glad you've fixed it.
https:/
File state/api/
https:/
state/api/
[]params.
Password: password}},
This is much more readable.
https:/
File state/api/
https:/
state/api/
s.APIState.
Maybe add a comment here about why s.APIState.
used here instead of s.usermanager.
to think about it for a bit.
https:/
File state/apiserver
https:/
state/apiserver
utils.RandomPas
Play devil's advocate: this change means the user has a fixed password
instead of a random one. Are you sure that doesn't negatively impact any
existing tests? I can't think of why it would but it is a test
functionality change.
https:/
File state/user_test.go (right):
https:/
state/user_
{
addSomeUser might be a better name, indicating that a test/throwaway
user is being created. It also hints at the username being used.
Jesse Meek (waigani) wrote : | # |
On 2014/06/03 05:17:26, menn0 wrote:
> LGTM - only minor comments and praise added in-line. The user add
tests look
> much better.
> One thing: can this actually land without schema migration support?
> https:/
> File cmd/juju/
https:/
> cmd/juju/
> Sorry for missing this when I implemented --password
> https:/
> File cmd/juju/
https:/
> cmd/juju/
> errors.Trace() here too?
https:/
> File state/api/
https:/
> state/api/
params.
> []params.
> These names were terrible! Glad you've fixed it.
https:/
> File state/api/
https:/
> state/api/
[]params.
> username, DisplayName: displayName, Password: password}},
> This is much more readable.
https:/
> File state/api/
https:/
> state/api/
s.APIState.
> "", "AddUser", userArgs, results)
> Maybe add a comment here about why s.APIState.
is used
> here instead of s.usermanager.
think about
> it for a bit.
https:/
> File state/apiserver
https:/
> state/apiserver
utils.RandomPas
> Play devil's advocate: this change means the user has a fixed password
instead
> of a random one. Are you sure that doesn't negatively impact any
existing tests?
> I can't think of why it would but it is a test functionality change.
> https:/
> File state/user_test.go (right):
https:/
> state/user_
*state.User {
> addSomeUser might be a better name, indicating that a test/throwaway
user is
> being created. It also hints at the username being used.
LGTM. Just one suggestion inline. The bulk of the changes look to be
updating AddUser to take an extra arg. As to schema migration support, I
think adding fields is fine - its when we change or remove them ...
Jesse Meek (waigani) wrote : | # |
https:/
File state/user_test.go (right):
https:/
state/user_
It seems you are making a user for every test. Why not makeUser in
SetUpTest?
Tim Penhey (thumper) wrote : | # |
New branch is moving to github.
https:/
File state/api/
https:/
state/api/
s.APIState.
On 2014/06/03 05:17:26, menn0 wrote:
> Maybe add a comment here about why s.APIState.
is used
> here instead of s.usermanager.
think about
> it for a bit.
Done.
https:/
File state/apiserver
https:/
state/apiserver
utils.RandomPas
On 2014/06/03 05:17:26, menn0 wrote:
> Play devil's advocate: this change means the user has a fixed password
instead
> of a random one. Are you sure that doesn't negatively impact any
existing tests?
> I can't think of why it would but it is a test functionality change.
Sure, but the tests all still pass :-)
I'm considering a way to change the way we create test fixture objects.
A cunning plan.
https:/
File state/user_test.go (right):
https:/
state/user_
On 2014/06/03 21:29:26, waigani wrote:
> It seems you are making a user for every test. Why not makeUser in
SetUpTest?
Not quite every test :-)
Unmerged revisions
- 2819. By Tim Penhey
-
Fix the test.
- 2818. By Tim Penhey
-
Lots of test rework.
- 2817. By Tim Penhey
-
Merge trunk.
- 2816. By Tim Penhey
-
Minor cleanups
- 2815. By Tim Penhey
-
Update other test call sites when they don't care about user details.
- 2814. By Tim Penhey
-
Update client calls
- 2813. By Tim Penhey
-
Fix the state tests.
- 2812. By Tim Penhey
-
The state changes for more params.
- 2811. By Tim Penhey
-
Extra params for AddUser.
Preview Diff
1 | === modified file 'agent/bootstrap.go' | |||
2 | --- agent/bootstrap.go 2014-05-08 07:04:54 +0000 | |||
3 | +++ agent/bootstrap.go 2014-06-03 04:22:37 +0000 | |||
4 | @@ -121,7 +121,7 @@ | |||
5 | 121 | func initBootstrapUser(st *state.State, passwordHash string) error { | 121 | func initBootstrapUser(st *state.State, passwordHash string) error { |
6 | 122 | logger.Debugf("adding admin user") | 122 | logger.Debugf("adding admin user") |
7 | 123 | // Set up initial authentication. | 123 | // Set up initial authentication. |
9 | 124 | u, err := st.AddUser("admin", "") | 124 | u, err := st.AddUser("admin", "", "") |
10 | 125 | if err != nil { | 125 | if err != nil { |
11 | 126 | return err | 126 | return err |
12 | 127 | } | 127 | } |
13 | 128 | 128 | ||
14 | === modified file 'cmd/juju/authorizedkeys_test.go' | |||
15 | --- cmd/juju/authorizedkeys_test.go 2014-05-22 06:03:06 +0000 | |||
16 | +++ cmd/juju/authorizedkeys_test.go 2014-06-03 04:22:37 +0000 | |||
17 | @@ -139,8 +139,7 @@ | |||
18 | 139 | key1 := sshtesting.ValidKeyOne.Key + " user@host" | 139 | key1 := sshtesting.ValidKeyOne.Key + " user@host" |
19 | 140 | key2 := sshtesting.ValidKeyTwo.Key + " another@host" | 140 | key2 := sshtesting.ValidKeyTwo.Key + " another@host" |
20 | 141 | s.setAuthorizedKeys(c, key1, key2) | 141 | s.setAuthorizedKeys(c, key1, key2) |
23 | 142 | _, err := s.State.AddUser("fred", "password") | 142 | s.AddUser(c, "fred") |
22 | 143 | c.Assert(err, gc.IsNil) | ||
24 | 144 | 143 | ||
25 | 145 | context, err := coretesting.RunCommand(c, envcmd.Wrap(&ListKeysCommand{}), "--user", "fred") | 144 | context, err := coretesting.RunCommand(c, envcmd.Wrap(&ListKeysCommand{}), "--user", "fred") |
26 | 146 | c.Assert(err, gc.IsNil) | 145 | c.Assert(err, gc.IsNil) |
27 | @@ -174,8 +173,7 @@ | |||
28 | 174 | func (s *AddKeySuite) TestAddKeyNonDefaultUser(c *gc.C) { | 173 | func (s *AddKeySuite) TestAddKeyNonDefaultUser(c *gc.C) { |
29 | 175 | key1 := sshtesting.ValidKeyOne.Key + " user@host" | 174 | key1 := sshtesting.ValidKeyOne.Key + " user@host" |
30 | 176 | s.setAuthorizedKeys(c, key1) | 175 | s.setAuthorizedKeys(c, key1) |
33 | 177 | _, err := s.State.AddUser("fred", "password") | 176 | s.AddUser(c, "fred") |
32 | 178 | c.Assert(err, gc.IsNil) | ||
34 | 179 | 177 | ||
35 | 180 | key2 := sshtesting.ValidKeyTwo.Key + " another@host" | 178 | key2 := sshtesting.ValidKeyTwo.Key + " another@host" |
36 | 181 | context, err := coretesting.RunCommand(c, envcmd.Wrap(&AddKeysCommand{}), "--user", "fred", key2) | 179 | context, err := coretesting.RunCommand(c, envcmd.Wrap(&AddKeysCommand{}), "--user", "fred", key2) |
37 | @@ -206,8 +204,7 @@ | |||
38 | 206 | key1 := sshtesting.ValidKeyOne.Key + " user@host" | 204 | key1 := sshtesting.ValidKeyOne.Key + " user@host" |
39 | 207 | key2 := sshtesting.ValidKeyTwo.Key + " another@host" | 205 | key2 := sshtesting.ValidKeyTwo.Key + " another@host" |
40 | 208 | s.setAuthorizedKeys(c, key1, key2) | 206 | s.setAuthorizedKeys(c, key1, key2) |
43 | 209 | _, err := s.State.AddUser("fred", "password") | 207 | s.AddUser(c, "fred") |
42 | 210 | c.Assert(err, gc.IsNil) | ||
44 | 211 | 208 | ||
45 | 212 | context, err := coretesting.RunCommand(c, envcmd.Wrap(&DeleteKeysCommand{}), | 209 | context, err := coretesting.RunCommand(c, envcmd.Wrap(&DeleteKeysCommand{}), |
46 | 213 | "--user", "fred", sshtesting.ValidKeyTwo.Fingerprint) | 210 | "--user", "fred", sshtesting.ValidKeyTwo.Fingerprint) |
47 | @@ -240,8 +237,7 @@ | |||
48 | 240 | func (s *ImportKeySuite) TestImportKeyNonDefaultUser(c *gc.C) { | 237 | func (s *ImportKeySuite) TestImportKeyNonDefaultUser(c *gc.C) { |
49 | 241 | key1 := sshtesting.ValidKeyOne.Key + " user@host" | 238 | key1 := sshtesting.ValidKeyOne.Key + " user@host" |
50 | 242 | s.setAuthorizedKeys(c, key1) | 239 | s.setAuthorizedKeys(c, key1) |
53 | 243 | _, err := s.State.AddUser("fred", "password") | 240 | s.AddUser(c, "fred") |
52 | 244 | c.Assert(err, gc.IsNil) | ||
54 | 245 | 241 | ||
55 | 246 | context, err := coretesting.RunCommand(c, envcmd.Wrap(&ImportKeysCommand{}), "--user", "fred", "lp:validuser") | 242 | context, err := coretesting.RunCommand(c, envcmd.Wrap(&ImportKeysCommand{}), "--user", "fred", "lp:validuser") |
56 | 247 | c.Assert(err, gc.IsNil) | 243 | c.Assert(err, gc.IsNil) |
57 | 248 | 244 | ||
58 | === modified file 'cmd/juju/user_add.go' | |||
59 | --- cmd/juju/user_add.go 2014-05-26 00:06:16 +0000 | |||
60 | +++ cmd/juju/user_add.go 2014-06-03 04:22:37 +0000 | |||
61 | @@ -8,6 +8,7 @@ | |||
62 | 8 | "os" | 8 | "os" |
63 | 9 | "strings" | 9 | "strings" |
64 | 10 | 10 | ||
65 | 11 | "github.com/juju/errors" | ||
66 | 11 | "launchpad.net/gnuflag" | 12 | "launchpad.net/gnuflag" |
67 | 12 | 13 | ||
68 | 13 | "launchpad.net/juju-core/cmd" | 14 | "launchpad.net/juju-core/cmd" |
69 | @@ -33,40 +34,49 @@ | |||
70 | 33 | 34 | ||
71 | 34 | type UserAddCommand struct { | 35 | type UserAddCommand struct { |
72 | 35 | envcmd.EnvCommandBase | 36 | envcmd.EnvCommandBase |
76 | 36 | User string | 37 | User string |
77 | 37 | Password string | 38 | DisplayName string |
78 | 38 | outPath string | 39 | Password string |
79 | 40 | outPath string | ||
80 | 39 | } | 41 | } |
81 | 40 | 42 | ||
82 | 41 | func (c *UserAddCommand) Info() *cmd.Info { | 43 | func (c *UserAddCommand) Info() *cmd.Info { |
83 | 42 | return &cmd.Info{ | 44 | return &cmd.Info{ |
84 | 43 | Name: "add", | 45 | Name: "add", |
86 | 44 | Args: "<username> <password>", | 46 | Args: "<username> [<display name>]", |
87 | 45 | Purpose: "adds a user", | 47 | Purpose: "adds a user", |
88 | 46 | Doc: userAddCommandDoc, | 48 | Doc: userAddCommandDoc, |
89 | 47 | } | 49 | } |
90 | 48 | } | 50 | } |
91 | 49 | 51 | ||
92 | 50 | func (c *UserAddCommand) SetFlags(f *gnuflag.FlagSet) { | 52 | func (c *UserAddCommand) SetFlags(f *gnuflag.FlagSet) { |
96 | 51 | f.StringVar(&(c.Password), "password", "", "Password for new user") | 53 | f.StringVar(&c.Password, "password", "", "Password for new user") |
97 | 52 | f.StringVar(&(c.outPath), "o", "", "Output an environment file for new user") | 54 | f.StringVar(&c.outPath, "o", "", "Output an environment file for new user") |
98 | 53 | f.StringVar(&(c.outPath), "output", "", "") | 55 | f.StringVar(&c.outPath, "output", "", "") |
99 | 54 | } | 56 | } |
100 | 55 | 57 | ||
101 | 56 | func (c *UserAddCommand) Init(args []string) error { | 58 | func (c *UserAddCommand) Init(args []string) error { |
104 | 57 | switch len(args) { | 59 | if len(args) == 0 { |
103 | 58 | case 0: | ||
105 | 59 | return fmt.Errorf("no username supplied") | 60 | return fmt.Errorf("no username supplied") |
112 | 60 | case 1: | 61 | } |
113 | 61 | c.User = args[0] | 62 | c.User, args = args[0], args[1:] |
114 | 62 | default: | 63 | if len(args) > 0 { |
115 | 63 | return cmd.CheckEmpty(args[1:]) | 64 | c.DisplayName, args = args[0], args[1:] |
116 | 64 | } | 65 | } |
117 | 65 | return nil | 66 | return cmd.CheckEmpty(args) |
118 | 67 | } | ||
119 | 68 | |||
120 | 69 | type addUserAPI interface { | ||
121 | 70 | AddUser(username, displayname, password string) error | ||
122 | 71 | Close() error | ||
123 | 72 | } | ||
124 | 73 | |||
125 | 74 | var getAddUserAPI = func(c *UserAddCommand) (addUserAPI, error) { | ||
126 | 75 | return juju.NewUserManagerClient(c.EnvName) | ||
127 | 66 | } | 76 | } |
128 | 67 | 77 | ||
129 | 68 | func (c *UserAddCommand) Run(ctx *cmd.Context) error { | 78 | func (c *UserAddCommand) Run(ctx *cmd.Context) error { |
131 | 69 | client, err := juju.NewUserManagerClient(c.EnvName) | 79 | client, err := getAddUserAPI(c) |
132 | 70 | if err != nil { | 80 | if err != nil { |
133 | 71 | return err | 81 | return err |
134 | 72 | } | 82 | } |
135 | @@ -74,19 +84,24 @@ | |||
136 | 74 | if c.Password == "" { | 84 | if c.Password == "" { |
137 | 75 | c.Password, err = utils.RandomPassword() | 85 | c.Password, err = utils.RandomPassword() |
138 | 76 | if err != nil { | 86 | if err != nil { |
140 | 77 | return fmt.Errorf("Failed to generate password: %v", err) | 87 | return errors.Annotate(err, "failed to generate password") |
141 | 78 | } | 88 | } |
142 | 79 | } | 89 | } |
143 | 80 | 90 | ||
145 | 81 | err = client.AddUser(c.User, c.Password) | 91 | err = client.AddUser(c.User, c.DisplayName, c.Password) |
146 | 82 | if err != nil { | 92 | if err != nil { |
147 | 83 | return err | 93 | return err |
148 | 84 | } | 94 | } |
150 | 85 | fmt.Fprintf(ctx.Stdout, "user \"%s\" added with password \"%s\"\n", c.User, c.Password) | 95 | user := c.User |
151 | 96 | if c.DisplayName != "" { | ||
152 | 97 | user = fmt.Sprintf("%s (%s)", c.DisplayName, user) | ||
153 | 98 | } | ||
154 | 99 | |||
155 | 100 | fmt.Fprintf(ctx.Stdout, "user %q added with password %q\n", user, c.Password) | ||
156 | 86 | 101 | ||
157 | 87 | if c.outPath != "" { | 102 | if c.outPath != "" { |
158 | 88 | outPath := NormaliseJenvPath(ctx, c.outPath) | 103 | outPath := NormaliseJenvPath(ctx, c.outPath) |
160 | 89 | err := GenerateUserJenv(c.EnvName, c.User, c.Password, outPath) | 104 | err = GenerateUserJenv(c.EnvName, c.User, c.Password, outPath) |
161 | 90 | if err == nil { | 105 | if err == nil { |
162 | 91 | fmt.Fprintf(ctx.Stdout, "environment file written to %s\n", outPath) | 106 | fmt.Fprintf(ctx.Stdout, "environment file written to %s\n", outPath) |
163 | 92 | } | 107 | } |
164 | @@ -101,14 +116,14 @@ | |||
165 | 101 | return ctx.AbsPath(outPath) | 116 | return ctx.AbsPath(outPath) |
166 | 102 | } | 117 | } |
167 | 103 | 118 | ||
169 | 104 | func GenerateUserJenv(envName, user, password, outPath string) (err error) { | 119 | func GenerateUserJenv(envName, user, password, outPath string) error { |
170 | 105 | store, err := configstore.Default() | 120 | store, err := configstore.Default() |
171 | 106 | if err != nil { | 121 | if err != nil { |
173 | 107 | return | 122 | return errors.Trace(err) |
174 | 108 | } | 123 | } |
175 | 109 | storeInfo, err := store.ReadInfo(envName) | 124 | storeInfo, err := store.ReadInfo(envName) |
176 | 110 | if err != nil { | 125 | if err != nil { |
178 | 111 | return | 126 | return errors.Trace(err) |
179 | 112 | } | 127 | } |
180 | 113 | outputInfo := configstore.EnvironInfoData{} | 128 | outputInfo := configstore.EnvironInfoData{} |
181 | 114 | outputInfo.User = user | 129 | outputInfo.User = user |
182 | @@ -117,17 +132,17 @@ | |||
183 | 117 | outputInfo.CACert = storeInfo.APIEndpoint().CACert | 132 | outputInfo.CACert = storeInfo.APIEndpoint().CACert |
184 | 118 | yaml, err := cmd.FormatYaml(outputInfo) | 133 | yaml, err := cmd.FormatYaml(outputInfo) |
185 | 119 | if err != nil { | 134 | if err != nil { |
187 | 120 | return | 135 | return errors.Trace(err) |
188 | 121 | } | 136 | } |
189 | 122 | 137 | ||
190 | 123 | outFile, err := os.Create(outPath) | 138 | outFile, err := os.Create(outPath) |
191 | 124 | if err != nil { | 139 | if err != nil { |
193 | 125 | return | 140 | return errors.Trace(err) |
194 | 126 | } | 141 | } |
195 | 127 | defer outFile.Close() | 142 | defer outFile.Close() |
197 | 128 | _, err = outFile.Write(yaml) | 143 | outFile.Write(yaml) |
198 | 129 | if err != nil { | 144 | if err != nil { |
200 | 130 | return | 145 | return errors.Trace(err) |
201 | 131 | } | 146 | } |
203 | 132 | return | 147 | return nil |
204 | 133 | } | 148 | } |
205 | 134 | 149 | ||
206 | === modified file 'cmd/juju/user_add_test.go' | |||
207 | --- cmd/juju/user_add_test.go 2014-05-26 00:06:16 +0000 | |||
208 | +++ cmd/juju/user_add_test.go 2014-06-03 04:22:37 +0000 | |||
209 | @@ -4,11 +4,10 @@ | |||
210 | 4 | package main | 4 | package main |
211 | 5 | 5 | ||
212 | 6 | import ( | 6 | import ( |
214 | 7 | "bytes" | 7 | "errors" |
215 | 8 | "fmt" | ||
216 | 8 | "io/ioutil" | 9 | "io/ioutil" |
217 | 9 | "path/filepath" | 10 | "path/filepath" |
218 | 10 | "regexp" | ||
219 | 11 | "strings" | ||
220 | 12 | 11 | ||
221 | 13 | jc "github.com/juju/testing/checkers" | 12 | jc "github.com/juju/testing/checkers" |
222 | 14 | gc "launchpad.net/gocheck" | 13 | gc "launchpad.net/gocheck" |
223 | @@ -16,107 +15,180 @@ | |||
224 | 16 | 15 | ||
225 | 17 | "launchpad.net/juju-core/cmd" | 16 | "launchpad.net/juju-core/cmd" |
226 | 18 | "launchpad.net/juju-core/cmd/envcmd" | 17 | "launchpad.net/juju-core/cmd/envcmd" |
228 | 19 | jujutesting "launchpad.net/juju-core/juju/testing" | 18 | "launchpad.net/juju-core/environs/configstore" |
229 | 20 | "launchpad.net/juju-core/testing" | 19 | "launchpad.net/juju-core/testing" |
230 | 21 | ) | 20 | ) |
231 | 22 | 21 | ||
232 | 23 | // All of the functionality of the AddUser api call is contained elsewhere. | 22 | // All of the functionality of the AddUser api call is contained elsewhere. |
233 | 24 | // This suite provides basic tests for the "user add" command | 23 | // This suite provides basic tests for the "user add" command |
234 | 25 | type UserAddCommandSuite struct { | 24 | type UserAddCommandSuite struct { |
236 | 26 | jujutesting.JujuConnSuite | 25 | testing.FakeJujuHomeSuite |
237 | 26 | mockAPI *mockAddUserAPI | ||
238 | 27 | } | 27 | } |
239 | 28 | 28 | ||
240 | 29 | var _ = gc.Suite(&UserAddCommandSuite{}) | 29 | var _ = gc.Suite(&UserAddCommandSuite{}) |
241 | 30 | 30 | ||
242 | 31 | func (s *UserAddCommandSuite) SetUpTest(c *gc.C) { | ||
243 | 32 | s.FakeJujuHomeSuite.SetUpTest(c) | ||
244 | 33 | s.mockAPI = &mockAddUserAPI{} | ||
245 | 34 | s.PatchValue(&getAddUserAPI, func(c *UserAddCommand) (addUserAPI, error) { | ||
246 | 35 | return s.mockAPI, nil | ||
247 | 36 | }) | ||
248 | 37 | } | ||
249 | 38 | |||
250 | 31 | func newUserAddCommand() cmd.Command { | 39 | func newUserAddCommand() cmd.Command { |
251 | 32 | return envcmd.Wrap(&UserAddCommand{}) | 40 | return envcmd.Wrap(&UserAddCommand{}) |
252 | 33 | } | 41 | } |
253 | 34 | 42 | ||
292 | 35 | func (s *UserAddCommandSuite) TestUserAdd(c *gc.C) { | 43 | func (s *UserAddCommandSuite) TestAddUserJustUsername(c *gc.C) { |
293 | 36 | _, err := testing.RunCommand(c, newUserAddCommand(), "foobar") | 44 | context, err := testing.RunCommand(c, newUserAddCommand(), "foobar") |
294 | 37 | c.Assert(err, gc.IsNil) | 45 | c.Assert(err, gc.IsNil) |
295 | 38 | 46 | c.Assert(s.mockAPI.username, gc.Equals, "foobar") | |
296 | 39 | _, err = testing.RunCommand(c, newUserAddCommand(), "foobar") | 47 | c.Assert(s.mockAPI.displayname, gc.Equals, "") |
297 | 40 | c.Assert(err, gc.ErrorMatches, "Failed to create user: user already exists") | 48 | // Password is generated |
298 | 41 | } | 49 | c.Assert(s.mockAPI.password, gc.Not(gc.Equals), "") |
299 | 42 | 50 | expected := fmt.Sprintf(`user "foobar" added with password %q`, s.mockAPI.password) | |
300 | 43 | func (s *UserAddCommandSuite) TestTooManyArgs(c *gc.C) { | 51 | c.Assert(testing.Stdout(context), gc.Equals, expected+"\n") |
301 | 44 | _, err := testing.RunCommand(c, newUserAddCommand(), "foobar", "whoops") | 52 | } |
302 | 45 | c.Assert(err, gc.ErrorMatches, `unrecognized args: \["whoops"\]`) | 53 | |
303 | 46 | } | 54 | func (s *UserAddCommandSuite) TestAddUserUsernameAndDisplayname(c *gc.C) { |
304 | 47 | 55 | context, err := testing.RunCommand(c, newUserAddCommand(), "foobar", "Foo Bar") | |
305 | 48 | func (s *UserAddCommandSuite) TestNotEnoughArgs(c *gc.C) { | 56 | c.Assert(err, gc.IsNil) |
306 | 49 | _, err := testing.RunCommand(c, newUserAddCommand()) | 57 | c.Assert(s.mockAPI.username, gc.Equals, "foobar") |
307 | 50 | c.Assert(err, gc.ErrorMatches, `no username supplied`) | 58 | c.Assert(s.mockAPI.displayname, gc.Equals, "Foo Bar") |
308 | 51 | } | 59 | // Password is generated |
309 | 52 | 60 | c.Assert(s.mockAPI.password, gc.Not(gc.Equals), "") | |
310 | 53 | func (s *UserAddCommandSuite) TestGeneratePassword(c *gc.C) { | 61 | expected := fmt.Sprintf(`user "Foo Bar (foobar)" added with password %q`, s.mockAPI.password) |
311 | 54 | ctx, err := testing.RunCommand(c, newUserAddCommand(), "foobar") | 62 | c.Assert(testing.Stdout(context), gc.Equals, expected+"\n") |
312 | 55 | 63 | } | |
313 | 56 | c.Assert(err, gc.IsNil) | 64 | |
314 | 57 | user, password, filename := parseUserAddStdout(c, ctx) | 65 | func (s *UserAddCommandSuite) TestAddUserUsernameAndDisplaynameWithPassword(c *gc.C) { |
315 | 58 | c.Assert(user, gc.Equals, "foobar") | 66 | context, err := testing.RunCommand(c, newUserAddCommand(), "foobar", "Foo Bar", "--password", "password") |
316 | 59 | // Let's not try to assume too much about the password generation | 67 | c.Assert(err, gc.IsNil) |
317 | 60 | // algorithm other than there will be at least 10 characters. | 68 | c.Assert(s.mockAPI.username, gc.Equals, "foobar") |
318 | 61 | c.Assert(password, gc.Matches, "..........+") | 69 | c.Assert(s.mockAPI.displayname, gc.Equals, "Foo Bar") |
319 | 62 | c.Assert(filename, gc.Equals, "") | 70 | c.Assert(s.mockAPI.password, gc.Equals, "password") |
320 | 63 | } | 71 | expected := `user "Foo Bar (foobar)" added with password "password"` |
321 | 64 | 72 | c.Assert(testing.Stdout(context), gc.Equals, expected+"\n") | |
322 | 65 | func (s *UserAddCommandSuite) TestUserSpecifiedPassword(c *gc.C) { | 73 | } |
323 | 66 | ctx, err := testing.RunCommand(c, newUserAddCommand(), "foobar", "--password", "frogdog") | 74 | |
324 | 67 | c.Assert(err, gc.IsNil) | 75 | func (s *UserAddCommandSuite) TestAddUserErrorResponse(c *gc.C) { |
325 | 68 | 76 | s.mockAPI.failMessage = "failed to create user, chaos ensues" | |
326 | 69 | user, password, filename := parseUserAddStdout(c, ctx) | 77 | context, err := testing.RunCommand(c, newUserAddCommand(), "foobar") |
327 | 70 | c.Assert(user, gc.Equals, "foobar") | 78 | c.Assert(err, gc.ErrorMatches, "failed to create user, chaos ensues") |
328 | 71 | c.Assert(password, gc.Equals, "frogdog") | 79 | c.Assert(s.mockAPI.username, gc.Equals, "foobar") |
329 | 72 | c.Assert(filename, gc.Equals, "") | 80 | c.Assert(s.mockAPI.displayname, gc.Equals, "") |
330 | 81 | c.Assert(testing.Stdout(context), gc.Equals, "") | ||
331 | 82 | } | ||
332 | 83 | |||
333 | 84 | func (s *UserAddCommandSuite) TestInit(c *gc.C) { | ||
334 | 85 | for i, test := range []struct { | ||
335 | 86 | args []string | ||
336 | 87 | user string | ||
337 | 88 | displayname string | ||
338 | 89 | password string | ||
339 | 90 | outPath string | ||
340 | 91 | errorString string | ||
341 | 92 | }{ | ||
342 | 93 | { | ||
343 | 94 | errorString: "no username supplied", | ||
344 | 95 | }, { | ||
345 | 96 | args: []string{"foobar"}, | ||
346 | 97 | user: "foobar", | ||
347 | 98 | }, { | ||
348 | 99 | args: []string{"foobar", "Foo Bar"}, | ||
349 | 100 | user: "foobar", | ||
350 | 101 | displayname: "Foo Bar", | ||
351 | 102 | }, { | ||
352 | 103 | args: []string{"foobar", "Foo Bar", "extra"}, | ||
353 | 104 | errorString: `unrecognized args: \["extra"\]`, | ||
354 | 105 | }, { | ||
355 | 106 | args: []string{"foobar", "--password", "password"}, | ||
356 | 107 | user: "foobar", | ||
357 | 108 | password: "password", | ||
358 | 109 | }, { | ||
359 | 110 | args: []string{"foobar", "--output", "somefile"}, | ||
360 | 111 | user: "foobar", | ||
361 | 112 | outPath: "somefile", | ||
362 | 113 | }, { | ||
363 | 114 | args: []string{"foobar", "-o", "somefile"}, | ||
364 | 115 | user: "foobar", | ||
365 | 116 | outPath: "somefile", | ||
366 | 117 | }, | ||
367 | 118 | } { | ||
368 | 119 | c.Logf("test %d", i) | ||
369 | 120 | addUserCmd := &UserAddCommand{} | ||
370 | 121 | err := testing.InitCommand(addUserCmd, test.args) | ||
371 | 122 | if test.errorString == "" { | ||
372 | 123 | c.Check(addUserCmd.User, gc.Equals, test.user) | ||
373 | 124 | c.Check(addUserCmd.DisplayName, gc.Equals, test.displayname) | ||
374 | 125 | c.Check(addUserCmd.Password, gc.Equals, test.password) | ||
375 | 126 | c.Check(addUserCmd.outPath, gc.Equals, test.outPath) | ||
376 | 127 | } else { | ||
377 | 128 | c.Check(err, gc.ErrorMatches, test.errorString) | ||
378 | 129 | } | ||
379 | 130 | } | ||
380 | 131 | } | ||
381 | 132 | |||
382 | 133 | func fakeBootstrapEnvironment(c *gc.C, envName string) { | ||
383 | 134 | store, err := configstore.Default() | ||
384 | 135 | c.Assert(err, gc.IsNil) | ||
385 | 136 | envInfo, err := store.CreateInfo(envName) | ||
386 | 137 | c.Assert(err, gc.IsNil) | ||
387 | 138 | envInfo.SetBootstrapConfig(map[string]interface{}{"random": "extra data"}) | ||
388 | 139 | envInfo.SetAPIEndpoint(configstore.APIEndpoint{ | ||
389 | 140 | Addresses: []string{"localhost:12345"}, | ||
390 | 141 | CACert: testing.CACert, | ||
391 | 142 | }) | ||
392 | 143 | envInfo.SetAPICredentials(configstore.APICredentials{ | ||
393 | 144 | User: "admin", | ||
394 | 145 | Password: "password", | ||
395 | 146 | }) | ||
396 | 147 | err = envInfo.Write() | ||
397 | 148 | c.Assert(err, gc.IsNil) | ||
398 | 73 | } | 149 | } |
399 | 74 | 150 | ||
400 | 75 | func (s *UserAddCommandSuite) TestJenvOutput(c *gc.C) { | 151 | func (s *UserAddCommandSuite) TestJenvOutput(c *gc.C) { |
401 | 152 | fakeBootstrapEnvironment(c, "erewhemos") | ||
402 | 76 | outputName := filepath.Join(c.MkDir(), "output") | 153 | outputName := filepath.Join(c.MkDir(), "output") |
403 | 77 | ctx, err := testing.RunCommand(c, newUserAddCommand(), | 154 | ctx, err := testing.RunCommand(c, newUserAddCommand(), |
404 | 78 | "foobar", "--password", "password", "--output", outputName) | 155 | "foobar", "--password", "password", "--output", outputName) |
405 | 79 | c.Assert(err, gc.IsNil) | 156 | c.Assert(err, gc.IsNil) |
406 | 80 | 157 | ||
411 | 81 | user, password, filename := parseUserAddStdout(c, ctx) | 158 | expected := fmt.Sprintf(`user "foobar" added with password %q`, s.mockAPI.password) |
412 | 82 | c.Assert(user, gc.Equals, "foobar") | 159 | expected = fmt.Sprintf("%s\nenvironment file written to %s.jenv\n", expected, outputName) |
413 | 83 | c.Assert(password, gc.Equals, "password") | 160 | c.Assert(testing.Stdout(ctx), gc.Equals, expected) |
410 | 84 | c.Assert(filename, gc.Equals, outputName+".jenv") | ||
414 | 85 | 161 | ||
416 | 86 | raw, err := ioutil.ReadFile(filename) | 162 | raw, err := ioutil.ReadFile(outputName + ".jenv") |
417 | 87 | c.Assert(err, gc.IsNil) | 163 | c.Assert(err, gc.IsNil) |
418 | 88 | d := map[string]interface{}{} | 164 | d := map[string]interface{}{} |
419 | 89 | err = goyaml.Unmarshal(raw, &d) | 165 | err = goyaml.Unmarshal(raw, &d) |
420 | 90 | c.Assert(err, gc.IsNil) | 166 | c.Assert(err, gc.IsNil) |
421 | 91 | c.Assert(d["user"], gc.Equals, "foobar") | 167 | c.Assert(d["user"], gc.Equals, "foobar") |
422 | 92 | c.Assert(d["password"], gc.Equals, "password") | 168 | c.Assert(d["password"], gc.Equals, "password") |
450 | 93 | _, found := d["state-servers"] | 169 | c.Assert(d["state-servers"], gc.DeepEquals, []interface{}{"localhost:12345"}) |
451 | 94 | c.Assert(found, gc.Equals, true) | 170 | c.Assert(d["ca-cert"], gc.DeepEquals, testing.CACert) |
452 | 95 | _, found = d["ca-cert"] | 171 | _, found := d["bootstrap-config"] |
453 | 96 | c.Assert(found, gc.Equals, true) | 172 | c.Assert(found, jc.IsFalse) |
454 | 97 | } | 173 | } |
455 | 98 | 174 | ||
456 | 99 | // parseUserAddStdout parses the output from the "juju user add" | 175 | type mockAddUserAPI struct { |
457 | 100 | // command and checks that it has the correct form, returning the | 176 | failMessage string |
458 | 101 | // interesting parts. The .jenv filename will be an empty string when | 177 | username string |
459 | 102 | // it wasn't included in the output. | 178 | displayname string |
460 | 103 | func parseUserAddStdout(c *gc.C, ctx *cmd.Context) (user string, password string, filename string) { | 179 | password string |
461 | 104 | stdout := strings.TrimSpace(ctx.Stdout.(*bytes.Buffer).String()) | 180 | } |
462 | 105 | lines := strings.Split(stdout, "\n") | 181 | |
463 | 106 | c.Assert(len(lines), jc.LessThan, 3) | 182 | func (m *mockAddUserAPI) AddUser(username, displayname, password string) error { |
464 | 107 | 183 | m.username = username | |
465 | 108 | reLine0 := regexp.MustCompile(`^user "(.+)" added with password "(.+)"$`) | 184 | m.displayname = displayname |
466 | 109 | line0Matches := reLine0.FindStringSubmatch(lines[0]) | 185 | m.password = password |
467 | 110 | c.Assert(len(line0Matches), gc.Equals, 3) | 186 | if m.failMessage == "" { |
468 | 111 | user, password = line0Matches[1], line0Matches[2] | 187 | return nil |
442 | 112 | |||
443 | 113 | if len(lines) == 2 { | ||
444 | 114 | reLine1 := regexp.MustCompile(`^environment file written to (.+)$`) | ||
445 | 115 | line1Matches := reLine1.FindStringSubmatch(lines[1]) | ||
446 | 116 | c.Assert(len(line1Matches), gc.Equals, 2) | ||
447 | 117 | filename = line1Matches[1] | ||
448 | 118 | } else { | ||
449 | 119 | filename = "" | ||
469 | 120 | } | 188 | } |
471 | 121 | return | 189 | return errors.New(m.failMessage) |
472 | 190 | } | ||
473 | 191 | |||
474 | 192 | func (*mockAddUserAPI) Close() error { | ||
475 | 193 | return nil | ||
476 | 122 | } | 194 | } |
477 | 123 | 195 | ||
478 | === modified file 'juju/conn_test.go' | |||
479 | --- juju/conn_test.go 2014-05-20 04:27:02 +0000 | |||
480 | +++ juju/conn_test.go 2014-06-03 04:22:37 +0000 | |||
481 | @@ -501,7 +501,7 @@ | |||
482 | 501 | 501 | ||
483 | 502 | func (s *DeployLocalSuite) TestDeployOwnerTag(c *gc.C) { | 502 | func (s *DeployLocalSuite) TestDeployOwnerTag(c *gc.C) { |
484 | 503 | usermanager := usermanager.NewClient(s.APIState) | 503 | usermanager := usermanager.NewClient(s.APIState) |
486 | 504 | err := usermanager.AddUser("foobar", "") | 504 | err := usermanager.AddUser("foobar", "", "") |
487 | 505 | c.Assert(err, gc.IsNil) | 505 | c.Assert(err, gc.IsNil) |
488 | 506 | service, err := juju.DeployService(s.State, | 506 | service, err := juju.DeployService(s.State, |
489 | 507 | juju.DeployServiceParams{ | 507 | juju.DeployServiceParams{ |
490 | 508 | 508 | ||
491 | === modified file 'juju/testing/conn.go' | |||
492 | --- juju/testing/conn.go 2014-05-26 08:25:47 +0000 | |||
493 | +++ juju/testing/conn.go 2014-06-03 04:22:37 +0000 | |||
494 | @@ -98,6 +98,12 @@ | |||
495 | 98 | s.setUpConn(c) | 98 | s.setUpConn(c) |
496 | 99 | } | 99 | } |
497 | 100 | 100 | ||
498 | 101 | func (s *JujuConnSuite) AddUser(c *gc.C, username string) *state.User { | ||
499 | 102 | user, err := s.State.AddUser(username, "", "password") | ||
500 | 103 | c.Assert(err, gc.IsNil) | ||
501 | 104 | return user | ||
502 | 105 | } | ||
503 | 106 | |||
504 | 101 | func (s *JujuConnSuite) StateInfo(c *gc.C) *state.Info { | 107 | func (s *JujuConnSuite) StateInfo(c *gc.C) *state.Info { |
505 | 102 | info, _, err := s.Conn.Environ.StateInfo() | 108 | info, _, err := s.Conn.Environ.StateInfo() |
506 | 103 | c.Assert(err, gc.IsNil) | 109 | c.Assert(err, gc.IsNil) |
507 | 104 | 110 | ||
508 | === modified file 'provider/dummy/environs.go' | |||
509 | --- provider/dummy/environs.go 2014-05-16 13:03:36 +0000 | |||
510 | +++ provider/dummy/environs.go 2014-06-03 04:22:37 +0000 | |||
511 | @@ -629,7 +629,7 @@ | |||
512 | 629 | if err := st.SetAdminMongoPassword(utils.UserPasswordHash(password, utils.CompatSalt)); err != nil { | 629 | if err := st.SetAdminMongoPassword(utils.UserPasswordHash(password, utils.CompatSalt)); err != nil { |
513 | 630 | panic(err) | 630 | panic(err) |
514 | 631 | } | 631 | } |
516 | 632 | _, err = st.AddUser("admin", password) | 632 | _, err = st.AddUser("admin", "", password) |
517 | 633 | if err != nil { | 633 | if err != nil { |
518 | 634 | panic(err) | 634 | panic(err) |
519 | 635 | } | 635 | } |
520 | 636 | 636 | ||
521 | === modified file 'state/api/params/params.go' | |||
522 | --- state/api/params/params.go 2014-05-27 08:30:44 +0000 | |||
523 | +++ state/api/params/params.go 2014-06-03 04:22:37 +0000 | |||
524 | @@ -382,10 +382,20 @@ | |||
525 | 382 | Keys []string | 382 | Keys []string |
526 | 383 | } | 383 | } |
527 | 384 | 384 | ||
528 | 385 | // ModifyUsers holds the parameters for making a UserManager Add or Modify calls. | ||
529 | 386 | type ModifyUsers struct { | ||
530 | 387 | Changes []ModifyUser | ||
531 | 388 | } | ||
532 | 389 | |||
533 | 385 | // ModifyUser stores the parameters used for a UserManager.Add|Remove call | 390 | // ModifyUser stores the parameters used for a UserManager.Add|Remove call |
534 | 386 | type ModifyUser struct { | 391 | type ModifyUser struct { |
537 | 387 | Tag string | 392 | // Tag is here purely for backwards compatability. Older clients will |
538 | 388 | Password string | 393 | // attempt to use the EntityPassword structure, so we need a Tag here |
539 | 394 | // (which will be treated as Username) | ||
540 | 395 | Tag string | ||
541 | 396 | Username string | ||
542 | 397 | DisplayName string | ||
543 | 398 | Password string | ||
544 | 389 | } | 399 | } |
545 | 390 | 400 | ||
546 | 391 | // MarshalJSON implements json.Marshaler. | 401 | // MarshalJSON implements json.Marshaler. |
547 | 392 | 402 | ||
548 | === modified file 'state/api/usermanager/client.go' | |||
549 | --- state/api/usermanager/client.go 2014-05-23 11:24:54 +0000 | |||
550 | +++ state/api/usermanager/client.go 2014-06-03 04:22:37 +0000 | |||
551 | @@ -29,14 +29,15 @@ | |||
552 | 29 | return c.st.Close() | 29 | return c.st.Close() |
553 | 30 | } | 30 | } |
554 | 31 | 31 | ||
561 | 32 | func (c *Client) AddUser(tag, password string) error { | 32 | func (c *Client) AddUser(username, displayName, password string) error { |
562 | 33 | if !names.IsUser(tag) { | 33 | if !names.IsUser(username) { |
563 | 34 | return fmt.Errorf("invalid user name %q", tag) | 34 | return fmt.Errorf("invalid user name %q", username) |
564 | 35 | } | 35 | } |
565 | 36 | u := params.EntityPassword{Tag: tag, Password: password} | 36 | userArgs := params.ModifyUsers{ |
566 | 37 | p := params.EntityPasswords{Changes: []params.EntityPassword{u}} | 37 | Changes: []params.ModifyUser{{Username: username, DisplayName: displayName, Password: password}}, |
567 | 38 | } | ||
568 | 38 | results := new(params.ErrorResults) | 39 | results := new(params.ErrorResults) |
570 | 39 | err := c.call("AddUser", p, results) | 40 | err := c.call("AddUser", userArgs, results) |
571 | 40 | if err != nil { | 41 | if err != nil { |
572 | 41 | return err | 42 | return err |
573 | 42 | } | 43 | } |
574 | 43 | 44 | ||
575 | === modified file 'state/api/usermanager/client_test.go' | |||
576 | --- state/api/usermanager/client_test.go 2014-05-23 11:24:54 +0000 | |||
577 | +++ state/api/usermanager/client_test.go 2014-06-03 04:22:37 +0000 | |||
578 | @@ -8,6 +8,7 @@ | |||
579 | 8 | 8 | ||
580 | 9 | jujutesting "launchpad.net/juju-core/juju/testing" | 9 | jujutesting "launchpad.net/juju-core/juju/testing" |
581 | 10 | "launchpad.net/juju-core/state" | 10 | "launchpad.net/juju-core/state" |
582 | 11 | "launchpad.net/juju-core/state/api/params" | ||
583 | 11 | "launchpad.net/juju-core/state/api/usermanager" | 12 | "launchpad.net/juju-core/state/api/usermanager" |
584 | 12 | ) | 13 | ) |
585 | 13 | 14 | ||
586 | @@ -26,14 +27,26 @@ | |||
587 | 26 | } | 27 | } |
588 | 27 | 28 | ||
589 | 28 | func (s *usermanagerSuite) TestAddUser(c *gc.C) { | 29 | func (s *usermanagerSuite) TestAddUser(c *gc.C) { |
591 | 29 | err := s.usermanager.AddUser("foobar", "password") | 30 | err := s.usermanager.AddUser("foobar", "Foo Bar", "password") |
592 | 31 | c.Assert(err, gc.IsNil) | ||
593 | 32 | _, err = s.State.User("foobar") | ||
594 | 33 | c.Assert(err, gc.IsNil) | ||
595 | 34 | } | ||
596 | 35 | |||
597 | 36 | func (s *usermanagerSuite) TestAddUserOldClient(c *gc.C) { | ||
598 | 37 | userArgs := params.EntityPasswords{ | ||
599 | 38 | Changes: []params.EntityPassword{{Tag: "foobar", Password: "password"}}, | ||
600 | 39 | } | ||
601 | 40 | results := new(params.ErrorResults) | ||
602 | 41 | |||
603 | 42 | err := s.APIState.Call("UserManager", "", "AddUser", userArgs, results) | ||
604 | 30 | c.Assert(err, gc.IsNil) | 43 | c.Assert(err, gc.IsNil) |
605 | 31 | _, err = s.State.User("foobar") | 44 | _, err = s.State.User("foobar") |
606 | 32 | c.Assert(err, gc.IsNil) | 45 | c.Assert(err, gc.IsNil) |
607 | 33 | } | 46 | } |
608 | 34 | 47 | ||
609 | 35 | func (s *usermanagerSuite) TestRemoveUser(c *gc.C) { | 48 | func (s *usermanagerSuite) TestRemoveUser(c *gc.C) { |
611 | 36 | err := s.usermanager.AddUser("foobar", "password") | 49 | err := s.usermanager.AddUser("foobar", "Foo Bar", "password") |
612 | 37 | c.Assert(err, gc.IsNil) | 50 | c.Assert(err, gc.IsNil) |
613 | 38 | _, err = s.State.User("foobar") | 51 | _, err = s.State.User("foobar") |
614 | 39 | c.Assert(err, gc.IsNil) | 52 | c.Assert(err, gc.IsNil) |
615 | @@ -45,12 +58,12 @@ | |||
616 | 45 | } | 58 | } |
617 | 46 | 59 | ||
618 | 47 | func (s *usermanagerSuite) TestAddExistingUser(c *gc.C) { | 60 | func (s *usermanagerSuite) TestAddExistingUser(c *gc.C) { |
620 | 48 | err := s.usermanager.AddUser("foobar", "password") | 61 | err := s.usermanager.AddUser("foobar", "Foo Bar", "password") |
621 | 49 | c.Assert(err, gc.IsNil) | 62 | c.Assert(err, gc.IsNil) |
622 | 50 | 63 | ||
623 | 51 | // Try adding again | 64 | // Try adding again |
626 | 52 | err = s.usermanager.AddUser("foobar", "password") | 65 | err = s.usermanager.AddUser("foobar", "Foo Bar", "password") |
627 | 53 | c.Assert(err, gc.ErrorMatches, "Failed to create user: user already exists") | 66 | c.Assert(err, gc.ErrorMatches, "failed to create user: user already exists") |
628 | 54 | } | 67 | } |
629 | 55 | 68 | ||
630 | 56 | func (s *usermanagerSuite) TestCantRemoveAdminUser(c *gc.C) { | 69 | func (s *usermanagerSuite) TestCantRemoveAdminUser(c *gc.C) { |
631 | 57 | 70 | ||
632 | === modified file 'state/apiserver/charms_test.go' | |||
633 | --- state/apiserver/charms_test.go 2014-03-13 23:30:56 +0000 | |||
634 | +++ state/apiserver/charms_test.go 2014-06-03 04:22:37 +0000 | |||
635 | @@ -36,12 +36,9 @@ | |||
636 | 36 | 36 | ||
637 | 37 | func (s *authHttpSuite) SetUpTest(c *gc.C) { | 37 | func (s *authHttpSuite) SetUpTest(c *gc.C) { |
638 | 38 | s.JujuConnSuite.SetUpTest(c) | 38 | s.JujuConnSuite.SetUpTest(c) |
643 | 39 | password, err := utils.RandomPassword() | 39 | user := s.AddUser(c, "joe") |
640 | 40 | c.Assert(err, gc.IsNil) | ||
641 | 41 | user, err := s.State.AddUser("joe", password) | ||
642 | 42 | c.Assert(err, gc.IsNil) | ||
644 | 43 | s.userTag = user.Tag() | 40 | s.userTag = user.Tag() |
646 | 44 | s.password = password | 41 | s.password = "password" |
647 | 45 | } | 42 | } |
648 | 46 | 43 | ||
649 | 47 | func (s *authHttpSuite) sendRequest(c *gc.C, tag, password, method, uri, contentType string, body io.Reader) (*http.Response, error) { | 44 | func (s *authHttpSuite) sendRequest(c *gc.C, tag, password, method, uri, contentType string, body io.Reader) (*http.Response, error) { |
650 | 48 | 45 | ||
651 | === modified file 'state/apiserver/client/api_test.go' | |||
652 | --- state/apiserver/client/api_test.go 2014-05-13 04:30:48 +0000 | |||
653 | +++ state/apiserver/client/api_test.go 2014-06-03 04:22:37 +0000 | |||
654 | @@ -277,8 +277,7 @@ | |||
655 | 277 | setDefaultPassword(c, u) | 277 | setDefaultPassword(c, u) |
656 | 278 | add(u) | 278 | add(u) |
657 | 279 | 279 | ||
660 | 280 | u, err = s.State.AddUser("other", "password") | 280 | u = s.AddUser(c, "other") |
659 | 281 | c.Assert(err, gc.IsNil) | ||
661 | 282 | setDefaultPassword(c, u) | 281 | setDefaultPassword(c, u) |
662 | 283 | add(u) | 282 | add(u) |
663 | 284 | 283 | ||
664 | 285 | 284 | ||
665 | === modified file 'state/apiserver/client/client_test.go' | |||
666 | --- state/apiserver/client/client_test.go 2014-05-16 02:43:52 +0000 | |||
667 | +++ state/apiserver/client/client_test.go 2014-06-03 04:22:37 +0000 | |||
668 | @@ -27,7 +27,6 @@ | |||
669 | 27 | "launchpad.net/juju-core/state" | 27 | "launchpad.net/juju-core/state" |
670 | 28 | "launchpad.net/juju-core/state/api" | 28 | "launchpad.net/juju-core/state/api" |
671 | 29 | "launchpad.net/juju-core/state/api/params" | 29 | "launchpad.net/juju-core/state/api/params" |
672 | 30 | "launchpad.net/juju-core/state/api/usermanager" | ||
673 | 31 | "launchpad.net/juju-core/state/apiserver/client" | 30 | "launchpad.net/juju-core/state/apiserver/client" |
674 | 32 | "launchpad.net/juju-core/state/presence" | 31 | "launchpad.net/juju-core/state/presence" |
675 | 33 | coretesting "launchpad.net/juju-core/testing" | 32 | coretesting "launchpad.net/juju-core/testing" |
676 | @@ -834,12 +833,10 @@ | |||
677 | 834 | defer restore() | 833 | defer restore() |
678 | 835 | curl, _ := addCharm(c, store, "dummy") | 834 | curl, _ := addCharm(c, store, "dummy") |
679 | 836 | 835 | ||
683 | 837 | usermanager := usermanager.NewClient(s.APIState) | 836 | s.AddUser(c, "foobar") |
681 | 838 | err := usermanager.AddUser("foobar", "password") | ||
682 | 839 | c.Assert(err, gc.IsNil) | ||
684 | 840 | s.APIState = s.OpenAPIAs(c, "user-foobar", "password") | 837 | s.APIState = s.OpenAPIAs(c, "user-foobar", "password") |
685 | 841 | 838 | ||
687 | 842 | err = s.APIState.Client().ServiceDeploy( | 839 | err := s.APIState.Client().ServiceDeploy( |
688 | 843 | curl.String(), "service", 3, "", constraints.Value{}, "", | 840 | curl.String(), "service", 3, "", constraints.Value{}, "", |
689 | 844 | ) | 841 | ) |
690 | 845 | c.Assert(err, gc.IsNil) | 842 | c.Assert(err, gc.IsNil) |
691 | 846 | 843 | ||
692 | === modified file 'state/apiserver/login_test.go' | |||
693 | --- state/apiserver/login_test.go 2014-04-17 13:14:49 +0000 | |||
694 | +++ state/apiserver/login_test.go 2014-06-03 04:22:37 +0000 | |||
695 | @@ -130,8 +130,7 @@ | |||
696 | 130 | st, err := api.Open(info, fastDialOpts) | 130 | st, err := api.Open(info, fastDialOpts) |
697 | 131 | c.Assert(err, gc.IsNil) | 131 | c.Assert(err, gc.IsNil) |
698 | 132 | defer st.Close() | 132 | defer st.Close() |
701 | 133 | u, err := s.State.AddUser("inactive", "password") | 133 | u := s.AddUser(c, "inactive") |
700 | 134 | c.Assert(err, gc.IsNil) | ||
702 | 135 | err = u.Deactivate() | 134 | err = u.Deactivate() |
703 | 136 | c.Assert(err, gc.IsNil) | 135 | c.Assert(err, gc.IsNil) |
704 | 137 | 136 | ||
705 | 138 | 137 | ||
706 | === modified file 'state/apiserver/usermanager/usermanager.go' | |||
707 | --- state/apiserver/usermanager/usermanager.go 2014-04-25 13:57:06 +0000 | |||
708 | +++ state/apiserver/usermanager/usermanager.go 2014-06-03 04:22:37 +0000 | |||
709 | @@ -6,6 +6,7 @@ | |||
710 | 6 | import ( | 6 | import ( |
711 | 7 | "fmt" | 7 | "fmt" |
712 | 8 | 8 | ||
713 | 9 | "github.com/juju/errors" | ||
714 | 9 | "github.com/juju/loggo" | 10 | "github.com/juju/loggo" |
715 | 10 | 11 | ||
716 | 11 | "launchpad.net/juju-core/state" | 12 | "launchpad.net/juju-core/state" |
717 | @@ -17,7 +18,7 @@ | |||
718 | 17 | 18 | ||
719 | 18 | // UserManager defines the methods on the usermanager API end point. | 19 | // UserManager defines the methods on the usermanager API end point. |
720 | 19 | type UserManager interface { | 20 | type UserManager interface { |
722 | 20 | AddUser(arg params.EntityPasswords) (params.ErrorResults, error) | 21 | AddUser(arg params.ModifyUsers) (params.ErrorResults, error) |
723 | 21 | RemoveUser(arg params.Entities) (params.ErrorResults, error) | 22 | RemoveUser(arg params.Entities) (params.ErrorResults, error) |
724 | 22 | } | 23 | } |
725 | 23 | 24 | ||
726 | @@ -48,7 +49,7 @@ | |||
727 | 48 | nil | 49 | nil |
728 | 49 | } | 50 | } |
729 | 50 | 51 | ||
731 | 51 | func (api *UserManagerAPI) AddUser(args params.EntityPasswords) (params.ErrorResults, error) { | 52 | func (api *UserManagerAPI) AddUser(args params.ModifyUsers) (params.ErrorResults, error) { |
732 | 52 | result := params.ErrorResults{ | 53 | result := params.ErrorResults{ |
733 | 53 | Results: make([]params.ErrorResult, len(args.Changes)), | 54 | Results: make([]params.ErrorResult, len(args.Changes)), |
734 | 54 | } | 55 | } |
735 | @@ -65,9 +66,13 @@ | |||
736 | 65 | result.Results[0].Error = common.ServerError(common.ErrPerm) | 66 | result.Results[0].Error = common.ServerError(common.ErrPerm) |
737 | 66 | continue | 67 | continue |
738 | 67 | } | 68 | } |
740 | 68 | _, err := api.state.AddUser(arg.Tag, arg.Password) | 69 | username := arg.Username |
741 | 70 | if username == "" { | ||
742 | 71 | username = arg.Tag | ||
743 | 72 | } | ||
744 | 73 | _, err := api.state.AddUser(username, arg.DisplayName, arg.Password) | ||
745 | 69 | if err != nil { | 74 | if err != nil { |
747 | 70 | err = fmt.Errorf("Failed to create user: %v", err) | 75 | err = errors.Annotate(err, "failed to create user") |
748 | 71 | result.Results[i].Error = common.ServerError(err) | 76 | result.Results[i].Error = common.ServerError(err) |
749 | 72 | continue | 77 | continue |
750 | 73 | } | 78 | } |
751 | 74 | 79 | ||
752 | === modified file 'state/apiserver/usermanager/usermanager_test.go' | |||
753 | --- state/apiserver/usermanager/usermanager_test.go 2014-04-17 12:47:50 +0000 | |||
754 | +++ state/apiserver/usermanager/usermanager_test.go 2014-06-03 04:22:37 +0000 | |||
755 | @@ -44,32 +44,36 @@ | |||
756 | 44 | } | 44 | } |
757 | 45 | 45 | ||
758 | 46 | func (s *userManagerSuite) TestAddUser(c *gc.C) { | 46 | func (s *userManagerSuite) TestAddUser(c *gc.C) { |
765 | 47 | arg := params.EntityPassword{ | 47 | args := params.ModifyUsers{ |
766 | 48 | Tag: "foobar", | 48 | Changes: []params.ModifyUser{{ |
767 | 49 | Password: "password", | 49 | Username: "foobar", |
768 | 50 | } | 50 | DisplayName: "Foo Bar", |
769 | 51 | 51 | Password: "password", | |
770 | 52 | args := params.EntityPasswords{Changes: []params.EntityPassword{arg}} | 52 | }}} |
771 | 53 | 53 | ||
772 | 54 | result, err := s.usermanager.AddUser(args) | 54 | result, err := s.usermanager.AddUser(args) |
773 | 55 | // Check that the call is succesful | 55 | // Check that the call is succesful |
774 | 56 | c.Assert(err, gc.IsNil) | 56 | c.Assert(err, gc.IsNil) |
776 | 57 | c.Assert(result, gc.DeepEquals, params.ErrorResults{Results: []params.ErrorResult{params.ErrorResult{Error: nil}}}) | 57 | c.Assert(result.Results, gc.HasLen, 1) |
777 | 58 | c.Assert(result.Results[0], gc.DeepEquals, params.ErrorResult{Error: nil}) | ||
778 | 58 | // Check that the call results in a new user being created | 59 | // Check that the call results in a new user being created |
779 | 59 | user, err := s.State.User("foobar") | 60 | user, err := s.State.User("foobar") |
780 | 60 | c.Assert(err, gc.IsNil) | 61 | c.Assert(err, gc.IsNil) |
781 | 61 | c.Assert(user, gc.NotNil) | 62 | c.Assert(user, gc.NotNil) |
782 | 63 | c.Assert(user.Name(), gc.Equals, "foobar") | ||
783 | 64 | c.Assert(user.DisplayName(), gc.Equals, "Foo Bar") | ||
784 | 62 | } | 65 | } |
785 | 63 | 66 | ||
786 | 64 | func (s *userManagerSuite) TestRemoveUser(c *gc.C) { | 67 | func (s *userManagerSuite) TestRemoveUser(c *gc.C) { |
791 | 65 | arg := params.EntityPassword{ | 68 | args := params.ModifyUsers{ |
792 | 66 | Tag: "foobar", | 69 | Changes: []params.ModifyUser{{ |
793 | 67 | Password: "password", | 70 | Username: "foobar", |
794 | 68 | } | 71 | DisplayName: "Foo Bar", |
795 | 72 | Password: "password", | ||
796 | 73 | }}} | ||
797 | 69 | removeArg := params.Entity{ | 74 | removeArg := params.Entity{ |
798 | 70 | Tag: "foobar", | 75 | Tag: "foobar", |
799 | 71 | } | 76 | } |
800 | 72 | args := params.EntityPasswords{Changes: []params.EntityPassword{arg}} | ||
801 | 73 | removeArgs := params.Entities{Entities: []params.Entity{removeArg}} | 77 | removeArgs := params.Entities{Entities: []params.Entity{removeArg}} |
802 | 74 | _, err := s.usermanager.AddUser(args) | 78 | _, err := s.usermanager.AddUser(args) |
803 | 75 | c.Assert(err, gc.IsNil) | 79 | c.Assert(err, gc.IsNil) |
804 | @@ -83,21 +87,22 @@ | |||
805 | 83 | c.Assert(err, gc.IsNil) | 87 | c.Assert(err, gc.IsNil) |
806 | 84 | // Removal makes the user in active | 88 | // Removal makes the user in active |
807 | 85 | c.Assert(user.IsDeactivated(), gc.Equals, true) | 89 | c.Assert(user.IsDeactivated(), gc.Equals, true) |
809 | 86 | c.Assert(user.PasswordValid(arg.Password), gc.Equals, false) | 90 | c.Assert(user.PasswordValid(args.Changes[0].Password), gc.Equals, false) |
810 | 87 | } | 91 | } |
811 | 88 | 92 | ||
812 | 89 | // Since removing a user just deacitvates them you cannot add a user | 93 | // Since removing a user just deacitvates them you cannot add a user |
813 | 90 | // that has been previously been removed | 94 | // that has been previously been removed |
814 | 91 | // TODO(mattyw) 2014-03-07 bug #1288745 | 95 | // TODO(mattyw) 2014-03-07 bug #1288745 |
815 | 92 | func (s *userManagerSuite) TestCannotAddRemoveAdd(c *gc.C) { | 96 | func (s *userManagerSuite) TestCannotAddRemoveAdd(c *gc.C) { |
816 | 93 | arg := params.EntityPassword{ | ||
817 | 94 | Tag: "addremove", | ||
818 | 95 | Password: "password", | ||
819 | 96 | } | ||
820 | 97 | removeArg := params.Entity{ | 97 | removeArg := params.Entity{ |
821 | 98 | Tag: "foobar", | 98 | Tag: "foobar", |
822 | 99 | } | 99 | } |
824 | 100 | args := params.EntityPasswords{Changes: []params.EntityPassword{arg}} | 100 | args := params.ModifyUsers{ |
825 | 101 | Changes: []params.ModifyUser{{ | ||
826 | 102 | Username: "foobar", | ||
827 | 103 | DisplayName: "Foo Bar", | ||
828 | 104 | Password: "password", | ||
829 | 105 | }}} | ||
830 | 101 | removeArgs := params.Entities{Entities: []params.Entity{removeArg}} | 106 | removeArgs := params.Entities{Entities: []params.Entity{removeArg}} |
831 | 102 | _, err := s.usermanager.AddUser(args) | 107 | _, err := s.usermanager.AddUser(args) |
832 | 103 | c.Assert(err, gc.IsNil) | 108 | c.Assert(err, gc.IsNil) |
833 | @@ -106,7 +111,7 @@ | |||
834 | 106 | c.Assert(err, gc.IsNil) | 111 | c.Assert(err, gc.IsNil) |
835 | 107 | _, err = s.State.User("addremove") | 112 | _, err = s.State.User("addremove") |
836 | 108 | result, err := s.usermanager.AddUser(args) | 113 | result, err := s.usermanager.AddUser(args) |
838 | 109 | expectedError := apiservertesting.ServerError("Failed to create user: user already exists") | 114 | expectedError := apiservertesting.ServerError("failed to create user: user already exists") |
839 | 110 | c.Assert(result, gc.DeepEquals, params.ErrorResults{ | 115 | c.Assert(result, gc.DeepEquals, params.ErrorResults{ |
840 | 111 | Results: []params.ErrorResult{ | 116 | Results: []params.ErrorResult{ |
841 | 112 | params.ErrorResult{expectedError}}}) | 117 | params.ErrorResult{expectedError}}}) |
842 | 113 | 118 | ||
843 | === modified file 'state/compat_test.go' | |||
844 | --- state/compat_test.go 2014-05-20 04:27:02 +0000 | |||
845 | +++ state/compat_test.go 2014-06-03 04:22:37 +0000 | |||
846 | @@ -68,7 +68,7 @@ | |||
847 | 68 | } | 68 | } |
848 | 69 | 69 | ||
849 | 70 | func (s *compatSuite) TestGetServiceWithoutNetworksIsOK(c *gc.C) { | 70 | func (s *compatSuite) TestGetServiceWithoutNetworksIsOK(c *gc.C) { |
851 | 71 | _, err := s.state.AddUser(AdminUser, "pass") | 71 | _, err := s.state.AddUser(AdminUser, "", "pass") |
852 | 72 | c.Assert(err, gc.IsNil) | 72 | c.Assert(err, gc.IsNil) |
853 | 73 | charm := addCharm(c, s.state, "quantal", testing.Charms.Dir("mysql")) | 73 | charm := addCharm(c, s.state, "quantal", testing.Charms.Dir("mysql")) |
854 | 74 | service, err := s.state.AddService("mysql", "user-admin", charm, nil, nil) | 74 | service, err := s.state.AddService("mysql", "user-admin", charm, nil, nil) |
855 | 75 | 75 | ||
856 | === modified file 'state/conn_test.go' | |||
857 | --- state/conn_test.go 2014-05-27 02:47:01 +0000 | |||
858 | +++ state/conn_test.go 2014-06-03 04:22:37 +0000 | |||
859 | @@ -59,7 +59,7 @@ | |||
860 | 59 | cs.services = cs.MgoSuite.Session.DB("juju").C("services") | 59 | cs.services = cs.MgoSuite.Session.DB("juju").C("services") |
861 | 60 | cs.units = cs.MgoSuite.Session.DB("juju").C("units") | 60 | cs.units = cs.MgoSuite.Session.DB("juju").C("units") |
862 | 61 | cs.stateServers = cs.MgoSuite.Session.DB("juju").C("stateServers") | 61 | cs.stateServers = cs.MgoSuite.Session.DB("juju").C("stateServers") |
864 | 62 | cs.State.AddUser(state.AdminUser, "pass") | 62 | cs.State.AddUser(state.AdminUser, "", "pass") |
865 | 63 | } | 63 | } |
866 | 64 | 64 | ||
867 | 65 | func (cs *ConnSuite) TearDownTest(c *gc.C) { | 65 | func (cs *ConnSuite) TearDownTest(c *gc.C) { |
868 | 66 | 66 | ||
869 | === modified file 'state/megawatcher_internal_test.go' | |||
870 | --- state/megawatcher_internal_test.go 2014-05-20 04:27:02 +0000 | |||
871 | +++ state/megawatcher_internal_test.go 2014-06-03 04:22:37 +0000 | |||
872 | @@ -47,7 +47,7 @@ | |||
873 | 47 | s.BaseSuite.SetUpTest(c) | 47 | s.BaseSuite.SetUpTest(c) |
874 | 48 | s.MgoSuite.SetUpTest(c) | 48 | s.MgoSuite.SetUpTest(c) |
875 | 49 | s.State = TestingInitialize(c, nil, Policy(nil)) | 49 | s.State = TestingInitialize(c, nil, Policy(nil)) |
877 | 50 | s.State.AddUser(AdminUser, "pass") | 50 | s.State.AddUser(AdminUser, "", "pass") |
878 | 51 | } | 51 | } |
879 | 52 | 52 | ||
880 | 53 | func (s *storeManagerStateSuite) TearDownTest(c *gc.C) { | 53 | func (s *storeManagerStateSuite) TearDownTest(c *gc.C) { |
881 | 54 | 54 | ||
882 | === modified file 'state/state_test.go' | |||
883 | --- state/state_test.go 2014-05-27 06:12:16 +0000 | |||
884 | +++ state/state_test.go 2014-06-03 04:22:37 +0000 | |||
885 | @@ -2372,7 +2372,7 @@ | |||
886 | 2372 | svc := s.AddTestingService(c, "ser-vice2", s.AddTestingCharm(c, "mysql")) | 2372 | svc := s.AddTestingService(c, "ser-vice2", s.AddTestingCharm(c, "mysql")) |
887 | 2373 | _, err = svc.AddUnit() | 2373 | _, err = svc.AddUnit() |
888 | 2374 | c.Assert(err, gc.IsNil) | 2374 | c.Assert(err, gc.IsNil) |
890 | 2375 | _, err = s.State.AddUser("arble", "pass") | 2375 | _, err = s.State.AddUser("arble", "", "pass") |
891 | 2376 | c.Assert(err, gc.IsNil) | 2376 | c.Assert(err, gc.IsNil) |
892 | 2377 | s.AddTestingService(c, "wordpress", s.AddTestingCharm(c, "wordpress")) | 2377 | s.AddTestingService(c, "wordpress", s.AddTestingCharm(c, "wordpress")) |
893 | 2378 | eps, err := s.State.InferEndpoints([]string{"wordpress", "ser-vice2"}) | 2378 | eps, err := s.State.InferEndpoints([]string{"wordpress", "ser-vice2"}) |
894 | @@ -2464,7 +2464,7 @@ | |||
895 | 2464 | c.Assert(err, gc.IsNil) | 2464 | c.Assert(err, gc.IsNil) |
896 | 2465 | 2465 | ||
897 | 2466 | // Parse a user entity name. | 2466 | // Parse a user entity name. |
899 | 2467 | user, err := s.State.AddUser("arble", "pass") | 2467 | user, err := s.State.AddUser("arble", "", "pass") |
900 | 2468 | c.Assert(err, gc.IsNil) | 2468 | c.Assert(err, gc.IsNil) |
901 | 2469 | coll, id, err = state.ParseTag(s.State, user.Tag()) | 2469 | coll, id, err = state.ParseTag(s.State, user.Tag()) |
902 | 2470 | c.Assert(coll, gc.Equals, "users") | 2470 | c.Assert(coll, gc.Equals, "users") |
903 | 2471 | 2471 | ||
904 | === modified file 'state/user.go' | |||
905 | --- state/user.go 2014-05-21 03:07:36 +0000 | |||
906 | +++ state/user.go 2014-06-03 04:22:37 +0000 | |||
907 | @@ -22,9 +22,9 @@ | |||
908 | 22 | } | 22 | } |
909 | 23 | 23 | ||
910 | 24 | // AddUser adds a user to the state. | 24 | // AddUser adds a user to the state. |
914 | 25 | func (st *State) AddUser(name, password string) (*User, error) { | 25 | func (st *State) AddUser(username, displayName, password string) (*User, error) { |
915 | 26 | if !names.IsUser(name) { | 26 | if !names.IsUser(username) { |
916 | 27 | return nil, fmt.Errorf("invalid user name %q", name) | 27 | return nil, errors.Errorf("invalid user name %q", username) |
917 | 28 | } | 28 | } |
918 | 29 | salt, err := utils.RandomSalt() | 29 | salt, err := utils.RandomSalt() |
919 | 30 | if err != nil { | 30 | if err != nil { |
920 | @@ -33,23 +33,24 @@ | |||
921 | 33 | u := &User{ | 33 | u := &User{ |
922 | 34 | st: st, | 34 | st: st, |
923 | 35 | doc: userDoc{ | 35 | doc: userDoc{ |
925 | 36 | Name: name, | 36 | Name: username, |
926 | 37 | DisplayName: displayName, | ||
927 | 37 | PasswordHash: utils.UserPasswordHash(password, salt), | 38 | PasswordHash: utils.UserPasswordHash(password, salt), |
928 | 38 | PasswordSalt: salt, | 39 | PasswordSalt: salt, |
929 | 39 | }, | 40 | }, |
930 | 40 | } | 41 | } |
931 | 41 | ops := []txn.Op{{ | 42 | ops := []txn.Op{{ |
932 | 42 | C: st.users.Name, | 43 | C: st.users.Name, |
934 | 43 | Id: name, | 44 | Id: username, |
935 | 44 | Assert: txn.DocMissing, | 45 | Assert: txn.DocMissing, |
936 | 45 | Insert: &u.doc, | 46 | Insert: &u.doc, |
937 | 46 | }} | 47 | }} |
938 | 47 | err = st.runTransaction(ops) | 48 | err = st.runTransaction(ops) |
939 | 48 | if err == txn.ErrAborted { | 49 | if err == txn.ErrAborted { |
941 | 49 | err = fmt.Errorf("user already exists") | 50 | err = errors.New("user already exists") |
942 | 50 | } | 51 | } |
943 | 51 | if err != nil { | 52 | if err != nil { |
945 | 52 | return nil, err | 53 | return nil, errors.Trace(err) |
946 | 53 | } | 54 | } |
947 | 54 | return u, nil | 55 | return u, nil |
948 | 55 | } | 56 | } |
949 | @@ -68,7 +69,7 @@ | |||
950 | 68 | func (st *State) User(name string) (*User, error) { | 69 | func (st *State) User(name string) (*User, error) { |
951 | 69 | u := &User{st: st} | 70 | u := &User{st: st} |
952 | 70 | if err := st.getUser(name, &u.doc); err != nil { | 71 | if err := st.getUser(name, &u.doc); err != nil { |
954 | 71 | return nil, err | 72 | return nil, errors.Trace(err) |
955 | 72 | } | 73 | } |
956 | 73 | return u, nil | 74 | return u, nil |
957 | 74 | } | 75 | } |
958 | @@ -81,7 +82,8 @@ | |||
959 | 81 | 82 | ||
960 | 82 | type userDoc struct { | 83 | type userDoc struct { |
961 | 83 | Name string `bson:"_id_"` | 84 | Name string `bson:"_id_"` |
963 | 84 | Deactivated bool // Removing users means they still exist, but are marked deactivated | 85 | DisplayName string |
964 | 86 | Deactivated bool // Removing users means they still exist, but are marked deactivated | ||
965 | 85 | PasswordHash string | 87 | PasswordHash string |
966 | 86 | PasswordSalt string | 88 | PasswordSalt string |
967 | 87 | } | 89 | } |
968 | @@ -91,6 +93,11 @@ | |||
969 | 91 | return u.doc.Name | 93 | return u.doc.Name |
970 | 92 | } | 94 | } |
971 | 93 | 95 | ||
972 | 96 | // DisplayName returns the display name of the user. | ||
973 | 97 | func (u *User) DisplayName() string { | ||
974 | 98 | return u.doc.DisplayName | ||
975 | 99 | } | ||
976 | 100 | |||
977 | 94 | // Tag returns the Tag for | 101 | // Tag returns the Tag for |
978 | 95 | // the user ("user-$username") | 102 | // the user ("user-$username") |
979 | 96 | func (u *User) Tag() string { | 103 | func (u *User) Tag() string { |
980 | 97 | 104 | ||
981 | === modified file 'state/user_test.go' | |||
982 | --- state/user_test.go 2014-05-22 00:48:08 +0000 | |||
983 | +++ state/user_test.go 2014-06-03 04:22:37 +0000 | |||
984 | @@ -17,67 +17,69 @@ | |||
985 | 17 | ConnSuite | 17 | ConnSuite |
986 | 18 | } | 18 | } |
987 | 19 | 19 | ||
989 | 20 | var _ = gc.Suite(&UserSuite{}) | 20 | var ( |
990 | 21 | _ = gc.Suite(&UserSuite{}) | ||
991 | 22 | ) | ||
992 | 21 | 23 | ||
993 | 22 | func (s *UserSuite) TestAddUserInvalidNames(c *gc.C) { | 24 | func (s *UserSuite) TestAddUserInvalidNames(c *gc.C) { |
994 | 23 | for _, name := range []string{ | 25 | for _, name := range []string{ |
995 | 24 | "", | 26 | "", |
996 | 25 | "b^b", | 27 | "b^b", |
997 | 26 | } { | 28 | } { |
999 | 27 | u, err := s.State.AddUser(name, "password") | 29 | u, err := s.State.AddUser(name, "ignored", "ignored") |
1000 | 28 | c.Assert(err, gc.ErrorMatches, `invalid user name "`+regexp.QuoteMeta(name)+`"`) | 30 | c.Assert(err, gc.ErrorMatches, `invalid user name "`+regexp.QuoteMeta(name)+`"`) |
1001 | 29 | c.Assert(u, gc.IsNil) | 31 | c.Assert(u, gc.IsNil) |
1002 | 30 | } | 32 | } |
1003 | 31 | } | 33 | } |
1004 | 32 | 34 | ||
1006 | 33 | func (s *UserSuite) TestAddUserValidName(c *gc.C) { | 35 | func (s *UserSuite) addUser(c *gc.C, name, displayName, password string) *state.User { |
1007 | 36 | user, err := s.State.AddUser(name, displayName, password) | ||
1008 | 37 | c.Assert(err, gc.IsNil) | ||
1009 | 38 | c.Assert(user, gc.NotNil) | ||
1010 | 39 | c.Assert(user.Name(), gc.Equals, name) | ||
1011 | 40 | c.Assert(user.DisplayName(), gc.Equals, displayName) | ||
1012 | 41 | c.Assert(user.PasswordValid(password), jc.IsTrue) | ||
1013 | 42 | return user | ||
1014 | 43 | } | ||
1015 | 44 | |||
1016 | 45 | func (s *UserSuite) makeUser(c *gc.C) *state.User { | ||
1017 | 46 | return s.addUser(c, "someuser", "displayName", "a-password") | ||
1018 | 47 | } | ||
1019 | 48 | |||
1020 | 49 | func (s *UserSuite) TestAddUser(c *gc.C) { | ||
1021 | 34 | name := "f00-Bar.ram77" | 50 | name := "f00-Bar.ram77" |
1042 | 35 | u, err := s.State.AddUser(name, "password") | 51 | displayName := "Display" |
1043 | 36 | c.Check(u, gc.NotNil) | 52 | password := "password" |
1044 | 37 | c.Assert(err, gc.IsNil) | 53 | |
1045 | 38 | c.Assert(u.Name(), gc.Equals, name) | 54 | s.addUser(c, name, displayName, password) |
1046 | 39 | } | 55 | |
1047 | 40 | 56 | user, err := s.State.User(name) | |
1048 | 41 | func (s *UserSuite) TestAddUser(c *gc.C) { | 57 | c.Assert(err, gc.IsNil) |
1049 | 42 | u, err := s.State.AddUser("aa", "b") | 58 | c.Check(user, gc.NotNil) |
1050 | 43 | c.Check(u, gc.NotNil) | 59 | c.Assert(user.Name(), gc.Equals, name) |
1051 | 44 | c.Assert(err, gc.IsNil) | 60 | c.Assert(user.DisplayName(), gc.Equals, displayName) |
1052 | 45 | 61 | c.Assert(user.PasswordValid(password), jc.IsTrue) | |
1033 | 46 | c.Assert(u.Name(), gc.Equals, "aa") | ||
1034 | 47 | c.Assert(u.PasswordValid("b"), jc.IsTrue) | ||
1035 | 48 | |||
1036 | 49 | u1, err := s.State.User("aa") | ||
1037 | 50 | c.Check(u1, gc.NotNil) | ||
1038 | 51 | c.Assert(err, gc.IsNil) | ||
1039 | 52 | |||
1040 | 53 | c.Assert(u1.Name(), gc.Equals, "aa") | ||
1041 | 54 | c.Assert(u1.PasswordValid("b"), jc.IsTrue) | ||
1053 | 55 | } | 62 | } |
1054 | 56 | 63 | ||
1055 | 57 | func (s *UserSuite) TestCheckUserExists(c *gc.C) { | 64 | func (s *UserSuite) TestCheckUserExists(c *gc.C) { |
1065 | 58 | u, err := s.State.AddUser("aa", "b") | 65 | user := s.makeUser(c) |
1066 | 59 | c.Check(u, gc.NotNil) | 66 | exists, err := state.CheckUserExists(s.State, user.Name()) |
1067 | 60 | c.Assert(err, gc.IsNil) | 67 | c.Assert(err, gc.IsNil) |
1068 | 61 | e, err := state.CheckUserExists(s.State, "aa") | 68 | c.Assert(exists, jc.IsTrue) |
1069 | 62 | c.Assert(err, gc.IsNil) | 69 | exists, err = state.CheckUserExists(s.State, "notAUser") |
1070 | 63 | c.Assert(e, gc.Equals, true) | 70 | c.Assert(err, gc.IsNil) |
1071 | 64 | e, err = state.CheckUserExists(s.State, "notAUser") | 71 | c.Assert(exists, jc.IsFalse) |
1063 | 65 | c.Assert(err, gc.IsNil) | ||
1064 | 66 | c.Assert(e, gc.Equals, false) | ||
1072 | 67 | } | 72 | } |
1073 | 68 | 73 | ||
1074 | 69 | func (s *UserSuite) TestSetPassword(c *gc.C) { | 74 | func (s *UserSuite) TestSetPassword(c *gc.C) { |
1078 | 70 | u, err := s.State.AddUser("someuser", "password") | 75 | user := s.makeUser(c) |
1076 | 71 | c.Assert(err, gc.IsNil) | ||
1077 | 72 | |||
1079 | 73 | testSetPassword(c, func() (state.Authenticator, error) { | 76 | testSetPassword(c, func() (state.Authenticator, error) { |
1081 | 74 | return s.State.User(u.Name()) | 77 | return s.State.User(user.Name()) |
1082 | 75 | }) | 78 | }) |
1083 | 76 | } | 79 | } |
1084 | 77 | 80 | ||
1085 | 78 | func (s *UserSuite) TestAddUserSetsSalt(c *gc.C) { | 81 | func (s *UserSuite) TestAddUserSetsSalt(c *gc.C) { |
1088 | 79 | u, err := s.State.AddUser("someuser", "a-password") | 82 | u := s.makeUser(c) |
1087 | 80 | c.Assert(err, gc.IsNil) | ||
1089 | 81 | salt, hash := state.GetUserPasswordSaltAndHash(u) | 83 | salt, hash := state.GetUserPasswordSaltAndHash(u) |
1090 | 82 | c.Check(hash, gc.Not(gc.Equals), "") | 84 | c.Check(hash, gc.Not(gc.Equals), "") |
1091 | 83 | c.Check(salt, gc.Not(gc.Equals), "") | 85 | c.Check(salt, gc.Not(gc.Equals), "") |
1092 | @@ -86,8 +88,7 @@ | |||
1093 | 86 | } | 88 | } |
1094 | 87 | 89 | ||
1095 | 88 | func (s *UserSuite) TestSetPasswordChangesSalt(c *gc.C) { | 90 | func (s *UserSuite) TestSetPasswordChangesSalt(c *gc.C) { |
1098 | 89 | u, err := s.State.AddUser("someuser", "a-password") | 91 | u := s.makeUser(c) |
1097 | 90 | c.Assert(err, gc.IsNil) | ||
1099 | 91 | origSalt, origHash := state.GetUserPasswordSaltAndHash(u) | 92 | origSalt, origHash := state.GetUserPasswordSaltAndHash(u) |
1100 | 92 | c.Check(origSalt, gc.Not(gc.Equals), "") | 93 | c.Check(origSalt, gc.Not(gc.Equals), "") |
1101 | 93 | // Even though the password is the same, we take this opportunity to | 94 | // Even though the password is the same, we take this opportunity to |
1102 | @@ -101,10 +102,9 @@ | |||
1103 | 101 | } | 102 | } |
1104 | 102 | 103 | ||
1105 | 103 | func (s *UserSuite) TestSetPasswordHash(c *gc.C) { | 104 | func (s *UserSuite) TestSetPasswordHash(c *gc.C) { |
1108 | 104 | u, err := s.State.AddUser("someuser", "password") | 105 | u := s.makeUser(c) |
1107 | 105 | c.Assert(err, gc.IsNil) | ||
1109 | 106 | 106 | ||
1111 | 107 | err = u.SetPasswordHash(utils.UserPasswordHash("foo", utils.CompatSalt), utils.CompatSalt) | 107 | err := u.SetPasswordHash(utils.UserPasswordHash("foo", utils.CompatSalt), utils.CompatSalt) |
1112 | 108 | c.Assert(err, gc.IsNil) | 108 | c.Assert(err, gc.IsNil) |
1113 | 109 | 109 | ||
1114 | 110 | c.Assert(u.PasswordValid("foo"), jc.IsTrue) | 110 | c.Assert(u.PasswordValid("foo"), jc.IsTrue) |
1115 | @@ -120,10 +120,9 @@ | |||
1116 | 120 | } | 120 | } |
1117 | 121 | 121 | ||
1118 | 122 | func (s *UserSuite) TestSetPasswordHashWithSalt(c *gc.C) { | 122 | func (s *UserSuite) TestSetPasswordHashWithSalt(c *gc.C) { |
1121 | 123 | u, err := s.State.AddUser("someuser", "password") | 123 | u := s.makeUser(c) |
1120 | 124 | c.Assert(err, gc.IsNil) | ||
1122 | 125 | 124 | ||
1124 | 126 | err = u.SetPasswordHash(utils.UserPasswordHash("foo", "salted"), "salted") | 125 | err := u.SetPasswordHash(utils.UserPasswordHash("foo", "salted"), "salted") |
1125 | 127 | c.Assert(err, gc.IsNil) | 126 | c.Assert(err, gc.IsNil) |
1126 | 128 | 127 | ||
1127 | 129 | c.Assert(u.PasswordValid("foo"), jc.IsTrue) | 128 | c.Assert(u.PasswordValid("foo"), jc.IsTrue) |
1128 | @@ -133,11 +132,10 @@ | |||
1129 | 133 | } | 132 | } |
1130 | 134 | 133 | ||
1131 | 135 | func (s *UserSuite) TestPasswordValidUpdatesSalt(c *gc.C) { | 134 | func (s *UserSuite) TestPasswordValidUpdatesSalt(c *gc.C) { |
1134 | 136 | u, err := s.State.AddUser("someuser", "password") | 135 | u := s.makeUser(c) |
1133 | 137 | c.Assert(err, gc.IsNil) | ||
1135 | 138 | 136 | ||
1136 | 139 | compatHash := utils.UserPasswordHash("foo", utils.CompatSalt) | 137 | compatHash := utils.UserPasswordHash("foo", utils.CompatSalt) |
1138 | 140 | err = u.SetPasswordHash(compatHash, "") | 138 | err := u.SetPasswordHash(compatHash, "") |
1139 | 141 | c.Assert(err, gc.IsNil) | 139 | c.Assert(err, gc.IsNil) |
1140 | 142 | beforeSalt, beforeHash := state.GetUserPasswordSaltAndHash(u) | 140 | beforeSalt, beforeHash := state.GetUserPasswordSaltAndHash(u) |
1141 | 143 | c.Assert(beforeSalt, gc.Equals, "") | 141 | c.Assert(beforeSalt, gc.Equals, "") |
1142 | @@ -160,20 +158,11 @@ | |||
1143 | 160 | c.Assert(lastHash, gc.Equals, afterHash) | 158 | c.Assert(lastHash, gc.Equals, afterHash) |
1144 | 161 | } | 159 | } |
1145 | 162 | 160 | ||
1146 | 163 | func (s *UserSuite) TestName(c *gc.C) { | ||
1147 | 164 | u, err := s.State.AddUser("someuser", "password") | ||
1148 | 165 | c.Assert(err, gc.IsNil) | ||
1149 | 166 | |||
1150 | 167 | c.Assert(u.Name(), gc.Equals, "someuser") | ||
1151 | 168 | c.Assert(u.Tag(), gc.Equals, "user-someuser") | ||
1152 | 169 | } | ||
1153 | 170 | |||
1154 | 171 | func (s *UserSuite) TestDeactivate(c *gc.C) { | 161 | func (s *UserSuite) TestDeactivate(c *gc.C) { |
1157 | 172 | u, err := s.State.AddUser("someuser", "password") | 162 | u := s.makeUser(c) |
1156 | 173 | c.Assert(err, gc.IsNil) | ||
1158 | 174 | c.Assert(u.IsDeactivated(), gc.Equals, false) | 163 | c.Assert(u.IsDeactivated(), gc.Equals, false) |
1159 | 175 | 164 | ||
1161 | 176 | err = u.Deactivate() | 165 | err := u.Deactivate() |
1162 | 177 | c.Assert(err, gc.IsNil) | 166 | c.Assert(err, gc.IsNil) |
1163 | 178 | c.Assert(u.IsDeactivated(), gc.Equals, true) | 167 | c.Assert(u.IsDeactivated(), gc.Equals, true) |
1164 | 179 | c.Assert(u.PasswordValid(""), gc.Equals, false) | 168 | c.Assert(u.PasswordValid(""), gc.Equals, false) |
Reviewers: mp+221823_ code.launchpad. net,
Message:
Please take a look.
Description:
Add a display name option for new users
This ended up being more work than I hoped as I refactored the apiserver
parts, many tests, and made sure there was backwards compatability with
older client apis.
Also changed how the CLI command was tested so it didn't need a
JujuConnSuite.
https:/ /code.launchpad .net/~thumper/ juju-core/ user-display- name/+merge/ 221823
(do not edit description out of merge proposal)
Please review this at https:/ /codereview. appspot. com/102970043/
Affected files (+353, -240 lines): authorizedkeys_ test.go user_add. go user_add_ test.go conn.go dummy/environs. go params/ params. go usermanager/ client. go usermanager/ client_ test.go /charms_ test.go /client/ api_test. go /client/ client_ test.go /login_ test.go /usermanager/ usermanager. go /usermanager/ usermanager_ test.go test.go er_internal_ test.go
A [revision details]
M agent/bootstrap.go
M cmd/juju/
M cmd/juju/
M cmd/juju/
M juju/conn_test.go
M juju/testing/
M provider/
M state/api/
M state/api/
M state/api/
M state/apiserver
M state/apiserver
M state/apiserver
M state/apiserver
M state/apiserver
M state/apiserver
M state/compat_
M state/conn_test.go
M state/megawatch
M state/state_test.go
M state/user.go
M state/user_test.go