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