diff --git a/agent/agentbootstrap/bootstrap_test.go b/agent/agentbootstrap/bootstrap_test.go index c1bf4124bb5..4309ec46a5a 100644 --- a/agent/agentbootstrap/bootstrap_test.go +++ b/agent/agentbootstrap/bootstrap_test.go @@ -204,9 +204,6 @@ func (s *bootstrapSuite) TestInitializeState(c *gc.C) { modelTag := model.Tag().(names.ModelTag) controllerTag := names.NewControllerTag(controllerCfg.ControllerUUID()) s.assertCanLogInAsAdmin(c, modelTag, controllerTag, testing.DefaultMongoPassword) - user, err := st.User(model.Owner()) - c.Assert(err, jc.ErrorIsNil) - c.Check(user.PasswordValid(testing.DefaultMongoPassword), jc.IsTrue) // Check that controller model configuration has been added, and // model constraints set. diff --git a/apiserver/common/modelmanagerinterface.go b/apiserver/common/modelmanagerinterface.go index a93750d6b16..5fafda0d7be 100644 --- a/apiserver/common/modelmanagerinterface.go +++ b/apiserver/common/modelmanagerinterface.go @@ -15,7 +15,6 @@ import ( "github.com/juju/juju/core/credential" "github.com/juju/juju/core/network" "github.com/juju/juju/core/objectstore" - "github.com/juju/juju/core/permission" "github.com/juju/juju/core/status" "github.com/juju/juju/core/watcher" environscloudspec "github.com/juju/juju/environs/cloudspec" @@ -92,7 +91,6 @@ type Model interface { // needs a Model with this model. Once this is gone ControllerUUID can be // removed from this interface. ControllerUUID() string - AddUser(state.UserAccessSpec) (permission.UserAccess, error) SetCloudCredential(tag names.CloudCredentialTag) (bool, error) } diff --git a/apiserver/facades/client/client/backend.go b/apiserver/facades/client/client/backend.go index 2d6d6b5d455..399d0f69f03 100644 --- a/apiserver/facades/client/client/backend.go +++ b/apiserver/facades/client/client/backend.go @@ -15,7 +15,6 @@ import ( "github.com/juju/juju/apiserver/common/storagecommon" "github.com/juju/juju/core/crossmodel" "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/internal/charm" @@ -64,7 +63,6 @@ type Model interface { CloudCredentialTag() (names.CloudCredentialTag, bool) Config() (*config.Config, error) Owner() names.UserTag - AddUser(state.UserAccessSpec) (permission.UserAccess, error) StatusHistory(status.StatusHistoryFilter) ([]status.StatusInfo, error) LatestToolsVersion() version.Number Status() (status.StatusInfo, error) diff --git a/apiserver/facades/client/client/package_mock_test.go b/apiserver/facades/client/client/package_mock_test.go index e3a7c911904..f88140d4e49 100644 --- a/apiserver/facades/client/client/package_mock_test.go +++ b/apiserver/facades/client/client/package_mock_test.go @@ -15,7 +15,6 @@ import ( client "github.com/juju/juju/apiserver/facades/client/client" crossmodel "github.com/juju/juju/core/crossmodel" - permission "github.com/juju/juju/core/permission" status "github.com/juju/juju/core/status" config "github.com/juju/juju/environs/config" charm "github.com/juju/juju/internal/charm" @@ -891,45 +890,6 @@ func (m *MockModel) EXPECT() *MockModelMockRecorder { return m.recorder } -// AddUser mocks base method. -func (m *MockModel) AddUser(arg0 state.UserAccessSpec) (permission.UserAccess, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "AddUser", arg0) - ret0, _ := ret[0].(permission.UserAccess) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// AddUser indicates an expected call of AddUser. -func (mr *MockModelMockRecorder) AddUser(arg0 any) *MockModelAddUserCall { - mr.mock.ctrl.T.Helper() - call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddUser", reflect.TypeOf((*MockModel)(nil).AddUser), arg0) - return &MockModelAddUserCall{Call: call} -} - -// MockModelAddUserCall wrap *gomock.Call -type MockModelAddUserCall struct { - *gomock.Call -} - -// Return rewrite *gomock.Call.Return -func (c *MockModelAddUserCall) Return(arg0 permission.UserAccess, arg1 error) *MockModelAddUserCall { - c.Call = c.Call.Return(arg0, arg1) - return c -} - -// Do rewrite *gomock.Call.Do -func (c *MockModelAddUserCall) Do(f func(state.UserAccessSpec) (permission.UserAccess, error)) *MockModelAddUserCall { - c.Call = c.Call.Do(f) - return c -} - -// DoAndReturn rewrite *gomock.Call.DoAndReturn -func (c *MockModelAddUserCall) DoAndReturn(f func(state.UserAccessSpec) (permission.UserAccess, error)) *MockModelAddUserCall { - c.Call = c.Call.DoAndReturn(f) - return c -} - // CloudCredentialTag mocks base method. func (m *MockModel) CloudCredentialTag() (names.CloudCredentialTag, bool) { m.ctrl.T.Helper() diff --git a/apiserver/facades/client/modelmanager/modelinfo_test.go b/apiserver/facades/client/modelmanager/modelinfo_test.go index 3b9c698f6bf..49d5e209cb0 100644 --- a/apiserver/facades/client/modelmanager/modelinfo_test.go +++ b/apiserver/facades/client/modelmanager/modelinfo_test.go @@ -814,7 +814,6 @@ type mockState struct { cloudUsers map[string]permission.Access model *mockModel controllerModel *mockModel - users []permission.UserAccess machines []common.Machine controllerNodes []common.ControllerNode cfgDefaults config.ModelDefaultAttributes @@ -988,36 +987,6 @@ func (st *mockState) Close() error { return st.NextErr() } -func (st *mockState) AddControllerUser(spec state.UserAccessSpec) (permission.UserAccess, error) { - st.MethodCall(st, "AddControllerUser", spec) - return permission.UserAccess{}, st.NextErr() -} - -func (st *mockState) UserAccess(tag names.UserTag, target names.Tag) (permission.UserAccess, error) { - st.MethodCall(st, "ModelUser", tag, target) - for _, user := range st.users { - if user.UserTag != tag { - continue - } - nextErr := st.NextErr() - if nextErr != nil { - return permission.UserAccess{}, nextErr - } - return user, nil - } - return permission.UserAccess{}, st.NextErr() -} - -func (st *mockState) RemoveUserAccess(subject names.UserTag, target names.Tag) error { - st.MethodCall(st, "RemoveUserAccess", subject, target) - return st.NextErr() -} - -func (st *mockState) SetUserAccess(subject names.UserTag, target names.Tag, access permission.Access) (permission.UserAccess, error) { - st.MethodCall(st, "SetUserAccess", subject, target, access) - return permission.UserAccess{}, st.NextErr() -} - func (st *mockState) ModelConfigDefaultValues(cloud string) (config.ModelDefaultAttributes, error) { st.MethodCall(st, "ModelConfigDefaultValues", cloud) return st.cfgDefaults, nil @@ -1305,11 +1274,6 @@ func (m *mockModel) MigrationMode() state.MigrationMode { return m.migrationStatus } -func (m *mockModel) AddUser(spec state.UserAccessSpec) (permission.UserAccess, error) { - m.MethodCall(m, "AddUser", spec) - return permission.UserAccess{}, m.NextErr() -} - func (m *mockModel) SetCloudCredential(tag names.CloudCredentialTag) (bool, error) { m.MethodCall(m, "SetCloudCredential", tag) return m.setCloudCredentialF(tag) diff --git a/apiserver/facades/client/modelmanager/modelmanager_test.go b/apiserver/facades/client/modelmanager/modelmanager_test.go index b154059f3d9..e77e368ae76 100644 --- a/apiserver/facades/client/modelmanager/modelmanager_test.go +++ b/apiserver/facades/client/modelmanager/modelmanager_test.go @@ -1383,8 +1383,7 @@ func (s *modelManagerStateSuite) TestAdminCanCreateModelForSomeoneElse(c *gc.C) c.Assert(model.OwnerTag, gc.Equals, owner.String()) c.Assert(model.Name, gc.Equals, "test-model") c.Assert(model.Type, gc.Equals, "iaas") - // Make sure that the environment created does actually have the correct - // owner, and that owner is actually allowed to use the environment. + newState, err := s.StatePool().Get(model.UUID) c.Assert(err, jc.ErrorIsNil) defer newState.Release() @@ -1392,8 +1391,6 @@ func (s *modelManagerStateSuite) TestAdminCanCreateModelForSomeoneElse(c *gc.C) newModel, err := newState.Model() c.Assert(err, jc.ErrorIsNil) c.Assert(newModel.Owner(), gc.Equals, owner) - _, err = newState.UserAccess(owner, newModel.ModelTag()) - c.Assert(err, jc.ErrorIsNil) } func (s *modelManagerStateSuite) TestNonAdminCannotCreateModelForSomeoneElse(c *gc.C) { diff --git a/cmd/jujud-controller/agent/bootstrap_test.go b/cmd/jujud-controller/agent/bootstrap_test.go index 1a9f2a1e257..81e5fc4a98b 100644 --- a/cmd/jujud-controller/agent/bootstrap_test.go +++ b/cmd/jujud-controller/agent/bootstrap_test.go @@ -389,13 +389,6 @@ func (s *BootstrapSuite) TestInitialPassword(c *gc.C) { err = adminDB.Login("admin", info.Password) c.Assert(err, jc.ErrorIsNil) - // Check that the admin user has been given an appropriate password - st, closer := s.getSystemState(c) - defer closer() - u, err := st.User(names.NewLocalUserTag("admin")) - c.Assert(err, jc.ErrorIsNil) - c.Assert(u.PasswordValid(testPassword), jc.IsTrue) - // Check that the machine configuration has been given a new // password and that we can connect to mongo as that machine // and that the in-mongo password also verifies correctly. @@ -408,7 +401,7 @@ func (s *BootstrapSuite) TestInitialPassword(c *gc.C) { c.Assert(err, jc.ErrorIsNil) defer session.Close() - st, closer = s.getSystemState(c) + st, closer := s.getSystemState(c) defer closer() node, err := st.ControllerNode("0") diff --git a/state/action.go b/state/action.go index 365d0bb51d6..8099dcae20b 100644 --- a/state/action.go +++ b/state/action.go @@ -682,8 +682,11 @@ func (m *Model) EnqueueAction(operationID string, receiver names.Tag, return nil, errors.New("action name required") } + checkNotDead := true receiverCollectionName, receiverId, err := m.st.tagToCollectionAndId(receiver) - if err != nil { + if errors.Is(err, errors.NotImplemented) { + checkNotDead = false + } else if err != nil { return nil, errors.Trace(err) } doc, ndoc, err := newActionDoc(m.st, operationID, receiver, actionName, payload, parallel, executionGroup) @@ -695,11 +698,16 @@ func (m *Model) EnqueueAction(operationID string, receiver names.Tag, doc.Status = ActionError doc.Message = actionError.Error() } - ops := []txn.Op{{ - C: receiverCollectionName, - Id: receiverId, - Assert: notDeadDoc, - }, { + + var ops []txn.Op + if checkNotDead { + ops = append(ops, txn.Op{ + C: receiverCollectionName, + Id: receiverId, + Assert: notDeadDoc, + }) + } + ops = append(ops, []txn.Op{{ C: operationsC, Id: m.st.docID(operationID), Assert: txn.DocExists, @@ -708,7 +716,7 @@ func (m *Model) EnqueueAction(operationID string, receiver names.Tag, Id: doc.DocId, Assert: txn.DocMissing, Insert: doc, - }} + }}...) if actionError == nil { ops = append(ops, txn.Op{ C: actionNotificationsC, @@ -719,11 +727,14 @@ func (m *Model) EnqueueAction(operationID string, receiver names.Tag, } buildTxn := func(attempt int) ([]txn.Op, error) { - if notDead, err := isNotDead(m.st, receiverCollectionName, receiverId); err != nil { - return nil, err - } else if !notDead { - return nil, stateerrors.ErrDead - } else if attempt != 0 { + if checkNotDead { + if notDead, err := isNotDead(m.st, receiverCollectionName, receiverId); err != nil { + return nil, err + } else if !notDead { + return nil, stateerrors.ErrDead + } + } + if attempt != 0 { _, err := m.Operation(operationID) if err != nil { return nil, errors.Trace(err) diff --git a/state/allcollections.go b/state/allcollections.go index 176467a7ee7..4339a422e33 100644 --- a/state/allcollections.go +++ b/state/allcollections.go @@ -114,17 +114,6 @@ func allCollections() CollectionSchema { // migration minions. migrationsMinionSyncC: {global: true}, - // This collection holds user information that's not specific to any - // one model. - usersC: { - global: true, - }, - - // This collection holds users that are relative to controllers. - controllerUsersC: { - global: true, - }, - // This collection is used as a unique key restraint. The _id field is // a concatenation of multiple fields that form a compound index, // allowing us to ensure users cannot have the same name for two @@ -144,16 +133,6 @@ func allCollections() CollectionSchema { global: true, }, - // This collection is basically a standard SQL intersection table; it - // references the global records of the users allowed access to a - // given operation. - permissionsC: { - global: true, - indexes: []mgo.Index{{ - Key: []string{"object-global-key", "subject-global-key"}, - }}, - }, - // ----------------- // Local collections @@ -511,7 +490,6 @@ const ( containerRefsC = "containerRefs" controllersC = "controllers" controllerNodesC = "controllerNodes" - controllerUsersC = "controllerusers" dockerResourcesC = "dockerResources" filesystemAttachmentsC = "filesystemAttachments" filesystemsC = "filesystems" @@ -531,7 +509,6 @@ const ( openedPortsC = "openedPorts" operationsC = "operations" payloadsC = "payloads" - permissionsC = "permissions" providerIDsC = "providerIDs" relationScopesC = "relationscopes" relationsC = "relations" @@ -556,7 +533,6 @@ const ( unitStatesC = "unitstates" upgradeInfoC = "upgradeInfo" usermodelnameC = "usermodelname" - usersC = "users" volumeAttachmentsC = "volumeattachments" volumeAttachmentPlanC = "volumeattachmentplan" volumesC = "volumes" diff --git a/state/applicationoffers.go b/state/applicationoffers.go index 6dbfef83d45..e80c102a538 100644 --- a/state/applicationoffers.go +++ b/state/applicationoffers.go @@ -22,17 +22,6 @@ import ( "github.com/juju/juju/internal/uuid" ) -const ( - // applicationOfferGlobalKey is the key for an application offer. - applicationOfferGlobalKey = "ao" -) - -// applicationOfferKey will return the key for a given offer using the -// offer uuid and the applicationOfferGlobalKey. -func applicationOfferKey(offerUUID string) string { - return fmt.Sprintf("%s#%s", applicationOfferGlobalKey, offerUUID) -} - // applicationOfferDoc represents the internal state of a application offer in MongoDB. type applicationOfferDoc struct { DocID string `bson:"_id"` diff --git a/state/controller.go b/state/controller.go index c1205efc1f5..a77ea525449 100644 --- a/state/controller.go +++ b/state/controller.go @@ -4,8 +4,6 @@ package state import ( - "fmt" - "github.com/juju/errors" "github.com/juju/mgo/v3" "github.com/juju/mgo/v3/bson" @@ -18,17 +16,8 @@ import ( const ( // ControllerSettingsGlobalKey is the key for the controller and its settings. ControllerSettingsGlobalKey = "controllerSettings" - - // controllerGlobalKey is the key for controller. - controllerGlobalKey = "c" ) -// controllerKey will return the key for a given controller using the -// controller uuid and the controllerGlobalKey. -func controllerKey(controllerUUID string) string { - return fmt.Sprintf("%s#%s", controllerGlobalKey, controllerUUID) -} - // Controller encapsulates state for the Juju controller as a whole, // as opposed to model specific functionality. // diff --git a/state/controlleruser.go b/state/controlleruser.go deleted file mode 100644 index 4e9f0262b65..00000000000 --- a/state/controlleruser.go +++ /dev/null @@ -1,97 +0,0 @@ -// Copyright 2016 Canonical Ltd. -// Licensed under the AGPLv3, see LICENCE file for details. - -package state - -import ( - "fmt" - "strings" - "time" - - "github.com/juju/errors" - "github.com/juju/mgo/v3" - "github.com/juju/mgo/v3/txn" - "github.com/juju/names/v5" - - "github.com/juju/juju/core/permission" -) - -const defaultControllerPermission = permission.LoginAccess - -// setAccess changes the user's access permissions on the controller. -func (st *State) setControllerAccess(access permission.Access, userGlobalKey string) error { - if err := permission.ValidateControllerAccess(access); err != nil { - return errors.Trace(err) - } - op := updatePermissionOp(controllerKey(st.ControllerUUID()), userGlobalKey, access) - - err := st.db().RunTransaction([]txn.Op{op}) - if err == txn.ErrAborted { - return errors.NotFoundf("existing permissions") - } - return errors.Trace(err) -} - -// controllerUser a model userAccessDoc. -func (st *State) controllerUser(user names.UserTag) (userAccessDoc, error) { - controllerUser := userAccessDoc{} - controllerUsers, closer := st.db().GetCollection(controllerUsersC) - defer closer() - - username := strings.ToLower(user.Id()) - err := controllerUsers.FindId(username).One(&controllerUser) - if err == mgo.ErrNotFound { - return userAccessDoc{}, errors.NotFoundf("controller user %q", user.Id()) - } - // DateCreated is inserted as UTC, but read out as local time. So we - // convert it back to UTC here. - controllerUser.DateCreated = controllerUser.DateCreated.UTC() - return controllerUser, nil -} - -func createControllerUserOps(controllerUUID string, user, createdBy names.UserTag, displayName string, dateCreated time.Time, access permission.Access) []txn.Op { - creatorname := createdBy.Id() - doc := &userAccessDoc{ - ID: userAccessID(user), - ObjectUUID: controllerUUID, - UserName: user.Id(), - DisplayName: displayName, - CreatedBy: creatorname, - DateCreated: dateCreated, - } - ops := []txn.Op{ - createPermissionOp(controllerKey(controllerUUID), userGlobalKey(userAccessID(user)), access), - { - C: controllerUsersC, - Id: userAccessID(user), - Assert: txn.DocMissing, - Insert: doc, - }, - } - return ops -} - -func removeControllerUserOps(controllerUUID string, user names.UserTag) []txn.Op { - return []txn.Op{ - removePermissionOp(controllerKey(controllerUUID), userGlobalKey(userAccessID(user))), - { - C: controllerUsersC, - Id: userAccessID(user), - Assert: txn.DocExists, - Remove: true, - }} - -} - -// RemoveControllerUser removes a user from the database. -func (st *State) removeControllerUser(user names.UserTag) error { - ops := removeControllerUserOps(st.ControllerUUID(), user) - err := st.db().RunTransaction(ops) - if err == txn.ErrAborted { - err = errors.NewNotFound(nil, fmt.Sprintf("controller user %q does not exist", user.Id())) - } - if err != nil { - return errors.Trace(err) - } - return nil -} diff --git a/state/errors.go b/state/errors.go index ae3c4050dcc..0cb83917229 100644 --- a/state/errors.go +++ b/state/errors.go @@ -14,15 +14,12 @@ var ( newProviderIDNotUniqueError = stateerrors.NewProviderIDNotUniqueError newParentDeviceHasChildrenError = stateerrors.NewParentDeviceHasChildrenError newErrCharmAlreadyUploaded = stateerrors.NewErrCharmAlreadyUploaded - newDeletedUserError = stateerrors.NewDeletedUserError newVersionInconsistentError = stateerrors.NewVersionInconsistentError IsCharmAlreadyUploadedError = stateerrors.IsCharmAlreadyUploadedError IsProviderIDNotUniqueError = stateerrors.IsProviderIDNotUniqueError IsParentDeviceHasChildrenError = stateerrors.IsParentDeviceHasChildrenError IsNotAlive = stateerrors.IsNotAlive - IsDeletedUserError = stateerrors.IsDeletedUserError - IsNeverLoggedInError = stateerrors.IsNeverLoggedInError IsVersionInconsistentError = stateerrors.IsVersionInconsistentError ) diff --git a/state/export_test.go b/state/export_test.go index 28458e8dace..beefc730714 100644 --- a/state/export_test.go +++ b/state/export_test.go @@ -48,7 +48,6 @@ const ( ModelEntityRefsC = modelEntityRefsC ApplicationsC = applicationsC ControllersC = controllersC - UsersC = usersC StorageInstancesC = storageInstancesC GlobalSettingsC = globalSettingsC SettingsC = settingsC diff --git a/state/initialize.go b/state/initialize.go index d202ede40aa..a247ea9d885 100644 --- a/state/initialize.go +++ b/state/initialize.go @@ -17,7 +17,6 @@ import ( "github.com/juju/juju/core/status" environscloudspec "github.com/juju/juju/environs/cloudspec" "github.com/juju/juju/environs/config" - "github.com/juju/juju/internal/password" "github.com/juju/juju/internal/storage" ) @@ -168,20 +167,7 @@ func Initialize(args InitializeParams, providerConfigSchemaGetter config.ConfigS if err != nil { return nil, errors.Trace(err) } - salt, err := password.RandomSalt() - if err != nil { - return nil, err - } - - dateCreated := st.nowToTheSecond() - ops := createInitialUserOps( - args.ControllerConfig.ControllerUUID(), - args.ControllerModelArgs.Owner, - args.AdminPassword, - salt, - dateCreated, - ) - + var ops []txn.Op ops = append(ops, txn.Op{ C: controllersC, diff --git a/state/interface_test.go b/state/interface_test.go index 004452f1e3d..b42c9536e49 100644 --- a/state/interface_test.go +++ b/state/interface_test.go @@ -13,7 +13,6 @@ var ( _ Entity = (*UnitAgent)(nil) _ Entity = (*Application)(nil) _ Entity = (*Model)(nil) - _ Entity = (*User)(nil) _ EntityWithApplication = (*Unit)(nil) @@ -29,7 +28,6 @@ var ( _ Authenticator = (*Machine)(nil) _ Authenticator = (*Unit)(nil) - _ Authenticator = (*User)(nil) _ NotifyWatcherFactory = (*Machine)(nil) _ NotifyWatcherFactory = (*Unit)(nil) diff --git a/state/migration_import_tasks.go b/state/migration_import_tasks.go index a5d3b0522c2..298c2990712 100644 --- a/state/migration_import_tasks.go +++ b/state/migration_import_tasks.go @@ -15,7 +15,6 @@ import ( "github.com/juju/juju/core/crossmodel" "github.com/juju/juju/core/network/firewall" - "github.com/juju/juju/core/permission" "github.com/juju/juju/environs/config" ) @@ -183,12 +182,6 @@ func (i ImportApplicationOffer) addApplicationOfferOps(src ApplicationOfferInput Insert: args.applicationOfferDoc, }, } - for userName, access := range args.acl { - user := names.NewUserTag(userName) - h := createPermissionOp(applicationOfferKey( - args.applicationOfferDoc.OfferUUID), userGlobalKey(userAccessID(user)), permission.Access(access)) - ops = append(ops, h) - } return ops, nil } diff --git a/state/migration_import_tasks_test.go b/state/migration_import_tasks_test.go index 53be748051e..daae43438fe 100644 --- a/state/migration_import_tasks_test.go +++ b/state/migration_import_tasks_test.go @@ -9,12 +9,10 @@ import ( "github.com/juju/description/v8" "github.com/juju/errors" "github.com/juju/mgo/v3/txn" - "github.com/juju/names/v5" jc "github.com/juju/testing/checkers" "go.uber.org/mock/gomock" gc "gopkg.in/check.v1" - "github.com/juju/juju/core/permission" "github.com/juju/juju/environs/config" "github.com/juju/juju/internal/uuid" ) @@ -67,12 +65,7 @@ func (s *MigrationImportTasksSuite) TestImportApplicationOffers(c *gc.C) { runner.Add(runner.applicationOffersRefOp(refOp, 2)) entity.EXPECT().ACL().Return(map[string]string{"fred": "consume"}).Times(2) - permissionOp := createPermissionOp(applicationOfferKey( - offerUUID.String()), userGlobalKey(userAccessID(names.NewUserTag("fred"))), permission.ConsumeAccess) - permissionOp2 := createPermissionOp(applicationOfferKey( - offerUUID2.String()), userGlobalKey(userAccessID(names.NewUserTag("fred"))), permission.ConsumeAccess) - - runner.Add(runner.transaction([]applicationOfferDoc{offerDoc, secondOfferDoc}, []txn.Op{permissionOp, permissionOp2}, refOp)) + runner.Add(runner.transaction([]applicationOfferDoc{offerDoc, secondOfferDoc}, refOp)) err = runner.Run(ctrl) c.Assert(err, jc.ErrorIsNil) @@ -167,11 +160,11 @@ func (s *ImportApplicationOfferRunner) docID(offerName, docID string) func(ctrl } } -func (s *ImportApplicationOfferRunner) transaction(offerDocs []applicationOfferDoc, permissionOps []txn.Op, ops ...txn.Op) func(ctrl *gomock.Controller) { +func (s *ImportApplicationOfferRunner) transaction(offerDocs []applicationOfferDoc, ops ...txn.Op) func(ctrl *gomock.Controller) { return func(ctrl *gomock.Controller) { useOps := make([]txn.Op, 0) - for i, doc := range offerDocs { + for _, doc := range offerDocs { useOps = append(useOps, []txn.Op{ { C: applicationOffersC, @@ -179,7 +172,6 @@ func (s *ImportApplicationOfferRunner) transaction(offerDocs []applicationOfferD Assert: txn.DocMissing, Insert: doc, }, - permissionOps[i], }...) } useOps = append(useOps, ops...) diff --git a/state/migration_internal_test.go b/state/migration_internal_test.go index a62376a0137..3e51629ee16 100644 --- a/state/migration_internal_test.go +++ b/state/migration_internal_test.go @@ -21,7 +21,6 @@ func (s *MigrationSuite) TestKnownCollections(c *gc.C) { cloudimagemetadataC, constraintsC, modelsC, - permissionsC, settingsC, sequenceC, sshHostKeysC, @@ -85,11 +84,6 @@ func (s *MigrationSuite) TestKnownCollections(c *gc.C) { // We don't export the controller model at this stage. controllersC, controllerNodesC, - // Users aren't migrated. - usersC, - // Controller users contain extra data about users therefore - // are not migrated either. - controllerUsersC, // userenvnameC is just to provide a unique key constraint. usermodelnameC, // reference counts are implementation details that should be @@ -225,32 +219,6 @@ func (s *MigrationSuite) TestModelDocFields(c *gc.C) { s.AssertExportedFields(c, modelDoc{}, fields) } -func (s *MigrationSuite) TestUserAccessDocFields(c *gc.C) { - fields := set.NewStrings( - // ID is the same as UserName (but lowercased) - "ID", - // ObjectUUID shouldn't be exported, and is inherited - // from the model definition. - "ObjectUUID", - // Tracked fields: - "UserName", - "DisplayName", - "CreatedBy", - "DateCreated", - ) - s.AssertExportedFields(c, userAccessDoc{}, fields) -} - -func (s *MigrationSuite) TestPermissionDocFields(c *gc.C) { - fields := set.NewStrings( - "ID", - "ObjectGlobalKey", - "SubjectGlobalKey", - "Access", - ) - s.AssertExportedFields(c, permissionDoc{}, fields) -} - func (s *MigrationSuite) TestMachineDocFields(c *gc.C) { ignored := set.NewStrings( // DocID is the model + machine id diff --git a/state/model.go b/state/model.go index fafcf552388..ab781aa53bb 100644 --- a/state/model.go +++ b/state/model.go @@ -30,11 +30,6 @@ import ( // settings and constraints. const modelGlobalKey = "e" -// modelKey will create the key for a given model using the modelGlobalKey. -func modelKey(modelUUID string) string { - return fmt.Sprintf("%s#%s", modelGlobalKey, modelUUID) -} - // ModelType signals the type of a model - IAAS or CAAS type ModelType string diff --git a/state/state.go b/state/state.go index 0bc23e8d6d4..67480746c89 100644 --- a/state/state.go +++ b/state/state.go @@ -33,7 +33,6 @@ import ( "github.com/juju/juju/core/network" corenetwork "github.com/juju/juju/core/network" "github.com/juju/juju/core/objectstore" - "github.com/juju/juju/core/permission" "github.com/juju/juju/core/status" "github.com/juju/juju/core/upgrade" jujuversion "github.com/juju/juju/core/version" @@ -159,10 +158,6 @@ func (st *State) ControllerOwner() (names.UserTag, error) { return names.NewUserTag(owner), nil } -func ControllerAccess(st *State, tag names.Tag) (permission.UserAccess, error) { - return st.UserAccess(tag.(names.UserTag), st.controllerTag) -} - // setDyingModelToDead sets current dying model to dead. func (st *State) setDyingModelToDead() error { buildTxn := func(attempt int) ([]txn.Op, error) { @@ -241,12 +236,6 @@ func (st *State) RemoveExportingModelDocs() error { } func (st *State) removeAllModelDocs(modelAssertion bson.D) error { - // Remove permissions first, because we potentially - // remove parent documents in the following stage. - if err := st.removeAllModelPermissions(); err != nil { - return errors.Annotate(err, "removing permissions") - } - // TODO(secrets) - fix when ref counts are done. //if err := cleanupSecretBackendRefCountAfterModelMigrationDone(st); err != nil { // // We have to do this before secrets get removed. @@ -316,41 +305,6 @@ func (st *State) removeAllModelDocs(modelAssertion bson.D) error { return errors.Trace(st.db().RunTransaction(ops)) } -// removeAllModelPermissions removes all direct permissions documents for -// this model, and all permissions for offers hosted by this model. -func (st *State) removeAllModelPermissions() error { - var permOps []txn.Op - permPattern := bson.M{ - "_id": bson.M{"$regex": "^" + permissionID(modelKey(st.ModelUUID()), "")}, - } - ops, err := st.removeInCollectionOps(permissionsC, permPattern) - if err != nil { - return errors.Trace(err) - } - permOps = append(permOps, ops...) - - applicationOffersCollection, closer := st.db().GetCollection(applicationOffersC) - defer closer() - - var offerDocs []applicationOfferDoc - if err := applicationOffersCollection.Find(bson.D{}).All(&offerDocs); err != nil { - return errors.Annotate(err, "getting application offer documents") - } - - for _, offer := range offerDocs { - permPattern = bson.M{ - "_id": bson.M{"$regex": "^" + permissionID(applicationOfferKey(offer.OfferUUID), "")}, - } - ops, err = st.removeInCollectionOps(permissionsC, permPattern) - if err != nil { - return errors.Trace(err) - } - permOps = append(permOps, ops...) - } - err = st.db().RunTransaction(permOps) - return errors.Trace(err) -} - // removeAllInCollectionRaw removes all the documents from the given // named collection. func (st *State) removeAllInCollectionRaw(name string) error { @@ -915,11 +869,7 @@ func (st *State) tagToCollectionAndId(tag names.Tag) (string, interface{}, error coll = unitsC id = st.docID(id) case names.UserTag: - coll = usersC - if !tag.IsLocal() { - return "", nil, fmt.Errorf("%q is not a local user", tag.Id()) - } - id = tag.Name() + return "", nil, errors.NotImplementedf("users have been moved to domain") case names.RelationTag: coll = relationsC id = st.docID(id) diff --git a/state/state_test.go b/state/state_test.go index 04e5f45c4ad..ac89d2813db 100644 --- a/state/state_test.go +++ b/state/state_test.go @@ -38,7 +38,6 @@ import ( "github.com/juju/juju/core/network" coreos "github.com/juju/juju/core/os" "github.com/juju/juju/core/os/ostype" - "github.com/juju/juju/core/permission" "github.com/juju/juju/core/status" "github.com/juju/juju/core/upgrade" jujuversion "github.com/juju/juju/core/version" @@ -2948,16 +2947,6 @@ func (s *StateSuite) insertFakeModelDocs(c *gc.C, st *state.State) string { c.Assert(n, gc.Not(gc.Equals), 0) } - // Add a model user whose permissions should get removed - // when the model is. - _, err := s.Model.AddUser( - state.UserAccessSpec{ - User: names.NewUserTag("amelia@external"), - CreatedBy: s.Owner, - Access: permission.ReadAccess, - }) - c.Assert(err, jc.ErrorIsNil) - return state.UserModelNameIndex(s.Model.Owner().Id(), s.Model.Name()) } diff --git a/state/user.go b/state/user.go deleted file mode 100644 index 1ff1151f6e9..00000000000 --- a/state/user.go +++ /dev/null @@ -1,510 +0,0 @@ -// Copyright 2012-2014 Canonical Ltd. -// Licensed under the AGPLv3, see LICENCE file for details. - -// NOTE: the users that are being stored in the database here are only -// the local users, like "admin" or "bob". In the world -// where we have external user providers hooked up, there are no records -// in the database for users that are authenticated elsewhere. - -package state - -import ( - "fmt" - "strings" - "time" - - "github.com/juju/errors" - "github.com/juju/mgo/v3" - "github.com/juju/mgo/v3/bson" - "github.com/juju/mgo/v3/txn" - "github.com/juju/names/v5" - - "github.com/juju/juju/core/permission" - internalpassword "github.com/juju/juju/internal/password" -) - -const userGlobalKeyPrefix = "us" - -func userGlobalKey(userID string) string { - return fmt.Sprintf("%s#%s", userGlobalKeyPrefix, userID) -} - -// AddUser adds a user to the database. -func (st *State) AddUser(name, displayName, password, creator string) (*User, error) { - return st.addUser(name, displayName, password, creator, nil) -} - -// AddUserWithSecretKey adds the user with the specified name, and assigns it -// a randomly generated secret key. This secret key may be used for the user -// and controller to mutually authenticate one another, without relying -// on TLS certificates. -// -// The new user will not have a password. A password must be set, clearing the -// secret key in the process, before the user can login normally. -func (st *State) AddUserWithSecretKey(name, displayName, creator string) (*User, error) { - return st.addUser(name, displayName, "", creator, []byte("big-secret")) -} - -func (st *State) addUser(name, displayName, password, creator string, secretKey []byte) (*User, error) { - - if !names.IsValidUserName(name) { - return nil, errors.Errorf("invalid user name %q", name) - } - lowercaseName := strings.ToLower(name) - - foundUser := &User{st: st} - err := st.getUser(names.NewUserTag(name).Name(), &foundUser.doc) - // No error, the user is already there - if err == nil { - if foundUser.doc.Deleted { - // the user was deleted, we update it - return st.recreateExistingUser(foundUser, name, displayName, password, creator, secretKey) - } else { - return nil, errors.AlreadyExistsf("user %s", name) - } - } - - // There is an error different from not found - if err != nil && !errors.Is(err, errors.NotFound) { - return nil, errors.Trace(err) - } - - dateCreated := st.nowToTheSecond() - user := &User{ - st: st, - doc: userDoc{ - DocID: lowercaseName, - Name: name, - DisplayName: displayName, - SecretKey: secretKey, - CreatedBy: creator, - DateCreated: dateCreated, - Deleted: false, - RemovalLog: []userRemovedLogEntry{}, - }, - } - - if password != "" { - salt, err := internalpassword.RandomSalt() - if err != nil { - return nil, err - } - user.doc.PasswordHash = internalpassword.UserPasswordHash(password, salt) - user.doc.PasswordSalt = salt - } - - ops := []txn.Op{{ - C: usersC, - Id: lowercaseName, - Assert: txn.DocMissing, - Insert: &user.doc, - }} - controllerUserOps := createControllerUserOps(st.ControllerUUID(), - names.NewUserTag(name), - names.NewUserTag(creator), - displayName, - dateCreated, - defaultControllerPermission) - ops = append(ops, controllerUserOps...) - - err = st.db().RunTransaction(ops) - if err != nil { - if err == txn.ErrAborted { - err = errors.Errorf("username unavailable") - } - return nil, errors.Trace(err) - } - return user, nil -} - -func (st *State) recreateExistingUser(u *User, name, displayName, password, creator string, secretKey []byte) (*User, error) { - dateCreated := st.nowToTheSecond() - buildTxn := func(attempt int) ([]txn.Op, error) { - if attempt > 0 { - err := u.Refresh() - if err != nil { - return nil, errors.Trace(err) - } - if !u.IsDeleted() { - return nil, errors.AlreadyExistsf("user %s", name) - } - } - - updateUser := bson.D{{"$set", bson.D{ - {"deleted", false}, - {"name", name}, - {"displayname", displayName}, - {"createdby", creator}, - {"datecreated", dateCreated}, - {"secretkey", secretKey}, - }}} - - // update the password - if password != "" { - salt, err := internalpassword.RandomSalt() - if err != nil { - return nil, err - } - updateUser = append(updateUser, - bson.DocElem{"$set", bson.D{ - {"passwordhash", internalpassword.UserPasswordHash(password, salt)}, - {"passwordsalt", salt}, - }}, - ) - } - - var ops []txn.Op - - // remove previous controller permissions - if _, err := u.st.controllerUser(u.UserTag()); err == nil { - ops = append(ops, removeControllerUserOps(st.ControllerUUID(), u.UserTag())...) - } else if err != nil && !errors.Is(err, errors.NotFound) { - return nil, errors.Trace(err) - } - - // create default new ones - ops = append(ops, createControllerUserOps(st.ControllerUUID(), - u.UserTag(), - names.NewUserTag(creator), - displayName, - dateCreated, - defaultControllerPermission)...) - - // update user doc - ops = append(ops, txn.Op{ - C: usersC, - Id: strings.ToLower(u.Name()), - Assert: bson.M{ - "deleted": true, - }, - Update: updateUser, - }) - - return ops, nil - } - - if err := u.st.db().RunRaw(buildTxn); err != nil { - return nil, errors.Trace(err) - } - - // recreate the user object - return st.User(u.UserTag()) -} - -// RemoveUser marks the user as deleted. This obviates the ability of a user -// to function, but keeps the userDoc retaining provenance, i.e. auditing. -func (st *State) RemoveUser(tag names.UserTag) error { - lowercaseName := strings.ToLower(tag.Name()) - - u, err := st.User(tag) - if err != nil { - return errors.Trace(err) - } - if u.IsDeleted() { - return nil - } - - buildTxn := func(attempt int) ([]txn.Op, error) { - if attempt > 0 { - // If it is not our first attempt, refresh the user. - if err := u.Refresh(); err != nil { - return nil, errors.Trace(err) - } - if u.IsDeleted() { - return nil, nil - } - } - - // remove the user from the controller - ops := removeControllerUserOps(st.ControllerUUID(), tag) - - // new entry in the removal log - newRemovalLogEntry := userRemovedLogEntry{ - RemovedBy: u.doc.CreatedBy, - DateCreated: u.doc.DateCreated, - DateRemoved: st.nowToTheSecond(), - } - ops = append(ops, txn.Op{ - Id: lowercaseName, - C: usersC, - Assert: txn.DocExists, - Update: bson.M{ - "$set": bson.M{ - "deleted": true, - }, - "$push": bson.M{ - "removallog": bson.M{"$each": []userRemovedLogEntry{newRemovalLogEntry}}, - }, - }, - }) - return ops, nil - } - - // Use raw transactions to avoid model filtering - return st.db().RunRaw(buildTxn) -} - -func createInitialUserOps(controllerUUID string, user names.UserTag, password, salt string, dateCreated time.Time) []txn.Op { - lowercaseName := strings.ToLower(user.Name()) - doc := userDoc{ - DocID: lowercaseName, - Name: user.Name(), - DisplayName: user.Name(), - PasswordHash: internalpassword.UserPasswordHash(password, salt), - PasswordSalt: salt, - CreatedBy: user.Name(), - DateCreated: dateCreated, - } - ops := []txn.Op{{ - C: usersC, - Id: lowercaseName, - Assert: txn.DocMissing, - Insert: &doc, - }} - controllerUserOps := createControllerUserOps(controllerUUID, - names.NewUserTag(user.Name()), - names.NewUserTag(user.Name()), - user.Name(), - dateCreated, - // first user is controller admin. - permission.SuperuserAccess) - - ops = append(ops, controllerUserOps...) - return ops -} - -// getUser fetches information about the user with the -// given name into the provided userDoc. -func (st *State) getUser(name string, udoc *userDoc) error { - users, closer := st.db().GetCollection(usersC) - defer closer() - - name = strings.ToLower(name) - err := users.Find(bson.D{{"_id", name}}).One(udoc) - if err == mgo.ErrNotFound { - err = errors.NotFoundf("user %q", name) - } - // DateCreated is inserted as UTC, but read out as local time. So we - // convert it back to UTC here. - udoc.DateCreated = udoc.DateCreated.UTC() - return err -} - -// User returns the state User for the given name. -func (st *State) User(tag names.UserTag) (*User, error) { - if !tag.IsLocal() { - return nil, errors.NotFoundf("user %q", tag.Id()) - } - user := &User{st: st} - if err := st.getUser(tag.Name(), &user.doc); err != nil { - return nil, errors.Trace(err) - } - if user.doc.Deleted { - // This error is returned to the apiserver and from there to the api - // client. So we don't annotate with information regarding deletion. - // TODO(redir): We'll return a deletedUserError in the future so we can - // return more appropriate errors, e.g. username not available. - return nil, newDeletedUserError(user.Name()) - } - return user, nil -} - -// User represents a local user in the database. -type User struct { - st *State - doc userDoc -} - -type userDoc struct { - DocID string `bson:"_id"` - Name string `bson:"name"` - DisplayName string `bson:"displayname"` - Deactivated bool `bson:"deactivated,omitempty"` - Deleted bool `bson:"deleted,omitempty"` // Deleted users are marked deleted but not removed. - SecretKey []byte `bson:"secretkey,omitempty"` - PasswordHash string `bson:"passwordhash"` - PasswordSalt string `bson:"passwordsalt"` - CreatedBy string `bson:"createdby"` - DateCreated time.Time `bson:"datecreated"` - // RemovalLog keeps a track of removals for this user - RemovalLog []userRemovedLogEntry `bson:"removallog"` -} - -// userRemovedLog contains a log of entries added every time the user -// doc has been removed -type userRemovedLogEntry struct { - RemovedBy string `bson:"removedby"` - DateCreated time.Time `bson:"datecreated"` - DateRemoved time.Time `bson:"dateremoved"` -} - -// String returns "" where is the Name of the user. -func (u *User) String() string { - return u.UserTag().Id() -} - -// Name returns the User name. -func (u *User) Name() string { - return u.doc.Name -} - -// DisplayName returns the display name of the User. -func (u *User) DisplayName() string { - return u.doc.DisplayName -} - -// CreatedBy returns the name of the User that created this User. -func (u *User) CreatedBy() string { - return u.doc.CreatedBy -} - -// DateCreated returns when this User was created in UTC. -func (u *User) DateCreated() time.Time { - return u.doc.DateCreated.UTC() -} - -// Tag returns the Tag for the User. -func (u *User) Tag() names.Tag { - return u.UserTag() -} - -// UserTag returns the Tag for the User. -func (u *User) UserTag() names.UserTag { - name := u.doc.Name - return names.NewLocalUserTag(name) -} - -// SecretKey returns the user's secret key, if any. -func (u *User) SecretKey() []byte { - return u.doc.SecretKey -} - -// SetPassword sets the password associated with the User. -func (u *User) SetPassword(password string) error { - if err := u.ensureNotDeleted(); err != nil { - return errors.Annotate(err, "cannot set password") - } - salt, err := internalpassword.RandomSalt() - if err != nil { - return err - } - return u.SetPasswordHash(internalpassword.UserPasswordHash(password, salt), salt) -} - -// SetPasswordHash stores the hash and the salt of the -// password. If the User has a secret key set then it -// will be cleared. -func (u *User) SetPasswordHash(pwHash string, pwSalt string) error { - if err := u.ensureNotDeleted(); err != nil { - // If we do get a late set of the password this is fine b/c we have an - // explicit check before login. - return errors.Annotate(err, "cannot set password hash") - } - update := bson.D{{"$set", bson.D{ - {"passwordhash", pwHash}, - {"passwordsalt", pwSalt}, - }}} - if u.doc.SecretKey != nil { - update = append(update, - bson.DocElem{"$unset", bson.D{{"secretkey", ""}}}, - ) - } - lowercaseName := strings.ToLower(u.Name()) - ops := []txn.Op{{ - C: usersC, - Id: lowercaseName, - Assert: txn.DocExists, - Update: update, - }} - if err := u.st.db().RunTransaction(ops); err != nil { - return errors.Annotatef(err, "cannot set password of user %q", u.Name()) - } - u.doc.PasswordHash = pwHash - u.doc.PasswordSalt = pwSalt - u.doc.SecretKey = nil - return nil -} - -// PasswordValid returns whether the given password is valid for the User. The -// caller should call user.Refresh before calling this. -func (u *User) PasswordValid(password string) bool { - // If the User is deactivated or deleted, there is no point in carrying on. - // Since any authentication checks are done very soon after the user is - // read from the database, there is a very small timeframe where a user - // could be disabled after it has been read but prior to being checked, but - // in practice, this isn't a problem. - if u.IsDisabled() || u.IsDeleted() { - return false - } - if u.doc.PasswordSalt != "" { - return internalpassword.UserPasswordHash(password, u.doc.PasswordSalt) == u.doc.PasswordHash - } - return false -} - -// Refresh refreshes information about the User from the state. -func (u *User) Refresh() error { - var udoc userDoc - if err := u.st.getUser(u.Name(), &udoc); err != nil { - return err - } - u.doc = udoc - return nil -} - -// Disable deactivates the user. Disabled identities cannot log in. -func (u *User) Disable() error { - if err := u.ensureNotDeleted(); err != nil { - return errors.Annotate(err, "cannot disable") - } - owner, err := u.st.ControllerOwner() - if err != nil { - return errors.Trace(err) - } - if u.doc.Name == owner.Name() { - return errors.Unauthorizedf("cannot disable controller model owner") - } - return errors.Annotatef(u.setDeactivated(true), "cannot disable user %q", u.Name()) -} - -func (u *User) setDeactivated(value bool) error { - lowercaseName := strings.ToLower(u.Name()) - ops := []txn.Op{{ - C: usersC, - Id: lowercaseName, - Assert: txn.DocExists, - Update: bson.D{{"$set", bson.D{{"deactivated", value}}}}, - }} - if err := u.st.db().RunTransaction(ops); err != nil { - if err == txn.ErrAborted { - err = fmt.Errorf("user no longer exists") - } - return err - } - u.doc.Deactivated = value - return nil -} - -// IsDisabled returns whether the user is currently enabled. -func (u *User) IsDisabled() bool { - // Yes, this is a cached value, but in practice the user object is - // never held around for a long time. - return u.doc.Deactivated -} - -// IsDeleted returns whether the user is currently deleted. -func (u *User) IsDeleted() bool { - return u.doc.Deleted -} - -// ensureNotDeleted refreshes the user to ensure it wasn't deleted since we -// acquired it. -func (u *User) ensureNotDeleted() error { - if err := u.Refresh(); err != nil { - return errors.Trace(err) - } - if u.doc.Deleted { - return newDeletedUserError(u.Name()) - } - return nil -} diff --git a/state/useraccess.go b/state/useraccess.go deleted file mode 100644 index 9f00f6e38d8..00000000000 --- a/state/useraccess.go +++ /dev/null @@ -1,220 +0,0 @@ -// Copyright 2016 Canonical Ltd. -// Licensed under the AGPLv3, see LICENCE file for details. - -package state - -import ( - "fmt" - "strings" - "time" - - "github.com/juju/errors" - "github.com/juju/mgo/v3/txn" - "github.com/juju/names/v5" - - "github.com/juju/juju/core/permission" - "github.com/juju/juju/core/user" -) - -type userAccessDoc struct { - ID string `bson:"_id"` - ObjectUUID string `bson:"object-uuid"` - UserName string `bson:"user"` - DisplayName string `bson:"displayname"` - CreatedBy string `bson:"createdby"` - DateCreated time.Time `bson:"datecreated"` -} - -// UserAccessSpec defines the attributes that can be set when adding a new -// user access. -type UserAccessSpec struct { - User names.UserTag - CreatedBy names.UserTag - DisplayName string - Access permission.Access -} - -// userAccessTarget defines the target of a user access granting. -type userAccessTarget struct { - uuid string - globalKey string -} - -// AddUser adds a new user for the model to the database. -func (m *Model) AddUser(spec UserAccessSpec) (permission.UserAccess, error) { - if err := permission.ValidateModelAccess(spec.Access); err != nil { - return permission.UserAccess{}, errors.Annotate(err, "adding model user") - } - target := userAccessTarget{ - uuid: m.UUID(), - globalKey: modelGlobalKey, - } - return m.st.addUserAccess(spec, target) -} - -func (st *State) addUserAccess(spec UserAccessSpec, target userAccessTarget) (permission.UserAccess, error) { - // Ensure local user exists in state before adding them as a model user. - if spec.User.IsLocal() { - localUser, err := st.User(spec.User) - if err != nil { - return permission.UserAccess{}, errors.Annotate(err, fmt.Sprintf("user %q does not exist locally", spec.User.Name())) - } - if spec.DisplayName == "" { - spec.DisplayName = localUser.DisplayName() - } - } - - // Ensure local createdBy user exists. - if spec.CreatedBy.IsLocal() { - if _, err := st.User(spec.CreatedBy); err != nil { - return permission.UserAccess{}, errors.Annotatef(err, "createdBy user %q does not exist locally", spec.CreatedBy.Name()) - } - } - var ( - ops []txn.Op - err error - targetTag names.Tag - ) - switch target.globalKey { - case modelGlobalKey: - return permission.UserAccess{}, nil - case controllerGlobalKey: - ops = createControllerUserOps( - st.ControllerUUID(), - spec.User, - spec.CreatedBy, - spec.DisplayName, - st.nowToTheSecond(), - spec.Access) - targetTag = st.controllerTag - default: - return permission.UserAccess{}, errors.NotSupportedf("user access global key %q", target.globalKey) - } - err = st.db().RunTransactionFor(target.uuid, ops) - if err == txn.ErrAborted { - err = errors.AlreadyExistsf("user access %q", spec.User.Id()) - } - if err != nil { - return permission.UserAccess{}, errors.Trace(err) - } - return st.UserAccess(spec.User, targetTag) -} - -// userAccessID returns the document id of the user access. -func userAccessID(user names.UserTag) string { - username := user.Id() - return strings.ToLower(username) -} - -// NewControllerUserAccess returns a new permission.UserAccess for the given userDoc and -// current Controller. -func NewControllerUserAccess(st *State, userDoc userAccessDoc) (permission.UserAccess, error) { - perm, err := st.userPermission(controllerKey(st.ControllerUUID()), userGlobalKey(strings.ToLower(userDoc.UserName))) - if err != nil { - return permission.UserAccess{}, errors.Annotate(err, "obtaining controller permission") - } - return newUserAccess(perm, userDoc, names.NewControllerTag(userDoc.ObjectUUID)) -} - -// UserPermission returns the access permission for the passed subject and target. -func (st *State) UserPermission(subject names.UserTag, target names.Tag) (permission.Access, error) { - if err := st.userMayHaveAccess(subject); err != nil { - return "", errors.Trace(err) - } - - switch target.Kind() { - case names.ModelTagKind, names.ControllerTagKind: - access, err := st.UserAccess(subject, target) - if err != nil { - return "", errors.Trace(err) - } - return access.Access, nil - default: - return "", errors.NotValidf("%q as a target", target.Kind()) - } -} - -func newUserAccess(perm *userPermission, userDoc userAccessDoc, object names.Tag) (permission.UserAccess, error) { - userName, err := user.NewName(userDoc.UserName) - if err != nil { - return permission.UserAccess{}, err - } - return permission.UserAccess{ - UserID: userDoc.ID, - UserTag: names.NewUserTag(userDoc.UserName), - Object: object, - Access: perm.access(), - CreatedBy: names.NewUserTag(userDoc.CreatedBy), - DateCreated: userDoc.DateCreated.UTC(), - DisplayName: userDoc.DisplayName, - UserName: userName, - }, nil -} - -func (st *State) userMayHaveAccess(tag names.UserTag) error { - if !tag.IsLocal() { - // external users may have access - return nil - } - localUser, err := st.User(tag) - if err != nil { - return errors.Trace(err) - } - // Since deleted users will throw an error above, we need to check whether the user has been disabled here. - if localUser.IsDisabled() { - return errors.Errorf("user %q is disabled", tag.Id()) - } - return nil -} - -// UserAccess returns a new permission.UserAccess for the passed subject and target. -func (st *State) UserAccess(subject names.UserTag, target names.Tag) (permission.UserAccess, error) { - if err := st.userMayHaveAccess(subject); err != nil { - return permission.UserAccess{}, errors.Trace(err) - } - - var ( - userDoc userAccessDoc - err error - ) - switch target.Kind() { - case names.ModelTagKind: - case names.ControllerTagKind: - userDoc, err = st.controllerUser(subject) - if err == nil { - return NewControllerUserAccess(st, userDoc) - } - default: - return permission.UserAccess{}, errors.NotValidf("%q as a target", target.Kind()) - } - return permission.UserAccess{}, errors.Trace(err) -} - -// SetUserAccess sets level on to . -func (st *State) SetUserAccess(subject names.UserTag, target names.Tag, access permission.Access) (permission.UserAccess, error) { - err := access.Validate() - if err != nil { - return permission.UserAccess{}, errors.Trace(err) - } - switch target.Kind() { - case names.ModelTagKind: - case names.ControllerTagKind: - err = st.setControllerAccess(access, userGlobalKey(userAccessID(subject))) - default: - return permission.UserAccess{}, errors.NotValidf("%q as a target", target.Kind()) - } - if err != nil { - return permission.UserAccess{}, errors.Trace(err) - } - return st.UserAccess(subject, target) -} - -// RemoveUserAccess removes access for subject to the passed tag. -func (st *State) RemoveUserAccess(subject names.UserTag, target names.Tag) error { - switch target.Kind() { - case names.ModelTagKind: - case names.ControllerTagKind: - return errors.Trace(st.removeControllerUser(subject)) - } - return errors.NotValidf("%q as a target", target.Kind()) -} diff --git a/state/userpermission.go b/state/userpermission.go deleted file mode 100644 index 18fd4c37779..00000000000 --- a/state/userpermission.go +++ /dev/null @@ -1,114 +0,0 @@ -// Copyright 2016 Canonical Ltd. -// Licensed under the AGPLv3, see LICENCE file for details. - -package state - -import ( - "fmt" - - "github.com/juju/errors" - "github.com/juju/mgo/v3" - "github.com/juju/mgo/v3/bson" - "github.com/juju/mgo/v3/txn" - - "github.com/juju/juju/core/permission" -) - -// permission represents the permission a user has -// on a given scope. -type userPermission struct { - doc permissionDoc -} - -type permissionDoc struct { - ID string `bson:"_id"` - // ObjectGlobalKey holds the id for the object of the permission. - // ie. a model globalKey or a controller globalKey. - ObjectGlobalKey string `bson:"object-global-key"` - // SubjectGlobalKey holds the id for the user/group that is given permission. - SubjectGlobalKey string `bson:"subject-global-key"` - // Access is the permission level. - Access string `bson:"access"` -} - -func stringToAccess(a string) permission.Access { - return permission.Access(a) -} - -func accessToString(a permission.Access) string { - return string(a) -} - -// userPermission returns a Permission for the given Subject and User. -func (st *State) userPermission(objectGlobalKey, subjectGlobalKey string) (*userPermission, error) { - result := &userPermission{} - permissions, closer := st.db().GetCollection(permissionsC) - defer closer() - - id := permissionID(objectGlobalKey, subjectGlobalKey) - err := permissions.FindId(id).One(&result.doc) - if err == mgo.ErrNotFound { - return nil, errors.NotFoundf("user permission for %q on %q", subjectGlobalKey, objectGlobalKey) - } - return result, nil -} - -func (p *userPermission) access() permission.Access { - return stringToAccess(p.doc.Access) -} - -func permissionID(objectGlobalKey, subjectGlobalKey string) string { - // example: e#deadbeef#us#jim - // e: object global key - // deadbeef: object uuid - // us#jim: subject global key - // the first element (e in this example) is the global key for the object - // (model in this example) - // the second, is the : prefixed model uuid - // the third, in this example is a user with name jim, hence the globalKey - // ( a user global key) being us#jim. - // another example, now with controller and user maria: - // c#:deadbeef#us#maria - // c: object global key, in this case controller. - // :deadbeef controller uuid - // us#maria: its the user global key for maria. - // if this where for model, it would be e#us#maria - return fmt.Sprintf("%s#%s", objectGlobalKey, subjectGlobalKey) -} - -func updatePermissionOp(objectGlobalKey, subjectGlobalKey string, access permission.Access) txn.Op { - return txn.Op{ - C: permissionsC, - Id: permissionID(objectGlobalKey, subjectGlobalKey), - Assert: txn.DocExists, - Update: bson.D{{"$set", bson.D{{"access", accessToString(access)}}}}, - } -} - -func removePermissionOp(objectGlobalKey, subjectGlobalKey string) txn.Op { - return txn.Op{ - C: permissionsC, - Id: permissionID(objectGlobalKey, subjectGlobalKey), - Assert: txn.DocExists, - Remove: true, - } - -} -func createPermissionOp(objectGlobalKey, subjectGlobalKey string, access permission.Access) txn.Op { - doc := makePermissionDoc(objectGlobalKey, subjectGlobalKey, access) - return txn.Op{ - C: permissionsC, - Id: permissionID(objectGlobalKey, subjectGlobalKey), - Assert: txn.DocMissing, - Insert: doc, - } -} - -func makePermissionDoc(objectGlobalKey, subjectGlobalKey string, access permission.Access) *permissionDoc { - return &permissionDoc{ - ID: permissionID(objectGlobalKey, subjectGlobalKey), - SubjectGlobalKey: subjectGlobalKey, - ObjectGlobalKey: objectGlobalKey, - Access: accessToString(access), - } -}