Example #1
0
// 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
}
Example #2
0
// maybeUseGroupPermission returns a permission.UserAccess updated
// with the group permissions that apply to it if higher than
// current.
// If the passed UserAccess is empty (controller user lacks permissions)
// but the group is not, a stand-in will be created to hold the group
// permissions.
func maybeUseGroupPermission(
	userGetter userAccessFunc,
	externalUser permission.UserAccess,
	controllerTag names.ControllerTag,
	userTag names.UserTag,
) (permission.UserAccess, error) {

	everyoneTag := names.NewUserTag(EveryoneTagName)
	everyone, err := userGetter(everyoneTag, controllerTag)
	if errors.IsNotFound(err) {
		return externalUser, nil
	}
	if err != nil {
		return permission.UserAccess{}, errors.Trace(err)
	}
	if permission.IsEmptyUserAccess(externalUser) &&
		!permission.IsEmptyUserAccess(everyone) {
		externalUser = newControllerUserFromGroup(everyone, userTag)
	}

	if everyone.Access.EqualOrGreaterControllerAccessThan(externalUser.Access) {
		externalUser.Access = everyone.Access
	}
	return externalUser, nil
}
Example #3
0
// HasPermission returns true if the specified user has the specified
// permission on target.
func HasPermission(userGetter userAccessFunc, utag names.Tag,
	requestedPermission permission.Access, target names.Tag) (bool, error) {

	validForKind := false
	switch requestedPermission {
	case permission.LoginAccess, permission.AddModelAccess, permission.SuperuserAccess:
		validForKind = target.Kind() == names.ControllerTagKind
	case permission.ReadAccess, permission.WriteAccess, permission.AdminAccess:
		validForKind = target.Kind() == names.ModelTagKind
	}

	if !validForKind {
		return false, nil
	}

	userTag, ok := utag.(names.UserTag)
	if !ok {
		// lets not reveal more than is strictly necessary
		return false, nil
	}

	user, err := userGetter(userTag, target)
	if err != nil && !errors.IsNotFound(err) {
		return false, errors.Annotatef(err, "while obtaining %s user", target.Kind())
	}
	// there is a special case for external users, a group called everyone@external
	if target.Kind() == names.ControllerTagKind && !userTag.IsLocal() {
		controllerTag, ok := target.(names.ControllerTag)
		if !ok {
			return false, errors.NotValidf("controller tag")
		}

		// 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.
		user, err = maybeUseGroupPermission(userGetter, user, controllerTag, userTag)
		if err != nil {
			return false, errors.Trace(err)
		}
		if permission.IsEmptyUserAccess(user) {
			return false, nil
		}
	}
	// returning this kind of information would be too much information to reveal too.
	if errors.IsNotFound(err) {
		return false, nil
	}
	modelPermission := user.Access.EqualOrGreaterModelAccessThan(requestedPermission) && target.Kind() == names.ModelTagKind
	controllerPermission := user.Access.EqualOrGreaterControllerAccessThan(requestedPermission) && target.Kind() == names.ControllerTagKind
	if !controllerPermission && !modelPermission {
		return false, nil
	}
	return true, nil
}
Example #4
0
File: admin.go Project: kat-co/juju
// Tag implements state.Entity.Tag.
func (u *modelUserEntity) Tag() names.Tag {
	if u.user != nil {
		return u.user.UserTag()
	}
	if !permission.IsEmptyUserAccess(u.modelUser) {
		return u.modelUser.UserTag
	}
	return u.controllerUser.UserTag

}
Example #5
0
File: admin.go Project: kat-co/juju
// LastLogin implements loginEntity.LastLogin.
func (u *modelUserEntity) LastLogin() (time.Time, error) {
	// The last connection for the model takes precedence over
	// the local user last login time.
	var err error
	var t time.Time
	if !permission.IsEmptyUserAccess(u.modelUser) {
		t, err = u.st.LastModelConnection(u.modelUser.UserTag)
	} else {
		err = state.NeverConnectedError("controller user")
	}
	if state.IsNeverConnectedError(err) || permission.IsEmptyUserAccess(u.modelUser) {
		if u.user != nil {
			// There's a global user, so use that login time instead.
			return u.user.LastLogin()
		}
		// Since we're implementing LastLogin, we need
		// to implement LastLogin error semantics too.
		err = state.NeverLoggedInError(err.Error())
	}
	return t, errors.Trace(err)
}
Example #6
0
File: admin.go Project: kat-co/juju
// UpdateLastLogin implements loginEntity.UpdateLastLogin.
func (u *modelUserEntity) UpdateLastLogin() error {
	var err error

	if !permission.IsEmptyUserAccess(u.modelUser) {
		if u.modelUser.Object.Kind() != names.ModelTagKind {
			return errors.NotValidf("%s as model user", u.modelUser.Object.Kind())
		}

		err = u.st.UpdateLastModelConnection(u.modelUser.UserTag)
	}

	if u.user != nil {
		err1 := u.user.UpdateLastLogin()
		if err == nil {
			return err1
		}
	}
	if err != nil {
		return errors.Trace(err)
	}
	return nil
}
Example #7
0
File: admin.go Project: kat-co/juju
// 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 = &params.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
}