github.com/wallyworld/juju@v0.0.0-20161013125918-6cf1bc9d917a/apiserver/tools_test.go (about) 1 // Copyright 2012, 2013 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package apiserver_test 5 6 import ( 7 "crypto/sha256" 8 "encoding/json" 9 "fmt" 10 "io" 11 "io/ioutil" 12 "net/http" 13 "net/url" 14 "path" 15 "strings" 16 17 "github.com/juju/errors" 18 jc "github.com/juju/testing/checkers" 19 "github.com/juju/utils" 20 "github.com/juju/utils/arch" 21 "github.com/juju/utils/series" 22 "github.com/juju/version" 23 gc "gopkg.in/check.v1" 24 "gopkg.in/macaroon-bakery.v1/httpbakery" 25 26 apiauthentication "github.com/juju/juju/api/authentication" 27 apitesting "github.com/juju/juju/api/testing" 28 commontesting "github.com/juju/juju/apiserver/common/testing" 29 "github.com/juju/juju/apiserver/params" 30 envtesting "github.com/juju/juju/environs/testing" 31 envtools "github.com/juju/juju/environs/tools" 32 toolstesting "github.com/juju/juju/environs/tools/testing" 33 "github.com/juju/juju/state" 34 "github.com/juju/juju/state/binarystorage" 35 "github.com/juju/juju/testing" 36 "github.com/juju/juju/testing/factory" 37 coretools "github.com/juju/juju/tools" 38 jujuversion "github.com/juju/juju/version" 39 ) 40 41 // charmsCommonSuite wraps authHTTPSuite and adds 42 // some helper methods suitable for working with the 43 // tools endpoint. 44 type toolsCommonSuite struct { 45 authHTTPSuite 46 } 47 48 func (s *toolsCommonSuite) toolsURL(c *gc.C, query string) *url.URL { 49 uri := s.baseURL(c) 50 uri.Path = fmt.Sprintf("/model/%s/tools", s.modelUUID) 51 uri.RawQuery = query 52 return uri 53 } 54 55 func (s *toolsCommonSuite) toolsURI(c *gc.C, query string) string { 56 if query != "" && query[0] == '?' { 57 query = query[1:] 58 } 59 return s.toolsURL(c, query).String() 60 } 61 62 func (s *toolsCommonSuite) downloadRequest(c *gc.C, version version.Binary, uuid string) *http.Response { 63 url := s.toolsURL(c, "") 64 if uuid == "" { 65 url.Path = fmt.Sprintf("/tools/%s", version) 66 } else { 67 url.Path = fmt.Sprintf("/model/%s/tools/%s", uuid, version) 68 } 69 return s.sendRequest(c, httpRequestParams{method: "GET", url: url.String()}) 70 } 71 72 func (s *toolsCommonSuite) assertUploadResponse(c *gc.C, resp *http.Response, agentTools *coretools.Tools) { 73 toolsResponse := s.assertResponse(c, resp, http.StatusOK) 74 c.Check(toolsResponse.Error, gc.IsNil) 75 c.Check(toolsResponse.ToolsList, jc.DeepEquals, coretools.List{agentTools}) 76 } 77 78 func (s *toolsCommonSuite) assertGetFileResponse(c *gc.C, resp *http.Response, expBody, expContentType string) { 79 body := assertResponse(c, resp, http.StatusOK, expContentType) 80 c.Check(string(body), gc.Equals, expBody) 81 } 82 83 func (s *toolsCommonSuite) assertErrorResponse(c *gc.C, resp *http.Response, expCode int, expError string) { 84 toolsResponse := s.assertResponse(c, resp, expCode) 85 c.Assert(toolsResponse.Error, gc.NotNil) 86 c.Assert(toolsResponse.Error.Message, gc.Matches, expError) 87 } 88 89 func (s *toolsCommonSuite) assertResponse(c *gc.C, resp *http.Response, expStatus int) params.ToolsResult { 90 body := assertResponse(c, resp, expStatus, params.ContentTypeJSON) 91 var toolsResponse params.ToolsResult 92 err := json.Unmarshal(body, &toolsResponse) 93 c.Assert(err, jc.ErrorIsNil, gc.Commentf("body: %s", body)) 94 return toolsResponse 95 } 96 97 type toolsSuite struct { 98 toolsCommonSuite 99 commontesting.BlockHelper 100 } 101 102 var _ = gc.Suite(&toolsSuite{}) 103 104 func (s *toolsSuite) SetUpTest(c *gc.C) { 105 s.toolsCommonSuite.SetUpTest(c) 106 s.BlockHelper = commontesting.NewBlockHelper(s.APIState) 107 s.AddCleanup(func(*gc.C) { s.BlockHelper.Close() }) 108 } 109 110 func (s *toolsSuite) TestToolsUploadedSecurely(c *gc.C) { 111 info := s.APIInfo(c) 112 uri := "http://" + info.Addrs[0] + "/tools" 113 s.sendRequest(c, httpRequestParams{ 114 method: "PUT", 115 url: uri, 116 expectError: `.*malformed HTTP response.*`, 117 }) 118 } 119 120 func (s *toolsSuite) TestRequiresAuth(c *gc.C) { 121 resp := s.sendRequest(c, httpRequestParams{method: "GET", url: s.toolsURI(c, "")}) 122 s.assertErrorResponse(c, resp, http.StatusUnauthorized, "no credentials provided") 123 } 124 125 func (s *toolsSuite) TestRequiresPOST(c *gc.C) { 126 resp := s.authRequest(c, httpRequestParams{method: "PUT", url: s.toolsURI(c, "")}) 127 s.assertErrorResponse(c, resp, http.StatusMethodNotAllowed, `unsupported method: "PUT"`) 128 } 129 130 func (s *toolsSuite) TestAuthRequiresUser(c *gc.C) { 131 // Add a machine and try to login. 132 machine, err := s.State.AddMachine("quantal", state.JobHostUnits) 133 c.Assert(err, jc.ErrorIsNil) 134 err = machine.SetProvisioned("foo", "fake_nonce", nil) 135 c.Assert(err, jc.ErrorIsNil) 136 password, err := utils.RandomPassword() 137 c.Assert(err, jc.ErrorIsNil) 138 err = machine.SetPassword(password) 139 c.Assert(err, jc.ErrorIsNil) 140 141 resp := s.sendRequest(c, httpRequestParams{tag: machine.Tag().String(), password: password, method: "POST", url: s.toolsURI(c, "")}) 142 s.assertErrorResponse(c, resp, http.StatusUnauthorized, "machine 0 not provisioned") 143 144 // Now try a user login. 145 resp = s.authRequest(c, httpRequestParams{method: "POST", url: s.toolsURI(c, "")}) 146 s.assertErrorResponse(c, resp, http.StatusBadRequest, "expected binaryVersion argument") 147 } 148 149 func (s *toolsSuite) TestUploadRequiresVersion(c *gc.C) { 150 resp := s.authRequest(c, httpRequestParams{method: "POST", url: s.toolsURI(c, "")}) 151 s.assertErrorResponse(c, resp, http.StatusBadRequest, "expected binaryVersion argument") 152 } 153 154 func (s *toolsSuite) TestUploadFailsWithNoTools(c *gc.C) { 155 // Create an empty file. 156 tempFile, err := ioutil.TempFile(c.MkDir(), "tools") 157 c.Assert(err, jc.ErrorIsNil) 158 159 resp := s.uploadRequest(c, s.toolsURI(c, "?binaryVersion=1.18.0-quantal-amd64"), "application/x-tar-gz", tempFile.Name()) 160 s.assertErrorResponse(c, resp, http.StatusBadRequest, "no tools uploaded") 161 } 162 163 func (s *toolsSuite) TestUploadFailsWithInvalidContentType(c *gc.C) { 164 // Create an empty file. 165 tempFile, err := ioutil.TempFile(c.MkDir(), "tools") 166 c.Assert(err, jc.ErrorIsNil) 167 168 // Now try with the default Content-Type. 169 resp := s.uploadRequest(c, s.toolsURI(c, "?binaryVersion=1.18.0-quantal-amd64"), "application/octet-stream", tempFile.Name()) 170 s.assertErrorResponse( 171 c, resp, http.StatusBadRequest, "expected Content-Type: application/x-tar-gz, got: application/octet-stream") 172 } 173 174 func (s *toolsSuite) setupToolsForUpload(c *gc.C) (coretools.List, version.Binary, string) { 175 localStorage := c.MkDir() 176 vers := version.MustParseBinary("1.9.0-quantal-amd64") 177 versionStrings := []string{vers.String()} 178 expectedTools := toolstesting.MakeToolsWithCheckSum(c, localStorage, "released", versionStrings) 179 toolsFile := envtools.StorageName(vers, "released") 180 return expectedTools, vers, path.Join(localStorage, toolsFile) 181 } 182 183 func (s *toolsSuite) TestUpload(c *gc.C) { 184 // Make some fake tools. 185 expectedTools, v, toolPath := s.setupToolsForUpload(c) 186 vers := v.String() 187 // Now try uploading them. 188 resp := s.uploadRequest( 189 c, s.toolsURI(c, "?binaryVersion="+vers), "application/x-tar-gz", toolPath) 190 191 // Check the response. 192 expectedTools[0].URL = fmt.Sprintf("%s/model/%s/tools/%s", s.baseURL(c), s.State.ModelUUID(), vers) 193 s.assertUploadResponse(c, resp, expectedTools[0]) 194 195 // Check the contents. 196 metadata, uploadedData := s.getToolsFromStorage(c, s.State, vers) 197 expectedData, err := ioutil.ReadFile(toolPath) 198 c.Assert(err, jc.ErrorIsNil) 199 c.Assert(uploadedData, gc.DeepEquals, expectedData) 200 allMetadata := s.getToolsMetadataFromStorage(c, s.State) 201 c.Assert(allMetadata, jc.DeepEquals, []binarystorage.Metadata{metadata}) 202 } 203 204 func (s *toolsSuite) TestBlockUpload(c *gc.C) { 205 // Make some fake tools. 206 _, v, toolPath := s.setupToolsForUpload(c) 207 vers := v.String() 208 // Block all changes. 209 s.BlockAllChanges(c, "TestUpload") 210 // Now try uploading them. 211 resp := s.uploadRequest( 212 c, s.toolsURI(c, "?binaryVersion="+vers), "application/x-tar-gz", toolPath) 213 toolsResponse := s.assertResponse(c, resp, http.StatusBadRequest) 214 s.AssertBlocked(c, toolsResponse.Error, "TestUpload") 215 216 // Check the contents. 217 storage, err := s.State.ToolsStorage() 218 c.Assert(err, jc.ErrorIsNil) 219 defer storage.Close() 220 _, _, err = storage.Open(vers) 221 c.Assert(errors.IsNotFound(err), jc.IsTrue) 222 } 223 224 func (s *toolsSuite) TestUploadAllowsTopLevelPath(c *gc.C) { 225 // Backwards compatibility check, that we can upload tools to 226 // https://host:port/tools 227 expectedTools, vers, toolPath := s.setupToolsForUpload(c) 228 url := s.toolsURL(c, "binaryVersion="+vers.String()) 229 url.Path = "/tools" 230 resp := s.uploadRequest(c, url.String(), "application/x-tar-gz", toolPath) 231 // Check the response. 232 expectedTools[0].URL = fmt.Sprintf("%s/model/%s/tools/%s", s.baseURL(c), s.State.ModelUUID(), vers) 233 s.assertUploadResponse(c, resp, expectedTools[0]) 234 } 235 236 func (s *toolsSuite) TestUploadAllowsModelUUIDPath(c *gc.C) { 237 // Check that we can upload tools to https://host:port/ModelUUID/tools 238 expectedTools, vers, toolPath := s.setupToolsForUpload(c) 239 url := s.toolsURL(c, "binaryVersion="+vers.String()) 240 url.Path = fmt.Sprintf("/model/%s/tools", s.State.ModelUUID()) 241 resp := s.uploadRequest(c, url.String(), "application/x-tar-gz", toolPath) 242 // Check the response. 243 expectedTools[0].URL = fmt.Sprintf("%s/model/%s/tools/%s", s.baseURL(c), s.State.ModelUUID(), vers) 244 s.assertUploadResponse(c, resp, expectedTools[0]) 245 } 246 247 func (s *toolsSuite) TestUploadAllowsOtherModelUUIDPath(c *gc.C) { 248 envState := s.setupOtherModel(c) 249 // Check that we can upload tools to https://host:port/ModelUUID/tools 250 expectedTools, vers, toolPath := s.setupToolsForUpload(c) 251 url := s.toolsURL(c, "binaryVersion="+vers.String()) 252 url.Path = fmt.Sprintf("/model/%s/tools", envState.ModelUUID()) 253 resp := s.uploadRequest(c, url.String(), "application/x-tar-gz", toolPath) 254 // Check the response. 255 expectedTools[0].URL = fmt.Sprintf("%s/model/%s/tools/%s", s.baseURL(c), envState.ModelUUID(), vers) 256 s.assertUploadResponse(c, resp, expectedTools[0]) 257 } 258 259 func (s *toolsSuite) TestUploadRejectsWrongModelUUIDPath(c *gc.C) { 260 // Check that we cannot access the tools at https://host:port/BADModelUUID/tools 261 url := s.toolsURL(c, "") 262 url.Path = "/model/dead-beef-123456/tools" 263 resp := s.authRequest(c, httpRequestParams{method: "POST", url: url.String()}) 264 s.assertErrorResponse(c, resp, http.StatusNotFound, `unknown model: "dead-beef-123456"`) 265 } 266 267 func (s *toolsSuite) TestUploadSeriesExpanded(c *gc.C) { 268 // Make some fake tools. 269 expectedTools, v, toolPath := s.setupToolsForUpload(c) 270 vers := v.String() 271 // Now try uploading them. The tools will be cloned for 272 // each additional series specified. 273 params := "?binaryVersion=" + vers + "&series=quantal,precise" 274 resp := s.uploadRequest(c, s.toolsURI(c, params), "application/x-tar-gz", toolPath) 275 c.Assert(resp.StatusCode, gc.Equals, http.StatusOK) 276 277 // Check the response. 278 info := s.APIInfo(c) 279 expectedTools[0].URL = fmt.Sprintf("%s/model/%s/tools/%s", s.baseURL(c), info.ModelTag.Id(), vers) 280 s.assertUploadResponse(c, resp, expectedTools[0]) 281 282 // Check the contents. 283 storage, err := s.State.ToolsStorage() 284 c.Assert(err, jc.ErrorIsNil) 285 defer storage.Close() 286 expectedData, err := ioutil.ReadFile(toolPath) 287 c.Assert(err, jc.ErrorIsNil) 288 for _, series := range []string{"precise", "quantal"} { 289 v.Series = series 290 _, r, err := storage.Open(v.String()) 291 c.Assert(err, jc.ErrorIsNil) 292 uploadedData, err := ioutil.ReadAll(r) 293 r.Close() 294 c.Assert(err, jc.ErrorIsNil) 295 c.Assert(uploadedData, gc.DeepEquals, expectedData) 296 } 297 298 // ensure other series *aren't* there. 299 v.Series = "trusty" 300 _, err = storage.Metadata(v.String()) 301 c.Assert(err, jc.Satisfies, errors.IsNotFound) 302 } 303 304 func (s *toolsSuite) TestDownloadModelUUIDPath(c *gc.C) { 305 v := version.Binary{ 306 Number: jujuversion.Current, 307 Arch: arch.HostArch(), 308 Series: series.HostSeries(), 309 } 310 tools := s.storeFakeTools(c, s.State, "abc", binarystorage.Metadata{ 311 Version: v.String(), 312 Size: 3, 313 SHA256: "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad", 314 }) 315 s.testDownload(c, tools, s.State.ModelUUID()) 316 } 317 318 func (s *toolsSuite) TestDownloadOtherModelUUIDPath(c *gc.C) { 319 envState := s.setupOtherModel(c) 320 v := version.Binary{ 321 Number: jujuversion.Current, 322 Arch: arch.HostArch(), 323 Series: series.HostSeries(), 324 } 325 tools := s.storeFakeTools(c, envState, "abc", binarystorage.Metadata{ 326 Version: v.String(), 327 Size: 3, 328 SHA256: "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad", 329 }) 330 s.testDownload(c, tools, envState.ModelUUID()) 331 } 332 333 func (s *toolsSuite) TestDownloadTopLevelPath(c *gc.C) { 334 v := version.Binary{ 335 Number: jujuversion.Current, 336 Arch: arch.HostArch(), 337 Series: series.HostSeries(), 338 } 339 tools := s.storeFakeTools(c, s.State, "abc", binarystorage.Metadata{ 340 Version: v.String(), 341 Size: 3, 342 SHA256: "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad", 343 }) 344 s.testDownload(c, tools, "") 345 } 346 347 func (s *toolsSuite) TestDownloadFetchesAndCaches(c *gc.C) { 348 // The tools are not in binarystorage, so the download request causes 349 // the API server to search for the tools in simplestreams, fetch 350 // them, and then cache them in binarystorage. 351 vers := version.MustParseBinary("1.23.0-trusty-amd64") 352 stor := s.DefaultToolsStorage 353 envtesting.RemoveTools(c, stor, "released") 354 tools := envtesting.AssertUploadFakeToolsVersions(c, stor, "released", "released", vers)[0] 355 data := s.testDownload(c, tools, "") 356 357 metadata, cachedData := s.getToolsFromStorage(c, s.State, tools.Version.String()) 358 c.Assert(metadata.Size, gc.Equals, tools.Size) 359 c.Assert(metadata.SHA256, gc.Equals, tools.SHA256) 360 c.Assert(string(cachedData), gc.Equals, string(data)) 361 } 362 363 func (s *toolsSuite) TestDownloadFetchesAndVerifiesSize(c *gc.C) { 364 // Upload fake tools, then upload over the top so the SHA256 hash does not match. 365 s.PatchValue(&jujuversion.Current, testing.FakeVersionNumber) 366 stor := s.DefaultToolsStorage 367 envtesting.RemoveTools(c, stor, "released") 368 current := version.Binary{ 369 Number: jujuversion.Current, 370 Arch: arch.HostArch(), 371 Series: series.HostSeries(), 372 } 373 tools := envtesting.AssertUploadFakeToolsVersions(c, stor, "released", "released", current)[0] 374 err := stor.Put(envtools.StorageName(tools.Version, "released"), strings.NewReader("!"), 1) 375 c.Assert(err, jc.ErrorIsNil) 376 377 resp := s.downloadRequest(c, tools.Version, "") 378 s.assertErrorResponse(c, resp, http.StatusBadRequest, "error fetching tools: size mismatch for .*") 379 s.assertToolsNotStored(c, tools.Version.String()) 380 } 381 382 func (s *toolsSuite) TestDownloadFetchesAndVerifiesHash(c *gc.C) { 383 // Upload fake tools, then upload over the top so the SHA256 hash does not match. 384 s.PatchValue(&jujuversion.Current, testing.FakeVersionNumber) 385 stor := s.DefaultToolsStorage 386 envtesting.RemoveTools(c, stor, "released") 387 current := version.Binary{ 388 Number: jujuversion.Current, 389 Arch: arch.HostArch(), 390 Series: series.HostSeries(), 391 } 392 tools := envtesting.AssertUploadFakeToolsVersions(c, stor, "released", "released", current)[0] 393 sameSize := strings.Repeat("!", int(tools.Size)) 394 err := stor.Put(envtools.StorageName(tools.Version, "released"), strings.NewReader(sameSize), tools.Size) 395 c.Assert(err, jc.ErrorIsNil) 396 397 resp := s.downloadRequest(c, tools.Version, "") 398 s.assertErrorResponse(c, resp, http.StatusBadRequest, "error fetching tools: hash mismatch for .*") 399 s.assertToolsNotStored(c, tools.Version.String()) 400 } 401 402 func (s *toolsSuite) storeFakeTools(c *gc.C, st *state.State, content string, metadata binarystorage.Metadata) *coretools.Tools { 403 storage, err := st.ToolsStorage() 404 c.Assert(err, jc.ErrorIsNil) 405 defer storage.Close() 406 err = storage.Add(strings.NewReader(content), metadata) 407 c.Assert(err, jc.ErrorIsNil) 408 return &coretools.Tools{ 409 Version: version.MustParseBinary(metadata.Version), 410 Size: metadata.Size, 411 SHA256: metadata.SHA256, 412 } 413 } 414 415 func (s *toolsSuite) getToolsFromStorage(c *gc.C, st *state.State, vers string) (binarystorage.Metadata, []byte) { 416 storage, err := st.ToolsStorage() 417 c.Assert(err, jc.ErrorIsNil) 418 defer storage.Close() 419 metadata, r, err := storage.Open(vers) 420 c.Assert(err, jc.ErrorIsNil) 421 data, err := ioutil.ReadAll(r) 422 r.Close() 423 c.Assert(err, jc.ErrorIsNil) 424 return metadata, data 425 } 426 427 func (s *toolsSuite) getToolsMetadataFromStorage(c *gc.C, st *state.State) []binarystorage.Metadata { 428 storage, err := st.ToolsStorage() 429 c.Assert(err, jc.ErrorIsNil) 430 defer storage.Close() 431 metadata, err := storage.AllMetadata() 432 c.Assert(err, jc.ErrorIsNil) 433 return metadata 434 } 435 436 func (s *toolsSuite) assertToolsNotStored(c *gc.C, vers string) { 437 storage, err := s.State.ToolsStorage() 438 c.Assert(err, jc.ErrorIsNil) 439 defer storage.Close() 440 _, err = storage.Metadata(vers) 441 c.Assert(err, jc.Satisfies, errors.IsNotFound) 442 } 443 444 func (s *toolsSuite) testDownload(c *gc.C, tools *coretools.Tools, uuid string) []byte { 445 resp := s.downloadRequest(c, tools.Version, uuid) 446 defer resp.Body.Close() 447 data, err := ioutil.ReadAll(resp.Body) 448 c.Assert(err, jc.ErrorIsNil) 449 c.Assert(data, gc.HasLen, int(tools.Size)) 450 451 hash := sha256.New() 452 hash.Write(data) 453 c.Assert(fmt.Sprintf("%x", hash.Sum(nil)), gc.Equals, tools.SHA256) 454 return data 455 } 456 457 func (s *toolsSuite) TestDownloadRejectsWrongModelUUIDPath(c *gc.C) { 458 current := version.Binary{ 459 Number: jujuversion.Current, 460 Arch: arch.HostArch(), 461 Series: series.HostSeries(), 462 } 463 resp := s.downloadRequest(c, current, "dead-beef-123456") 464 s.assertErrorResponse(c, resp, http.StatusNotFound, `unknown model: "dead-beef-123456"`) 465 } 466 467 type toolsWithMacaroonsSuite struct { 468 toolsCommonSuite 469 } 470 471 var _ = gc.Suite(&toolsWithMacaroonsSuite{}) 472 473 func (s *toolsWithMacaroonsSuite) SetUpTest(c *gc.C) { 474 s.macaroonAuthEnabled = true 475 s.toolsCommonSuite.SetUpTest(c) 476 } 477 478 func (s *toolsWithMacaroonsSuite) TestWithNoBasicAuthReturnsDischargeRequiredError(c *gc.C) { 479 resp := s.sendRequest(c, httpRequestParams{ 480 method: "POST", 481 url: s.toolsURI(c, ""), 482 }) 483 484 charmResponse := s.assertResponse(c, resp, http.StatusUnauthorized) 485 c.Assert(charmResponse.Error, gc.NotNil) 486 c.Assert(charmResponse.Error.Message, gc.Equals, "verification failed: no macaroons") 487 c.Assert(charmResponse.Error.Code, gc.Equals, params.CodeDischargeRequired) 488 c.Assert(charmResponse.Error.Info, gc.NotNil) 489 c.Assert(charmResponse.Error.Info.Macaroon, gc.NotNil) 490 } 491 492 func (s *toolsWithMacaroonsSuite) TestCanPostWithDischargedMacaroon(c *gc.C) { 493 checkCount := 0 494 s.DischargerLogin = func() string { 495 checkCount++ 496 return s.userTag.Id() 497 } 498 resp := s.sendRequest(c, httpRequestParams{ 499 do: s.doer(), 500 method: "POST", 501 url: s.toolsURI(c, ""), 502 }) 503 s.assertErrorResponse(c, resp, http.StatusBadRequest, "expected binaryVersion argument") 504 c.Assert(checkCount, gc.Equals, 1) 505 } 506 507 func (s *toolsWithMacaroonsSuite) TestCanPostWithLocalLogin(c *gc.C) { 508 // Create a new local user that we can log in as 509 // using macaroon authentication. 510 const password = "hunter2" 511 user := s.Factory.MakeUser(c, &factory.UserParams{Password: password}) 512 513 // Install a "web-page" visitor that deals with the interaction 514 // method that Juju controllers support for authenticating local 515 // users. Note: the use of httpbakery.NewMultiVisitor is necessary 516 // to trigger httpbakery to query the authentication methods and 517 // bypass browser authentication. 518 var prompted bool 519 jar := apitesting.NewClearableCookieJar() 520 client := utils.GetNonValidatingHTTPClient() 521 client.Jar = jar 522 bakeryClient := httpbakery.NewClient() 523 bakeryClient.Client = client 524 bakeryClient.WebPageVisitor = httpbakery.NewMultiVisitor(apiauthentication.NewVisitor( 525 user.UserTag().Id(), 526 func(username string) (string, error) { 527 c.Assert(username, gc.Equals, user.UserTag().Id()) 528 prompted = true 529 return password, nil 530 }, 531 )) 532 bakeryDo := func(req *http.Request) (*http.Response, error) { 533 var body io.ReadSeeker 534 if req.Body != nil { 535 body = req.Body.(io.ReadSeeker) 536 req.Body = nil 537 } 538 return bakeryClient.DoWithBodyAndCustomError(req, body, bakeryGetError) 539 } 540 541 resp := s.sendRequest(c, httpRequestParams{ 542 method: "POST", 543 url: s.toolsURI(c, ""), 544 tag: user.UserTag().String(), 545 password: "", // no password forces macaroon usage 546 do: bakeryDo, 547 }) 548 s.assertErrorResponse(c, resp, http.StatusBadRequest, "expected binaryVersion argument") 549 c.Assert(prompted, jc.IsTrue) 550 } 551 552 // doer returns a Do function that can make a bakery request 553 // appropriate for a charms endpoint. 554 func (s *toolsWithMacaroonsSuite) doer() func(*http.Request) (*http.Response, error) { 555 return bakeryDo(nil, bakeryGetError) 556 }