github.com/wallyworld/juju@v0.0.0-20161013125918-6cf1bc9d917a/api/testing/macaroonsuite.go (about) 1 // Copyright 2015 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package testing 5 6 import ( 7 "net/http" 8 "net/http/cookiejar" 9 "net/url" 10 11 "github.com/juju/errors" 12 jc "github.com/juju/testing/checkers" 13 gc "gopkg.in/check.v1" 14 "gopkg.in/juju/names.v2" 15 "gopkg.in/macaroon-bakery.v1/bakery/checkers" 16 "gopkg.in/macaroon-bakery.v1/bakerytest" 17 "gopkg.in/macaroon-bakery.v1/httpbakery" 18 19 "github.com/juju/juju/api" 20 "github.com/juju/juju/controller" 21 jujutesting "github.com/juju/juju/juju/testing" 22 "github.com/juju/juju/permission" 23 "github.com/juju/juju/state" 24 "github.com/juju/juju/testing/factory" 25 ) 26 27 // MacaroonSuite wraps a JujuConnSuite with macaroon authentication 28 // enabled. 29 type MacaroonSuite struct { 30 jujutesting.JujuConnSuite 31 32 // discharger holds the third-party discharger used 33 // for authentication. 34 discharger *bakerytest.Discharger 35 36 // DischargerLogin is called by the discharger when an 37 // API macaroon is discharged. It should either return 38 // the chosen username or an empty string, in which case 39 // the discharge is denied. 40 // If this is nil, func() {return ""} is implied. 41 DischargerLogin func() string 42 } 43 44 func (s *MacaroonSuite) SetUpTest(c *gc.C) { 45 s.discharger = bakerytest.NewDischarger(nil, func(req *http.Request, cond, arg string) ([]checkers.Caveat, error) { 46 if cond != "is-authenticated-user" { 47 return nil, errors.New("unknown caveat") 48 } 49 var username string 50 if s.DischargerLogin != nil { 51 username = s.DischargerLogin() 52 } 53 if username == "" { 54 return nil, errors.New("login denied by discharger") 55 } 56 return []checkers.Caveat{checkers.DeclaredCaveat("username", username)}, nil 57 }) 58 s.JujuConnSuite.ControllerConfigAttrs = map[string]interface{}{ 59 controller.IdentityURL: s.discharger.Location(), 60 } 61 s.JujuConnSuite.SetUpTest(c) 62 } 63 64 func (s *MacaroonSuite) TearDownTest(c *gc.C) { 65 s.discharger.Close() 66 s.JujuConnSuite.TearDownTest(c) 67 } 68 69 // AddModelUser is a convenience function that adds an external 70 // user to the current model. It will panic 71 // if the user name is local. 72 func (s *MacaroonSuite) AddModelUser(c *gc.C, username string) { 73 if names.NewUserTag(username).IsLocal() { 74 panic("cannot use MacaroonSuite.AddModelUser to add a local name") 75 } 76 s.Factory.MakeModelUser(c, &factory.ModelUserParams{ 77 User: username, 78 }) 79 } 80 81 // AddControllerUser is a convenience funcation that adds 82 // a controller user with the specified access. 83 func (s *MacaroonSuite) AddControllerUser(c *gc.C, username string, access permission.Access) { 84 _, err := s.State.AddControllerUser(state.UserAccessSpec{ 85 User: names.NewUserTag(username), 86 CreatedBy: s.AdminUserTag(c), 87 Access: access, 88 }) 89 c.Assert(err, jc.ErrorIsNil) 90 } 91 92 // OpenAPI opens a connection to the API using the given information. 93 // and empty DialOpts. If info is nil, s.APIInfo(c) is used. 94 // If jar is non-nil, it will be used as the store for the cookies created 95 // as a result of API interaction. 96 func (s *MacaroonSuite) OpenAPI(c *gc.C, info *api.Info, jar http.CookieJar) api.Connection { 97 if info == nil { 98 info = s.APIInfo(c) 99 } 100 bakeryClient := httpbakery.NewClient() 101 if jar != nil { 102 bakeryClient.Client.Jar = jar 103 } 104 conn, err := api.Open(info, api.DialOpts{ 105 BakeryClient: bakeryClient, 106 }) 107 c.Assert(err, gc.IsNil) 108 return conn 109 } 110 111 // APIInfo returns API connection info suitable for 112 // connecting to the API using macaroon authentication. 113 func (s *MacaroonSuite) APIInfo(c *gc.C) *api.Info { 114 info := s.JujuConnSuite.APIInfo(c) 115 info.Tag = nil 116 info.Password = "" 117 return info 118 } 119 120 // NewClearableCookieJar returns a new ClearableCookieJar. 121 func NewClearableCookieJar() *ClearableCookieJar { 122 jar, err := cookiejar.New(nil) 123 if err != nil { 124 panic(err) 125 } 126 return &ClearableCookieJar{ 127 jar: jar, 128 } 129 } 130 131 // ClearableCookieJar implements a cookie jar 132 // that can be cleared of all cookies for testing purposes. 133 type ClearableCookieJar struct { 134 jar http.CookieJar 135 } 136 137 // Clear clears all the cookies in the jar. 138 // It is not OK to call Clear concurrently 139 // with the other methods. 140 func (jar *ClearableCookieJar) Clear() { 141 newJar, err := cookiejar.New(nil) 142 if err != nil { 143 panic(err) 144 } 145 jar.jar = newJar 146 } 147 148 // Cookies implements http.CookieJar.Cookies. 149 func (jar *ClearableCookieJar) Cookies(u *url.URL) []*http.Cookie { 150 return jar.jar.Cookies(u) 151 } 152 153 // Cookies implements http.CookieJar.SetCookies. 154 func (jar *ClearableCookieJar) SetCookies(u *url.URL, cookies []*http.Cookie) { 155 jar.jar.SetCookies(u, cookies) 156 }