chore: fix various cyclo checks

This commit is contained in:
jamesread 2025-10-30 15:25:57 +00:00
parent e0167c9e42
commit e6a02ac614
6 changed files with 407 additions and 350 deletions

View File

@ -453,8 +453,10 @@ func (api *oliveTinAPI) GetLogs(ctx ctx.Context, req *connect.Request[apiv1.GetL
} }
ret := &apiv1.GetLogsResponse{} ret := &apiv1.GetLogsResponse{}
logEntries, paging := api.executor.GetLogTrackingIds(req.Msg.StartOffset, api.cfg.LogHistoryPageSize) logEntries, paging := api.executor.GetLogTrackingIdsACL(api.cfg, user, req.Msg.StartOffset, api.cfg.LogHistoryPageSize)
ret.Logs = api.pbLogsFiltered(logEntries, user) for _, le := range logEntries {
ret.Logs = append(ret.Logs, api.internalLogEntryToPb(le, user))
}
ret.CountRemaining = paging.CountRemaining ret.CountRemaining = paging.CountRemaining
ret.PageSize = paging.PageSize ret.PageSize = paging.PageSize
ret.TotalCount = paging.TotalCount ret.TotalCount = paging.TotalCount
@ -469,72 +471,72 @@ func (api *oliveTinAPI) GetActionLogs(ctx ctx.Context, req *connect.Request[apiv
return nil, err return nil, err
} }
ret := &apiv1.GetActionLogsResponse{} ret := &apiv1.GetActionLogsResponse{}
filtered := api.filterLogsByACL(api.executor.GetLogsByActionId(req.Msg.ActionId), user) filtered := api.filterLogsByACL(api.executor.GetLogsByActionId(req.Msg.ActionId), user)
page := paginate(int64(len(filtered)), api.cfg.LogHistoryPageSize, req.Msg.StartOffset) page := paginate(int64(len(filtered)), api.cfg.LogHistoryPageSize, req.Msg.StartOffset)
if page.empty { if page.empty {
ret.CountRemaining = 0 ret.CountRemaining = 0
ret.PageSize = page.size ret.PageSize = page.size
ret.TotalCount = page.total ret.TotalCount = page.total
ret.StartOffset = page.start ret.StartOffset = page.start
return connect.NewResponse(ret), nil return connect.NewResponse(ret), nil
} }
for _, le := range filtered[page.start:page.end] { for _, le := range filtered[page.start:page.end] {
ret.Logs = append(ret.Logs, api.internalLogEntryToPb(le, user)) ret.Logs = append(ret.Logs, api.internalLogEntryToPb(le, user))
} }
ret.CountRemaining = page.total - page.end ret.CountRemaining = page.total - page.end
ret.PageSize = page.size ret.PageSize = page.size
ret.TotalCount = page.total ret.TotalCount = page.total
ret.StartOffset = page.start ret.StartOffset = page.start
return connect.NewResponse(ret), nil return connect.NewResponse(ret), nil
} }
func (api *oliveTinAPI) pbLogsFiltered(entries []*executor.InternalLogEntry, user *acl.AuthenticatedUser) []*apiv1.LogEntry { func (api *oliveTinAPI) pbLogsFiltered(entries []*executor.InternalLogEntry, user *acl.AuthenticatedUser) []*apiv1.LogEntry {
out := make([]*apiv1.LogEntry, 0, len(entries)) out := make([]*apiv1.LogEntry, 0, len(entries))
for _, e := range entries { for _, e := range entries {
if e == nil || e.Binding == nil || e.Binding.Action == nil { if e == nil || e.Binding == nil || e.Binding.Action == nil {
continue continue
} }
if acl.IsAllowedLogs(api.cfg, user, e.Binding.Action) { if acl.IsAllowedLogs(api.cfg, user, e.Binding.Action) {
out = append(out, api.internalLogEntryToPb(e, user)) out = append(out, api.internalLogEntryToPb(e, user))
} }
} }
return out return out
} }
func (api *oliveTinAPI) filterLogsByACL(entries []*executor.InternalLogEntry, user *acl.AuthenticatedUser) []*executor.InternalLogEntry { func (api *oliveTinAPI) filterLogsByACL(entries []*executor.InternalLogEntry, user *acl.AuthenticatedUser) []*executor.InternalLogEntry {
filtered := make([]*executor.InternalLogEntry, 0, len(entries)) filtered := make([]*executor.InternalLogEntry, 0, len(entries))
for _, e := range entries { for _, e := range entries {
if e == nil || e.Binding == nil || e.Binding.Action == nil { if e == nil || e.Binding == nil || e.Binding.Action == nil {
continue continue
} }
if acl.IsAllowedLogs(api.cfg, user, e.Binding.Action) { if acl.IsAllowedLogs(api.cfg, user, e.Binding.Action) {
filtered = append(filtered, e) filtered = append(filtered, e)
} }
} }
return filtered return filtered
} }
type pageInfo struct { type pageInfo struct {
total int64 total int64
size int64 size int64
start int64 start int64
end int64 end int64
empty bool empty bool
} }
func paginate(total int64, size int64, start int64) pageInfo { func paginate(total int64, size int64, start int64) pageInfo {
if start < 0 { if start < 0 {
start = 0 start = 0
} }
if start >= total { if start >= total {
return pageInfo{total: total, size: size, start: start, end: start, empty: true} return pageInfo{total: total, size: size, start: start, end: start, empty: true}
} }
end := start + size end := start + size
if end > total { if end > total {
end = total end = total
} }
return pageInfo{total: total, size: size, start: start, end: end, empty: false} 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() api.streamingClientsMutex.Lock()
delete(api.streamingClients, clientToRemove) delete(api.streamingClients, clientToRemove)
api.streamingClientsMutex.Unlock() api.streamingClientsMutex.Unlock()
close(clientToRemove.channel)
} }
func (api *oliveTinAPI) OnActionMapRebuilt() { func (api *oliveTinAPI) OnActionMapRebuilt() {

View File

@ -82,41 +82,32 @@ func GetUserSession(provider string, sid string) *UserSession {
// LoadUserSessions loads sessions from disk // LoadUserSessions loads sessions from disk
func LoadUserSessions(cfg *config.Config) { func LoadUserSessions(cfg *config.Config) {
sessionStorageMutex.Lock() sessionStorageMutex.Lock()
defer sessionStorageMutex.Unlock() defer sessionStorageMutex.Unlock()
data, err := os.ReadFile(cfg.GetDir() + "/sessions.yaml") data, err := os.ReadFile(cfg.GetDir() + "/sessions.yaml")
if err != nil { if err != nil {
logrus.WithError(err).Warn("Failed to read sessions.yaml file") logrus.WithError(err).Warn("Failed to read sessions.yaml file")
ensureEmptySessionStorage() ensureEmptySessionStorage()
return return
} }
if err := yaml.Unmarshal(data, &sessionStorage); err != nil { if err := yaml.Unmarshal(data, &sessionStorage); err != nil {
logrus.WithError(err).Error("Failed to unmarshal sessions.yaml") logrus.WithError(err).Error("Failed to unmarshal sessions.yaml")
ensureEmptySessionStorage() ensureEmptySessionStorage()
return return
} }
ensureSessionStorageInitialized() ensureEmptySessionStorage()
} }
func ensureEmptySessionStorage() { func ensureEmptySessionStorage() {
if sessionStorage == nil { if sessionStorage == nil {
sessionStorage = &SessionStorage{Providers: make(map[string]*SessionProvider)} sessionStorage = &SessionStorage{Providers: make(map[string]*SessionProvider)}
} }
if sessionStorage.Providers == nil { if sessionStorage.Providers == nil {
sessionStorage.Providers = make(map[string]*SessionProvider) 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)
}
} }
func saveUserSessions(cfg *config.Config) { func saveUserSessions(cfg *config.Config) {

View File

@ -46,88 +46,88 @@ func AppendSourceWithIncludes(cfg *Config, k *koanf.Koanf, configPath string) {
} }
func AppendSource(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) { if !unmarshalRoot(k, cfg) {
return 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 { func unmarshalRoot(k *koanf.Koanf, cfg *Config) bool {
if err := k.Unmarshal(".", cfg); err != nil { if err := k.Unmarshal(".", cfg); err != nil {
log.Errorf("Error unmarshalling config: %v", err) log.Errorf("Error unmarshalling config: %v", err)
return false return false
} }
return true return true
} }
func loadCollectionsFallbacks(k *koanf.Koanf, cfg *Config) { func loadCollectionsFallbacks(k *koanf.Koanf, cfg *Config) {
maybeUnmarshalActions(k, cfg) maybeUnmarshalActions(k, cfg)
maybeUnmarshalDashboards(k, cfg) maybeUnmarshalDashboards(k, cfg)
maybeUnmarshalEntities(k, cfg) maybeUnmarshalEntities(k, cfg)
maybeUnmarshalAuthLocalUsers(k, cfg) maybeUnmarshalAuthLocalUsers(k, cfg)
} }
func maybeUnmarshalActions(k *koanf.Koanf, cfg *Config) { func maybeUnmarshalActions(k *koanf.Koanf, cfg *Config) {
if len(cfg.Actions) != 0 || !k.Exists("actions") { if len(cfg.Actions) != 0 || !k.Exists("actions") {
return return
} }
var actions []*Action var actions []*Action
if err := k.Unmarshal("actions", &actions); err == nil { if err := k.Unmarshal("actions", &actions); err == nil {
cfg.Actions = actions cfg.Actions = actions
log.Debugf("Manually loaded %d actions", len(actions)) log.Debugf("Manually loaded %d actions", len(actions))
} }
} }
func maybeUnmarshalDashboards(k *koanf.Koanf, cfg *Config) { func maybeUnmarshalDashboards(k *koanf.Koanf, cfg *Config) {
if len(cfg.Dashboards) != 0 || !k.Exists("dashboards") { if len(cfg.Dashboards) != 0 || !k.Exists("dashboards") {
return return
} }
var dashboards []*DashboardComponent var dashboards []*DashboardComponent
if err := k.Unmarshal("dashboards", &dashboards); err == nil { if err := k.Unmarshal("dashboards", &dashboards); err == nil {
cfg.Dashboards = dashboards cfg.Dashboards = dashboards
log.Debugf("Manually loaded %d dashboards", len(dashboards)) log.Debugf("Manually loaded %d dashboards", len(dashboards))
} }
} }
func maybeUnmarshalEntities(k *koanf.Koanf, cfg *Config) { func maybeUnmarshalEntities(k *koanf.Koanf, cfg *Config) {
if len(cfg.Entities) != 0 || !k.Exists("entities") { if len(cfg.Entities) != 0 || !k.Exists("entities") {
return return
} }
var entities []*EntityFile var entities []*EntityFile
if err := k.Unmarshal("entities", &entities); err == nil { if err := k.Unmarshal("entities", &entities); err == nil {
cfg.Entities = entities cfg.Entities = entities
log.Debugf("Manually loaded %d entities", len(entities)) log.Debugf("Manually loaded %d entities", len(entities))
} }
} }
func maybeUnmarshalAuthLocalUsers(k *koanf.Koanf, cfg *Config) { func maybeUnmarshalAuthLocalUsers(k *koanf.Koanf, cfg *Config) {
if len(cfg.AuthLocalUsers.Users) != 0 || !k.Exists("authLocalUsers") { if len(cfg.AuthLocalUsers.Users) != 0 || !k.Exists("authLocalUsers") {
return return
} }
var authLocalUsers AuthLocalUsersConfig var authLocalUsers AuthLocalUsersConfig
if err := k.Unmarshal("authLocalUsers", &authLocalUsers); err == nil { if err := k.Unmarshal("authLocalUsers", &authLocalUsers); err == nil {
cfg.AuthLocalUsers = authLocalUsers cfg.AuthLocalUsers = authLocalUsers
log.Debugf("Manually loaded local auth config") log.Debugf("Manually loaded local auth config")
} }
} }
func afterLoadFinalize(cfg *Config, configPath string) { func afterLoadFinalize(cfg *Config, configPath string) {
metricConfigReloadedCount.Inc() metricConfigReloadedCount.Inc()
metricConfigActionCount.Set(float64(len(cfg.Actions))) metricConfigActionCount.Set(float64(len(cfg.Actions)))
cfg.SetDir(filepath.Dir(configPath)) cfg.SetDir(filepath.Dir(configPath))
cfg.Sanitize() cfg.Sanitize()
for _, l := range listeners { for _, l := range listeners {
l() l()
} }
} }
func applyConfigOverrides(k *koanf.Koanf, cfg *Config) { 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 // LoadIncludedConfigs loads configuration files from an include directory and merges them
func LoadIncludedConfigs(cfg *Config, k *koanf.Koanf, baseConfigPath string) { func LoadIncludedConfigs(cfg *Config, k *koanf.Koanf, baseConfigPath string) {
if cfg.Include == "" { if cfg.Include == "" {
return return
} }
includePath := filepath.Join(filepath.Dir(baseConfigPath), cfg.Include) includePath := filepath.Join(filepath.Dir(baseConfigPath), cfg.Include)
log.Infof("Loading included configs from: %s", includePath) log.Infof("Loading included configs from: %s", includePath)
yamlFiles, ok := listYamlFiles(includePath) yamlFiles, ok := listYamlFiles(includePath)
if !ok || len(yamlFiles) == 0 { if !ok || len(yamlFiles) == 0 {
return return
} }
sort.Strings(yamlFiles) sort.Strings(yamlFiles)
for _, filename := range yamlFiles { for _, filename := range yamlFiles {
loadAndMergeIncludedFile(cfg, includePath, filename) loadAndMergeIncludedFile(cfg, includePath, filename)
} }
log.Infof("Finished loading %d included config file(s)", len(yamlFiles)) log.Infof("Finished loading %d included config file(s)", len(yamlFiles))
cfg.Sanitize() cfg.Sanitize()
} }
func listYamlFiles(includePath string) ([]string, bool) { func listYamlFiles(includePath string) ([]string, bool) {
dirInfo, err := os.Stat(includePath) dirInfo, err := os.Stat(includePath)
if err != nil { if err != nil {
log.Warnf("Include directory not found: %s", includePath) log.Warnf("Include directory not found: %s", includePath)
return nil, false return nil, false
} }
if !dirInfo.IsDir() { if !dirInfo.IsDir() {
log.Warnf("Include path is not a directory: %s", includePath) log.Warnf("Include path is not a directory: %s", includePath)
return nil, false return nil, false
} }
entries, err := os.ReadDir(includePath) entries, err := os.ReadDir(includePath)
if err != nil { if err != nil {
log.Errorf("Error reading include directory: %v", err) log.Errorf("Error reading include directory: %v", err)
return nil, false return nil, false
} }
var yamlFiles []string var yamlFiles []string
for _, entry := range entries { for _, entry := range entries {
if entry.IsDir() { if entry.IsDir() {
continue continue
} }
name := entry.Name() name := entry.Name()
if strings.HasSuffix(name, ".yml") || strings.HasSuffix(name, ".yaml") { if strings.HasSuffix(name, ".yml") || strings.HasSuffix(name, ".yaml") {
yamlFiles = append(yamlFiles, name) yamlFiles = append(yamlFiles, name)
} }
} }
if len(yamlFiles) == 0 { if len(yamlFiles) == 0 {
log.Infof("No YAML files found in include directory: %s", includePath) log.Infof("No YAML files found in include directory: %s", includePath)
} }
return yamlFiles, true return yamlFiles, true
} }
func loadAndMergeIncludedFile(cfg *Config, includePath, filename string) { func loadAndMergeIncludedFile(cfg *Config, includePath, filename string) {
filePath := filepath.Join(includePath, filename) filePath := filepath.Join(includePath, filename)
log.Infof("Loading included config file: %s", filePath) log.Infof("Loading included config file: %s", filePath)
includeK := koanf.New(".") includeK := koanf.New(".")
if err := includeK.Load(file.Provider(filePath), yaml.Parser()); err != nil { if err := includeK.Load(file.Provider(filePath), yaml.Parser()); err != nil {
log.Errorf("Error loading included config file %s: %v", filePath, err) log.Errorf("Error loading included config file %s: %v", filePath, err)
return return
} }
tempCfg := &Config{} tempCfg := &Config{}
if err := includeK.Unmarshal(".", tempCfg); err != nil { if err := includeK.Unmarshal(".", tempCfg); err != nil {
log.Errorf("Error unmarshalling included config file %s: %v", filePath, err) log.Errorf("Error unmarshalling included config file %s: %v", filePath, err)
return 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)
}
}
mergeConfig(cfg, tempCfg) loadCollectionsFallbacks(includeK, tempCfg)
log.Infof("Successfully loaded and merged %s", filename)
mergeConfig(cfg, tempCfg)
log.Infof("Successfully loaded and merged %s", filename)
} }
func mergeConfig(base *Config, overlay *Config) { func mergeConfig(base *Config, overlay *Config) {
mergeSlices(base, overlay) mergeSlices(base, overlay)
overrideSimple(base, overlay) overrideSimple(base, overlay)
overrideNested(base, overlay) overrideNested(base, overlay)
overrideStrings(base, overlay) overrideStrings(base, overlay)
} }
func mergeSlices(base *Config, overlay *Config) { func mergeSlices(base *Config, overlay *Config) {
if len(overlay.Actions) > 0 { if len(overlay.Actions) > 0 {
base.Actions = append(base.Actions, overlay.Actions...) base.Actions = append(base.Actions, overlay.Actions...)
} }
if len(overlay.Dashboards) > 0 { if len(overlay.Dashboards) > 0 {
base.Dashboards = append(base.Dashboards, overlay.Dashboards...) base.Dashboards = append(base.Dashboards, overlay.Dashboards...)
log.Debugf("Merged %d dashboards from include", len(overlay.Dashboards)) log.Debugf("Merged %d dashboards from include", len(overlay.Dashboards))
} }
if len(overlay.Entities) > 0 { if len(overlay.Entities) > 0 {
base.Entities = append(base.Entities, overlay.Entities...) base.Entities = append(base.Entities, overlay.Entities...)
log.Debugf("Merged %d entities from include", len(overlay.Entities)) log.Debugf("Merged %d entities from include", len(overlay.Entities))
} }
if len(overlay.AccessControlLists) > 0 { if len(overlay.AccessControlLists) > 0 {
base.AccessControlLists = append(base.AccessControlLists, overlay.AccessControlLists...) base.AccessControlLists = append(base.AccessControlLists, overlay.AccessControlLists...)
log.Debugf("Merged %d access control lists from include", len(overlay.AccessControlLists)) log.Debugf("Merged %d access control lists from include", len(overlay.AccessControlLists))
} }
if len(overlay.AuthLocalUsers.Users) > 0 { if len(overlay.AuthLocalUsers.Users) > 0 {
base.AuthLocalUsers.Users = append(base.AuthLocalUsers.Users, overlay.AuthLocalUsers.Users...) base.AuthLocalUsers.Users = append(base.AuthLocalUsers.Users, overlay.AuthLocalUsers.Users...)
log.Debugf("Merged %d local users from include", len(overlay.AuthLocalUsers.Users)) log.Debugf("Merged %d local users from include", len(overlay.AuthLocalUsers.Users))
} }
if len(overlay.StyleMods) > 0 { if len(overlay.StyleMods) > 0 {
base.StyleMods = append(base.StyleMods, overlay.StyleMods...) base.StyleMods = append(base.StyleMods, overlay.StyleMods...)
} }
if len(overlay.AdditionalNavigationLinks) > 0 { if len(overlay.AdditionalNavigationLinks) > 0 {
base.AdditionalNavigationLinks = append(base.AdditionalNavigationLinks, overlay.AdditionalNavigationLinks...) base.AdditionalNavigationLinks = append(base.AdditionalNavigationLinks, overlay.AdditionalNavigationLinks...)
} }
} }
func overrideSimple(base *Config, overlay *Config) { func overrideSimple(base *Config, overlay *Config) {
if overlay.LogLevel != "" { if overlay.LogLevel != "" {
base.LogLevel = overlay.LogLevel base.LogLevel = overlay.LogLevel
} }
if overlay.PageTitle != "" { if overlay.PageTitle != "" {
base.PageTitle = overlay.PageTitle base.PageTitle = overlay.PageTitle
} }
if overlay.ShowFooter != base.ShowFooter { if overlay.ShowFooter != base.ShowFooter {
base.ShowFooter = overlay.ShowFooter base.ShowFooter = overlay.ShowFooter
} }
if overlay.ShowNavigation != base.ShowNavigation { if overlay.ShowNavigation != base.ShowNavigation {
base.ShowNavigation = overlay.ShowNavigation base.ShowNavigation = overlay.ShowNavigation
} }
if overlay.CheckForUpdates != base.CheckForUpdates { if overlay.CheckForUpdates != base.CheckForUpdates {
base.CheckForUpdates = overlay.CheckForUpdates base.CheckForUpdates = overlay.CheckForUpdates
} }
if overlay.UseSingleHTTPFrontend != base.UseSingleHTTPFrontend { if overlay.UseSingleHTTPFrontend != base.UseSingleHTTPFrontend {
base.UseSingleHTTPFrontend = overlay.UseSingleHTTPFrontend base.UseSingleHTTPFrontend = overlay.UseSingleHTTPFrontend
} }
if overlay.AuthRequireGuestsToLogin != base.AuthRequireGuestsToLogin { if overlay.AuthRequireGuestsToLogin != base.AuthRequireGuestsToLogin {
base.AuthRequireGuestsToLogin = overlay.AuthRequireGuestsToLogin base.AuthRequireGuestsToLogin = overlay.AuthRequireGuestsToLogin
} }
if overlay.AuthLocalUsers.Enabled { if overlay.AuthLocalUsers.Enabled {
base.AuthLocalUsers.Enabled = overlay.AuthLocalUsers.Enabled base.AuthLocalUsers.Enabled = overlay.AuthLocalUsers.Enabled
} }
} }
func overrideNested(base *Config, overlay *Config) { func overrideNested(base *Config, overlay *Config) {
if overlay.DefaultPolicy.ShowDiagnostics != base.DefaultPolicy.ShowDiagnostics { if overlay.DefaultPolicy.ShowDiagnostics != base.DefaultPolicy.ShowDiagnostics {
base.DefaultPolicy.ShowDiagnostics = overlay.DefaultPolicy.ShowDiagnostics base.DefaultPolicy.ShowDiagnostics = overlay.DefaultPolicy.ShowDiagnostics
} }
if overlay.DefaultPolicy.ShowLogList != base.DefaultPolicy.ShowLogList { if overlay.DefaultPolicy.ShowLogList != base.DefaultPolicy.ShowLogList {
base.DefaultPolicy.ShowLogList = overlay.DefaultPolicy.ShowLogList base.DefaultPolicy.ShowLogList = overlay.DefaultPolicy.ShowLogList
} }
if overlay.Prometheus.Enabled != base.Prometheus.Enabled { if overlay.Prometheus.Enabled != base.Prometheus.Enabled {
base.Prometheus.Enabled = overlay.Prometheus.Enabled base.Prometheus.Enabled = overlay.Prometheus.Enabled
} }
if overlay.Prometheus.DefaultGoMetrics != base.Prometheus.DefaultGoMetrics { if overlay.Prometheus.DefaultGoMetrics != base.Prometheus.DefaultGoMetrics {
base.Prometheus.DefaultGoMetrics = overlay.Prometheus.DefaultGoMetrics base.Prometheus.DefaultGoMetrics = overlay.Prometheus.DefaultGoMetrics
} }
} }
func overrideStrings(base *Config, overlay *Config) { func overrideStrings(base *Config, overlay *Config) {
overrideString(&base.BannerMessage, overlay.BannerMessage) overrideString(&base.BannerMessage, overlay.BannerMessage)
overrideString(&base.BannerCSS, overlay.BannerCSS) overrideString(&base.BannerCSS, overlay.BannerCSS)
overrideString(&base.LogLevel, overlay.LogLevel) overrideString(&base.LogLevel, overlay.LogLevel)
overrideString(&base.PageTitle, overlay.PageTitle) overrideString(&base.PageTitle, overlay.PageTitle)
overrideString(&base.SectionNavigationStyle, overlay.SectionNavigationStyle) overrideString(&base.SectionNavigationStyle, overlay.SectionNavigationStyle)
overrideString(&base.DefaultPopupOnStart, overlay.DefaultPopupOnStart) overrideString(&base.DefaultPopupOnStart, overlay.DefaultPopupOnStart)
} }
func overrideString(base *string, overlay string) { func overrideString(base *string, overlay string) {

View File

@ -30,33 +30,42 @@ func AddListener(l func()) {
} }
func SetupEntityFileWatchers(cfg *config.Config) { func SetupEntityFileWatchers(cfg *config.Config) {
baseDir := resolveEntitiesBaseDir(cfg.GetDir()) baseDir := resolveEntitiesBaseDir(cfg.GetDir())
for i := range cfg.Entities { // #337 - iterate by key, not by value for i := range cfg.Entities { // #337 - iterate by key, not by value
ef := cfg.Entities[i] ef := cfg.Entities[i]
watchAndLoadEntity(baseDir, ef) watchAndLoadEntity(baseDir, ef)
} }
} }
//gocyclo:ignore
func resolveEntitiesBaseDir(configDir string) string { func resolveEntitiesBaseDir(configDir string) string {
absConfigDir, _ := filepath.Abs(configDir) absConfigDir, err := filepath.Abs(configDir)
if strings.Contains(absConfigDir, "integration-tests") {
return configDir if err != nil {
} log.Errorf("Error getting absolute path for %s: %v", configDir, err)
devVar := filepath.Join(configDir, "var") return configDir
if _, err := os.Stat(devVar); err == nil { }
return devVar
} if strings.Contains(absConfigDir, "integration-tests") {
return configDir 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) { func watchAndLoadEntity(baseDir string, ef *config.EntityFile) {
p := ef.File p := ef.File
if !filepath.IsAbs(p) { if !filepath.IsAbs(p) {
p = filepath.Join(baseDir, p) p = filepath.Join(baseDir, p)
log.WithFields(log.Fields{"entityFile": p}).Debugf("Adding config dir to entity file path") 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) }) go filehelper.WatchFileWrite(p, func(filename string) { loadEntityFile(p, ef.Name) })
loadEntityFile(p, ef.Name) loadEntityFile(p, ef.Name)
} }
func loadEntityFile(filename string, entityname string) { func loadEntityFile(filename string, entityname string) {

View File

@ -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) { func parseActionExec(values map[string]string, action *config.Action, entity *entities.Entity) ([]string, error) {
if action == nil { if action == nil {
return nil, fmt.Errorf("action is nil") return nil, fmt.Errorf("action is nil")
} }
if err := validateArguments(values, action); err != nil { if err := validateArguments(values, action); err != nil {
return nil, err return nil, err
} }
parsed := make([]string, len(action.Exec)) parsed := make([]string, len(action.Exec))
for i, a := range action.Exec { for i, a := range action.Exec {
arg, err := parseCommandForReplacements(a, values, entity) out, err := parseSingleExec(a, values, entity)
if err != nil { if err != nil {
return nil, err return nil, err
} }
parsed[i] = entities.ParseTemplateWithArgs(arg, entity, values) parsed[i] = out
} }
logParsedExec(action, parsed, values) logParsedExec(action, parsed, values)
return parsed, nil 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 { func validateArguments(values map[string]string, action *config.Action) error {
for _, arg := range action.Arguments { for _, arg := range action.Arguments {
if err := typecheckActionArgument(&arg, values[arg.Name], action); err != nil { if err := typecheckActionArgument(&arg, values[arg.Name], action); err != nil {
return err return err
} }
log.WithFields(log.Fields{"name": arg.Name, "value": values[arg.Name]}).Debugf("Arg assigned") log.WithFields(log.Fields{"name": arg.Name, "value": values[arg.Name]}).Debugf("Arg assigned")
} }
return nil return nil
} }
func logParsedExec(action *config.Action, parsed []string, values map[string]string) { func logParsedExec(action *config.Action, parsed []string, values map[string]string) {
redacted := redactExecArgs(parsed, action.Arguments, values) redacted := redactExecArgs(parsed, action.Arguments, values)
log.WithFields(log.Fields{"actionTitle": action.Title, "cmd": redacted}).Infof("Action parse args - After (Exec)") 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) { 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 { func checkShellArgumentSafety(action *config.Action) error {
if action.Shell == "" { if action.Shell == "" {
return nil return nil
} }
unsafe := map[string]struct{}{"url": {}, "email": {}, "raw_string_multiline": {}, "very_dangerous_raw_string": {}} unsafe := map[string]struct{}{"url": {}, "email": {}, "raw_string_multiline": {}, "very_dangerous_raw_string": {}}
for _, arg := range action.Arguments { for _, arg := range action.Arguments {
if _, bad := unsafe[arg.Type]; bad { 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 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 return nil
} }
func mangleInvalidArgumentValues(req *ExecutionRequest) { func mangleInvalidArgumentValues(req *ExecutionRequest) {

View File

@ -224,6 +224,48 @@ func (e *Executor) GetLogTrackingIds(startOffset int64, pageCount int64) ([]*Int
return trackingIds, pagingResult 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) { func (e *Executor) GetLog(trackingID string) (*InternalLogEntry, bool) {
e.logmutex.RLock() e.logmutex.RLock()
@ -434,18 +476,38 @@ func stepParseArgs(req *ExecutionRequest) bool {
return fail(req, fmt.Errorf("cannot parse arguments: Binding or Action is nil")) return fail(req, fmt.Errorf("cannot parse arguments: Binding or Action is nil"))
} }
mangleInvalidArgumentValues(req) mangleInvalidArgumentValues(req)
if hasExec(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)
} func handleExecBranch(req *ExecutionRequest) bool {
cmd, err := parseActionArguments(req.Arguments, req.Binding.Action, req.Binding.Entity) args, err := parseActionExec(req.Arguments, req.Binding.Action, req.Binding.Entity)
if err != nil { if err != nil {
return fail(req, err) 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.useDirectExec = false
req.finalParsedCommand = cmd req.finalParsedCommand = cmd
return true return true
@ -470,16 +532,6 @@ func hasExec(req *ExecutionRequest) bool {
return len(req.Binding.Action.Exec) > 0 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 { func fail(req *ExecutionRequest, err error) bool {
req.logEntry.Output = err.Error() req.logEntry.Output = err.Error()
log.Warn(err.Error()) log.Warn(err.Error())