func (s *MigrationImportSuite) AssertUserEqual(c *gc.C, newUser, oldUser *state.ModelUser) { c.Assert(newUser.UserName(), gc.Equals, oldUser.UserName()) c.Assert(newUser.DisplayName(), gc.Equals, oldUser.DisplayName()) c.Assert(newUser.CreatedBy(), gc.Equals, oldUser.CreatedBy()) c.Assert(newUser.DateCreated(), gc.Equals, oldUser.DateCreated()) c.Assert(newUser.ReadOnly(), gc.Equals, oldUser.ReadOnly()) connTime, err := oldUser.LastConnection() if state.IsNeverConnectedError(err) { _, err := newUser.LastConnection() // The new user should also return an error for last connection. c.Assert(err, jc.Satisfies, state.IsNeverConnectedError) } else { c.Assert(err, jc.ErrorIsNil) newTime, err := newUser.LastConnection() c.Assert(err, jc.ErrorIsNil) c.Assert(newTime, gc.Equals, connTime) } }
func (a *admin) doLogin(req params.LoginRequest, loginVersion int) (params.LoginResultV1, error) { var fail params.LoginResultV1 a.mu.Lock() defer a.mu.Unlock() if a.loggedIn { // This can only happen if Login is called concurrently. return fail, errAlreadyLoggedIn } // authedApi is the API method finder we'll use after getting logged in. var authedApi rpc.MethodFinder = newApiRoot(a.root.state, a.root.resources, a.root) // Use the login validation function, if one was specified. if a.srv.validator != nil { err := a.srv.validator(req) switch err { case params.UpgradeInProgressError: authedApi = newUpgradingRoot(authedApi) case AboutToRestoreError: authedApi = newAboutToRestoreRoot(authedApi) case RestoreInProgressError: authedApi = newRestoreInProgressRoot(authedApi) case nil: // in this case no need to wrap authed api so we do nothing default: return fail, errors.Trace(err) } } var agentPingerNeeded = true isUser := true kind := names.UserTagKind if req.AuthTag != "" { var err error kind, err = names.TagKind(req.AuthTag) if err != nil || kind != names.UserTagKind { isUser = false // Users are not rate limited, all other entities are. if !a.srv.limiter.Acquire() { logger.Debugf("rate limiting for agent %s", req.AuthTag) return fail, common.ErrTryAgain } defer a.srv.limiter.Release() } } serverOnlyLogin := a.root.modelUUID == "" entity, lastConnection, err := doCheckCreds(a.root.state, req, !serverOnlyLogin, a.srv.authCtxt) if err != nil { if err, ok := errors.Cause(err).(*common.DischargeRequiredError); ok { loginResult := params.LoginResultV1{ DischargeRequired: err.Macaroon, DischargeRequiredReason: err.Error(), } logger.Infof("login failed with discharge-required error: %v", err) return loginResult, nil } if a.maintenanceInProgress() { // An upgrade, restore or similar operation is in // progress. It is possible for logins to fail until this // is complete due to incomplete or updating data. Mask // transitory and potentially confusing errors from failed // logins with a more helpful one. return fail, MaintenanceNoLoginError } // Here we have a special case. The machine agents that manage // models in the controller model need to be able to // open API connections to other models. In those cases, we // need to look in the controller database to check the creds // against the machine if and only if the entity tag is a machine tag, // and the machine exists in the controller model, and the // machine has the manage state job. If all those parts are valid, we // can then check the credentials against the controller model // machine. if kind != names.MachineTagKind { return fail, errors.Trace(err) } entity, err = a.checkCredsOfControllerMachine(req) if err != nil { return fail, errors.Trace(err) } // If we are here, then the entity will refer to a controller // machine in the controller model, and we don't need a pinger // for it as we already have one running in the machine agent api // worker for the controller model. agentPingerNeeded = false } a.root.entity = entity if a.reqNotifier != nil { a.reqNotifier.login(entity.Tag().String()) } // We have authenticated the user; enable the appropriate API // to serve to them. a.loggedIn = true if agentPingerNeeded { if err := startPingerIfAgent(a.root, entity); err != nil { return fail, errors.Trace(err) } } var maybeUserInfo *params.AuthUserInfo var envUser *state.ModelUser // Send back user info if user if isUser && !serverOnlyLogin { maybeUserInfo = ¶ms.AuthUserInfo{ Identity: entity.Tag().String(), LastConnection: lastConnection, } envUser, err = a.root.state.ModelUser(entity.Tag().(names.UserTag)) if err != nil { return fail, errors.Annotatef(err, "missing ModelUser for logged in user %s", entity.Tag()) } if envUser.ReadOnly() { logger.Debugf("model user %s is READ ONLY", entity.Tag()) } } // Fetch the API server addresses from state. hostPorts, err := a.root.state.APIHostPorts() if err != nil { return fail, errors.Trace(err) } logger.Debugf("hostPorts: %v", hostPorts) environ, err := a.root.state.Model() if err != nil { return fail, errors.Trace(err) } loginResult := params.LoginResultV1{ Servers: params.FromNetworkHostsPorts(hostPorts), ModelTag: environ.Tag().String(), ControllerTag: environ.ControllerTag().String(), Facades: DescribeFacades(), UserInfo: maybeUserInfo, ServerVersion: jujuversion.Current.String(), } // For sufficiently modern login versions, stop serving the // controller model at the root of the API. if serverOnlyLogin { authedApi = newRestrictedRoot(authedApi) // Remove the ModelTag from the response as there is no // model here. loginResult.ModelTag = "" // Strip out the facades that are not supported from the result. var facades []params.FacadeVersions for _, facade := range loginResult.Facades { if restrictedRootNames.Contains(facade.Name) { facades = append(facades, facade) } } loginResult.Facades = facades } if envUser != nil { authedApi = newClientAuthRoot(authedApi, envUser) } a.root.rpcConn.ServeFinder(authedApi, serverError) return loginResult, nil }