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 }