// UserAccess returns the access the user has on the model state // and the host controller. func UserAccess(st *state.State, utag names.UserTag) (modelUser, controllerUser permission.UserAccess, err error) { var none permission.UserAccess modelUser, err = st.UserAccess(utag, st.ModelTag()) if err != nil && !errors.IsNotFound(err) { return none, none, errors.Trace(err) } controllerUser, err = state.ControllerAccess(st, utag) if err != nil && !errors.IsNotFound(err) { return none, none, errors.Trace(err) } // TODO(perrito666) remove the following section about everyone group // when groups are implemented, this accounts only for the lack of a local // ControllerUser when logging in from an external user that has not been granted // permissions on the controller but there are permissions for the special // everyone group. if !utag.IsLocal() { controllerUser, err = maybeUseGroupPermission(st.UserAccess, controllerUser, st.ControllerTag(), utag) if err != nil { return none, none, errors.Annotatef(err, "obtaining ControllerUser for everyone group") } } if permission.IsEmptyUserAccess(modelUser) && permission.IsEmptyUserAccess(controllerUser) { return none, none, errors.NotFoundf("model or controller user") } return modelUser, controllerUser, nil }
// login is the internal version of the Login API call. func (a *admin) login(req params.LoginRequest, loginVersion int) (params.LoginResult, error) { var fail params.LoginResult a.mu.Lock() defer a.mu.Unlock() if a.loggedIn { // This can only happen if Login is called concurrently. return fail, errAlreadyLoggedIn } // apiRoot is the API root exposed to the client after authentication. var apiRoot rpc.Root = 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: apiRoot = restrictRoot(apiRoot, upgradeMethodsOnly) case AboutToRestoreError: apiRoot = restrictRoot(apiRoot, aboutToRestoreMethodsOnly) case RestoreInProgressError: apiRoot = restrictAll(apiRoot, restoreInProgressError) case nil: // in this case no need to wrap authed api so we do nothing default: return fail, errors.Trace(err) } } 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() } } controllerOnlyLogin := a.root.modelUUID == "" controllerMachineLogin := false entity, lastConnection, err := a.checkCreds(req, isUser) if err != nil { if err, ok := errors.Cause(err).(*common.DischargeRequiredError); ok { loginResult := params.LoginResult{ 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) } if errors.Cause(err) != common.ErrBadCreds { return fail, err } entity, err = a.checkControllerMachineCreds(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. controllerMachineLogin = true } a.root.entity = entity a.apiObserver.Login(entity.Tag(), a.root.state.ModelTag(), controllerMachineLogin, req.UserData) // We have authenticated the user; enable the appropriate API // to serve to them. a.loggedIn = true if !controllerMachineLogin { if err := startPingerIfAgent(a.srv.clock, a.root, entity); err != nil { return fail, errors.Trace(err) } } var maybeUserInfo *params.AuthUserInfo var modelUser permission.UserAccess var everyoneGroupUser permission.UserAccess // Send back user info if user if isUser { maybeUserInfo = ¶ms.AuthUserInfo{ Identity: entity.Tag().String(), LastConnection: lastConnection, } userTag := entity.Tag().(names.UserTag) // TODO(perrito666) remove the following section about everyone group // when groups are implemented, this accounts only for the lack of a local // ControllerUser when logging in from an external user that has not been granted // permissions on the controller but there are permissions for the special // everyone group. if !userTag.IsLocal() { everyoneTag := names.NewUserTag(common.EveryoneTagName) everyoneGroupUser, err = state.ControllerAccess(a.root.state, everyoneTag) if err != nil && !errors.IsNotFound(err) { return fail, errors.Annotatef(err, "obtaining ControllerUser for everyone group") } } modelUser, err = a.root.state.UserAccess(userTag, a.root.state.ModelTag()) if err != nil && !errors.IsNotFound(err) { return fail, errors.Annotatef(err, "obtaining ModelUser for logged in user %s", entity.Tag()) } controllerUser, err := state.ControllerAccess(a.root.state, entity.Tag()) if err != nil && !errors.IsNotFound(err) { return fail, errors.Annotatef(err, "obtaining ControllerUser for logged in user %s", entity.Tag()) } if permission.IsEmptyUserAccess(modelUser) && permission.IsEmptyUserAccess(controllerUser) && permission.IsEmptyUserAccess(everyoneGroupUser) { return fail, errors.NotFoundf("model or controller access for logged in user %q", userTag.Canonical()) } maybeUserInfo.ControllerAccess = string(controllerUser.Access) maybeUserInfo.ModelAccess = string(modelUser.Access) logger.Tracef("controller user %s has %v", entity.Tag(), controllerUser.Access) logger.Tracef("model user %s has %s", entity.Tag(), modelUser.Access) } // 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) model, err := a.root.state.Model() if err != nil { return fail, errors.Trace(err) } if isUser && model.MigrationMode() == state.MigrationModeImporting { apiRoot = restrictAll(apiRoot, errors.New("migration in progress, model is importing")) } loginResult := params.LoginResult{ Servers: params.FromNetworkHostsPorts(hostPorts), ControllerTag: model.ControllerTag().String(), UserInfo: maybeUserInfo, ServerVersion: jujuversion.Current.String(), } if controllerOnlyLogin { loginResult.Facades = filterFacades(isControllerFacade) apiRoot = restrictRoot(apiRoot, controllerFacadesOnly) } else { loginResult.ModelTag = model.Tag().String() loginResult.Facades = filterFacades(isModelFacade) apiRoot = restrictRoot(apiRoot, modelFacadesOnly) } a.root.rpcConn.ServeRoot(apiRoot, serverError) return loginResult, nil }