1
// Copyright 2016 Canonical Ltd.
2
// Licensed under the AGPLv3, see LICENCE file for details.
11
"github.com/juju/errors"
12
"gopkg.in/juju/charm.v6-unstable"
13
"gopkg.in/macaroon-bakery.v1/httpbakery"
14
"gopkg.in/macaroon.v1"
17
// newMacaroonJar returns a new macaroonJar wrapping the given cache and
18
// expecting to be used against the given URL. Both the cache and url must
20
func newMacaroonJar(cache MacaroonCache, serverURL *url.URL) (*macaroonJar, error) {
21
jar, err := cookiejar.New(nil)
23
return nil, errors.Trace(err)
32
// macaroonJar is a special form of http.CookieJar that uses a backing
33
// MacaroonCache to populate the jar and store updated macaroons.
34
// This is a fairly specifically crafted type in order to deal with the fact
35
// that gopkg.in/juju/charmrepo.v2-unstable/csclient.Client does all the work
36
// of handling updated macaroons. If a request with a macaroon returns with a
37
// DischargeRequiredError, csclient.Client will discharge the returned
38
// macaroon's caveats, and then save the final macaroon in the cookiejar, then
39
// retry the request. This type handles populating the macaroon from the
40
// macaroon cache (which for Juju's purposes will be a wrapper around state),
41
// and then responds to csclient's setcookies call to save the new macaroon
42
// into state for the appropriate charm.
44
// Note that Activate and Deactivate are not safe to call concurrently.
45
type macaroonJar struct {
46
underlying http.CookieJar
49
currentCharm *charm.URL
53
// Activate empties the cookiejar and loads the macaroon for the given charm
54
// (if any) into the cookiejar, avoiding the mechanism in SetCookies
55
// that records new macaroons. This also enables the functionality of storing
56
// macaroons in SetCookies.
57
// If the macaroonJar is nil, this is NOP.
58
func (j *macaroonJar) Activate(cURL *charm.URL) error {
62
if err := j.reset(); err != nil {
63
return errors.Trace(err)
67
m, err := j.cache.Get(cURL)
69
return errors.Trace(err)
72
httpbakery.SetCookie(j.underlying, j.serverURL, m)
77
// Deactivate empties the cookiejar and disables the functionality of storing
78
// macaroons in SetCookies.
79
// If the macaroonJar is nil, this is NOP.
80
func (j *macaroonJar) Deactivate() error {
87
// reset empties the cookiejar and disables the functionality of storing
88
// macaroons in SetCookies.
89
func (j *macaroonJar) reset() error {
93
// clear out the cookie jar to ensure we don't have any cruft left over.
94
underlying, err := cookiejar.New(nil)
96
// currently this is impossible, since the above never actually
98
return errors.Trace(err)
100
j.underlying = underlying
104
// SetCookies handles the receipt of the cookies in a reply for the
105
// given URL. Cookies do not persist past a successive call to Activate or
106
// Deactivate. If the jar is currently activated, macaroons set via this method
107
// will be stored in the underlying MacaroonCache for the currently activated
109
func (j *macaroonJar) SetCookies(u *url.URL, cookies []*http.Cookie) {
110
j.underlying.SetCookies(u, cookies)
112
if j.currentCharm == nil {
113
// nothing else to do
117
mac, err := extractMacaroon(cookies)
119
j.err = errors.Trace(err)
120
logger.Errorf(err.Error())
126
if err := j.cache.Set(j.currentCharm, mac); err != nil {
127
j.err = errors.Trace(err)
128
logger.Errorf("Failed to store macaroon for %s: %s", j.currentCharm, err)
132
// Cookies returns the cookies stored in the underlying cookiejar.
133
func (j macaroonJar) Cookies(u *url.URL) []*http.Cookie {
134
return j.underlying.Cookies(u)
137
// Error returns any error encountered during SetCookies.
138
func (j *macaroonJar) Error() error {
145
func extractMacaroon(cookies []*http.Cookie) (macaroon.Slice, error) {
146
macs := httpbakery.MacaroonsForURL(jarFromCookies(cookies), nil)
149
// no macaroons in cookies, that's ok.
155
return nil, errors.Errorf("Expected exactly one macaroon, received %d", len(macs))
159
// jarFromCookies is a bit of sleight of hand to get the cookies we already
160
// have to be in the form of a http.CookieJar that is suitable for using with
161
// the httpbakery's MacaroonsForURL function, which expects to extract
162
// the cookies from a cookiejar first.
163
type jarFromCookies []*http.Cookie
165
func (jarFromCookies) SetCookies(_ *url.URL, _ []*http.Cookie) {}
167
func (j jarFromCookies) Cookies(_ *url.URL) []*http.Cookie {
168
return []*http.Cookie(j)