~nskaggs/+junk/xenial-test

« back to all changes in this revision

Viewing changes to src/github.com/juju/juju/charmstore/jar.go

  • Committer: Nicholas Skaggs
  • Date: 2016-10-24 20:56:05 UTC
  • Revision ID: nicholas.skaggs@canonical.com-20161024205605-z8lta0uvuhtxwzwl
Initi with beta15

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
// Copyright 2016 Canonical Ltd.
 
2
// Licensed under the AGPLv3, see LICENCE file for details.
 
3
 
 
4
package charmstore
 
5
 
 
6
import (
 
7
        "net/http"
 
8
        "net/http/cookiejar"
 
9
        "net/url"
 
10
 
 
11
        "github.com/juju/errors"
 
12
        "gopkg.in/juju/charm.v6-unstable"
 
13
        "gopkg.in/macaroon-bakery.v1/httpbakery"
 
14
        "gopkg.in/macaroon.v1"
 
15
)
 
16
 
 
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
 
19
// be non-nil.
 
20
func newMacaroonJar(cache MacaroonCache, serverURL *url.URL) (*macaroonJar, error) {
 
21
        jar, err := cookiejar.New(nil)
 
22
        if err != nil {
 
23
                return nil, errors.Trace(err)
 
24
        }
 
25
        return &macaroonJar{
 
26
                underlying: jar,
 
27
                cache:      cache,
 
28
                serverURL:  serverURL,
 
29
        }, nil
 
30
}
 
31
 
 
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.
 
43
//
 
44
// Note that Activate and Deactivate are not safe to call concurrently.
 
45
type macaroonJar struct {
 
46
        underlying   http.CookieJar
 
47
        cache        MacaroonCache
 
48
        serverURL    *url.URL
 
49
        currentCharm *charm.URL
 
50
        err          error
 
51
}
 
52
 
 
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 {
 
59
        if j == nil {
 
60
                return nil
 
61
        }
 
62
        if err := j.reset(); err != nil {
 
63
                return errors.Trace(err)
 
64
        }
 
65
        j.currentCharm = cURL
 
66
 
 
67
        m, err := j.cache.Get(cURL)
 
68
        if err != nil {
 
69
                return errors.Trace(err)
 
70
        }
 
71
        if m != nil {
 
72
                httpbakery.SetCookie(j.underlying, j.serverURL, m)
 
73
        }
 
74
        return nil
 
75
}
 
76
 
 
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 {
 
81
        if j == nil {
 
82
                return nil
 
83
        }
 
84
        return j.reset()
 
85
}
 
86
 
 
87
// reset empties the cookiejar and disables the functionality of storing
 
88
// macaroons in SetCookies.
 
89
func (j *macaroonJar) reset() error {
 
90
        j.err = nil
 
91
        j.currentCharm = nil
 
92
 
 
93
        // clear out the cookie jar to ensure we don't have any cruft left over.
 
94
        underlying, err := cookiejar.New(nil)
 
95
        if err != nil {
 
96
                // currently this is impossible, since the above never actually
 
97
                // returns an error
 
98
                return errors.Trace(err)
 
99
        }
 
100
        j.underlying = underlying
 
101
        return nil
 
102
}
 
103
 
 
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
 
108
// charm.
 
109
func (j *macaroonJar) SetCookies(u *url.URL, cookies []*http.Cookie) {
 
110
        j.underlying.SetCookies(u, cookies)
 
111
 
 
112
        if j.currentCharm == nil {
 
113
                // nothing else to do
 
114
                return
 
115
        }
 
116
 
 
117
        mac, err := extractMacaroon(cookies)
 
118
        if err != nil {
 
119
                j.err = errors.Trace(err)
 
120
                logger.Errorf(err.Error())
 
121
                return
 
122
        }
 
123
        if mac == nil {
 
124
                return
 
125
        }
 
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)
 
129
        }
 
130
}
 
131
 
 
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)
 
135
}
 
136
 
 
137
// Error returns any error encountered during SetCookies.
 
138
func (j *macaroonJar) Error() error {
 
139
        if j == nil {
 
140
                return nil
 
141
        }
 
142
        return j.err
 
143
}
 
144
 
 
145
func extractMacaroon(cookies []*http.Cookie) (macaroon.Slice, error) {
 
146
        macs := httpbakery.MacaroonsForURL(jarFromCookies(cookies), nil)
 
147
        switch len(macs) {
 
148
        case 0:
 
149
                // no macaroons in cookies, that's ok.
 
150
                return nil, nil
 
151
        case 1:
 
152
                // hurray!
 
153
                return macs[0], nil
 
154
        default:
 
155
                return nil, errors.Errorf("Expected exactly one macaroon, received %d", len(macs))
 
156
        }
 
157
}
 
158
 
 
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
 
164
 
 
165
func (jarFromCookies) SetCookies(_ *url.URL, _ []*http.Cookie) {}
 
166
 
 
167
func (j jarFromCookies) Cookies(_ *url.URL) []*http.Cookie {
 
168
        return []*http.Cookie(j)
 
169
}