Skip to content

Commit

Permalink
Add the model permissions to the model details exposed through the al…
Browse files Browse the repository at this point in the history
…l watcher.
  • Loading branch information
howbazaar committed Jan 13, 2020
1 parent 660f506 commit 3345aa1
Show file tree
Hide file tree
Showing 6 changed files with 371 additions and 68 deletions.
1 change: 1 addition & 0 deletions core/multiwatcher/package_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ func (*ImportTest) TestImports(c *gc.C) {
"core/life",
"core/model",
"core/network",
"core/permission",
"core/status",
})
}
3 changes: 3 additions & 0 deletions core/multiwatcher/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"github.com/juju/juju/core/life"
"github.com/juju/juju/core/model"
"github.com/juju/juju/core/network"
"github.com/juju/juju/core/permission"
"github.com/juju/juju/core/status"
)

Expand Down Expand Up @@ -363,6 +364,8 @@ type ModelUpdate struct {
Status StatusInfo
Constraints constraints.Value
SLA ModelSLAInfo

UserPermissions map[string]permission.Access
}

// ModelSLAInfo describes the SLA info for a model.
Expand Down
3 changes: 3 additions & 0 deletions state/allcollections.go
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,9 @@ func allCollections() CollectionSchema {
// given operation.
permissionsC: {
global: true,
indexes: []mgo.Index{{
Key: []string{"object-global-key", "subject-global-key"},
}},
},

// This collection holds information cached by autocert certificate
Expand Down
218 changes: 189 additions & 29 deletions state/allwatcher.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"github.com/juju/juju/core/multiwatcher"
"github.com/juju/juju/core/network"
corenetwork "github.com/juju/juju/core/network"
"github.com/juju/juju/core/permission"
"github.com/juju/juju/core/status"
"github.com/juju/juju/environs/config"
"github.com/juju/juju/state/watcher"
Expand Down Expand Up @@ -102,6 +103,10 @@ func makeAllWatcherCollectionInfo(collNames []string) map[string]allWatcherState
collection.docType = reflect.TypeOf(backingApplicationOffer{})
case generationsC:
collection.docType = reflect.TypeOf(backingGeneration{})
case permissionsC:
// Permissions are attached to the Model that they are for.
collection.docType = reflect.TypeOf(backingPermission{})
collection.subsidiary = true
default:
logger.Criticalf("programming error: unknown collection %q", collName)
}
Expand Down Expand Up @@ -133,18 +138,7 @@ func (e *backingModel) isNotFoundAndModelDead(err error) bool {

func (e *backingModel) updated(ctx *allWatcherContext) error {
allWatcherLogger.Tracef(`model "%s" updated`, ctx.id)
settings, err := ctx.getSettings(modelGlobalKey)
if e.isNotFoundAndModelDead(err) {
// Treat it as if the model is removed.
return e.removed(ctx)
}
if err != nil {
return errors.Trace(err)
}
cfg, err := config.New(config.NoDefaults, settings)
if err != nil {
return errors.Trace(err)
}

// Update the context with the model type.
ctx.modelType_ = e.Type
info := &multiwatcher.ModelUpdate{
Expand All @@ -154,30 +148,65 @@ func (e *backingModel) updated(ctx *allWatcherContext) error {
Owner: e.Owner,
ControllerUUID: e.ControllerUUID,
IsController: ctx.state.IsController(),
Config: cfg.AllAttrs(),
SLA: multiwatcher.ModelSLAInfo{
Level: e.SLA.Level.String(),
Owner: e.SLA.Owner,
},
}
c, err := ctx.readConstraints(modelGlobalKey)
// Treat it as if the model is removed.
if e.isNotFoundAndModelDead(err) {
return e.removed(ctx)
}
if err != nil {
return errors.Trace(err)
}
info.Constraints = c

info.Status, err = ctx.getStatus(modelGlobalKey, "model")
if e.isNotFoundAndModelDead(err) {
// Treat it as if the model is removed.
return e.removed(ctx)
}
if err != nil {
return errors.Trace(err)
oldInfo := ctx.store.Get(info.EntityID())
if oldInfo == nil {
settings, err := ctx.getSettings(modelGlobalKey)
if e.isNotFoundAndModelDead(err) {
// Since we know this isn't in the store, stop looking for new
// things.
return nil
}
if err != nil {
return errors.Trace(err)
}
cfg, err := config.New(config.NoDefaults, settings)
if err != nil {
return errors.Trace(err)
}

info.Config = cfg.AllAttrs()

c, err := ctx.readConstraints(modelGlobalKey)
if e.isNotFoundAndModelDead(err) {
// Since we know this isn't in the store, stop looking for new
// things.
return nil
}
if err != nil {
return errors.Trace(err)
}
info.Constraints = c

info.Status, err = ctx.getStatus(modelGlobalKey, "model")
if e.isNotFoundAndModelDead(err) {
// Since we know this isn't in the store, stop looking for new
// things.
return nil
}
if err != nil {
return errors.Trace(err)
}

permissions, err := ctx.permissionsForModel(e.UUID)
if err != nil {
return errors.Trace(err)
}

info.UserPermissions = permissions
} else {
oldInfo := oldInfo.(*multiwatcher.ModelUpdate)
info.Config = oldInfo.Config
info.Constraints = oldInfo.Constraints
info.Status = oldInfo.Status
info.UserPermissions = oldInfo.UserPermissions
}

ctx.store.Update(info)
return nil
}
Expand All @@ -192,6 +221,82 @@ func (e *backingModel) mongoID() string {
return e.UUID
}

type backingPermission permissionDoc

func (e *backingPermission) modelAndUser(id string) (string, string, bool) {
parts := strings.Split(id, "#")

if len(parts) < 4 {
// Not valid for as far as we care about.
return "", "", false
}

// At this stage, we are only dealing with model user permissions.
if parts[0] != modelGlobalKey || parts[2] != userGlobalKeyPrefix {
return "", "", false
}
return parts[1], parts[3], true
}

func (e *backingPermission) updated(ctx *allWatcherContext) error {
allWatcherLogger.Tracef(`permission "%s" updated`, ctx.id)

modelUUID, user, ok := e.modelAndUser(ctx.id)
if !ok {
// Not valid for as far as we care about.
return nil
}

storeKey := &multiwatcher.ModelUpdate{
ModelUUID: modelUUID,
}

info0 := ctx.store.Get(storeKey.EntityID())
switch info := info0.(type) {
case nil:
// The parent info doesn't exist. Ignore the permission until it does.
return nil
case *multiwatcher.ModelUpdate:
// Set the access for the user in the permission map of the model.
info.UserPermissions[user] = permission.Access(e.Access)
}

ctx.store.Update(info0)
return nil
}

func (e *backingPermission) removed(ctx *allWatcherContext) error {
allWatcherLogger.Tracef(`permission "%s" removed`, ctx.id)

modelUUID, user, ok := e.modelAndUser(ctx.id)
if !ok {
// Not valid for as far as we care about.
return nil
}

storeKey := &multiwatcher.ModelUpdate{
ModelUUID: modelUUID,
}

info0 := ctx.store.Get(storeKey.EntityID())
switch info := info0.(type) {
case nil:
// The parent info doesn't exist. Nothing to remove from.
return nil
case *multiwatcher.ModelUpdate:
// Remove the user from the permission map.
delete(info.UserPermissions, user)
}

ctx.store.Update(info0)
return nil
}

func (e *backingPermission) mongoID() string {
logger.Criticalf("programming error: attempting to get mongoID from permissions document")
return ""
}

type backingMachine machineDoc

func (m *backingMachine) updateAgentVersion(info *multiwatcher.MachineInfo) {
Expand Down Expand Up @@ -1355,6 +1460,7 @@ func NewAllWatcherBacking(pool *StatePool) AllWatcherBacking {
generationsC,
instanceDataC,
openedPortsC,
permissionsC,
relationsC,
remoteApplicationsC,
statusesC,
Expand Down Expand Up @@ -1476,6 +1582,10 @@ func (b *allWatcherBacking) idForChange(change watcher.Change) (string, string,
if change.C == modelsC {
modelUUID := change.Id.(string)
return modelUUID, modelUUID, nil
} else if change.C == permissionsC {
// All permissions can just load using the system state.
modelUUID := b.stPool.SystemState().ModelUUID()
return modelUUID, change.Id.(string), nil
}

modelUUID, id, ok := splitDocID(change.Id.(string))
Expand Down Expand Up @@ -1575,6 +1685,7 @@ type allWatcherContext struct {
statuses map[string]status.StatusInfo
instances map[string]instanceData
openPorts map[string]portsDoc
userAccess map[string]map[string]permission.Access
}

func (ctx *allWatcherContext) loadSubsidiaryCollections() error {
Expand All @@ -1593,6 +1704,9 @@ func (ctx *allWatcherContext) loadSubsidiaryCollections() error {
if err := ctx.loadOpenedPorts(); err != nil {
return errors.Annotatef(err, "cache opened ports")
}
if err := ctx.loadPermissions(); err != nil {
return errors.Annotatef(err, "permissions")
}
return nil
}

Expand Down Expand Up @@ -1668,6 +1782,32 @@ func (ctx *allWatcherContext) loadOpenedPorts() error {
return nil
}

func (ctx *allWatcherContext) loadPermissions() error {
col, closer := ctx.state.db().GetCollection(permissionsC)
defer closer()

var docs []backingPermission
if err := col.Find(nil).All(&docs); err != nil {
return errors.Annotate(err, "cannot read all permissions")
}

ctx.userAccess = make(map[string]map[string]permission.Access)
for _, doc := range docs {
modelUUID, user, ok := doc.modelAndUser(doc.ID)
if !ok {
continue
}
modelPermissions := ctx.userAccess[modelUUID]
if modelPermissions == nil {
modelPermissions = make(map[string]permission.Access)
ctx.userAccess[modelUUID] = modelPermissions
}
modelPermissions[user] = permission.Access(doc.Access)
}

return nil
}

type constraintsWithID struct {
DocID string `bson:"_id"`
Nested constraintsDoc `bson:",inline"`
Expand Down Expand Up @@ -1801,6 +1941,26 @@ func (ctx *allWatcherContext) assignedMachineID(u *Unit) (string, error) {
}
}

func (ctx *allWatcherContext) permissionsForModel(uuid string) (map[string]permission.Access, error) {
if ctx.userAccess != nil {
return ctx.userAccess[uuid], nil
}
permissions, err := ctx.state.usersPermissions(modelKey(uuid))
if err != nil {
return nil, errors.Trace(err)
}
result := make(map[string]permission.Access)
for _, perm := range permissions {
user := userIDFromGlobalKey(perm.doc.SubjectGlobalKey)
if user == perm.doc.SubjectGlobalKey {
// Not a user subject
continue
}
result[user] = perm.access()
}
return result, nil
}

func (ctx *allWatcherContext) getOpenedPorts(unit *Unit) ([]corenetwork.PortRange, error) {
// NOTE: as we open ports on other networks, this code needs to be updated
// to look at more than just the default empty string subnet id.
Expand Down
Loading

0 comments on commit 3345aa1

Please sign in to comment.