github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/cmd/modelcmd/apicontext.go (about)

     1  // Copyright 2016 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package modelcmd
     5  
     6  import (
     7  	"net/http"
     8  	"net/url"
     9  	"os"
    10  
    11  	"github.com/juju/cmd"
    12  	"github.com/juju/errors"
    13  	"github.com/juju/gnuflag"
    14  	"github.com/juju/idmclient/ussologin"
    15  	"gopkg.in/juju/environschema.v1/form"
    16  	"gopkg.in/macaroon-bakery.v2-unstable/httpbakery"
    17  
    18  	"github.com/juju/juju/jujuclient"
    19  )
    20  
    21  // apiContext holds the context required for making connections to
    22  // APIs used by juju.
    23  type apiContext struct {
    24  	// jar holds the internal version of the cookie jar - it has
    25  	// methods that clients should not use, such as Save.
    26  	jar            *domainCookieJar
    27  	webPageVisitor httpbakery.Visitor
    28  }
    29  
    30  // AuthOpts holds flags relating to authentication.
    31  type AuthOpts struct {
    32  	// NoBrowser specifies that web-browser-based auth should
    33  	// not be used when authenticating.
    34  	NoBrowser bool
    35  }
    36  
    37  func (o *AuthOpts) SetFlags(f *gnuflag.FlagSet) {
    38  	f.BoolVar(&o.NoBrowser, "B", false, "Do not use web browser for authentication")
    39  	f.BoolVar(&o.NoBrowser, "no-browser-login", false, "")
    40  }
    41  
    42  // newAPIContext returns an API context that will use the given
    43  // context for user interactions when authorizing.
    44  // The returned API context must be closed after use.
    45  //
    46  // If ctxt is nil, no command-line authorization
    47  // will be supported.
    48  //
    49  // This function is provided for use by commands that cannot use
    50  // CommandBase. Most clients should use that instead.
    51  func newAPIContext(ctxt *cmd.Context, opts *AuthOpts, store jujuclient.CookieStore, controllerName string) (*apiContext, error) {
    52  	jar0, err := store.CookieJar(controllerName)
    53  	if err != nil {
    54  		return nil, errors.Trace(err)
    55  	}
    56  	// The JUJU_USER_DOMAIN environment variable specifies
    57  	// the preferred user domain when discharging third party caveats.
    58  	// We set up a cookie jar that will send it to all sites because
    59  	// we don't know where the third party might be.
    60  	jar := &domainCookieJar{
    61  		CookieJar: jar0,
    62  		domain:    os.Getenv("JUJU_USER_DOMAIN"),
    63  	}
    64  	var visitors []httpbakery.Visitor
    65  	if ctxt != nil && opts != nil && opts.NoBrowser {
    66  		filler := &form.IOFiller{
    67  			In:  ctxt.Stdin,
    68  			Out: ctxt.Stdout,
    69  		}
    70  		newVisitor := ussologin.NewVisitor("juju", filler, jujuclient.NewTokenStore())
    71  		visitors = append(visitors, newVisitor)
    72  	} else {
    73  		visitors = append(visitors, httpbakery.WebBrowserVisitor)
    74  	}
    75  	return &apiContext{
    76  		jar:            jar,
    77  		webPageVisitor: httpbakery.NewMultiVisitor(visitors...),
    78  	}, nil
    79  }
    80  
    81  // CookieJar returns the cookie jar used to make
    82  // HTTP requests.
    83  func (ctx *apiContext) CookieJar() http.CookieJar {
    84  	return ctx.jar
    85  }
    86  
    87  // NewBakeryClient returns a new httpbakery.Client, using the API context's
    88  // persistent cookie jar and web page visitor.
    89  func (ctx *apiContext) NewBakeryClient() *httpbakery.Client {
    90  	client := httpbakery.NewClient()
    91  	client.Jar = ctx.jar
    92  	client.WebPageVisitor = ctx.webPageVisitor
    93  	return client
    94  }
    95  
    96  // Close closes the API context, saving any cookies to the
    97  // persistent cookie jar.
    98  func (ctxt *apiContext) Close() error {
    99  	if err := ctxt.jar.Save(); err != nil {
   100  		return errors.Annotatef(err, "cannot save cookie jar")
   101  	}
   102  	return nil
   103  }
   104  
   105  const domainCookieName = "domain"
   106  
   107  // domainCookieJar implements a variant of CookieJar that
   108  // always includes a domain cookie regardless of the site.
   109  type domainCookieJar struct {
   110  	jujuclient.CookieJar
   111  	// domain holds the value of the domain cookie.
   112  	domain string
   113  }
   114  
   115  // Cookies implements http.CookieJar.Cookies by
   116  // adding the domain cookie when the domain is non-empty.
   117  func (j *domainCookieJar) Cookies(u *url.URL) []*http.Cookie {
   118  	cookies := j.CookieJar.Cookies(u)
   119  	if j.domain == "" {
   120  		return cookies
   121  	}
   122  	// Allow the site to override if it wants to.
   123  	for _, c := range cookies {
   124  		if c.Name == domainCookieName {
   125  			return cookies
   126  		}
   127  	}
   128  	return append(cookies, &http.Cookie{
   129  		Name:  domainCookieName,
   130  		Value: j.domain,
   131  	})
   132  }