feature: Policy support - allow hiding daignostics and logs (#569)

This commit is contained in:
James Read 2025-04-20 00:05:49 +01:00 committed by GitHub
parent f02982b451
commit c19428f6b6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 193 additions and 29 deletions

View File

@ -0,0 +1,14 @@
# Integration Test Config: Policy All False
#
logLevel: "DEBUG"
checkForUpdates: false
defaultPolicy:
showDiagnostics: false
showLogList: false
actions:
- title: sleep 2 seconds
shell: sleep 2
icon: "&#x1F971"

View File

@ -37,6 +37,15 @@ describe('config: general', function () {
*/
})
it('navbar contains default policy links', async function () {
await getRootAndWait()
const logListLink = await webdriver.findElements(By.css('[href="/logs"]'))
expect(logListLink).to.not.be.empty
const diagnosticsLink = await webdriver.findElements(By.css('[href="/diagnostics"]'))
expect(diagnosticsLink).to.not.be.empty
})
it('Footer contains promo', async function () {
const ftr = await webdriver.findElement(By.tagName('footer')).getText()

View File

@ -0,0 +1,32 @@
import {
getRootAndWait,
takeScreenshotOnFailure,
} from '../lib/elements.js'
import { By } from 'selenium-webdriver'
import { expect } from 'chai'
describe('config: policy-all-false', function () {
before(async function () {
await runner.start('policy-all-false')
});
after(async () => {
await runner.stop()
});
afterEach(function () {
takeScreenshotOnFailure(this.currentTest, webdriver);
});
it('navbar should not contain default policy links', async function () {
await getRootAndWait()
const logListLink = await webdriver.findElements(By.css('[href="/logs"]'))
expect(logListLink).to.be.empty
const diagnosticsLink = await webdriver.findElements(By.css('[href="/diagnostics"]'))
expect(diagnosticsLink).to.be.empty
})
})

View File

@ -49,6 +49,19 @@ message GetDashboardComponentsResponse {
string authenticated_user = 5;
string authenticated_user_provider = 6;
EffectivePolicy effective_policy = 7;
Diagnostics diagnostics = 8;
}
message Diagnostics {
string SshFoundKey = 1;
string SshFoundConfig = 2;
}
message EffectivePolicy {
bool show_diagnostics = 1;
bool show_log_list = 2;
}
message GetDashboardComponentsRequest {}

View File

@ -32,6 +32,8 @@ type AuthenticatedUser struct {
SID string
Acls []string
EffectivePolicy *config.ConfigurationPolicy
}
func (u *AuthenticatedUser) IsGuest() bool {
@ -43,15 +45,22 @@ func logAclNotMatched(cfg *config.Config, aclFunction string, user *Authenticate
log.WithFields(log.Fields{
"User": user.Username,
"Action": action.Title,
}).Debugf("%v - No ACLs Matched", aclFunction)
"ACL": acl.Name,
}).Debugf("%v - ACL Not Matched", aclFunction)
}
}
func logAclMatched(cfg *config.Config, aclFunction string, user *AuthenticatedUser, action *config.Action, acl *config.AccessControlList) {
actionTitle := "N/A"
if action != nil {
actionTitle = action.Title
}
if cfg.LogDebugOptions.AclMatched {
log.WithFields(log.Fields{
"User": user.Username,
"Action": action.Title,
"Action": actionTitle,
"ACL": acl.Name,
}).Debugf("%v - Matched ACL", aclFunction)
}
@ -209,6 +218,8 @@ func buildUserAcls(cfg *config.Config, user *AuthenticatedUser) {
continue
}
}
user.EffectivePolicy = getEffectivePolicy(cfg, user)
}
func hasGroupsMatch(matchUsergroups []string, usergroup string) bool {
@ -249,3 +260,32 @@ func getRelevantAcls(cfg *config.Config, actionAcls []string, user *Authenticate
return ret
}
func getEffectivePolicy(cfg *config.Config, user *AuthenticatedUser) *config.ConfigurationPolicy {
ret := &config.ConfigurationPolicy{
ShowDiagnostics: cfg.DefaultPolicy.ShowDiagnostics,
ShowLogList: cfg.DefaultPolicy.ShowLogList,
}
for _, acl := range cfg.AccessControlLists {
if slices.Contains(user.Acls, acl.Name) {
logAclMatched(cfg, "GetEffectivePolicy", user, nil, acl)
ret = buildConfigurationPolicy(ret, acl.Policy)
}
}
return ret
}
func buildConfigurationPolicy(ret *config.ConfigurationPolicy, policy config.ConfigurationPolicy) *config.ConfigurationPolicy {
if policy.ShowDiagnostics {
ret.ShowDiagnostics = policy.ShowDiagnostics
}
if policy.ShowLogList {
ret.ShowLogList = policy.ShowLogList
}
return ret
}

View File

@ -76,6 +76,13 @@ type AccessControlList struct {
MatchUsergroups []string
MatchUsernames []string
Permissions PermissionsList
Policy ConfigurationPolicy
}
// ConfigurationPolicy defines global settings which are overridden with an ACL.
type ConfigurationPolicy struct {
ShowDiagnostics bool
ShowLogList bool
}
type PrometheusConfig struct {
@ -123,6 +130,7 @@ type Config struct {
AuthOAuth2RedirectURL string
AuthOAuth2Providers map[string]*OAuth2Provider
DefaultPermissions PermissionsList
DefaultPolicy ConfigurationPolicy
AccessControlLists []*AccessControlList
WebUIDir string
CronSupportForSeconds bool
@ -243,5 +251,8 @@ func DefaultConfigWithBasePort(basePort int) *Config {
config.ListenAddressWebUI = fmt.Sprintf("localhost:%d", basePort+3)
config.ListenAddressPrometheus = fmt.Sprintf("localhost:%d", basePort+4)
config.DefaultPolicy.ShowDiagnostics = true
config.DefaultPolicy.ShowLogList = true
return &config
}

View File

@ -5,6 +5,7 @@ import (
acl "github.com/OliveTin/OliveTin/internal/acl"
config "github.com/OliveTin/OliveTin/internal/config"
executor "github.com/OliveTin/OliveTin/internal/executor"
installationinfo "github.com/OliveTin/OliveTin/internal/installationinfo"
sv "github.com/OliveTin/OliveTin/internal/stringvariables"
"sort"
)
@ -35,9 +36,32 @@ func buildDashboardResponse(ex *executor.Executor, cfg *config.Config, user *acl
}
})
res.EffectivePolicy = buildEffectivePolicy(user.EffectivePolicy)
res.Diagnostics = buildDiagnostics(res.EffectivePolicy.ShowDiagnostics)
return res
}
func buildEffectivePolicy(policy *config.ConfigurationPolicy) *apiv1.EffectivePolicy {
ret := &apiv1.EffectivePolicy{
ShowDiagnostics: policy.ShowDiagnostics,
ShowLogList: policy.ShowLogList,
}
return ret
}
func buildDiagnostics(showDiagnostics bool) *apiv1.Diagnostics {
ret := &apiv1.Diagnostics{}
if showDiagnostics {
ret.SshFoundKey = installationinfo.Runtime.SshFoundKey
ret.SshFoundConfig = installationinfo.Runtime.SshFoundConfig
}
return ret
}
func buildAction(actionId string, actionBinding *executor.ActionBinding, user *acl.AuthenticatedUser) *apiv1.Action {
action := actionBinding.Action

View File

@ -29,8 +29,6 @@ type webUISettings struct {
PageTitle string
SectionNavigationStyle string
DefaultIconForBack string
SshFoundKey string
SshFoundConfig string
EnableCustomJs bool
AuthLoginUrl string
AuthLocalLogin bool
@ -138,8 +136,6 @@ func generateWebUISettings(w http.ResponseWriter, r *http.Request) {
PageTitle: cfg.PageTitle,
SectionNavigationStyle: cfg.SectionNavigationStyle,
DefaultIconForBack: cfg.DefaultIconForBack,
SshFoundKey: installationinfo.Runtime.SshFoundKey,
SshFoundConfig: installationinfo.Runtime.SshFoundConfig,
EnableCustomJs: cfg.EnableCustomJs,
AuthLoginUrl: cfg.AuthLoginUrl,
AuthLocalLogin: cfg.AuthLocalUsers.Enabled,

View File

@ -35,12 +35,6 @@
</ul>
<ul id = "supplemental-links">
<li title = "Diagnostics">
<a id = "showDiagnostics">Diagnostics</a>
</li>
<li title = "Logs">
<a id = "showLogs">Logs</a>
</li>
</ul>
</nav>

View File

@ -0,0 +1,33 @@
export class NavigationBar {
constructor() {
this.navbar = document.getElementsByTagName('nav')[0]
this.mainLinks = document.getElementById('navigation-links')
this.supplementalLinks = document.getElementById('supplemental-links')
}
createLink(title, url, isSupplemental) {
const linkA = document.createElement('a')
linkA.href = url
linkA.innerText = title
const navigationLi = document.createElement('li')
navigationLi.appendChild(linkA)
navigationLi.title = title
if (isSupplemental) {
this.supplementalLinks.appendChild(navigationLi)
} else {
this.mainLinks.appendChild(navigationLi)
}
}
refreshSectionPolicyLinks(policy) {
if (policy.showDiagnostics) {
this.createLink('Diagnostics', '/diagnostics', true)
}
if (policy.showLogList) {
this.createLink('Logs', '/logs', true)
}
}
}

View File

@ -1,4 +1,5 @@
import './ActionButton.js' // To define action-button
import { NavigationBar } from './NavigationBar.js'
import { ExecutionDialog } from './ExecutionDialog.js'
import { ActionStatusDisplay } from './ActionStatusDisplay.js'
@ -76,6 +77,8 @@ function createAnnotation (key, val) {
* This is a weird function that just sets some globals.
*/
export function initMarshaller () {
window.navbar = new NavigationBar()
window.showSection = showSection
window.showSectionView = showSectionView
@ -124,6 +127,10 @@ export function marshalDashboardComponentsJsonToHtml (json) {
marshalActionsJsonToHtml(json)
marshalDashboardStructureToHtml(json)
window.navbar.refreshSectionPolicyLinks(json.effectivePolicy)
refreshDiagnostics(json)
}
document.body.setAttribute('initial-marshal-complete', 'true')
@ -342,8 +349,8 @@ export function setupSectionNavigation (style) {
}
registerSection('/', 'Actions', null, document.getElementById('showActions'))
registerSection('/diagnostics', 'Diagnostics', null, document.getElementById('showDiagnostics'))
registerSection('/logs', 'Logs', null, document.getElementById('showLogs'))
registerSection('/diagnostics', 'Diagnostics', null, null)
registerSection('/logs', 'Logs', null, null)
registerSection('/login', 'Login', null, null)
}
@ -368,9 +375,9 @@ function addLinkToSection (pathName, element) {
}
}
export function refreshDiagnostics () {
document.getElementById('diagnostics-sshfoundkey').innerHTML = window.settings.SshFoundKey
document.getElementById('diagnostics-sshfoundconfig').innerHTML = window.settings.SshFoundConfig
function refreshDiagnostics (json) {
document.getElementById('diagnostics-sshfoundkey').innerHTML = json.diagnostics.SshFoundKey
document.getElementById('diagnostics-sshfoundconfig').innerHTML = json.diagnostics.SshFoundConfig
}
function getSystemTitle (title) {
@ -400,17 +407,11 @@ function marshalSingleDashboard (dashboard, nav) {
oldLi.remove()
}
const navigationA = document.createElement('a')
navigationA.title = dashboard.title
navigationA.innerText = dashboard.title
const systemTitleUrl = '/' + getSystemTitle(dashboard.title)
registerSection('/' + getSystemTitle(section.title), section.title, null, navigationA)
window.navbar.createLink(dashboard.title, systemTitleUrl, false)
const navigationLi = document.createElement('li')
navigationLi.appendChild(navigationA)
navigationLi.title = dashboard.title
document.getElementById('navigation-links').appendChild(navigationLi)
registerSection(systemTitleUrl, section.title, null, null)
}
function marshalDashboardStructureToHtml (json) {

View File

@ -6,7 +6,6 @@ import {
marshalDashboardComponentsJsonToHtml,
marshalLogsJsonToHtml,
refreshServerConnectionLabel,
refreshDiagnostics
} from './js/marshaller.js'
import { checkWebsocketConnection } from './js/websocket.js'
@ -139,8 +138,6 @@ function processWebuiSettingsJson (settings) {
document.getElementsByTagName('main')[0].appendChild(loginForm)
window.settings = settings
refreshDiagnostics()
}
function processAdditionalLinks (links) {