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  }