Files
ziti/controller/model/identity_manager.go
T

888 lines
26 KiB
Go

/*
Copyright NetFoundry Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
https://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package model
import (
"encoding/json"
"errors"
"fmt"
"github.com/michaelquigley/pfxlog"
"github.com/openziti/foundation/v2/errorz"
"github.com/openziti/metrics"
"github.com/openziti/sdk-golang/ziti"
"github.com/openziti/storage/boltz"
"github.com/openziti/ziti/common/eid"
"github.com/openziti/ziti/common/pb/cmd_pb"
"github.com/openziti/ziti/common/pb/edge_cmd_pb"
"github.com/openziti/ziti/controller/change"
"github.com/openziti/ziti/controller/command"
"github.com/openziti/ziti/controller/db"
"github.com/openziti/ziti/controller/fields"
"github.com/openziti/ziti/controller/models"
"github.com/openziti/ziti/controller/network"
cmap "github.com/orcaman/concurrent-map/v2"
"go.etcd.io/bbolt"
"google.golang.org/protobuf/proto"
"sync"
"time"
)
const (
IdentityActiveIntervalSeconds = 60
minDefaultAdminPasswordLength = 5
maxDefaultAdminPasswordLength = 100
minDefaultAdminUsernameLength = 4
maxDefaultAdminUsernameLength = 100
minDefaultAdminNameLength = 4
maxDefaultAdminNameLength = 100
)
type IdentityManager struct {
baseEntityManager[*Identity, *db.Identity]
updateSdkInfoTimer metrics.Timer
identityStatusMap *identityStatusMap
}
func NewIdentityManager(env Env) *IdentityManager {
manager := &IdentityManager{
baseEntityManager: newBaseEntityManager[*Identity, *db.Identity](env, env.GetStores().Identity),
updateSdkInfoTimer: env.GetMetricsRegistry().Timer("identity.update-sdk-info"),
identityStatusMap: newIdentityStatusMap(IdentityActiveIntervalSeconds * time.Second),
}
manager.impl = manager
network.RegisterManagerDecoder[*Identity](env.GetHostController().GetNetwork().GetManagers(), manager)
RegisterCommand(env, &CreateIdentityWithEnrollmentsCmd{}, &edge_cmd_pb.CreateIdentityWithEnrollmentsCmd{})
RegisterCommand(env, &UpdateServiceConfigsCmd{}, &edge_cmd_pb.UpdateServiceConfigsCmd{})
return manager
}
func (self *IdentityManager) newModelEntity() *Identity {
return &Identity{}
}
func (self *IdentityManager) Create(entity *Identity, ctx *change.Context) error {
return network.DispatchCreate[*Identity](self, entity, ctx)
}
func (self *IdentityManager) ApplyCreate(cmd *command.CreateEntityCommand[*Identity], ctx boltz.MutateContext) error {
_, err := self.createEntity(cmd.Entity, ctx)
return err
}
func (self *IdentityManager) CreateWithEnrollments(identityModel *Identity, enrollmentsModels []*Enrollment, ctx *change.Context) error {
if identityModel.Id == "" {
identityModel.Id = eid.New()
}
for _, enrollment := range enrollmentsModels {
if enrollment.Id == "" {
enrollment.Id = eid.New()
}
enrollment.IdentityId = &identityModel.Id
}
cmd := &CreateIdentityWithEnrollmentsCmd{
manager: self,
identity: identityModel,
enrollments: enrollmentsModels,
ctx: ctx,
}
return self.Dispatch(cmd)
}
func (self *IdentityManager) ApplyCreateWithEnrollments(cmd *CreateIdentityWithEnrollmentsCmd, ctx boltz.MutateContext) error {
identityModel := cmd.identity
enrollmentsModels := cmd.enrollments
return self.GetDb().Update(ctx, func(ctx boltz.MutateContext) error {
boltEntity, err := identityModel.toBoltEntityForCreate(ctx.Tx(), self.env)
if err != nil {
return err
}
if err = self.GetStore().Create(ctx, boltEntity); err != nil {
pfxlog.Logger().WithError(err).Errorf("could not create %v in bolt storage", self.GetStore().GetSingularEntityType())
return err
}
for _, enrollment := range enrollmentsModels {
enrollment.IdentityId = &identityModel.Id
if err = enrollment.FillJwtInfo(self.env, identityModel.Id); err != nil {
return err
}
if _, err = self.env.GetManagers().Enrollment.createEntityInTx(ctx, enrollment); err != nil {
return err
}
}
return nil
})
}
func (self *IdentityManager) Update(entity *Identity, checker fields.UpdatedFields, ctx *change.Context) error {
return network.DispatchUpdate[*Identity](self, entity, checker, ctx)
}
func (self *IdentityManager) ApplyUpdate(cmd *command.UpdateEntityCommand[*Identity], ctx boltz.MutateContext) error {
var checker boltz.FieldChecker = self
if cmd.UpdatedFields != nil {
checker = &AndFieldChecker{first: self, second: cmd.UpdatedFields}
}
return self.updateEntity(cmd.Entity, checker, ctx)
}
func (self *IdentityManager) IsUpdated(field string) bool {
return field != db.FieldIdentityAuthenticators && field != db.FieldIdentityEnrollments && field != db.FieldIdentityIsDefaultAdmin
}
func (self *IdentityManager) ReadByName(name string) (*Identity, error) {
entity := &Identity{}
nameIndex := self.env.GetStores().Identity.GetNameIndex()
if err := self.readEntityWithIndex("name", []byte(name), nameIndex, entity); err != nil {
return nil, err
}
return entity, nil
}
func (self *IdentityManager) ReadDefaultAdmin() (*Identity, error) {
return self.ReadOneByQuery("isDefaultAdmin = true")
}
func (self *IdentityManager) ReadOneByQuery(query string) (*Identity, error) {
result, err := self.readEntityByQuery(query)
if err != nil {
return nil, err
}
if result == nil {
return nil, nil
}
return result.(*Identity), nil
}
func (self *IdentityManager) InitializeDefaultAdmin(username, password, name string) error {
identity, err := self.ReadDefaultAdmin()
if err != nil && !boltz.IsErrNotFoundErr(err) {
return err
}
if identity != nil {
return errors.New("already initialized: Ziti Edge default admin already defined")
}
if len(username) < minDefaultAdminUsernameLength {
return errorz.NewFieldError(fmt.Sprintf("username must be at least %v characters", minDefaultAdminUsernameLength), "username", username)
}
if len(password) < minDefaultAdminPasswordLength {
return errorz.NewFieldError(fmt.Sprintf("password must be at least %v characters", minDefaultAdminPasswordLength), "password", "******")
}
if len(name) < minDefaultAdminNameLength {
return errorz.NewFieldError(fmt.Sprintf("name must be at least %v characters", minDefaultAdminNameLength), "name", name)
}
if len(username) > maxDefaultAdminUsernameLength {
return errorz.NewFieldError(fmt.Sprintf("username must be at most %v characters", maxDefaultAdminUsernameLength), "username", username)
}
if len(password) > maxDefaultAdminPasswordLength {
return errorz.NewFieldError(fmt.Sprintf("password must be at most %v characters", maxDefaultAdminPasswordLength), "password", "******")
}
if len(name) > maxDefaultAdminNameLength {
return errorz.NewFieldError(fmt.Sprintf("name must be at most %v characters", maxDefaultAdminNameLength), "name", name)
}
identityType, err := self.env.GetManagers().IdentityType.ReadByName(db.DefaultIdentityType)
if err != nil {
return err
}
identityId := eid.New()
authenticatorId := eid.New()
defaultAdmin := &Identity{
BaseEntity: models.BaseEntity{
Id: identityId,
},
Name: name,
IdentityTypeId: identityType.Id,
IsDefaultAdmin: true,
IsAdmin: true,
}
authenticator := &Authenticator{
BaseEntity: models.BaseEntity{
Id: authenticatorId,
},
Method: db.MethodAuthenticatorUpdb,
IdentityId: identityId,
SubType: &AuthenticatorUpdb{
Username: username,
Password: password,
},
}
ctx := change.New().SetSourceType("cli.init").SetChangeAuthorType(change.AuthorTypeController)
if err = self.Create(defaultAdmin, ctx); err != nil {
return err
}
if err = self.env.GetManagers().Authenticator.Create(authenticator, ctx); err != nil {
return err
}
return nil
}
func (self *IdentityManager) CollectAuthenticators(id string, collector func(entity *Authenticator) error) error {
return self.GetDb().View(func(tx *bbolt.Tx) error {
_, err := self.readInTx(tx, id)
if err != nil {
return err
}
authenticatorIds := self.GetStore().GetRelatedEntitiesIdList(tx, id, db.FieldIdentityAuthenticators)
for _, authenticatorId := range authenticatorIds {
authenticator := &Authenticator{}
err := self.env.GetManagers().Authenticator.readEntityInTx(tx, authenticatorId, authenticator)
if err != nil {
return err
}
if err = collector(authenticator); err != nil {
return err
}
}
return nil
})
}
func (self *IdentityManager) visitAuthenticators(tx *bbolt.Tx, id string, visitor func(entity *Authenticator) bool) error {
_, err := self.readInTx(tx, id)
if err != nil {
return err
}
authenticatorIds := self.GetStore().GetRelatedEntitiesIdList(tx, id, db.FieldIdentityAuthenticators)
for _, authenticatorId := range authenticatorIds {
authenticator := &Authenticator{}
if err := self.env.GetManagers().Authenticator.readEntityInTx(tx, authenticatorId, authenticator); err != nil {
return err
}
if visitor(authenticator) {
return nil
}
}
return nil
}
func (self *IdentityManager) CollectEnrollments(id string, collector func(entity *Enrollment) error) error {
return self.GetDb().View(func(tx *bbolt.Tx) error {
return self.collectEnrollmentsInTx(tx, id, collector)
})
}
func (self *IdentityManager) collectEnrollmentsInTx(tx *bbolt.Tx, id string, collector func(entity *Enrollment) error) error {
_, err := self.readInTx(tx, id)
if err != nil {
return err
}
associationIds := self.GetStore().GetRelatedEntitiesIdList(tx, id, db.FieldIdentityEnrollments)
for _, enrollmentId := range associationIds {
enrollment, err := self.env.GetManagers().Enrollment.readInTx(tx, enrollmentId)
if err != nil {
return err
}
err = collector(enrollment)
if err != nil {
return err
}
}
return nil
}
func (self *IdentityManager) CreateWithAuthenticator(identity *Identity, authenticator *Authenticator, ctx *change.Context) (string, string, error) {
if identity.Id == "" {
identity.Id = eid.New()
}
if authenticator.Id == "" {
authenticator.Id = eid.New()
}
if authenticator.IdentityId == "" {
authenticator.IdentityId = identity.Id
}
identityType, err := self.env.GetManagers().IdentityType.ReadByIdOrName(identity.IdentityTypeId)
if err != nil && !boltz.IsErrNotFoundErr(err) {
return "", "", err
}
if identityType == nil {
apiErr := errorz.NewNotFound()
apiErr.Cause = errorz.NewFieldError("typeId not found", "typeId", identity.IdentityTypeId)
apiErr.AppendCause = true
return "", "", apiErr
}
err = self.env.GetDbProvider().GetDb().Update(ctx.NewMutateContext(), func(ctx boltz.MutateContext) error {
boltIdentity, err := identity.toBoltEntityForCreate(ctx.Tx(), self.env)
if err != nil {
return err
}
if err = self.env.GetStores().Identity.Create(ctx, boltIdentity); err != nil {
return err
}
boltAuthenticator, err := authenticator.toBoltEntityForCreate(ctx.Tx(), self.env)
if err != nil {
return err
}
if err = self.env.GetStores().Authenticator.Create(ctx, boltAuthenticator); err != nil {
return err
}
return nil
})
if err != nil {
return "", "", err
}
return identity.Id, authenticator.Id, nil
}
func (self *IdentityManager) GetServiceConfigs(id string) ([]ServiceConfig, error) {
var result []ServiceConfig
err := self.GetDb().View(func(tx *bbolt.Tx) error {
configs, err := self.env.GetStores().Identity.GetServiceConfigs(tx, id)
if err != nil {
return err
}
for _, config := range configs {
result = append(result, ServiceConfig{Service: config.ServiceId, Config: config.ConfigId})
}
return nil
})
if err != nil {
return nil, err
}
return result, nil
}
func (self *IdentityManager) AssignServiceConfigs(id string, serviceConfigs []ServiceConfig, ctx *change.Context) error {
cmd := &UpdateServiceConfigsCmd{
manager: self,
identityId: id,
add: true,
serviceConfigs: serviceConfigs,
ctx: ctx,
}
return self.Dispatch(cmd)
}
func (self *IdentityManager) RemoveServiceConfigs(id string, serviceConfigs []ServiceConfig, ctx *change.Context) error {
cmd := &UpdateServiceConfigsCmd{
manager: self,
identityId: id,
add: false,
serviceConfigs: serviceConfigs,
ctx: ctx,
}
return self.Dispatch(cmd)
}
func (self *IdentityManager) ApplyUpdateServiceConfigs(cmd *UpdateServiceConfigsCmd, ctx boltz.MutateContext) error {
if cmd.add {
return self.GetDb().Update(ctx, func(ctx boltz.MutateContext) error {
boltServiceConfigs, err := toBoltServiceConfigs(ctx.Tx(), self.env, cmd.serviceConfigs)
if err != nil {
return err
}
return self.env.GetStores().Identity.AssignServiceConfigs(ctx.Tx(), cmd.identityId, boltServiceConfigs...)
})
}
return self.GetDb().Update(ctx, func(ctx boltz.MutateContext) error {
boltServiceConfigs, err := toBoltServiceConfigs(ctx.Tx(), self.env, cmd.serviceConfigs)
if err != nil {
return err
}
return self.env.GetStores().Identity.RemoveServiceConfigs(ctx.Tx(), cmd.identityId, boltServiceConfigs...)
})
}
func (self *IdentityManager) QueryRoleAttributes(queryString string) ([]string, *models.QueryMetaData, error) {
index := self.env.GetStores().Identity.GetRoleAttributesIndex()
return self.queryRoleAttributes(index, queryString)
}
func (self *IdentityManager) PatchInfo(identity *Identity, changeCtx *change.Context) error {
start := time.Now()
checker := boltz.MapFieldChecker{
db.FieldIdentityEnvInfoArch: struct{}{},
db.FieldIdentityEnvInfoOs: struct{}{},
db.FieldIdentityEnvInfoOsRelease: struct{}{},
db.FieldIdentityEnvInfoOsVersion: struct{}{},
db.FieldIdentityEnvInfoDomain: struct{}{},
db.FieldIdentityEnvInfoHostname: struct{}{},
db.FieldIdentitySdkInfoBranch: struct{}{},
db.FieldIdentitySdkInfoRevision: struct{}{},
db.FieldIdentitySdkInfoType: struct{}{},
db.FieldIdentitySdkInfoVersion: struct{}{},
db.FieldIdentitySdkInfoAppId: struct{}{},
db.FieldIdentitySdkInfoAppVersion: struct{}{},
}
err := self.updateEntityBatch(identity, checker, changeCtx)
self.updateSdkInfoTimer.UpdateSince(start)
return err
}
// SetHasErConnection will register an identity as having an ER connection. The registration has a TTL depending on
// how the status map was configured.
func (self *IdentityManager) SetHasErConnection(identityId string) {
self.identityStatusMap.SetHasEdgeRouterConnection(identityId)
}
// HasErConnection will return true if the supplied identity id has a current an active ER connection registered.
func (self *IdentityManager) HasErConnection(id string) bool {
return self.identityStatusMap.HasEdgeRouterConnection(id)
}
func (self *IdentityManager) VisitIdentityAuthenticatorFingerprints(tx *bbolt.Tx, identityId string, visitor func(string) bool) (bool, error) {
stopVisit := false
err := self.visitAuthenticators(tx, identityId, func(authenticator *Authenticator) bool {
for _, authPrint := range authenticator.Fingerprints() {
if visitor(authPrint) {
stopVisit = true
return true
}
}
return false
})
return stopVisit, err
}
func (self *IdentityManager) ReadByExternalId(externalId string) (*Identity, error) {
query := fmt.Sprintf("%s = \"%v\"", db.FieldIdentityExternalId, externalId)
entity, err := self.readEntityByQuery(query)
if err != nil {
return nil, err
}
if entity == nil {
return nil, nil
}
identity, ok := entity.(*Identity)
if !ok {
return nil, fmt.Errorf("could not cast from %T to %T", entity, identity)
}
return identity, nil
}
func (self *IdentityManager) Disable(identityId string, duration time.Duration, ctx *change.Context) error {
if duration < 0 {
duration = 0
}
fieldMap := fields.UpdatedFieldsMap{
db.FieldIdentityDisabledAt: struct{}{},
db.FieldIdentityDisabledUntil: struct{}{},
}
lockedAt := time.Now()
var lockedUntil *time.Time
if duration != 0 {
until := lockedAt.Add(duration)
lockedUntil = &until
}
err := self.Update(&Identity{
BaseEntity: models.BaseEntity{
Id: identityId,
},
DisabledAt: &lockedAt,
DisabledUntil: lockedUntil,
}, fieldMap, ctx)
if err != nil {
return err
}
return self.GetEnv().GetManagers().ApiSession.DeleteByIdentityId(identityId, ctx)
}
func (self *IdentityManager) Enable(identityId string, ctx *change.Context) error {
fieldMap := fields.UpdatedFieldsMap{
db.FieldIdentityDisabledAt: struct{}{},
db.FieldIdentityDisabledUntil: struct{}{},
}
return self.Update(&Identity{
BaseEntity: models.BaseEntity{
Id: identityId,
},
DisabledAt: nil,
DisabledUntil: nil,
}, fieldMap, ctx)
}
func (self *IdentityManager) IdentityToProtobuf(entity *Identity) (*edge_cmd_pb.Identity, error) {
tags, err := edge_cmd_pb.EncodeTags(entity.Tags)
if err != nil {
return nil, err
}
var envInfo *edge_cmd_pb.Identity_EnvInfo
if entity.EnvInfo != nil {
envInfo = &edge_cmd_pb.Identity_EnvInfo{
Arch: entity.EnvInfo.Arch,
Os: entity.EnvInfo.Os,
OsRelease: entity.EnvInfo.OsRelease,
OsVersion: entity.EnvInfo.OsVersion,
Domain: entity.EnvInfo.Domain,
Hostname: entity.EnvInfo.Hostname,
}
}
var sdkInfo *edge_cmd_pb.Identity_SdkInfo
if entity.SdkInfo != nil {
sdkInfo = &edge_cmd_pb.Identity_SdkInfo{
AppId: entity.SdkInfo.AppId,
AppVersion: entity.SdkInfo.AppVersion,
Branch: entity.SdkInfo.Branch,
Revision: entity.SdkInfo.Revision,
Type: entity.SdkInfo.Type,
Version: entity.SdkInfo.Version,
}
}
precedenceMap := map[string]uint32{}
for k, v := range entity.ServiceHostingPrecedences {
precedenceMap[k] = uint32(v)
}
costMap := map[string]uint32{}
for k, v := range entity.ServiceHostingCosts {
costMap[k] = uint32(v)
}
appData, err := json.Marshal(entity.AppData)
if err != nil {
return nil, err
}
msg := &edge_cmd_pb.Identity{
Id: entity.Id,
Name: entity.Name,
Tags: tags,
IdentityTypeId: entity.IdentityTypeId,
IsDefaultAdmin: entity.IsDefaultAdmin,
IsAdmin: entity.IsAdmin,
RoleAttributes: entity.RoleAttributes,
EnvInfo: envInfo,
SdkInfo: sdkInfo,
DefaultHostingPrecedence: uint32(entity.DefaultHostingPrecedence),
DefaultHostingCost: uint32(entity.DefaultHostingCost),
ServiceHostingPrecedences: precedenceMap,
ServiceHostingCosts: costMap,
AppData: appData,
AuthPolicyId: entity.AuthPolicyId,
ExternalId: entity.ExternalId,
Disabled: entity.Disabled,
DisabledAt: timePtrToPb(entity.DisabledAt),
DisabledUntil: timePtrToPb(entity.DisabledUntil),
}
return msg, nil
}
func (self *IdentityManager) Marshall(entity *Identity) ([]byte, error) {
msg, err := self.IdentityToProtobuf(entity)
if err != nil {
return nil, err
}
return proto.Marshal(msg)
}
func (self *IdentityManager) ProtobufToIdentity(msg *edge_cmd_pb.Identity) (*Identity, error) {
var envInfo *EnvInfo
if msg.EnvInfo != nil {
envInfo = &EnvInfo{
Arch: msg.EnvInfo.Arch,
Os: msg.EnvInfo.Os,
OsRelease: msg.EnvInfo.OsRelease,
OsVersion: msg.EnvInfo.OsVersion,
Domain: msg.EnvInfo.Domain,
Hostname: msg.EnvInfo.Hostname,
}
}
var sdkInfo *SdkInfo
for msg.SdkInfo != nil {
sdkInfo = &SdkInfo{
AppId: msg.SdkInfo.AppId,
AppVersion: msg.SdkInfo.AppVersion,
Branch: msg.SdkInfo.Branch,
Revision: msg.SdkInfo.Revision,
Type: msg.SdkInfo.Type,
Version: msg.SdkInfo.Version,
}
}
precedenceMap := map[string]ziti.Precedence{}
for k, v := range msg.ServiceHostingPrecedences {
precedenceMap[k] = ziti.Precedence(v)
}
costMap := map[string]uint16{}
for k, v := range msg.ServiceHostingCosts {
costMap[k] = uint16(v)
}
appData := map[string]interface{}{}
if err := json.Unmarshal(msg.AppData, &appData); err != nil {
return nil, err
}
return &Identity{
BaseEntity: models.BaseEntity{
Id: msg.Id,
Tags: edge_cmd_pb.DecodeTags(msg.Tags),
},
Name: msg.Name,
IdentityTypeId: msg.IdentityTypeId,
IsDefaultAdmin: msg.IsDefaultAdmin,
IsAdmin: msg.IsAdmin,
RoleAttributes: msg.RoleAttributes,
EnvInfo: envInfo,
SdkInfo: sdkInfo,
DefaultHostingPrecedence: ziti.Precedence(msg.DefaultHostingPrecedence),
DefaultHostingCost: uint16(msg.DefaultHostingCost),
ServiceHostingPrecedences: precedenceMap,
ServiceHostingCosts: costMap,
AppData: appData,
AuthPolicyId: msg.AuthPolicyId,
ExternalId: msg.ExternalId,
Disabled: msg.Disabled,
DisabledAt: pbTimeToTimePtr(msg.DisabledAt),
DisabledUntil: pbTimeToTimePtr(msg.DisabledUntil),
}, nil
}
func (self *IdentityManager) Unmarshall(bytes []byte) (*Identity, error) {
msg := &edge_cmd_pb.Identity{}
if err := proto.Unmarshal(bytes, msg); err != nil {
return nil, err
}
return self.ProtobufToIdentity(msg)
}
type CreateIdentityWithEnrollmentsCmd struct {
manager *IdentityManager
identity *Identity
enrollments []*Enrollment
ctx *change.Context
}
func (self *CreateIdentityWithEnrollmentsCmd) Apply(ctx boltz.MutateContext) error {
return self.manager.ApplyCreateWithEnrollments(self, ctx)
}
func (self *CreateIdentityWithEnrollmentsCmd) Encode() ([]byte, error) {
identityMsg, err := self.manager.IdentityToProtobuf(self.identity)
if err != nil {
return nil, err
}
cmd := &edge_cmd_pb.CreateIdentityWithEnrollmentsCmd{
Identity: identityMsg,
Ctx: ContextToProtobuf(self.ctx),
}
for _, enrollment := range self.enrollments {
enrollmentMsg, err := self.manager.GetEnv().GetManagers().Enrollment.EnrollmentToProtobuf(enrollment)
if err != nil {
return nil, err
}
cmd.Enrollments = append(cmd.Enrollments, enrollmentMsg)
}
return cmd_pb.EncodeProtobuf(cmd)
}
func (self *CreateIdentityWithEnrollmentsCmd) Decode(env Env, msg *edge_cmd_pb.CreateIdentityWithEnrollmentsCmd) error {
self.manager = env.GetManagers().Identity
identity, err := self.manager.ProtobufToIdentity(msg.Identity)
if err != nil {
return err
}
self.identity = identity
self.ctx = ProtobufToContext(msg.Ctx)
for _, enrollmentMsg := range msg.Enrollments {
enrollment, err := self.manager.GetEnv().GetManagers().Enrollment.ProtobufToEnrollment(enrollmentMsg)
if err != nil {
return err
}
self.enrollments = append(self.enrollments, enrollment)
}
return nil
}
func (self *CreateIdentityWithEnrollmentsCmd) GetChangeContext() *change.Context {
return self.ctx
}
type identityStatusMap struct {
identityIdToErConStatus cmap.ConcurrentMap[string, *status]
initOnce sync.Once
activeDuration time.Duration
}
type status struct {
expiresAt time.Time
}
func newIdentityStatusMap(activeDuration time.Duration) *identityStatusMap {
return &identityStatusMap{
identityIdToErConStatus: cmap.New[*status](),
activeDuration: activeDuration,
}
}
func (statusMap *identityStatusMap) SetHasEdgeRouterConnection(identityId string) {
statusMap.initOnce.Do(statusMap.start)
statusMap.identityIdToErConStatus.Set(identityId, &status{
expiresAt: time.Now().Add(statusMap.activeDuration),
})
}
func (statusMap *identityStatusMap) HasEdgeRouterConnection(identityId string) bool {
if stat, ok := statusMap.identityIdToErConStatus.Get(identityId); ok {
now := time.Now()
ret := stat.expiresAt.After(now)
pfxlog.Logger().
WithField("identityId", identityId).
WithField("expiresAt", stat.expiresAt).
WithField("now", now).
Tracef("reporting identity from active ER conn pool: timedout")
return ret
}
pfxlog.Logger().
WithField("identityId", identityId).
Tracef("reporting identity from active ER conn pool: not found")
return false
}
func (statusMap *identityStatusMap) start() {
ticker := time.NewTicker(30 * time.Second)
go func() {
for range ticker.C {
var toRemove []string
now := time.Now()
statusMap.identityIdToErConStatus.IterCb(func(key string, stat *status) {
if stat.expiresAt.Before(now) {
pfxlog.Logger().
WithField("identityId", key).
WithField("expiresAt", stat.expiresAt).
WithField("now", now).
Debugf("removing identity from active ER conn pool: not found")
toRemove = append(toRemove, key)
}
})
for _, identityId := range toRemove {
statusMap.identityIdToErConStatus.Remove(identityId)
}
}
}()
}
type UpdateServiceConfigsCmd struct {
manager *IdentityManager
identityId string
add bool
serviceConfigs []ServiceConfig
ctx *change.Context
}
func (self *UpdateServiceConfigsCmd) Apply(ctx boltz.MutateContext) error {
return self.manager.ApplyUpdateServiceConfigs(self, ctx)
}
func (self *UpdateServiceConfigsCmd) Encode() ([]byte, error) {
cmd := &edge_cmd_pb.UpdateServiceConfigsCmd{
IdentityId: self.identityId,
Add: self.add,
Ctx: ContextToProtobuf(self.ctx),
}
for _, serviceConfig := range self.serviceConfigs {
cmd.ServiceConfigs = append(cmd.ServiceConfigs, &edge_cmd_pb.UpdateServiceConfigsCmd_ServiceConfig{
ServiceId: serviceConfig.Service,
ConfigId: serviceConfig.Config,
})
}
return cmd_pb.EncodeProtobuf(cmd)
}
func (self *UpdateServiceConfigsCmd) Decode(env Env, msg *edge_cmd_pb.UpdateServiceConfigsCmd) error {
self.manager = env.GetManagers().Identity
self.identityId = msg.IdentityId
self.add = msg.Add
self.ctx = ProtobufToContext(msg.Ctx)
for _, serviceConfig := range msg.ServiceConfigs {
self.serviceConfigs = append(self.serviceConfigs, ServiceConfig{
Service: serviceConfig.ServiceId,
Config: serviceConfig.ConfigId,
})
}
return nil
}
func (self *UpdateServiceConfigsCmd) GetChangeContext() *change.Context {
return self.ctx
}