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  }