~juju-qa/ubuntu/xenial/juju/xenial-2.0-beta3

« back to all changes in this revision

Viewing changes to src/gopkg.in/juju/charmstore.v5-unstable/internal/v5/api.go

  • Committer: Martin Packman
  • Date: 2016-03-30 19:31:08 UTC
  • mfrom: (1.1.41)
  • Revision ID: martin.packman@canonical.com-20160330193108-h9iz3ak334uk0z5r
Merge new upstream source 2.0~beta3

Show diffs side-by-side

added added

removed removed

Lines of Context:
15
15
        "time"
16
16
 
17
17
        "github.com/juju/httprequest"
 
18
        "github.com/juju/idmclient"
18
19
        "github.com/juju/loggo"
 
20
        "github.com/juju/mempool"
19
21
        "gopkg.in/errgo.v1"
20
22
        "gopkg.in/juju/charm.v6-unstable"
21
23
        "gopkg.in/juju/charmrepo.v2-unstable/csclient/params"
29
31
        "gopkg.in/juju/charmstore.v5-unstable/internal/agent"
30
32
        "gopkg.in/juju/charmstore.v5-unstable/internal/cache"
31
33
        "gopkg.in/juju/charmstore.v5-unstable/internal/charmstore"
32
 
        "gopkg.in/juju/charmstore.v5-unstable/internal/identity"
33
 
        "gopkg.in/juju/charmstore.v5-unstable/internal/mempool"
 
34
        "gopkg.in/juju/charmstore.v5-unstable/internal/entitycache"
34
35
        "gopkg.in/juju/charmstore.v5-unstable/internal/mongodoc"
35
36
        "gopkg.in/juju/charmstore.v5-unstable/internal/router"
36
37
)
53
54
 
54
55
        config         charmstore.ServerParams
55
56
        locator        bakery.PublicKeyLocator
56
 
        identityClient *identity.Client
 
57
        identityClient *idmclient.Client
 
58
        rootPath       string
57
59
 
58
60
        // searchCache is a cache of search results keyed on the query
59
61
        // parameters of the search. It should only be used for searches
75
77
        Handler *Handler
76
78
 
77
79
        // Store holds the charmstore Store instance
78
 
        // for the request.
79
 
        Store *charmstore.Store
 
80
        // for the request, associated with the channel specified
 
81
        // in the request.
 
82
        Store *StoreWithChannel
80
83
 
81
84
        // auth holds the results of any authorization that
82
85
        // has been done on this request.
83
86
        auth authorization
 
87
 
 
88
        // cache holds the per-request entity cache.
 
89
        Cache *entitycache.Cache
84
90
}
85
91
 
86
92
const (
88
94
        reqHandlerCacheSize       = 50
89
95
)
90
96
 
91
 
func New(pool *charmstore.Pool, config charmstore.ServerParams) *Handler {
 
97
func New(pool *charmstore.Pool, config charmstore.ServerParams, rootPath string) *Handler {
92
98
        h := &Handler{
93
99
                Pool:        pool,
94
100
                config:      config,
 
101
                rootPath:    rootPath,
95
102
                searchCache: cache.New(config.SearchCacheMaxAge),
96
103
                locator:     config.PublicKeyLocator,
97
 
                identityClient: identity.NewClient(&identity.Params{
98
 
                        URL:    config.IdentityAPIURL,
99
 
                        Client: agent.NewClient(config.AgentUsername, config.AgentKey),
 
104
                identityClient: idmclient.New(idmclient.NewParams{
 
105
                        BaseURL: config.IdentityAPIURL,
 
106
                        Client:  agent.NewClient(config.AgentUsername, config.AgentKey),
100
107
                }),
101
108
        }
102
109
        return h
106
113
func (h *Handler) Close() {
107
114
}
108
115
 
 
116
var (
 
117
        RequiredEntityFields = charmstore.FieldSelector(
 
118
                "baseurl",
 
119
                "user",
 
120
                "name",
 
121
                "revision",
 
122
                "series",
 
123
                "promulgated-revision",
 
124
                "promulgated-url",
 
125
                "development",
 
126
                "stable",
 
127
        )
 
128
        RequiredBaseEntityFields = charmstore.FieldSelector(
 
129
                "user",
 
130
                "name",
 
131
                "channelacls",
 
132
                "channelentities",
 
133
                "promulgated",
 
134
        )
 
135
)
 
136
 
 
137
// StoreWithChannel associates a Store with a channel that will be used
 
138
// to resolve any channel-ambiguous requests.
 
139
type StoreWithChannel struct {
 
140
        *charmstore.Store
 
141
        Channel params.Channel
 
142
}
 
143
 
 
144
func (s *StoreWithChannel) FindBestEntity(url *charm.URL, fields map[string]int) (*mongodoc.Entity, error) {
 
145
        return s.Store.FindBestEntity(url, s.Channel, fields)
 
146
}
 
147
 
 
148
func (s *StoreWithChannel) FindBaseEntity(url *charm.URL, fields map[string]int) (*mongodoc.BaseEntity, error) {
 
149
        return s.Store.FindBaseEntity(url, fields)
 
150
}
 
151
 
 
152
// ValidChannels holds the set of all allowed channels
 
153
// that can be passed as a "?channel=" parameter.
 
154
var ValidChannels = map[params.Channel]bool{
 
155
        params.UnpublishedChannel: true,
 
156
        params.DevelopmentChannel: true,
 
157
        params.StableChannel:      true,
 
158
}
 
159
 
109
160
// NewReqHandler returns an instance of a *ReqHandler
110
 
// suitable for handling an HTTP request. After use, the ReqHandler.Close
 
161
// suitable for handling the given HTTP request. After use, the ReqHandler.Close
111
162
// method should be called to close it.
112
163
//
113
164
// If no handlers are available, it returns an error with
114
165
// a charmstore.ErrTooManySessions cause.
115
 
func (h *Handler) NewReqHandler() (*ReqHandler, error) {
 
166
func (h *Handler) NewReqHandler(req *http.Request) (*ReqHandler, error) {
 
167
        req.ParseForm()
 
168
        // Validate all the values for channel, even though
 
169
        // most endpoints will only ever use the first one.
 
170
        // PUT to an archive is the notable exception.
 
171
        for _, ch := range req.Form["channel"] {
 
172
                if !ValidChannels[params.Channel(ch)] {
 
173
                        return nil, badRequestf(nil, "invalid channel %q specified in request", ch)
 
174
                }
 
175
        }
116
176
        store, err := h.Pool.RequestStore()
117
177
        if err != nil {
118
178
                if errgo.Cause(err) == charmstore.ErrTooManySessions {
122
182
        }
123
183
        rh := reqHandlerPool.Get().(*ReqHandler)
124
184
        rh.Handler = h
125
 
        rh.Store = store
 
185
        rh.Store = &StoreWithChannel{
 
186
                Store:   store,
 
187
                Channel: params.Channel(req.Form.Get("channel")),
 
188
        }
 
189
        rh.Cache = entitycache.New(rh.Store)
 
190
        rh.Cache.AddEntityFields(RequiredEntityFields)
 
191
        rh.Cache.AddBaseEntityFields(RequiredBaseEntityFields)
126
192
        return rh, nil
127
193
}
128
194
 
141
207
                        "debug/status":         router.HandleJSON(h.serveDebugStatus),
142
208
                        "list":                 router.HandleJSON(h.serveList),
143
209
                        "log":                  router.HandleErrors(h.serveLog),
 
210
                        "logout":               http.HandlerFunc(logout),
144
211
                        "search":               router.HandleJSON(h.serveSearch),
145
212
                        "search/interesting":   http.HandlerFunc(h.serveSearchInteresting),
146
213
                        "set-auth-cookie":      router.HandleErrors(h.serveSetAuthCookie),
153
220
                },
154
221
                Id: map[string]router.IdHandler{
155
222
                        "archive":     h.serveArchive,
156
 
                        "archive/":    resolveId(authId(h.serveArchiveFile)),
157
 
                        "diagram.svg": resolveId(authId(h.serveDiagram)),
 
223
                        "archive/":    resolveId(authId(h.serveArchiveFile), "blobname", "blobhash"),
 
224
                        "diagram.svg": resolveId(authId(h.serveDiagram), "bundledata"),
158
225
                        "expand-id":   resolveId(authId(h.serveExpandId)),
159
 
                        "icon.svg":    resolveId(authId(h.serveIcon)),
 
226
                        "icon.svg":    resolveId(authId(h.serveIcon), "contents", "blobname"),
 
227
                        "publish":     resolveId(h.servePublish),
160
228
                        "promulgate":  resolveId(h.serveAdminPromulgate),
161
 
                        "publish":     h.servePublish,
162
 
                        "readme":      resolveId(authId(h.serveReadMe)),
 
229
                        "readme":      resolveId(authId(h.serveReadMe), "contents", "blobname"),
163
230
                        "resources":   resolveId(authId(h.serveResources)),
164
231
                },
165
232
                Meta: map[string]router.BulkIncludeHandler{
169
236
                        "bundle-metadata":      h.EntityHandler(h.metaBundleMetadata, "bundledata"),
170
237
                        "bundles-containing":   h.EntityHandler(h.metaBundlesContaining),
171
238
                        "bundle-unit-count":    h.EntityHandler(h.metaBundleUnitCount, "bundleunitcount"),
 
239
                        "published":            h.EntityHandler(h.metaPublished, "development", "stable"),
172
240
                        "charm-actions":        h.EntityHandler(h.metaCharmActions, "charmactions"),
173
241
                        "charm-config":         h.EntityHandler(h.metaCharmConfig, "charmconfig"),
174
242
                        "charm-metadata":       h.EntityHandler(h.metaCharmMetadata, "charmmeta"),
175
243
                        "charm-related":        h.EntityHandler(h.metaCharmRelated, "charmprovidedinterfaces", "charmrequiredinterfaces"),
 
244
                        "common-info": h.puttableBaseEntityHandler(
 
245
                                h.metaCommonInfo,
 
246
                                h.putMetaCommonInfo,
 
247
                                "commoninfo",
 
248
                        ),
 
249
                        "common-info/": h.puttableBaseEntityHandler(
 
250
                                h.metaCommonInfoWithKey,
 
251
                                h.putMetaCommonInfoWithKey,
 
252
                                "commoninfo",
 
253
                        ),
176
254
                        "extra-info": h.puttableEntityHandler(
177
255
                                h.metaExtraInfo,
178
256
                                h.putMetaExtraInfo,
183
261
                                h.putMetaExtraInfoWithKey,
184
262
                                "extrainfo",
185
263
                        ),
186
 
                        "common-info": h.puttableBaseEntityHandler(
187
 
                                h.metaCommonInfo,
188
 
                                h.putMetaCommonInfo,
189
 
                                "commoninfo",
190
 
                        ),
191
 
                        "common-info/": h.puttableBaseEntityHandler(
192
 
                                h.metaCommonInfoWithKey,
193
 
                                h.putMetaCommonInfoWithKey,
194
 
                                "commoninfo",
195
 
                        ),
196
264
                        "hash":             h.EntityHandler(h.metaHash, "blobhash"),
197
265
                        "hash256":          h.EntityHandler(h.metaHash256, "blobhash256"),
198
266
                        "id":               h.EntityHandler(h.metaId, "_id"),
201
269
                        "id-revision":      h.EntityHandler(h.metaIdRevision, "_id"),
202
270
                        "id-series":        h.EntityHandler(h.metaIdSeries, "_id"),
203
271
                        "manifest":         h.EntityHandler(h.metaManifest, "blobname"),
204
 
                        "perm":             h.puttableBaseEntityHandler(h.metaPerm, h.putMetaPerm, "acls", "developmentacls"),
205
 
                        "perm/":            h.puttableBaseEntityHandler(h.metaPermWithKey, h.putMetaPermWithKey, "acls", "developmentacls"),
 
272
                        "perm":             h.puttableBaseEntityHandler(h.metaPerm, h.putMetaPerm, "channelacls"),
 
273
                        "perm/":            h.puttableBaseEntityHandler(h.metaPermWithKey, h.putMetaPermWithKey, "channelacls"),
206
274
                        "promulgated":      h.baseEntityHandler(h.metaPromulgated, "promulgated"),
 
275
                        "resources":        h.EntityHandler(h.metaResources, "charmmeta"),
207
276
                        "revision-info":    router.SingleIncludeHandler(h.metaRevisionInfo),
208
277
                        "stats":            h.EntityHandler(h.metaStats),
209
278
                        "supported-series": h.EntityHandler(h.metaSupportedSeries, "supportedseries"),
210
279
                        "tags":             h.EntityHandler(h.metaTags, "charmmeta", "bundledata"),
 
280
                        "terms":            h.EntityHandler(h.metaTerms, "charmmeta"),
211
281
 
212
282
                        // endpoints not yet implemented:
213
283
                        // "color": router.SingleIncludeHandler(h.metaColor),
227
297
// request-specific instance of ReqHandler and
228
298
// calling ServeHTTP on that.
229
299
func (h *Handler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
230
 
        // When requests in this handler use router.RelativeURL, we want
231
 
        // the "absolute path" there to be interpreted relative to the
232
 
        // root of this handler, not the absolute root of the web server,
233
 
        // which may be abitrarily many levels up.
234
 
        req.RequestURI = req.URL.Path
235
 
 
236
 
        rh, err := h.NewReqHandler()
 
300
        rh, err := h.NewReqHandler(req)
237
301
        if err != nil {
238
302
                router.WriteError(w, err)
239
303
                return
250
314
// NewAPIHandler returns a new Handler as an http Handler.
251
315
// It is defined for the convenience of callers that require a
252
316
// charmstore.NewAPIHandlerFunc.
253
 
func NewAPIHandler(pool *charmstore.Pool, config charmstore.ServerParams) charmstore.HTTPCloseHandler {
254
 
        return New(pool, config)
 
317
func NewAPIHandler(pool *charmstore.Pool, config charmstore.ServerParams, rootPath string) charmstore.HTTPCloseHandler {
 
318
        return New(pool, config, rootPath)
255
319
}
256
320
 
257
321
// Close closes the ReqHandler. This should always be called when the
258
322
// ReqHandler is done with.
259
323
func (h *ReqHandler) Close() {
260
324
        h.Store.Close()
 
325
        h.Cache.Close()
261
326
        h.Reset()
262
327
        reqHandlerPool.Put(h)
263
328
}
267
332
func (h *ReqHandler) Reset() {
268
333
        h.Store = nil
269
334
        h.Handler = nil
 
335
        h.Cache = nil
270
336
        h.auth = authorization{}
271
337
}
272
338
 
273
339
// ResolveURL implements router.Context.ResolveURL.
274
 
func (h ReqHandler) ResolveURL(url *charm.URL) (*router.ResolvedURL, error) {
275
 
        return resolveURL(h.Store, url)
 
340
func (h *ReqHandler) ResolveURL(url *charm.URL) (*router.ResolvedURL, error) {
 
341
        return resolveURL(h.Cache, url)
 
342
}
 
343
 
 
344
// ResolveURL implements router.Context.ResolveURLs.
 
345
func (h *ReqHandler) ResolveURLs(urls []*charm.URL) ([]*router.ResolvedURL, error) {
 
346
        h.Cache.StartFetch(urls)
 
347
        rurls := make([]*router.ResolvedURL, len(urls))
 
348
        for i, url := range urls {
 
349
                var err error
 
350
                rurls[i], err = resolveURL(h.Cache, url)
 
351
                if err != nil && errgo.Cause(err) != params.ErrNotFound {
 
352
                        return nil, err
 
353
                }
 
354
        }
 
355
        return rurls, nil
 
356
}
 
357
 
 
358
// WillIncludeMetadata implements router.Context.WillIncludeMetadata.
 
359
func (h *ReqHandler) WillIncludeMetadata(includes []string) {
 
360
        for _, inc := range includes {
 
361
                // Find what handler will be used for the include
 
362
                // and prime the cache so that it will preemptively fetch
 
363
                // any fields involved.
 
364
                fi, ok := h.Router.MetaHandler(inc).(*router.FieldIncludeHandler)
 
365
                if !ok || len(fi.P.Fields) == 0 {
 
366
                        continue
 
367
                }
 
368
                fields := make(map[string]int)
 
369
                for _, f := range fi.P.Fields {
 
370
                        fields[f] = 1
 
371
                }
 
372
                switch fi.P.Key.(type) {
 
373
                case entityHandlerKey:
 
374
                        h.Cache.AddEntityFields(fields)
 
375
                case baseEntityHandlerKey:
 
376
                        h.Cache.AddBaseEntityFields(fields)
 
377
                }
 
378
        }
276
379
}
277
380
 
278
381
// resolveURL implements URL resolving for the ReqHandler.
279
382
// It's defined as a separate function so it can be more
280
383
// easily unit-tested.
281
 
func resolveURL(store *charmstore.Store, url *charm.URL) (*router.ResolvedURL, error) {
282
 
        entity, err := store.FindBestEntity(url, "_id", "promulgated-revision")
283
 
        if err != nil && errgo.Cause(err) != params.ErrNotFound {
284
 
                return nil, errgo.Mask(err)
285
 
        }
286
 
        if errgo.Cause(err) == params.ErrNotFound {
287
 
                return nil, noMatchingURLError(url)
 
384
func resolveURL(cache *entitycache.Cache, url *charm.URL) (*router.ResolvedURL, error) {
 
385
        // We've added promulgated-url as a required field, so
 
386
        // we'll always get it from the Entity result.
 
387
        entity, err := cache.Entity(url, nil)
 
388
        if err != nil {
 
389
                return nil, errgo.Mask(err, errgo.Is(params.ErrNotFound))
288
390
        }
289
391
        rurl := &router.ResolvedURL{
290
392
                URL:                 *entity.URL,
291
393
                PromulgatedRevision: -1,
292
 
                Development:         url.Channel == charm.DevelopmentChannel,
293
394
        }
294
395
        if url.User == "" {
295
396
                rurl.PromulgatedRevision = entity.PromulgatedRevision
296
397
        }
 
398
        // Ensure the base URL is in the cache too, so that
 
399
        // its canonical URL is in the cache, so that when
 
400
        // we come to look up the base URL from the resolved
 
401
        // URL, it will hit the cached base entity.
 
402
        // We don't actually care if it succeeds or fails, so we ignore
 
403
        // the result.
 
404
        cache.BaseEntity(entity.BaseURL, nil)
297
405
        return rurl, nil
298
406
}
299
407
 
300
 
func noMatchingURLError(url *charm.URL) error {
301
 
        return errgo.WithCausef(nil, params.ErrNotFound, "no matching charm or bundle for %q", url)
302
 
}
303
 
 
304
408
type EntityHandlerFunc func(entity *mongodoc.Entity, id *router.ResolvedURL, path string, flags url.Values, req *http.Request) (interface{}, error)
305
409
 
306
410
type baseEntityHandlerFunc func(entity *mongodoc.BaseEntity, id *router.ResolvedURL, path string, flags url.Values, req *http.Request) (interface{}, error)
311
415
        return h.puttableEntityHandler(f, nil, fields...)
312
416
}
313
417
 
 
418
type entityHandlerKey struct{}
 
419
 
314
420
func (h *ReqHandler) puttableEntityHandler(get EntityHandlerFunc, handlePut router.FieldPutFunc, fields ...string) router.BulkIncludeHandler {
315
421
        handleGet := func(doc interface{}, id *router.ResolvedURL, path string, flags url.Values, req *http.Request) (interface{}, error) {
316
422
                edoc := doc.(*mongodoc.Entity)
317
423
                val, err := get(edoc, id, path, flags, req)
318
424
                return val, errgo.Mask(err, errgo.Any)
319
425
        }
320
 
        type entityHandlerKey struct{}
321
 
        return router.FieldIncludeHandler(router.FieldIncludeHandlerParams{
 
426
        return router.NewFieldIncludeHandler(router.FieldIncludeHandlerParams{
322
427
                Key:          entityHandlerKey{},
323
428
                Query:        h.entityQuery,
324
429
                Fields:       fields,
335
440
        return h.puttableBaseEntityHandler(f, nil, fields...)
336
441
}
337
442
 
 
443
type baseEntityHandlerKey struct{}
 
444
 
338
445
func (h *ReqHandler) puttableBaseEntityHandler(get baseEntityHandlerFunc, handlePut router.FieldPutFunc, fields ...string) router.BulkIncludeHandler {
339
446
        handleGet := func(doc interface{}, id *router.ResolvedURL, path string, flags url.Values, req *http.Request) (interface{}, error) {
340
447
                edoc := doc.(*mongodoc.BaseEntity)
341
448
                val, err := get(edoc, id, path, flags, req)
342
449
                return val, errgo.Mask(err, errgo.Any)
343
450
        }
344
 
        type baseEntityHandlerKey struct{}
345
 
        return router.FieldIncludeHandler(router.FieldIncludeHandlerParams{
 
451
        return router.NewFieldIncludeHandler(router.FieldIncludeHandlerParams{
346
452
                Key:          baseEntityHandlerKey{},
347
453
                Query:        h.baseEntityQuery,
348
454
                Fields:       fields,
418
524
        return nil
419
525
}
420
526
 
421
 
func (h *ReqHandler) baseEntityQuery(id *router.ResolvedURL, selector map[string]int, req *http.Request) (interface{}, error) {
422
 
        fields := make([]string, 0, len(selector))
423
 
        for k, v := range selector {
424
 
                if v == 0 {
425
 
                        continue
426
 
                }
427
 
                fields = append(fields, k)
428
 
        }
429
 
        val, err := h.Store.FindBaseEntity(&id.URL, fields...)
 
527
func (h *ReqHandler) baseEntityQuery(id *router.ResolvedURL, fields map[string]int, req *http.Request) (interface{}, error) {
 
528
        val, err := h.Cache.BaseEntity(&id.URL, fields)
430
529
        if errgo.Cause(err) == params.ErrNotFound {
431
530
                return nil, errgo.WithCausef(nil, params.ErrNotFound, "no matching charm or bundle for %s", id)
432
531
        }
437
536
}
438
537
 
439
538
func (h *ReqHandler) entityQuery(id *router.ResolvedURL, selector map[string]int, req *http.Request) (interface{}, error) {
440
 
        val, err := h.Store.FindEntity(id, fieldsFromSelector(selector)...)
 
539
        val, err := h.Cache.Entity(&id.URL, selector)
441
540
        if errgo.Cause(err) == params.ErrNotFound {
 
541
                logger.Infof("entity %#v not found: %#v", id, err)
442
542
                return nil, errgo.WithCausef(nil, params.ErrNotFound, "no matching charm or bundle for %s", id)
443
543
        }
444
544
        if err != nil {
447
547
        return val, nil
448
548
}
449
549
 
450
 
func fieldsFromSelector(selector map[string]int) []string {
451
 
        fields := make([]string, 0, len(selector))
452
 
        for k, v := range selector {
453
 
                if v == 0 {
454
 
                        continue
455
 
                }
456
 
                fields = append(fields, k)
457
 
        }
458
 
        return fields
459
 
}
460
 
 
461
550
var errNotImplemented = errgo.Newf("method not implemented")
462
551
 
463
552
// GET /debug
466
555
        router.WriteError(w, errNotImplemented)
467
556
}
468
557
 
469
 
// POST id/resources/name.stream
470
 
// https://github.com/juju/charmstore/blob/v4/docs/API.md#post-idresourcesnamestream
471
 
//
472
 
// GET  id/resources/name.stream[-revision]/arch/filename
473
 
// https://github.com/juju/charmstore/blob/v4/docs/API.md#get-idresourcesnamestream-revisionarchfilename
474
 
//
475
 
// PUT id/resources/[~user/]series/name.stream-revision/arch?sha256=hash
476
 
// https://github.com/juju/charmstore/blob/v4/docs/API.md#put-idresourcesuserseriesnamestream-revisionarchsha256hash
477
 
func (h *ReqHandler) serveResources(id *router.ResolvedURL, w http.ResponseWriter, req *http.Request) error {
478
 
        return errNotImplemented
479
 
}
480
 
 
481
558
// GET id/expand-id
482
559
// https://docs.google.com/a/canonical.com/document/d/1TgRA7jW_mmXoKH3JiwBbtPvQu7WiM6XMrz1wSrhTMXw/edit#bookmark=id.4xdnvxphb2si
483
560
func (h *ReqHandler) serveExpandId(id *router.ResolvedURL, w http.ResponseWriter, req *http.Request) error {
491
568
        // to return entities that match appropriately.
492
569
 
493
570
        // Retrieve all the entities with the same base URL.
494
 
        // Note that we don't do any permission checking of the returned URLs.
495
 
        // This is because we know that the user is allowed to read at
496
 
        // least the resolved URL passed into serveExpandId.
497
 
        // If this does not specify "development", then no development
498
 
        // revisions will be chosen, so the single ACL already checked
499
 
        // is sufficient. If it *does* specify "development", then we assume
500
 
        // that the development ACLs are more restrictive than the
501
 
        // non-development ACLs, and given that, we can allow all
502
 
        // the URLs.
503
 
        q := h.Store.EntitiesQuery(baseURL).Select(bson.D{{"_id", 1}, {"promulgated-url", 1}, {"development", 1}})
 
571
        q := h.Store.EntitiesQuery(baseURL).Select(bson.D{{"_id", 1}, {"promulgated-url", 1}})
504
572
        if id.PromulgatedRevision != -1 {
505
573
                q = q.Sort("-series", "-promulgated-revision")
506
574
        } else {
515
583
        // Collect all the expanded identifiers for each entity.
516
584
        response := make([]params.ExpandedId, 0, len(docs))
517
585
        for _, doc := range docs {
 
586
                if err := h.AuthorizeEntity(charmstore.EntityResolvedURL(doc), req); err != nil {
 
587
                        continue
 
588
                }
518
589
                url := doc.PreferredURL(id.PromulgatedRevision != -1)
519
590
                response = append(response, params.ExpandedId{Id: url.String()})
520
591
        }
601
672
        return entity.CharmConfig, nil
602
673
}
603
674
 
 
675
// GET id/meta/terms
 
676
// https://github.com/juju/charmstore/blob/v4/docs/API.md#get-idmetaterms
 
677
func (h *ReqHandler) metaTerms(entity *mongodoc.Entity, id *router.ResolvedURL, path string, flags url.Values, req *http.Request) (interface{}, error) {
 
678
        if entity.URL.Series == "bundle" {
 
679
                return nil, nil
 
680
        }
 
681
        if entity.CharmMeta == nil || len(entity.CharmMeta.Terms) == 0 {
 
682
                return []string{}, nil
 
683
        }
 
684
        return entity.CharmMeta.Terms, nil
 
685
}
 
686
 
604
687
// GET id/meta/color
605
688
func (h *ReqHandler) metaColor(id *router.ResolvedURL, path string, flags url.Values, req *http.Request) (interface{}, error) {
606
689
        return nil, errNotImplemented
625
708
// GET id/meta/hash256
626
709
// https://github.com/juju/charmstore/blob/v4/docs/API.md#get-idmetahash256
627
710
func (h *ReqHandler) metaHash256(entity *mongodoc.Entity, id *router.ResolvedURL, path string, flags url.Values, req *http.Request) (interface{}, error) {
628
 
        // TODO frankban: remove this lazy calculation after the cshash256
629
 
        // command is run in the production db. At that point, entities
630
 
        // always have their blobhash256 field populated, and there is no
631
 
        // need for this lazy evaluation anymore.
632
 
        if entity.BlobHash256 == "" {
633
 
                var err error
634
 
                if entity.BlobHash256, err = h.Store.UpdateEntitySHA256(id); err != nil {
635
 
                        return nil, errgo.Notef(err, "cannot retrieve the SHA256 hash for entity %s", entity.URL)
636
 
                }
637
 
        }
638
711
        return &params.HashResponse{
639
712
                Sum: entity.BlobHash256,
640
713
        }, nil
701
774
        } else {
702
775
                q = q.Sort("-revision")
703
776
        }
704
 
        var docs []*mongodoc.Entity
705
 
        if err := q.Select(bson.D{{"_id", 1}, {"promulgated-url", 1}}).All(&docs); err != nil {
706
 
                return "", errgo.Notef(err, "cannot get ids")
707
 
        }
708
 
 
709
 
        if len(docs) == 0 {
710
 
                return "", errgo.WithCausef(nil, params.ErrNotFound, "no matching charm or bundle for %s", id)
711
 
        }
712
777
        var response params.RevisionInfoResponse
713
 
        for _, doc := range docs {
 
778
        iter := h.Cache.Iter(q, nil)
 
779
        for iter.Next() {
 
780
                e := iter.Entity()
 
781
                rurl := charmstore.EntityResolvedURL(e)
 
782
                if err := h.AuthorizeEntity(rurl, req); err != nil {
 
783
                        // We're not authorized to see the entity, so leave it out.
 
784
                        // Note that the only time this will happen is when
 
785
                        // the original URL is promulgated and has a development channel,
 
786
                        // the charm has changed owners, and the old owner and
 
787
                        // the new one have different dev ACLs. It's easiest
 
788
                        // and most reliable just to check everything though.
 
789
                        continue
 
790
                }
714
791
                if id.PromulgatedRevision != -1 {
715
 
                        response.Revisions = append(response.Revisions, doc.PromulgatedURL)
 
792
                        response.Revisions = append(response.Revisions, rurl.PromulgatedURL())
716
793
                } else {
717
 
                        response.Revisions = append(response.Revisions, doc.URL)
 
794
                        response.Revisions = append(response.Revisions, &rurl.URL)
718
795
                }
719
796
        }
720
 
 
721
 
        // Write the response in JSON format.
 
797
        if err := iter.Err(); err != nil {
 
798
                return nil, errgo.Notef(err, "iteration failed")
 
799
        }
722
800
        return &response, nil
723
801
}
724
802
 
920
998
// GET id/meta/perm
921
999
// https://github.com/juju/charmstore/blob/v4/docs/API.md#get-idmetaperm
922
1000
func (h *ReqHandler) metaPerm(entity *mongodoc.BaseEntity, id *router.ResolvedURL, path string, flags url.Values, req *http.Request) (interface{}, error) {
923
 
        acls := entity.ACLs
924
 
        if id.Development {
925
 
                acls = entity.DevelopmentACLs
 
1001
        ch, err := h.entityChannel(id)
 
1002
        if err != nil {
 
1003
                return nil, errgo.Mask(err)
926
1004
        }
 
1005
        acls := entity.ChannelACLs[ch]
927
1006
        return params.PermResponse{
928
1007
                Read:  acls.Read,
929
1008
                Write: acls.Write,
937
1016
        if err := json.Unmarshal(*val, &perms); err != nil {
938
1017
                return errgo.Mask(err)
939
1018
        }
940
 
        field := "acls"
941
 
        if id.Development {
942
 
                field = "developmentacls"
943
 
        } else {
944
 
                isPublic := false
945
 
                for _, p := range perms.Read {
946
 
                        if p == params.Everyone {
947
 
                                isPublic = true
948
 
                                break
949
 
                        }
950
 
                }
951
 
                updater.UpdateField("public", isPublic, nil)
 
1019
        ch, err := h.entityChannel(id)
 
1020
        if err != nil {
 
1021
                return errgo.Mask(err)
952
1022
        }
953
 
        updater.UpdateField(field+".read", perms.Read, &audit.Entry{
 
1023
        // TODO use only one UpdateField operation?
 
1024
        updater.UpdateField(string("channelacls."+ch+".read"), perms.Read, &audit.Entry{
954
1025
                Op:     audit.OpSetPerm,
955
1026
                Entity: &id.URL,
956
1027
                ACL: &audit.ACL{
957
1028
                        Read: perms.Read,
958
1029
                },
959
1030
        })
960
 
        updater.UpdateField(field+".write", perms.Write, &audit.Entry{
 
1031
        updater.UpdateField(string("channelacls."+ch+".write"), perms.Write, &audit.Entry{
961
1032
                Op:     audit.OpSetPerm,
962
1033
                Entity: &id.URL,
963
1034
                ACL: &audit.ACL{
979
1050
// GET id/meta/perm/key
980
1051
// https://github.com/juju/charmstore/blob/v4/docs/API.md#get-idmetapermkey
981
1052
func (h *ReqHandler) metaPermWithKey(entity *mongodoc.BaseEntity, id *router.ResolvedURL, path string, flags url.Values, req *http.Request) (interface{}, error) {
982
 
        acls := entity.ACLs
983
 
        if id.Development {
984
 
                acls = entity.DevelopmentACLs
 
1053
        ch, err := h.entityChannel(id)
 
1054
        if err != nil {
 
1055
                return nil, errgo.Mask(err)
985
1056
        }
 
1057
        acls := entity.ChannelACLs[ch]
986
1058
        switch path {
987
1059
        case "/read":
988
1060
                return acls.Read, nil
995
1067
// PUT id/meta/perm/key
996
1068
// https://github.com/juju/charmstore/blob/v4/docs/API.md#put-idmetapermkey
997
1069
func (h *ReqHandler) putMetaPermWithKey(id *router.ResolvedURL, path string, val *json.RawMessage, updater *router.FieldUpdater, req *http.Request) error {
 
1070
        ch, err := h.entityChannel(id)
 
1071
        if err != nil {
 
1072
                return errgo.Mask(err)
 
1073
        }
998
1074
        var perms []string
999
1075
        if err := json.Unmarshal(*val, &perms); err != nil {
1000
1076
                return errgo.Mask(err)
1001
1077
        }
1002
 
        field := "acls"
1003
 
        if id.Development {
1004
 
                field = "developmentacls"
1005
 
        }
1006
1078
        switch path {
1007
1079
        case "/read":
1008
 
                updater.UpdateField(field+".read", perms, &audit.Entry{
 
1080
                updater.UpdateField(string("channelacls."+ch+".read"), perms, &audit.Entry{
1009
1081
                        Op:     audit.OpSetPerm,
1010
1082
                        Entity: &id.URL,
1011
1083
                        ACL: &audit.ACL{
1012
1084
                                Read: perms,
1013
1085
                        },
1014
1086
                })
1015
 
                if !id.Development {
1016
 
                        isPublic := false
1017
 
                        for _, p := range perms {
1018
 
                                if p == params.Everyone {
1019
 
                                        isPublic = true
1020
 
                                        break
1021
 
                                }
1022
 
                        }
1023
 
                        updater.UpdateField("public", isPublic, nil)
1024
 
                }
1025
1087
                updater.UpdateSearch()
1026
1088
                return nil
1027
1089
        case "/write":
1028
 
                updater.UpdateField(field+".write", perms, &audit.Entry{
 
1090
                updater.UpdateField(string("channelacls."+ch+".write"), perms, &audit.Entry{
1029
1091
                        Op:     audit.OpSetPerm,
1030
1092
                        Entity: &id.URL,
1031
1093
                        ACL: &audit.ACL{
1037
1099
        return errgo.WithCausef(nil, params.ErrNotFound, "unknown permission")
1038
1100
}
1039
1101
 
 
1102
// GET id/meta/published
 
1103
// https://github.com/juju/charmstore/blob/v4/docs/API.md#get-idmetapublished
 
1104
func (h *ReqHandler) metaPublished(entity *mongodoc.Entity, id *router.ResolvedURL, path string, flags url.Values, req *http.Request) (interface{}, error) {
 
1105
        baseEntity, err := h.Cache.BaseEntity(entity.URL, charmstore.FieldSelector("channelentities"))
 
1106
        if err != nil {
 
1107
                return nil, errgo.Mask(err)
 
1108
        }
 
1109
        info := make([]params.PublishedInfo, 0, 2)
 
1110
        if entity.Development {
 
1111
                info = append(info, params.PublishedInfo{
 
1112
                        Channel: params.DevelopmentChannel,
 
1113
                })
 
1114
        }
 
1115
        if entity.Stable {
 
1116
                info = append(info, params.PublishedInfo{
 
1117
                        Channel: params.StableChannel,
 
1118
                })
 
1119
        }
 
1120
        for i, pinfo := range info {
 
1121
                // The entity is current for a channel if any series within
 
1122
                // a channel refers to the entity.
 
1123
                for _, url := range baseEntity.ChannelEntities[pinfo.Channel] {
 
1124
                        if *url == *entity.URL {
 
1125
                                info[i].Current = true
 
1126
                        }
 
1127
                }
 
1128
        }
 
1129
        return &params.PublishedResponse{
 
1130
                Info: info,
 
1131
        }, nil
 
1132
}
 
1133
 
1040
1134
// GET id/meta/archive-upload-time
1041
1135
// https://github.com/juju/charmstore/blob/v4/docs/API.md#get-idmetaarchive-upload-time
1042
1136
func (h *ReqHandler) metaArchiveUploadTime(entity *mongodoc.Entity, id *router.ResolvedURL, path string, flags url.Values, req *http.Request) (interface{}, error) {
1079
1173
        }
1080
1174
        query := h.Store.DB.Entities().
1081
1175
                Find(findQuery).
1082
 
                Sort("-uploadtime").
1083
 
                Select(bson.D{{"_id", 1}, {"uploadtime", 1}})
 
1176
                Sort("-uploadtime")
 
1177
        iter := h.Cache.Iter(query, charmstore.FieldSelector("uploadtime"))
1084
1178
 
1085
1179
        results := []params.Published{}
1086
1180
        var count int
1087
 
        var entity mongodoc.Entity
1088
 
        iter := query.Iter()
1089
 
        for iter.Next(&entity) {
 
1181
        for iter.Next() {
 
1182
                entity := iter.Entity()
1090
1183
                // Ignore entities that aren't readable by the current user.
1091
 
                if err := h.AuthorizeEntity(charmstore.EntityResolvedURL(&entity), r); err != nil {
 
1184
                if err := h.AuthorizeEntity(charmstore.EntityResolvedURL(entity), r); err != nil {
1092
1185
                        continue
1093
1186
                }
1094
1187
                results = append(results, params.Published{
1097
1190
                })
1098
1191
                count++
1099
1192
                if limit > 0 && limit <= count {
 
1193
                        iter.Close()
1100
1194
                        break
1101
1195
                }
1102
1196
        }
1103
 
        if err := iter.Close(); err != nil {
 
1197
        if err := iter.Err(); err != nil {
1104
1198
                return nil, errgo.Mask(err)
1105
1199
        }
1106
1200
        return results, nil
1109
1203
// GET /macaroon
1110
1204
// See https://github.com/juju/charmstore/blob/v4/docs/API.md#get-macaroon
1111
1205
func (h *ReqHandler) serveMacaroon(_ http.Header, _ *http.Request) (interface{}, error) {
1112
 
        return h.newMacaroon()
 
1206
        // will return a macaroon that will enable access to everything except archives
 
1207
        // of charms that require agreement to terms and conditions.
 
1208
        return h.newMacaroon([]checkers.Caveat{checkers.DenyCaveat(OpAccessCharmWithTerms)}...)
1113
1209
}
1114
1210
 
1115
1211
// GET /delegatable-macaroon
1134
1230
                m, err := h.Store.Bakery.NewMacaroon("", nil, []checkers.Caveat{
1135
1231
                        checkers.DeclaredCaveat(UsernameAttr, auth.Username),
1136
1232
                        checkers.TimeBeforeCaveat(time.Now().Add(DelegatableMacaroonExpiry)),
1137
 
                        checkers.DenyCaveat(opAccessCharmWithTerms),
 
1233
                        checkers.DenyCaveat(OpAccessCharmWithTerms),
1138
1234
                })
1139
1235
                if err != nil {
1140
1236
                        return nil, errgo.Mask(err)
1158
1254
        // anyone to obtain a delegatable macaroon. This means
1159
1255
        // that we will be able to add the declared caveats to
1160
1256
        // the returned macaroon.
1161
 
        auth, err := h.authorizeEntityAndTerms(req, resolvedURLs)
 
1257
        auth, err := h.AuthorizeEntityAndTerms(req, resolvedURLs)
1162
1258
        if err != nil {
1163
1259
                return nil, errgo.Mask(err, errgo.Any)
1164
1260
        }
1193
1289
        if auth.Admin {
1194
1290
                return nil, errgo.WithCausef(nil, params.ErrForbidden, "admin credentials used")
1195
1291
        }
1196
 
        groups, err := h.groupsForUser(auth.Username)
 
1292
        groups, err := h.GroupsForUser(auth.Username)
1197
1293
        if err != nil {
1198
1294
                return nil, errgo.Mask(err, errgo.Any)
1199
1295
        }
1225
1321
        }
1226
1322
 
1227
1323
        if promulgate.Promulgated {
1228
 
                // Set write permissions for the non-development entity to promulgators
1229
 
                // only, so that the user cannot just publish newer promulgated
1230
 
                // versions of the charm or bundle. Promulgators are responsible of
1231
 
                // reviewing and publishing subsequent revisions of this entity.
 
1324
                // Set write permissions to promulgators only, so that
 
1325
                // the user cannot just publish newer promulgated
 
1326
                // versions of the charm or bundle. Promulgators are
 
1327
                // responsible of reviewing and publishing subsequent
 
1328
                // revisions of this entity.
1232
1329
                if err := h.updateBaseEntity(id, map[string]interface{}{
1233
 
                        "acls.write": []string{PromulgatorsGroup},
 
1330
                        "channelacls.stable.write": []string{PromulgatorsGroup},
1234
1331
                }, nil); err != nil {
1235
1332
                        return errgo.Notef(err, "cannot set permissions for %q", id)
1236
1333
                }
1250
1347
        return nil
1251
1348
}
1252
1349
 
 
1350
// validPublishChannels holds the set of channels that can
 
1351
// be the target of a publish request.
 
1352
var validPublishChannels = map[params.Channel]bool{
 
1353
        params.DevelopmentChannel: true,
 
1354
        params.StableChannel:      true,
 
1355
}
 
1356
 
1253
1357
// PUT id/publish
1254
1358
// See https://github.com/juju/charmstore/blob/v4/docs/API.md#put-idpublish
1255
 
func (h *ReqHandler) servePublish(id *charm.URL, w http.ResponseWriter, req *http.Request) error {
 
1359
func (h *ReqHandler) servePublish(id *router.ResolvedURL, w http.ResponseWriter, req *http.Request) error {
1256
1360
        // Perform basic validation of the request.
1257
1361
        if req.Method != "PUT" {
1258
1362
                return errgo.WithCausef(nil, params.ErrMethodNotAllowed, "%s not allowed", req.Method)
1259
1363
        }
1260
 
        if id.Channel != "" {
1261
 
                return errgo.WithCausef(nil, params.ErrForbidden, "can only set publish on published URL, %q provided", id)
1262
 
        }
1263
1364
 
1264
1365
        // Retrieve the requested action from the request body.
1265
1366
        var publish struct {
1266
1367
                params.PublishRequest `httprequest:",body"`
1267
1368
        }
1268
1369
        if err := httprequest.Unmarshal(httprequest.Params{Request: req}, &publish); err != nil {
1269
 
                return errgo.WithCausef(err, params.ErrBadRequest, "cannot unmarshal publish request body")
 
1370
                return badRequestf(err, "cannot unmarshal publish request body")
 
1371
        }
 
1372
        chans := publish.Channels
 
1373
        if len(chans) == 0 {
 
1374
                return badRequestf(nil, "no channels provided")
 
1375
        }
 
1376
        for _, c := range chans {
 
1377
                if !validPublishChannels[c] {
 
1378
                        return badRequestf(nil, "cannot publish to %q", c)
 
1379
                }
1270
1380
        }
1271
1381
 
1272
 
        // Retrieve the resolved URL for the entity to update. It will be referring
1273
 
        // to the entity under development is the action is to publish a charm or
1274
 
        // bundle, or the published one otherwise.
1275
 
        url := *id
1276
 
        if publish.Published {
1277
 
                url = *id.WithChannel(charm.DevelopmentChannel)
1278
 
        }
1279
 
        rurl, err := h.Router.Context.ResolveURL(&url)
 
1382
        // Retrieve the base entity so that we can check permissions.
 
1383
        baseEntity, err := h.Cache.BaseEntity(&id.URL, charmstore.FieldSelector("channelacls"))
1280
1384
        if err != nil {
1281
1385
                return errgo.Mask(err, errgo.Is(params.ErrNotFound))
1282
1386
        }
1283
1387
 
1284
 
        // Authorize the operation: users must have write permissions on the
1285
 
        // published charm or bundle.
1286
 
        prurl := *rurl
1287
 
        prurl.Development = false
1288
 
        if err := h.AuthorizeEntity(&prurl, req); err != nil {
1289
 
                return errgo.Mask(err, errgo.Any)
1290
 
        }
1291
 
 
1292
 
        // Update the entity.
1293
 
        if err := h.Store.SetDevelopment(rurl, !publish.Published); err != nil {
1294
 
                return errgo.NoteMask(err, "cannot publish or unpublish charm or bundle", errgo.Is(params.ErrNotFound))
1295
 
        }
1296
 
 
1297
 
        // Return information on the updated charm or bundle.
1298
 
        rurl.Development = !publish.Published
1299
 
        return httprequest.WriteJSON(w, http.StatusOK, &params.PublishResponse{
1300
 
                Id:            rurl.UserOwnedURL(),
1301
 
                PromulgatedId: rurl.PromulgatedURL(),
1302
 
        })
 
1388
        // Authorize the operation. Users must have write permissions on the ACLs
 
1389
        // on the channel being published to.
 
1390
        for _, c := range chans {
 
1391
                if _, err := h.authorize(req, baseEntity.ChannelACLs[c].Write, true, id); err != nil {
 
1392
                        return errgo.Mask(err, errgo.Any)
 
1393
                }
 
1394
        }
 
1395
 
 
1396
        // TODO(ericsnow) Actually handle the resources.
 
1397
        if len(publish.Resources) > 0 {
 
1398
                return errNotImplemented
 
1399
        }
 
1400
 
 
1401
        if err := h.Store.Publish(id, chans...); err != nil {
 
1402
                return errgo.NoteMask(err, "cannot publish charm or bundle", errgo.Is(params.ErrNotFound))
 
1403
        }
 
1404
        // TODO add publish audit
 
1405
        return nil
1303
1406
}
1304
1407
 
1305
1408
// serveSetAuthCookie sets the provided macaroon slice as a cookie on the
1351
1454
// ResolvedIdHandler returns an id handler that uses h.Router.Context.ResolveURL
1352
1455
// to resolves any entity ids before calling f with the resolved id.
1353
1456
//
 
1457
// Any specified fields will be added to the fields required by the cache, so
 
1458
// they will be pre-fetched by ResolveURL.
 
1459
//
1354
1460
// Note that it only accesses h.Router.Context when the returned
1355
1461
// handler is called.
1356
 
func (h *ReqHandler) ResolvedIdHandler(f ResolvedIdHandler) router.IdHandler {
 
1462
func (h *ReqHandler) ResolvedIdHandler(f ResolvedIdHandler, cacheFields ...string) router.IdHandler {
 
1463
        fields := charmstore.FieldSelector(cacheFields...)
1357
1464
        return func(id *charm.URL, w http.ResponseWriter, req *http.Request) error {
 
1465
                h.Cache.AddEntityFields(fields)
1358
1466
                rid, err := h.Router.Context.ResolveURL(id)
1359
1467
                if err != nil {
1360
1468
                        return errgo.Mask(err, errgo.Is(params.ErrNotFound))
1380
1488
                testAddAuditCallback(e)
1381
1489
        }
1382
1490
}
 
1491
 
 
1492
// logout handles the GET /v5/logout endpoint that is used to log out of
 
1493
// charmstore.
 
1494
func logout(w http.ResponseWriter, r *http.Request) {
 
1495
        for _, c := range r.Cookies() {
 
1496
                if !strings.HasPrefix(c.Name, "macaroon-") {
 
1497
                        continue
 
1498
                }
 
1499
                c.Value = ""
 
1500
                c.MaxAge = -1
 
1501
                c.Path = "/"
 
1502
                http.SetCookie(w, c)
 
1503
        }
 
1504
}