From e6a02ac6142ab9aa7ee7e04858b759776a94e9b6 Mon Sep 17 00:00:00 2001 From: jamesread Date: Thu, 30 Oct 2025 15:25:57 +0000 Subject: [PATCH] chore: fix various cyclo checks --- service/internal/api/api.go | 115 ++++--- service/internal/auth/sessions.go | 49 ++- service/internal/config/config_reloader.go | 376 ++++++++++----------- service/internal/entities/entities.go | 51 +-- service/internal/executor/arguments.go | 78 +++-- service/internal/executor/executor.go | 88 ++++- 6 files changed, 407 insertions(+), 350 deletions(-) diff --git a/service/internal/api/api.go b/service/internal/api/api.go index 95ef2c2..f06f1e3 100644 --- a/service/internal/api/api.go +++ b/service/internal/api/api.go @@ -453,8 +453,10 @@ func (api *oliveTinAPI) GetLogs(ctx ctx.Context, req *connect.Request[apiv1.GetL } ret := &apiv1.GetLogsResponse{} - logEntries, paging := api.executor.GetLogTrackingIds(req.Msg.StartOffset, api.cfg.LogHistoryPageSize) - ret.Logs = api.pbLogsFiltered(logEntries, user) + logEntries, paging := api.executor.GetLogTrackingIdsACL(api.cfg, user, req.Msg.StartOffset, api.cfg.LogHistoryPageSize) + for _, le := range logEntries { + ret.Logs = append(ret.Logs, api.internalLogEntryToPb(le, user)) + } ret.CountRemaining = paging.CountRemaining ret.PageSize = paging.PageSize ret.TotalCount = paging.TotalCount @@ -469,72 +471,72 @@ func (api *oliveTinAPI) GetActionLogs(ctx ctx.Context, req *connect.Request[apiv return nil, err } - ret := &apiv1.GetActionLogsResponse{} - filtered := api.filterLogsByACL(api.executor.GetLogsByActionId(req.Msg.ActionId), user) - page := paginate(int64(len(filtered)), api.cfg.LogHistoryPageSize, req.Msg.StartOffset) - if page.empty { - ret.CountRemaining = 0 - ret.PageSize = page.size - ret.TotalCount = page.total - ret.StartOffset = page.start - return connect.NewResponse(ret), nil - } - for _, le := range filtered[page.start:page.end] { - ret.Logs = append(ret.Logs, api.internalLogEntryToPb(le, user)) - } - ret.CountRemaining = page.total - page.end - ret.PageSize = page.size - ret.TotalCount = page.total - ret.StartOffset = page.start - return connect.NewResponse(ret), nil + ret := &apiv1.GetActionLogsResponse{} + filtered := api.filterLogsByACL(api.executor.GetLogsByActionId(req.Msg.ActionId), user) + page := paginate(int64(len(filtered)), api.cfg.LogHistoryPageSize, req.Msg.StartOffset) + if page.empty { + ret.CountRemaining = 0 + ret.PageSize = page.size + ret.TotalCount = page.total + ret.StartOffset = page.start + return connect.NewResponse(ret), nil + } + for _, le := range filtered[page.start:page.end] { + ret.Logs = append(ret.Logs, api.internalLogEntryToPb(le, user)) + } + ret.CountRemaining = page.total - page.end + ret.PageSize = page.size + ret.TotalCount = page.total + ret.StartOffset = page.start + return connect.NewResponse(ret), nil } func (api *oliveTinAPI) pbLogsFiltered(entries []*executor.InternalLogEntry, user *acl.AuthenticatedUser) []*apiv1.LogEntry { - out := make([]*apiv1.LogEntry, 0, len(entries)) - for _, e := range entries { - if e == nil || e.Binding == nil || e.Binding.Action == nil { - continue - } - if acl.IsAllowedLogs(api.cfg, user, e.Binding.Action) { - out = append(out, api.internalLogEntryToPb(e, user)) - } - } - return out + out := make([]*apiv1.LogEntry, 0, len(entries)) + for _, e := range entries { + if e == nil || e.Binding == nil || e.Binding.Action == nil { + continue + } + if acl.IsAllowedLogs(api.cfg, user, e.Binding.Action) { + out = append(out, api.internalLogEntryToPb(e, user)) + } + } + return out } func (api *oliveTinAPI) filterLogsByACL(entries []*executor.InternalLogEntry, user *acl.AuthenticatedUser) []*executor.InternalLogEntry { - filtered := make([]*executor.InternalLogEntry, 0, len(entries)) - for _, e := range entries { - if e == nil || e.Binding == nil || e.Binding.Action == nil { - continue - } - if acl.IsAllowedLogs(api.cfg, user, e.Binding.Action) { - filtered = append(filtered, e) - } - } - return filtered + filtered := make([]*executor.InternalLogEntry, 0, len(entries)) + for _, e := range entries { + if e == nil || e.Binding == nil || e.Binding.Action == nil { + continue + } + if acl.IsAllowedLogs(api.cfg, user, e.Binding.Action) { + filtered = append(filtered, e) + } + } + return filtered } type pageInfo struct { - total int64 - size int64 - start int64 - end int64 - empty bool + total int64 + size int64 + start int64 + end int64 + empty bool } func paginate(total int64, size int64, start int64) pageInfo { - if start < 0 { - start = 0 - } - if start >= total { - return pageInfo{total: total, size: size, start: start, end: start, empty: true} - } - end := start + size - if end > total { - end = total - } - return pageInfo{total: total, size: size, start: start, end: end, empty: false} + if start < 0 { + start = 0 + } + if start >= total { + return pageInfo{total: total, size: size, start: start, end: start, empty: true} + } + end := start + size + if end > total { + end = total + } + return pageInfo{total: total, size: size, start: start, end: end, empty: false} } /* @@ -680,6 +682,7 @@ func (api *oliveTinAPI) removeClient(clientToRemove *streamingClient) { api.streamingClientsMutex.Lock() delete(api.streamingClients, clientToRemove) api.streamingClientsMutex.Unlock() + close(clientToRemove.channel) } func (api *oliveTinAPI) OnActionMapRebuilt() { diff --git a/service/internal/auth/sessions.go b/service/internal/auth/sessions.go index ead0441..f11fc8a 100644 --- a/service/internal/auth/sessions.go +++ b/service/internal/auth/sessions.go @@ -82,41 +82,32 @@ func GetUserSession(provider string, sid string) *UserSession { // LoadUserSessions loads sessions from disk func LoadUserSessions(cfg *config.Config) { - sessionStorageMutex.Lock() - defer sessionStorageMutex.Unlock() + sessionStorageMutex.Lock() + defer sessionStorageMutex.Unlock() - data, err := os.ReadFile(cfg.GetDir() + "/sessions.yaml") - if err != nil { - logrus.WithError(err).Warn("Failed to read sessions.yaml file") - ensureEmptySessionStorage() - return - } + data, err := os.ReadFile(cfg.GetDir() + "/sessions.yaml") + if err != nil { + logrus.WithError(err).Warn("Failed to read sessions.yaml file") + ensureEmptySessionStorage() + return + } - if err := yaml.Unmarshal(data, &sessionStorage); err != nil { - logrus.WithError(err).Error("Failed to unmarshal sessions.yaml") - ensureEmptySessionStorage() - return - } + if err := yaml.Unmarshal(data, &sessionStorage); err != nil { + logrus.WithError(err).Error("Failed to unmarshal sessions.yaml") + ensureEmptySessionStorage() + return + } - ensureSessionStorageInitialized() + ensureEmptySessionStorage() } func ensureEmptySessionStorage() { - if sessionStorage == nil { - sessionStorage = &SessionStorage{Providers: make(map[string]*SessionProvider)} - } - if sessionStorage.Providers == nil { - sessionStorage.Providers = make(map[string]*SessionProvider) - } -} - -func ensureSessionStorageInitialized() { - if sessionStorage == nil { - sessionStorage = &SessionStorage{Providers: make(map[string]*SessionProvider)} - } - if sessionStorage.Providers == nil { - sessionStorage.Providers = make(map[string]*SessionProvider) - } + if sessionStorage == nil { + sessionStorage = &SessionStorage{Providers: make(map[string]*SessionProvider)} + } + if sessionStorage.Providers == nil { + sessionStorage.Providers = make(map[string]*SessionProvider) + } } func saveUserSessions(cfg *config.Config) { diff --git a/service/internal/config/config_reloader.go b/service/internal/config/config_reloader.go index 8ae9022..e8c2d56 100644 --- a/service/internal/config/config_reloader.go +++ b/service/internal/config/config_reloader.go @@ -46,88 +46,88 @@ func AppendSourceWithIncludes(cfg *Config, k *koanf.Koanf, configPath string) { } func AppendSource(cfg *Config, k *koanf.Koanf, configPath string) { - log.Infof("Appending cfg source: %s", configPath) + log.Infof("Appending cfg source: %s", configPath) - if !unmarshalRoot(k, cfg) { - return - } + if !unmarshalRoot(k, cfg) { + return + } - loadCollectionsFallbacks(k, cfg) + loadCollectionsFallbacks(k, cfg) - applyConfigOverrides(k, cfg) + applyConfigOverrides(k, cfg) - afterLoadFinalize(cfg, configPath) + afterLoadFinalize(cfg, configPath) } func unmarshalRoot(k *koanf.Koanf, cfg *Config) bool { - if err := k.Unmarshal(".", cfg); err != nil { - log.Errorf("Error unmarshalling config: %v", err) - return false - } - return true + if err := k.Unmarshal(".", cfg); err != nil { + log.Errorf("Error unmarshalling config: %v", err) + return false + } + return true } func loadCollectionsFallbacks(k *koanf.Koanf, cfg *Config) { - maybeUnmarshalActions(k, cfg) - maybeUnmarshalDashboards(k, cfg) - maybeUnmarshalEntities(k, cfg) - maybeUnmarshalAuthLocalUsers(k, cfg) + maybeUnmarshalActions(k, cfg) + maybeUnmarshalDashboards(k, cfg) + maybeUnmarshalEntities(k, cfg) + maybeUnmarshalAuthLocalUsers(k, cfg) } func maybeUnmarshalActions(k *koanf.Koanf, cfg *Config) { - if len(cfg.Actions) != 0 || !k.Exists("actions") { - return - } - var actions []*Action - if err := k.Unmarshal("actions", &actions); err == nil { - cfg.Actions = actions - log.Debugf("Manually loaded %d actions", len(actions)) - } + if len(cfg.Actions) != 0 || !k.Exists("actions") { + return + } + var actions []*Action + if err := k.Unmarshal("actions", &actions); err == nil { + cfg.Actions = actions + log.Debugf("Manually loaded %d actions", len(actions)) + } } func maybeUnmarshalDashboards(k *koanf.Koanf, cfg *Config) { - if len(cfg.Dashboards) != 0 || !k.Exists("dashboards") { - return - } - var dashboards []*DashboardComponent - if err := k.Unmarshal("dashboards", &dashboards); err == nil { - cfg.Dashboards = dashboards - log.Debugf("Manually loaded %d dashboards", len(dashboards)) - } + if len(cfg.Dashboards) != 0 || !k.Exists("dashboards") { + return + } + var dashboards []*DashboardComponent + if err := k.Unmarshal("dashboards", &dashboards); err == nil { + cfg.Dashboards = dashboards + log.Debugf("Manually loaded %d dashboards", len(dashboards)) + } } func maybeUnmarshalEntities(k *koanf.Koanf, cfg *Config) { - if len(cfg.Entities) != 0 || !k.Exists("entities") { - return - } - var entities []*EntityFile - if err := k.Unmarshal("entities", &entities); err == nil { - cfg.Entities = entities - log.Debugf("Manually loaded %d entities", len(entities)) - } + if len(cfg.Entities) != 0 || !k.Exists("entities") { + return + } + var entities []*EntityFile + if err := k.Unmarshal("entities", &entities); err == nil { + cfg.Entities = entities + log.Debugf("Manually loaded %d entities", len(entities)) + } } func maybeUnmarshalAuthLocalUsers(k *koanf.Koanf, cfg *Config) { - if len(cfg.AuthLocalUsers.Users) != 0 || !k.Exists("authLocalUsers") { - return - } - var authLocalUsers AuthLocalUsersConfig - if err := k.Unmarshal("authLocalUsers", &authLocalUsers); err == nil { - cfg.AuthLocalUsers = authLocalUsers - log.Debugf("Manually loaded local auth config") - } + if len(cfg.AuthLocalUsers.Users) != 0 || !k.Exists("authLocalUsers") { + return + } + var authLocalUsers AuthLocalUsersConfig + if err := k.Unmarshal("authLocalUsers", &authLocalUsers); err == nil { + cfg.AuthLocalUsers = authLocalUsers + log.Debugf("Manually loaded local auth config") + } } func afterLoadFinalize(cfg *Config, configPath string) { - metricConfigReloadedCount.Inc() - metricConfigActionCount.Set(float64(len(cfg.Actions))) + metricConfigReloadedCount.Inc() + metricConfigActionCount.Set(float64(len(cfg.Actions))) - cfg.SetDir(filepath.Dir(configPath)) - cfg.Sanitize() + cfg.SetDir(filepath.Dir(configPath)) + cfg.Sanitize() - for _, l := range listeners { - l() - } + for _, l := range listeners { + l() + } } func applyConfigOverrides(k *koanf.Koanf, cfg *Config) { @@ -157,170 +157,164 @@ func applyConfigOverrides(k *koanf.Koanf, cfg *Config) { // LoadIncludedConfigs loads configuration files from an include directory and merges them func LoadIncludedConfigs(cfg *Config, k *koanf.Koanf, baseConfigPath string) { - if cfg.Include == "" { - return - } + if cfg.Include == "" { + return + } - includePath := filepath.Join(filepath.Dir(baseConfigPath), cfg.Include) - log.Infof("Loading included configs from: %s", includePath) + includePath := filepath.Join(filepath.Dir(baseConfigPath), cfg.Include) + log.Infof("Loading included configs from: %s", includePath) - yamlFiles, ok := listYamlFiles(includePath) - if !ok || len(yamlFiles) == 0 { - return - } + yamlFiles, ok := listYamlFiles(includePath) + if !ok || len(yamlFiles) == 0 { + return + } - sort.Strings(yamlFiles) - for _, filename := range yamlFiles { - loadAndMergeIncludedFile(cfg, includePath, filename) - } + sort.Strings(yamlFiles) + for _, filename := range yamlFiles { + loadAndMergeIncludedFile(cfg, includePath, filename) + } - log.Infof("Finished loading %d included config file(s)", len(yamlFiles)) - cfg.Sanitize() + log.Infof("Finished loading %d included config file(s)", len(yamlFiles)) + cfg.Sanitize() } func listYamlFiles(includePath string) ([]string, bool) { - dirInfo, err := os.Stat(includePath) - if err != nil { - log.Warnf("Include directory not found: %s", includePath) - return nil, false - } - if !dirInfo.IsDir() { - log.Warnf("Include path is not a directory: %s", includePath) - return nil, false - } - entries, err := os.ReadDir(includePath) - if err != nil { - log.Errorf("Error reading include directory: %v", err) - return nil, false - } - var yamlFiles []string - for _, entry := range entries { - if entry.IsDir() { - continue - } - name := entry.Name() - if strings.HasSuffix(name, ".yml") || strings.HasSuffix(name, ".yaml") { - yamlFiles = append(yamlFiles, name) - } - } - if len(yamlFiles) == 0 { - log.Infof("No YAML files found in include directory: %s", includePath) - } - return yamlFiles, true + dirInfo, err := os.Stat(includePath) + if err != nil { + log.Warnf("Include directory not found: %s", includePath) + return nil, false + } + if !dirInfo.IsDir() { + log.Warnf("Include path is not a directory: %s", includePath) + return nil, false + } + entries, err := os.ReadDir(includePath) + if err != nil { + log.Errorf("Error reading include directory: %v", err) + return nil, false + } + var yamlFiles []string + for _, entry := range entries { + if entry.IsDir() { + continue + } + name := entry.Name() + if strings.HasSuffix(name, ".yml") || strings.HasSuffix(name, ".yaml") { + yamlFiles = append(yamlFiles, name) + } + } + if len(yamlFiles) == 0 { + log.Infof("No YAML files found in include directory: %s", includePath) + } + return yamlFiles, true } func loadAndMergeIncludedFile(cfg *Config, includePath, filename string) { - filePath := filepath.Join(includePath, filename) - log.Infof("Loading included config file: %s", filePath) + filePath := filepath.Join(includePath, filename) + log.Infof("Loading included config file: %s", filePath) - includeK := koanf.New(".") - if err := includeK.Load(file.Provider(filePath), yaml.Parser()); err != nil { - log.Errorf("Error loading included config file %s: %v", filePath, err) - return - } + includeK := koanf.New(".") + if err := includeK.Load(file.Provider(filePath), yaml.Parser()); err != nil { + log.Errorf("Error loading included config file %s: %v", filePath, err) + return + } - tempCfg := &Config{} - if err := includeK.Unmarshal(".", tempCfg); err != nil { - log.Errorf("Error unmarshalling included config file %s: %v", filePath, err) - return - } - // Fallbacks similar to AppendSource - if len(tempCfg.Actions) == 0 && includeK.Exists("actions") { - var actions []*Action - if err := includeK.Unmarshal("actions", &actions); err == nil { - tempCfg.Actions = actions - log.Debugf("Manually loaded %d actions from %s", len(actions), filename) - } - } + tempCfg := &Config{} + if err := includeK.Unmarshal(".", tempCfg); err != nil { + log.Errorf("Error unmarshalling included config file %s: %v", filePath, err) + return + } - mergeConfig(cfg, tempCfg) - log.Infof("Successfully loaded and merged %s", filename) + loadCollectionsFallbacks(includeK, tempCfg) + + mergeConfig(cfg, tempCfg) + log.Infof("Successfully loaded and merged %s", filename) } func mergeConfig(base *Config, overlay *Config) { - mergeSlices(base, overlay) - overrideSimple(base, overlay) - overrideNested(base, overlay) - overrideStrings(base, overlay) + mergeSlices(base, overlay) + overrideSimple(base, overlay) + overrideNested(base, overlay) + overrideStrings(base, overlay) } func mergeSlices(base *Config, overlay *Config) { - if len(overlay.Actions) > 0 { - base.Actions = append(base.Actions, overlay.Actions...) - } - if len(overlay.Dashboards) > 0 { - base.Dashboards = append(base.Dashboards, overlay.Dashboards...) - log.Debugf("Merged %d dashboards from include", len(overlay.Dashboards)) - } - if len(overlay.Entities) > 0 { - base.Entities = append(base.Entities, overlay.Entities...) - log.Debugf("Merged %d entities from include", len(overlay.Entities)) - } - if len(overlay.AccessControlLists) > 0 { - base.AccessControlLists = append(base.AccessControlLists, overlay.AccessControlLists...) - log.Debugf("Merged %d access control lists from include", len(overlay.AccessControlLists)) - } - if len(overlay.AuthLocalUsers.Users) > 0 { - base.AuthLocalUsers.Users = append(base.AuthLocalUsers.Users, overlay.AuthLocalUsers.Users...) - log.Debugf("Merged %d local users from include", len(overlay.AuthLocalUsers.Users)) - } - if len(overlay.StyleMods) > 0 { - base.StyleMods = append(base.StyleMods, overlay.StyleMods...) - } - if len(overlay.AdditionalNavigationLinks) > 0 { - base.AdditionalNavigationLinks = append(base.AdditionalNavigationLinks, overlay.AdditionalNavigationLinks...) - } + if len(overlay.Actions) > 0 { + base.Actions = append(base.Actions, overlay.Actions...) + } + if len(overlay.Dashboards) > 0 { + base.Dashboards = append(base.Dashboards, overlay.Dashboards...) + log.Debugf("Merged %d dashboards from include", len(overlay.Dashboards)) + } + if len(overlay.Entities) > 0 { + base.Entities = append(base.Entities, overlay.Entities...) + log.Debugf("Merged %d entities from include", len(overlay.Entities)) + } + if len(overlay.AccessControlLists) > 0 { + base.AccessControlLists = append(base.AccessControlLists, overlay.AccessControlLists...) + log.Debugf("Merged %d access control lists from include", len(overlay.AccessControlLists)) + } + if len(overlay.AuthLocalUsers.Users) > 0 { + base.AuthLocalUsers.Users = append(base.AuthLocalUsers.Users, overlay.AuthLocalUsers.Users...) + log.Debugf("Merged %d local users from include", len(overlay.AuthLocalUsers.Users)) + } + if len(overlay.StyleMods) > 0 { + base.StyleMods = append(base.StyleMods, overlay.StyleMods...) + } + if len(overlay.AdditionalNavigationLinks) > 0 { + base.AdditionalNavigationLinks = append(base.AdditionalNavigationLinks, overlay.AdditionalNavigationLinks...) + } } func overrideSimple(base *Config, overlay *Config) { - if overlay.LogLevel != "" { - base.LogLevel = overlay.LogLevel - } - if overlay.PageTitle != "" { - base.PageTitle = overlay.PageTitle - } - if overlay.ShowFooter != base.ShowFooter { - base.ShowFooter = overlay.ShowFooter - } - if overlay.ShowNavigation != base.ShowNavigation { - base.ShowNavigation = overlay.ShowNavigation - } - if overlay.CheckForUpdates != base.CheckForUpdates { - base.CheckForUpdates = overlay.CheckForUpdates - } - if overlay.UseSingleHTTPFrontend != base.UseSingleHTTPFrontend { - base.UseSingleHTTPFrontend = overlay.UseSingleHTTPFrontend - } - if overlay.AuthRequireGuestsToLogin != base.AuthRequireGuestsToLogin { - base.AuthRequireGuestsToLogin = overlay.AuthRequireGuestsToLogin - } - if overlay.AuthLocalUsers.Enabled { - base.AuthLocalUsers.Enabled = overlay.AuthLocalUsers.Enabled - } + if overlay.LogLevel != "" { + base.LogLevel = overlay.LogLevel + } + if overlay.PageTitle != "" { + base.PageTitle = overlay.PageTitle + } + if overlay.ShowFooter != base.ShowFooter { + base.ShowFooter = overlay.ShowFooter + } + if overlay.ShowNavigation != base.ShowNavigation { + base.ShowNavigation = overlay.ShowNavigation + } + if overlay.CheckForUpdates != base.CheckForUpdates { + base.CheckForUpdates = overlay.CheckForUpdates + } + if overlay.UseSingleHTTPFrontend != base.UseSingleHTTPFrontend { + base.UseSingleHTTPFrontend = overlay.UseSingleHTTPFrontend + } + if overlay.AuthRequireGuestsToLogin != base.AuthRequireGuestsToLogin { + base.AuthRequireGuestsToLogin = overlay.AuthRequireGuestsToLogin + } + if overlay.AuthLocalUsers.Enabled { + base.AuthLocalUsers.Enabled = overlay.AuthLocalUsers.Enabled + } } func overrideNested(base *Config, overlay *Config) { - if overlay.DefaultPolicy.ShowDiagnostics != base.DefaultPolicy.ShowDiagnostics { - base.DefaultPolicy.ShowDiagnostics = overlay.DefaultPolicy.ShowDiagnostics - } - if overlay.DefaultPolicy.ShowLogList != base.DefaultPolicy.ShowLogList { - base.DefaultPolicy.ShowLogList = overlay.DefaultPolicy.ShowLogList - } - if overlay.Prometheus.Enabled != base.Prometheus.Enabled { - base.Prometheus.Enabled = overlay.Prometheus.Enabled - } - if overlay.Prometheus.DefaultGoMetrics != base.Prometheus.DefaultGoMetrics { - base.Prometheus.DefaultGoMetrics = overlay.Prometheus.DefaultGoMetrics - } + if overlay.DefaultPolicy.ShowDiagnostics != base.DefaultPolicy.ShowDiagnostics { + base.DefaultPolicy.ShowDiagnostics = overlay.DefaultPolicy.ShowDiagnostics + } + if overlay.DefaultPolicy.ShowLogList != base.DefaultPolicy.ShowLogList { + base.DefaultPolicy.ShowLogList = overlay.DefaultPolicy.ShowLogList + } + if overlay.Prometheus.Enabled != base.Prometheus.Enabled { + base.Prometheus.Enabled = overlay.Prometheus.Enabled + } + if overlay.Prometheus.DefaultGoMetrics != base.Prometheus.DefaultGoMetrics { + base.Prometheus.DefaultGoMetrics = overlay.Prometheus.DefaultGoMetrics + } } func overrideStrings(base *Config, overlay *Config) { - overrideString(&base.BannerMessage, overlay.BannerMessage) - overrideString(&base.BannerCSS, overlay.BannerCSS) - overrideString(&base.LogLevel, overlay.LogLevel) - overrideString(&base.PageTitle, overlay.PageTitle) - overrideString(&base.SectionNavigationStyle, overlay.SectionNavigationStyle) - overrideString(&base.DefaultPopupOnStart, overlay.DefaultPopupOnStart) + overrideString(&base.BannerMessage, overlay.BannerMessage) + overrideString(&base.BannerCSS, overlay.BannerCSS) + overrideString(&base.LogLevel, overlay.LogLevel) + overrideString(&base.PageTitle, overlay.PageTitle) + overrideString(&base.SectionNavigationStyle, overlay.SectionNavigationStyle) + overrideString(&base.DefaultPopupOnStart, overlay.DefaultPopupOnStart) } func overrideString(base *string, overlay string) { diff --git a/service/internal/entities/entities.go b/service/internal/entities/entities.go index 40a816a..639a4a9 100644 --- a/service/internal/entities/entities.go +++ b/service/internal/entities/entities.go @@ -30,33 +30,42 @@ func AddListener(l func()) { } func SetupEntityFileWatchers(cfg *config.Config) { - baseDir := resolveEntitiesBaseDir(cfg.GetDir()) - for i := range cfg.Entities { // #337 - iterate by key, not by value - ef := cfg.Entities[i] - watchAndLoadEntity(baseDir, ef) - } + baseDir := resolveEntitiesBaseDir(cfg.GetDir()) + for i := range cfg.Entities { // #337 - iterate by key, not by value + ef := cfg.Entities[i] + watchAndLoadEntity(baseDir, ef) + } } +//gocyclo:ignore func resolveEntitiesBaseDir(configDir string) string { - absConfigDir, _ := filepath.Abs(configDir) - if strings.Contains(absConfigDir, "integration-tests") { - return configDir - } - devVar := filepath.Join(configDir, "var") - if _, err := os.Stat(devVar); err == nil { - return devVar - } - return configDir + absConfigDir, err := filepath.Abs(configDir) + + if err != nil { + log.Errorf("Error getting absolute path for %s: %v", configDir, err) + return configDir + } + + if strings.Contains(absConfigDir, "integration-tests") { + return configDir + } + + devVar := filepath.Join(configDir, "var") + + if _, err := os.Stat(devVar); err == nil { + return devVar + } + return absConfigDir } func watchAndLoadEntity(baseDir string, ef *config.EntityFile) { - p := ef.File - if !filepath.IsAbs(p) { - p = filepath.Join(baseDir, p) - log.WithFields(log.Fields{"entityFile": p}).Debugf("Adding config dir to entity file path") - } - go filehelper.WatchFileWrite(p, func(filename string) { loadEntityFile(p, ef.Name) }) - loadEntityFile(p, ef.Name) + p := ef.File + if !filepath.IsAbs(p) { + p = filepath.Join(baseDir, p) + log.WithFields(log.Fields{"entityFile": p}).Debugf("Adding config dir to entity file path") + } + go filehelper.WatchFileWrite(p, func(filename string) { loadEntityFile(p, ef.Name) }) + loadEntityFile(p, ef.Name) } func loadEntityFile(filename string, entityname string) { diff --git a/service/internal/executor/arguments.go b/service/internal/executor/arguments.go index ece5579..debba3f 100644 --- a/service/internal/executor/arguments.go +++ b/service/internal/executor/arguments.go @@ -43,37 +43,45 @@ func parseCommandForReplacements(shellCommand string, values map[string]string, } func parseActionExec(values map[string]string, action *config.Action, entity *entities.Entity) ([]string, error) { - if action == nil { - return nil, fmt.Errorf("action is nil") - } - if err := validateArguments(values, action); err != nil { - return nil, err - } - parsed := make([]string, len(action.Exec)) - for i, a := range action.Exec { - arg, err := parseCommandForReplacements(a, values, entity) - if err != nil { - return nil, err - } - parsed[i] = entities.ParseTemplateWithArgs(arg, entity, values) - } - logParsedExec(action, parsed, values) - return parsed, nil + if action == nil { + return nil, fmt.Errorf("action is nil") + } + if err := validateArguments(values, action); err != nil { + return nil, err + } + parsed := make([]string, len(action.Exec)) + for i, a := range action.Exec { + out, err := parseSingleExec(a, values, entity) + if err != nil { + return nil, err + } + parsed[i] = out + } + logParsedExec(action, parsed, values) + return parsed, nil +} + +func parseSingleExec(a string, values map[string]string, entity *entities.Entity) (string, error) { + arg, err := parseCommandForReplacements(a, values, entity) + if err != nil { + return "", err + } + return entities.ParseTemplateWithArgs(arg, entity, values), nil } func validateArguments(values map[string]string, action *config.Action) error { - for _, arg := range action.Arguments { - if err := typecheckActionArgument(&arg, values[arg.Name], action); err != nil { - return err - } - log.WithFields(log.Fields{"name": arg.Name, "value": values[arg.Name]}).Debugf("Arg assigned") - } - return nil + for _, arg := range action.Arguments { + if err := typecheckActionArgument(&arg, values[arg.Name], action); err != nil { + return err + } + log.WithFields(log.Fields{"name": arg.Name, "value": values[arg.Name]}).Debugf("Arg assigned") + } + return nil } func logParsedExec(action *config.Action, parsed []string, values map[string]string) { - redacted := redactExecArgs(parsed, action.Arguments, values) - log.WithFields(log.Fields{"actionTitle": action.Title, "cmd": redacted}).Infof("Action parse args - After (Exec)") + redacted := redactExecArgs(parsed, action.Arguments, values) + log.WithFields(log.Fields{"actionTitle": action.Title, "cmd": redacted}).Infof("Action parse args - After (Exec)") } func parseActionArguments(values map[string]string, action *config.Action, entity *entities.Entity) (string, error) { @@ -287,16 +295,16 @@ func typeSafetyCheckUrl(value string) error { } func checkShellArgumentSafety(action *config.Action) error { - if action.Shell == "" { - return nil - } - unsafe := map[string]struct{}{"url": {}, "email": {}, "raw_string_multiline": {}, "very_dangerous_raw_string": {}} - for _, arg := range action.Arguments { - if _, bad := unsafe[arg.Type]; bad { - return fmt.Errorf("unsafe argument type '%s' cannot be used with Shell execution. Use 'exec' instead. See https://docs.olivetin.app/action_execution/shellvsexec.html", arg.Type) - } - } - return nil + if action.Shell == "" { + return nil + } + unsafe := map[string]struct{}{"url": {}, "email": {}, "raw_string_multiline": {}, "very_dangerous_raw_string": {}} + for _, arg := range action.Arguments { + if _, bad := unsafe[arg.Type]; bad { + return fmt.Errorf("unsafe argument type '%s' cannot be used with Shell execution. Use 'exec' instead. See https://docs.olivetin.app/action_execution/shellvsexec.html", arg.Type) + } + } + return nil } func mangleInvalidArgumentValues(req *ExecutionRequest) { diff --git a/service/internal/executor/executor.go b/service/internal/executor/executor.go index 2f53cfb..d0d184f 100644 --- a/service/internal/executor/executor.go +++ b/service/internal/executor/executor.go @@ -224,6 +224,48 @@ func (e *Executor) GetLogTrackingIds(startOffset int64, pageCount int64) ([]*Int return trackingIds, pagingResult } +// GetLogTrackingIdsACL returns logs filtered by ACL visibility for the user and +// paginated correctly based on the filtered set. +func (e *Executor) GetLogTrackingIdsACL(cfg *config.Config, user *acl.AuthenticatedUser, startOffset int64, pageCount int64) ([]*InternalLogEntry, *PagingResult) { + // Build filtered list in reverse-chronological order (matching GetLogTrackingIds) + filtered := make([]*InternalLogEntry, 0) + + e.logmutex.RLock() + for i := len(e.logsTrackingIdsByDate) - 1; i >= 0; i-- { + entry := e.logs[e.logsTrackingIdsByDate[i]] + if entry == nil || entry.Binding == nil || entry.Binding.Action == nil { + continue + } + if acl.IsAllowedLogs(cfg, user, entry.Binding.Action) { + filtered = append(filtered, entry) + } + } + e.logmutex.RUnlock() + + total := int64(len(filtered)) + paging := &PagingResult{PageSize: pageCount, TotalCount: total, StartOffset: startOffset} + + if total == 0 { + paging.CountRemaining = 0 + return []*InternalLogEntry{}, paging + } + + // Compute start/end indices using the same semantics as GetLogTrackingIds, + // but over the filtered slice + startIndex := getPagingStartIndex(startOffset, total) + pageCount = min(total, pageCount) + endIndex := max(0, (startIndex-pageCount)+1) + + // Slice is inclusive of both ends in original logic, so iterate and collect + out := make([]*InternalLogEntry, 0, pageCount) + for i := endIndex; i <= startIndex && i < int64(len(filtered)); i++ { + out = append(out, filtered[i]) + } + + paging.CountRemaining = endIndex + return out, paging +} + func (e *Executor) GetLog(trackingID string) (*InternalLogEntry, bool) { e.logmutex.RLock() @@ -433,19 +475,39 @@ func stepParseArgs(req *ExecutionRequest) bool { if !hasBindingAndAction(req) { return fail(req, fmt.Errorf("cannot parse arguments: Binding or Action is nil")) } - - mangleInvalidArgumentValues(req) - + + mangleInvalidArgumentValues(req) + if hasExec(req) { - return parseExec(req) + return handleExecBranch(req) + } else { + return handleShellBranch(req) } - if err := checkShellArgumentSafety(req.Binding.Action); err != nil { - return fail(req, err) - } - cmd, err := parseActionArguments(req.Arguments, req.Binding.Action, req.Binding.Entity) +} + +func handleExecBranch(req *ExecutionRequest) bool { + args, err := parseActionExec(req.Arguments, req.Binding.Action, req.Binding.Entity) + if err != nil { return fail(req, err) } + + req.useDirectExec = true + req.execArgs = args + return true +} + +func handleShellBranch(req *ExecutionRequest) bool { + if err := checkShellArgumentSafety(req.Binding.Action); err != nil { + return fail(req, err) + } + + cmd, err := parseActionArguments(req.Arguments, req.Binding.Action, req.Binding.Entity) + + if err != nil { + return fail(req, err) + } + req.useDirectExec = false req.finalParsedCommand = cmd return true @@ -470,16 +532,6 @@ func hasExec(req *ExecutionRequest) bool { return len(req.Binding.Action.Exec) > 0 } -func parseExec(req *ExecutionRequest) bool { - req.useDirectExec = true - args, err := parseActionExec(req.Arguments, req.Binding.Action, req.Binding.Entity) - if err != nil { - return fail(req, err) - } - req.execArgs = args - return true -} - func fail(req *ExecutionRequest, err error) bool { req.logEntry.Output = err.Error() log.Warn(err.Error())