github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/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.v2-unstable/bakery/checkers" 16 "gopkg.in/macaroon-bakery.v2-unstable/bakerytest" 17 "gopkg.in/macaroon-bakery.v2-unstable/httpbakery" 18 "gopkg.in/macaroon.v2-unstable" 19 20 "github.com/juju/juju/api" 21 "github.com/juju/juju/controller" 22 jujutesting "github.com/juju/juju/juju/testing" 23 "github.com/juju/juju/permission" 24 "github.com/juju/juju/state" 25 "github.com/juju/juju/testing/factory" 26 ) 27 28 // MacaroonSuite wraps a JujuConnSuite with macaroon authentication 29 // enabled. 30 type MacaroonSuite struct { 31 jujutesting.JujuConnSuite 32 33 // discharger holds the third-party discharger used 34 // for authentication. 35 discharger *bakerytest.Discharger 36 37 // DischargerLogin is called by the discharger when an 38 // API macaroon is discharged. It should either return 39 // the chosen username or an empty string, in which case 40 // the discharge is denied. 41 // If this is nil, func() {return ""} is implied. 42 DischargerLogin func() string 43 } 44 45 func (s *MacaroonSuite) SetUpTest(c *gc.C) { 46 s.DischargerLogin = nil 47 s.discharger = bakerytest.NewDischarger(nil, func(req *http.Request, cond, arg string) ([]checkers.Caveat, error) { 48 if cond != "is-authenticated-user" { 49 return nil, errors.New("unknown caveat") 50 } 51 var username string 52 if s.DischargerLogin != nil { 53 username = s.DischargerLogin() 54 } 55 if username == "" { 56 return nil, errors.New("login denied by discharger") 57 } 58 return []checkers.Caveat{checkers.DeclaredCaveat("username", username)}, nil 59 }) 60 s.JujuConnSuite.ControllerConfigAttrs = map[string]interface{}{ 61 controller.IdentityURL: s.discharger.Location(), 62 } 63 s.JujuConnSuite.SetUpTest(c) 64 } 65 66 func (s *MacaroonSuite) TearDownTest(c *gc.C) { 67 s.discharger.Close() 68 s.JujuConnSuite.TearDownTest(c) 69 } 70 71 // DischargerLocation returns the URL of the third party caveat 72 // discharger. 73 func (s *MacaroonSuite) DischargerLocation() string { 74 return s.discharger.Location() 75 } 76 77 // AddModelUser is a convenience function that adds an external 78 // user to the current model. It will panic 79 // if the user name is local. 80 func (s *MacaroonSuite) AddModelUser(c *gc.C, username string) { 81 if names.NewUserTag(username).IsLocal() { 82 panic("cannot use MacaroonSuite.AddModelUser to add a local name") 83 } 84 s.Factory.MakeModelUser(c, &factory.ModelUserParams{ 85 User: username, 86 }) 87 } 88 89 // AddControllerUser is a convenience funcation that adds 90 // a controller user with the specified access. 91 func (s *MacaroonSuite) AddControllerUser(c *gc.C, username string, access permission.Access) { 92 _, err := s.State.AddControllerUser(state.UserAccessSpec{ 93 User: names.NewUserTag(username), 94 CreatedBy: s.AdminUserTag(c), 95 Access: access, 96 }) 97 c.Assert(err, jc.ErrorIsNil) 98 } 99 100 // OpenAPI opens a connection to the API using the given information. 101 // and empty DialOpts. If info is nil, s.APIInfo(c) is used. 102 // If jar is non-nil, it will be used as the store for the cookies created 103 // as a result of API interaction. 104 func (s *MacaroonSuite) OpenAPI(c *gc.C, info *api.Info, jar http.CookieJar) api.Connection { 105 if info == nil { 106 info = s.APIInfo(c) 107 } 108 bakeryClient := httpbakery.NewClient() 109 if jar != nil { 110 bakeryClient.Client.Jar = jar 111 } 112 conn, err := api.Open(info, api.DialOpts{ 113 BakeryClient: bakeryClient, 114 }) 115 c.Assert(err, gc.IsNil) 116 return conn 117 } 118 119 // APIInfo returns API connection info suitable for 120 // connecting to the API using macaroon authentication. 121 func (s *MacaroonSuite) APIInfo(c *gc.C) *api.Info { 122 info := s.JujuConnSuite.APIInfo(c) 123 info.Tag = nil 124 info.Password = "" 125 // Fill in any old macaroon to ensure we don't attempt 126 // an anonymous login. 127 mac, err := NewMacaroon("test") 128 c.Assert(err, jc.ErrorIsNil) 129 info.Macaroons = []macaroon.Slice{{mac}} 130 return info 131 } 132 133 // NewClearableCookieJar returns a new ClearableCookieJar. 134 func NewClearableCookieJar() *ClearableCookieJar { 135 jar, err := cookiejar.New(nil) 136 if err != nil { 137 panic(err) 138 } 139 return &ClearableCookieJar{ 140 jar: jar, 141 } 142 } 143 144 // ClearableCookieJar implements a cookie jar 145 // that can be cleared of all cookies for testing purposes. 146 type ClearableCookieJar struct { 147 jar http.CookieJar 148 } 149 150 // Clear clears all the cookies in the jar. 151 // It is not OK to call Clear concurrently 152 // with the other methods. 153 func (jar *ClearableCookieJar) Clear() { 154 newJar, err := cookiejar.New(nil) 155 if err != nil { 156 panic(err) 157 } 158 jar.jar = newJar 159 } 160 161 // Cookies implements http.CookieJar.Cookies. 162 func (jar *ClearableCookieJar) Cookies(u *url.URL) []*http.Cookie { 163 return jar.jar.Cookies(u) 164 } 165 166 // Cookies implements http.CookieJar.SetCookies. 167 func (jar *ClearableCookieJar) SetCookies(u *url.URL, cookies []*http.Cookie) { 168 jar.jar.SetCookies(u, cookies) 169 } 170 171 func MacaroonsEqual(c *gc.C, ms1, ms2 []macaroon.Slice) error { 172 if len(ms1) != len(ms2) { 173 return errors.Errorf("length mismatch, %d vs %d", len(ms1), len(ms2)) 174 } 175 176 for i := 0; i < len(ms1); i++ { 177 m1 := ms1[i] 178 m2 := ms2[i] 179 if len(m1) != len(m2) { 180 return errors.Errorf("length mismatch, %d vs %d", len(m1), len(m2)) 181 } 182 for i := 0; i < len(m1); i++ { 183 MacaroonEquals(c, m1[i], m2[i]) 184 } 185 } 186 return nil 187 } 188 189 func MacaroonEquals(c *gc.C, m1, m2 *macaroon.Macaroon) { 190 c.Assert(m1.Id(), jc.DeepEquals, m2.Id()) 191 c.Assert(m1.Signature(), jc.DeepEquals, m2.Signature()) 192 c.Assert(m1.Location(), jc.DeepEquals, m2.Location()) 193 } 194 195 func NewMacaroon(id string) (*macaroon.Macaroon, error) { 196 return macaroon.New(nil, []byte(id), "") 197 }