-
Notifications
You must be signed in to change notification settings - Fork 0
/
root.go
734 lines (654 loc) · 22 KB
/
root.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
// Copyright 2013 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.
package apiserver
import (
"context"
"fmt"
"net/url"
"reflect"
"sync"
"time"
"github.com/juju/clock"
"github.com/juju/errors"
"github.com/juju/names/v4"
"github.com/juju/rpcreflect"
"github.com/juju/version/v2"
"github.com/lestrrat-go/jwx/v2/jwt"
"github.com/juju/juju/apiserver/common"
"github.com/juju/juju/apiserver/facade"
"github.com/juju/juju/core/cache"
coredatabase "github.com/juju/juju/core/database"
"github.com/juju/juju/core/leadership"
"github.com/juju/juju/core/lease"
"github.com/juju/juju/core/multiwatcher"
"github.com/juju/juju/core/permission"
"github.com/juju/juju/rpc"
"github.com/juju/juju/rpc/params"
"github.com/juju/juju/state"
jujuversion "github.com/juju/juju/version"
)
var (
// maxClientPingInterval defines the timeframe until the ping timeout
// closes the monitored connection. TODO(mue): Idea by Roger:
// Move to API (e.g. params) so that the pinging there may
// depend on the interval.
maxClientPingInterval = 3 * time.Minute
)
type objectKey struct {
name string
version int
objId string
}
// apiHandler represents a single client's connection to the state
// after it has logged in. It contains an rpc.Root which it
// uses to dispatch API calls appropriately.
type apiHandler struct {
state *state.State
model *state.Model
rpcConn *rpc.Conn
resources *common.Resources
shared *sharedServerContext
entity state.Entity
loginToken jwt.Token
// An empty modelUUID means that the user has logged in through the
// root of the API server rather than the /model/:model-uuid/api
// path, logins processed with v2 or later will only offer the
// user manager and model manager api endpoints from here.
modelUUID string
// connectionID is shared between the API observer (including API
// requests and responses in the agent log) and the audit logger.
connectionID uint64
// serverHost is the host:port of the API server that the client
// connected to.
serverHost string
}
var _ = (*apiHandler)(nil)
// newAPIHandler returns a new apiHandler.
func newAPIHandler(srv *Server, st *state.State, rpcConn *rpc.Conn, modelUUID string, connectionID uint64, serverHost string) (*apiHandler, error) {
m, err := st.Model()
if err != nil {
if !errors.IsNotFound(err) {
return nil, errors.Trace(err)
}
// If this model used to be hosted on this controller but got
// migrated allow clients to connect and wait for a login
// request to decide whether the users should be redirected to
// the new controller for this model or not.
if _, migErr := st.CompletedMigration(); migErr != nil {
return nil, errors.Trace(err) // return original NotFound error
}
}
r := &apiHandler{
state: st,
model: m,
resources: common.NewResources(),
shared: srv.shared,
rpcConn: rpcConn,
modelUUID: modelUUID,
connectionID: connectionID,
serverHost: serverHost,
}
if err := r.resources.RegisterNamed("machineID", common.StringResource(srv.tag.Id())); err != nil {
return nil, errors.Trace(err)
}
if err := r.resources.RegisterNamed("dataDir", common.StringResource(srv.dataDir)); err != nil {
return nil, errors.Trace(err)
}
if err := r.resources.RegisterNamed("logDir", common.StringResource(srv.logDir)); err != nil {
return nil, errors.Trace(err)
}
// Facades involved with managing application offers need the auth context
// to mint and validate macaroons.
localOfferAccessEndpoint := url.URL{
Scheme: "https",
Host: serverHost,
Path: localOfferAccessLocationPath,
}
offerAuthCtxt := srv.offerAuthCtxt.WithDischargeURL(localOfferAccessEndpoint.String())
if err := r.resources.RegisterNamed(
"offerAccessAuthContext",
common.ValueResource{Value: offerAuthCtxt},
); err != nil {
return nil, errors.Trace(err)
}
return r, nil
}
// Resources returns the common resources.
func (r *apiHandler) Resources() *common.Resources {
return r.resources
}
// State returns the underlying state.
func (r *apiHandler) State() *state.State {
return r.state
}
// SharedContext returns the server shared context.
func (r *apiHandler) SharedContext() *sharedServerContext {
return r.shared
}
// Authorizer returns the authorizer used for accessing API method calls.
func (r *apiHandler) Authorizer() facade.Authorizer {
return r
}
func (r *apiHandler) getRpcConn() *rpc.Conn {
return r.rpcConn
}
// Kill implements rpc.Killer, cleaning up any resources that need
// cleaning up to ensure that all outstanding requests return.
func (r *apiHandler) Kill() {
r.resources.StopAll()
}
// srvCaller is our implementation of the rpcreflect.MethodCaller interface.
// It lives just long enough to encapsulate the methods that should be
// available for an RPC call and allow the RPC code to instantiate an object
// and place a call on its method.
type srvCaller struct {
objMethod rpcreflect.ObjMethod
creator func(id string) (reflect.Value, error)
}
// ParamsType defines the parameters that should be supplied to this function.
// See rpcreflect.MethodCaller for more detail.
func (s *srvCaller) ParamsType() reflect.Type {
return s.objMethod.Params
}
// ResultType defines the object that is returned from the function.`
// See rpcreflect.MethodCaller for more detail.
func (s *srvCaller) ResultType() reflect.Type {
return s.objMethod.Result
}
// Call takes the object Id and an instance of ParamsType to create an object and place
// a call on its method. It then returns an instance of ResultType.
func (s *srvCaller) Call(ctx context.Context, objId string, arg reflect.Value) (reflect.Value, error) {
objVal, err := s.creator(objId)
if err != nil {
return reflect.Value{}, err
}
return s.objMethod.Call(ctx, objVal, arg)
}
// apiRoot implements basic method dispatching to the facade registry.
type apiRoot struct {
clock clock.Clock
state *state.State
shared *sharedServerContext
facades *facade.Registry
resources *common.Resources
authorizer facade.Authorizer
objectMutex sync.RWMutex
objectCache map[objectKey]reflect.Value
requestRecorder facade.RequestRecorder
}
type apiRootHandler interface {
// State returns the underlying state.
State() *state.State
// SharedContext returns the server shared context.
SharedContext() *sharedServerContext
// Resources returns the common resources.
Resources() *common.Resources
// Authorizer returns the authorizer used for accessing API method calls.
Authorizer() facade.Authorizer
}
// newAPIRoot returns a new apiRoot.
func newAPIRoot(clock clock.Clock,
facades *facade.Registry,
root apiRootHandler,
requestRecorder facade.RequestRecorder,
) (*apiRoot, error) {
st := root.State()
r := &apiRoot{
clock: clock,
state: st,
shared: root.SharedContext(),
facades: facades,
resources: root.Resources(),
authorizer: root.Authorizer(),
objectCache: make(map[objectKey]reflect.Value),
requestRecorder: requestRecorder,
}
// Ensure that the model being requested is in our model cache.
// Client connections need it for status (or very soon will), and agents
// require it for model config and others.
// In all real cases we have a state object, but some test code avoids passing one
// in, in order to just probe endpoints.
if st != nil {
_, err := r.cachedModel(st.ModelUUID())
if err != nil {
return nil, errors.Annotate(err, "model cache")
}
}
return r, nil
}
// restrictAPIRoot calls restrictAPIRootDuringMaintenance, and
// then restricts the result further to the controller or model
// facades, depending on the type of login.
func restrictAPIRoot(
srv *Server,
apiRoot rpc.Root,
model *state.Model,
auth authResult,
clientVersion version.Number,
) (rpc.Root, error) {
if !auth.controllerMachineLogin {
// Controller agents are allowed to
// connect even during maintenance.
restrictedRoot, err := restrictAPIRootDuringMaintenance(
srv, apiRoot, model, auth.tag,
)
if err != nil {
return nil, errors.Trace(err)
}
apiRoot = restrictedRoot
// If the client version is different to the server version,
// add extra checks to ensure older incompatible clients cannot be used.
if clientVersion.Major != jujuversion.Current.Major {
apiRoot = restrictRoot(apiRoot, checkClientVersion(auth.userLogin, clientVersion))
}
}
if auth.controllerOnlyLogin {
apiRoot = restrictRoot(apiRoot, controllerFacadesOnly)
} else {
apiRoot = restrictRoot(apiRoot, modelFacadesOnly)
if model.Type() == state.ModelTypeCAAS {
apiRoot = restrictRoot(apiRoot, caasModelFacadesOnly)
}
}
return apiRoot, nil
}
// restrictAPIRootDuringMaintenance restricts the API root during
// maintenance events (upgrade or migration), depending
// on the authenticated client.
func restrictAPIRootDuringMaintenance(
srv *Server,
apiRoot rpc.Root,
model *state.Model,
authTag names.Tag,
) (rpc.Root, error) {
describeLogin := func() string {
if authTag == nil {
return "anonymous login"
}
return fmt.Sprintf("login for %s", names.ReadableString(authTag))
}
if !srv.upgradeComplete() {
if _, ok := authTag.(names.UserTag); ok {
// Users get access to a limited set of functionality
// while an upgrade is in progress.
return restrictRoot(apiRoot, upgradeMethodsOnly), nil
}
// Agent and anonymous logins are blocked during upgrade.
return nil, errors.Errorf("%s blocked because upgrade is in progress", describeLogin())
}
// For user logins, we limit access during migrations.
if _, ok := authTag.(names.UserTag); ok {
switch model.MigrationMode() {
case state.MigrationModeImporting:
// The user is not able to access a model that is currently being
// imported until the model has been activated.
apiRoot = restrictAll(apiRoot, errors.New("migration in progress, model is importing"))
case state.MigrationModeExporting:
// The user is not allowed to change anything in a model that is
// currently being moved to another controller.
apiRoot = restrictRoot(apiRoot, migrationClientMethodsOnly)
}
}
return apiRoot, nil
}
// Kill implements rpc.Killer, stopping the root's resources.
func (r *apiRoot) Kill() {
r.resources.StopAll()
}
// FindMethod looks up the given rootName and version in our facade registry
// and returns a MethodCaller that will be used by the RPC code to place calls on
// that facade.
// FindMethod uses the global registry apiserver/common.Facades.
// For more information about how FindMethod should work, see rpc/server.go and
// rpc/rpcreflect/value.go
func (r *apiRoot) FindMethod(rootName string, version int, methodName string) (rpcreflect.MethodCaller, error) {
goType, objMethod, err := r.lookupMethod(rootName, version, methodName)
if err != nil {
return nil, err
}
creator := func(id string) (reflect.Value, error) {
objKey := objectKey{name: rootName, version: version, objId: id}
r.objectMutex.RLock()
objValue, ok := r.objectCache[objKey]
r.objectMutex.RUnlock()
if ok {
return objValue, nil
}
r.objectMutex.Lock()
defer r.objectMutex.Unlock()
if objValue, ok := r.objectCache[objKey]; ok {
return objValue, nil
}
// Now that we have the write lock, check one more time in case
// someone got the write lock before us.
factory, err := r.facades.GetFactory(rootName, version)
if err != nil {
// We don't check for IsNotFound here, because it
// should have already been handled in the GetType
// check.
return reflect.Value{}, err
}
obj, err := factory(r.facadeContext(objKey))
if err != nil {
return reflect.Value{}, err
}
objValue = reflect.ValueOf(obj)
if !objValue.Type().AssignableTo(goType) {
return reflect.Value{}, errors.Errorf(
"internal error, %s(%d) claimed to return %s but returned %T",
rootName, version, goType, obj)
}
if goType.Kind() == reflect.Interface {
// If the original function wanted to return an
// interface type, the indirection in the factory via
// an interface{} strips the original interface
// information off. So here we have to create the
// interface again, and assign it.
asInterface := reflect.New(goType).Elem()
asInterface.Set(objValue)
objValue = asInterface
}
r.objectCache[objKey] = objValue
return objValue, nil
}
return &srvCaller{
creator: creator,
objMethod: objMethod,
}, nil
}
func (r *apiRoot) lookupMethod(rootName string, version int, methodName string) (reflect.Type, rpcreflect.ObjMethod, error) {
goType, err := r.facades.GetType(rootName, version)
if err != nil {
if errors.IsNotFound(err) {
return nil, rpcreflect.ObjMethod{}, &rpcreflect.CallNotImplementedError{
RootMethod: rootName,
Version: version,
}
}
return nil, rpcreflect.ObjMethod{}, err
}
rpcType := rpcreflect.ObjTypeOf(goType)
objMethod, err := rpcType.Method(methodName)
if err != nil {
if err == rpcreflect.ErrMethodNotFound {
return nil, rpcreflect.ObjMethod{}, &rpcreflect.CallNotImplementedError{
RootMethod: rootName,
Version: version,
Method: methodName,
}
}
return nil, rpcreflect.ObjMethod{}, err
}
return goType, objMethod, nil
}
func (r *apiRoot) dispose(key objectKey) {
r.objectMutex.Lock()
defer r.objectMutex.Unlock()
delete(r.objectCache, key)
}
func (r *apiRoot) cachedModel(uuid string) (*cache.Model, error) {
model, err := r.shared.controller.WaitForModel(uuid, r.clock)
if err != nil {
// Check the database...
exists, err2 := r.state.ModelExists(uuid)
if err2 != nil {
return nil, errors.Trace(err2)
}
if exists {
return nil, errors.Trace(err)
}
return nil, errors.NotFoundf("model %q", uuid)
}
return model, nil
}
func (r *apiRoot) facadeContext(key objectKey) *facadeContext {
return &facadeContext{
r: r,
key: key,
}
}
// facadeContext implements facade.Context
type facadeContext struct {
r *apiRoot
key objectKey
}
// Cancel is part of the facade.Context interface.
func (ctx *facadeContext) Cancel() <-chan struct{} {
return ctx.r.shared.cancel
}
// Auth is part of the facade.Context interface.
func (ctx *facadeContext) Auth() facade.Authorizer {
return ctx.r.authorizer
}
// Dispose is part of the facade.Context interface.
func (ctx *facadeContext) Dispose() {
ctx.r.dispose(ctx.key)
}
// Resources is part of the facade.Context interface.
func (ctx *facadeContext) Resources() facade.Resources {
return ctx.r.resources
}
// Presence implements facade.Context.
func (ctx *facadeContext) Presence() facade.Presence {
return ctx
}
// ModelPresence implements facade.ModelPresence.
func (ctx *facadeContext) ModelPresence(modelUUID string) facade.ModelPresence {
return ctx.r.shared.presence.Connections().ForModel(modelUUID)
}
// Hub implements facade.Context.
func (ctx *facadeContext) Hub() facade.Hub {
return ctx.r.shared.centralHub
}
// Controller implements facade.Context.
func (ctx *facadeContext) Controller() *cache.Controller {
return ctx.r.shared.controller
}
// CachedModel implements facade.Context.
func (ctx *facadeContext) CachedModel(uuid string) (*cache.Model, error) {
return ctx.r.cachedModel(uuid)
}
// State is part of the facade.Context interface.
func (ctx *facadeContext) State() *state.State {
return ctx.r.state
}
// StatePool is part of the facade.Context interface.
func (ctx *facadeContext) StatePool() *state.StatePool {
return ctx.r.shared.statePool
}
// MultiwatcherFactory is part of the facade.Context interface.
func (ctx *facadeContext) MultiwatcherFactory() multiwatcher.Factory {
return ctx.r.shared.multiwatcherFactory
}
// ID is part of the facade.Context interface.
func (ctx *facadeContext) ID() string {
return ctx.key.objId
}
// RequestRecorder defines a metrics collector for outbound requests.
func (ctx *facadeContext) RequestRecorder() facade.RequestRecorder {
return ctx.r.requestRecorder
}
// LeadershipClaimer is part of the facade.Context interface.
func (ctx *facadeContext) LeadershipClaimer(modelUUID string) (leadership.Claimer, error) {
claimer, err := ctx.r.shared.leaseManager.Claimer(
lease.ApplicationLeadershipNamespace,
modelUUID,
)
if err != nil {
return nil, errors.Trace(err)
}
return leadershipClaimer{claimer}, nil
}
// LeadershipRevoker is part of the facade.Context interface.
func (ctx *facadeContext) LeadershipRevoker(modelUUID string) (leadership.Revoker, error) {
revoker, err := ctx.r.shared.leaseManager.Revoker(
lease.ApplicationLeadershipNamespace,
modelUUID,
)
if err != nil {
return nil, errors.Trace(err)
}
return leadershipRevoker{revoker}, nil
}
// LeadershipChecker is part of the facade.Context interface.
func (ctx *facadeContext) LeadershipChecker() (leadership.Checker, error) {
checker, err := ctx.r.shared.leaseManager.Checker(
lease.ApplicationLeadershipNamespace,
ctx.State().ModelUUID(),
)
if err != nil {
return nil, errors.Trace(err)
}
return leadershipChecker{checker}, nil
}
// LeadershipPinner is part of the facade.Context interface.
// Pinning functionality is only available with the Raft leases implementation.
func (ctx *facadeContext) LeadershipPinner(modelUUID string) (leadership.Pinner, error) {
pinner, err := ctx.r.shared.leaseManager.Pinner(
lease.ApplicationLeadershipNamespace,
modelUUID,
)
if err != nil {
return nil, errors.Trace(err)
}
return leadershipPinner{pinner}, nil
}
// LeadershipReader is part of the facade.Context interface.
// It returns a reader that can be used to return all application leaders
// in the model.
func (ctx *facadeContext) LeadershipReader(modelUUID string) (leadership.Reader, error) {
reader, err := ctx.r.shared.leaseManager.Reader(
lease.ApplicationLeadershipNamespace,
modelUUID,
)
if err != nil {
return nil, errors.Trace(err)
}
return leadershipReader{reader}, nil
}
// SingularClaimer is part of the facade.Context interface.
func (ctx *facadeContext) SingularClaimer() (lease.Claimer, error) {
return ctx.r.shared.leaseManager.Claimer(
lease.SingularControllerNamespace,
ctx.State().ModelUUID(),
)
}
func (ctx *facadeContext) HTTPClient(purpose facade.HTTPClientPurpose) facade.HTTPClient {
switch purpose {
case facade.CharmhubHTTPClient:
return ctx.r.shared.charmhubHTTPClient
default:
return nil
}
}
// ControllerDB returns a TrackedDB reference for the controller database.
func (ctx *facadeContext) ControllerDB() (coredatabase.TrackedDB, error) {
db, err := ctx.r.shared.dbGetter.GetDB(coredatabase.ControllerNS)
return db, errors.Trace(err)
}
// adminRoot dispatches API calls to those available to an anonymous connection
// which has not logged in, which here is the admin facade.
type adminRoot struct {
*apiHandler
adminAPIs map[int]interface{}
}
// newAdminRoot creates a new AnonRoot which dispatches to the given Admin API implementation.
func newAdminRoot(h *apiHandler, adminAPIs map[int]interface{}) *adminRoot {
r := &adminRoot{
apiHandler: h,
adminAPIs: adminAPIs,
}
return r
}
func (r *adminRoot) FindMethod(rootName string, version int, methodName string) (rpcreflect.MethodCaller, error) {
if rootName != "Admin" {
return nil, &rpcreflect.CallNotImplementedError{
RootMethod: rootName,
Version: version,
}
}
if api, ok := r.adminAPIs[version]; ok {
return rpcreflect.ValueOf(reflect.ValueOf(api)).FindMethod(rootName, 0, methodName)
}
return nil, &rpc.RequestError{
Code: params.CodeNotSupported,
Message: "this version of Juju does not support login from old clients",
}
}
// AuthMachineAgent returns whether the current client is a machine agent.
// TODO(controlleragent) - add AuthControllerAgent function
func (r *apiHandler) AuthMachineAgent() bool {
_, isMachine := r.GetAuthTag().(names.MachineTag)
_, isControllerAgent := r.GetAuthTag().(names.ControllerAgentTag)
return isMachine || isControllerAgent
}
// AuthModelAgent return whether the current client is a model agent
func (r *apiHandler) AuthModelAgent() bool {
_, isModel := r.GetAuthTag().(names.ModelTag)
return isModel
}
// AuthApplicationAgent returns whether the current client is an application operator.
func (r *apiHandler) AuthApplicationAgent() bool {
_, isApp := r.GetAuthTag().(names.ApplicationTag)
return isApp
}
// AuthUnitAgent returns whether the current client is a unit agent.
func (r *apiHandler) AuthUnitAgent() bool {
_, isUnit := r.GetAuthTag().(names.UnitTag)
return isUnit
}
// AuthOwner returns whether the authenticated user's tag matches the
// given entity tag.
func (r *apiHandler) AuthOwner(tag names.Tag) bool {
return r.entity.Tag() == tag
}
// AuthController returns whether the authenticated user is a
// machine with running the ManageEnviron job.
func (r *apiHandler) AuthController() bool {
type hasIsManager interface {
IsManager() bool
}
m, ok := r.entity.(hasIsManager)
return ok && m.IsManager()
}
// AuthClient returns whether the authenticated entity is a client
// user.
func (r *apiHandler) AuthClient() bool {
_, isUser := r.GetAuthTag().(names.UserTag)
return isUser
}
// GetAuthTag returns the tag of the authenticated entity, if any.
func (r *apiHandler) GetAuthTag() names.Tag {
if r.entity == nil {
return nil
}
return r.entity.Tag()
}
// ConnectedModel returns the UUID of the model authenticated
// against. It's possible for it to be empty if the login was made
// directly to the root of the API instead of a model endpoint, but
// that method is deprecated.
func (r *apiHandler) ConnectedModel() string {
return r.modelUUID
}
func (r *apiHandler) userPermission(subject names.UserTag, target names.Tag) (permission.Access, error) {
if r.loginToken == nil {
return r.state.UserPermission(subject, target)
}
return permissionFromToken(r.loginToken, target.Kind())
}
type hasPermissionFunc func(operation permission.Access, target names.Tag) (bool, error)
// HasPermission returns true if the logged in user can perform <operation> on <target>.
func (r *apiHandler) HasPermission(operation permission.Access, target names.Tag) (bool, error) {
return common.HasPermission(r.userPermission, r.entity.Tag(), operation, target)
}
// DescribeFacades returns the list of available Facades and their Versions
func DescribeFacades(registry *facade.Registry) []params.FacadeVersions {
facades := registry.List()
result := make([]params.FacadeVersions, len(facades))
for i, f := range facades {
result[i].Name = f.Name
result[i].Versions = f.Versions
}
return result
}