feature: Policy support - allow hiding daignostics and logs (#569)
This commit is contained in:
parent
f02982b451
commit
c19428f6b6
|
|
@ -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: "🥱"
|
||||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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
|
||||
})
|
||||
})
|
||||
|
|
@ -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 {}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
Loading…
Reference in New Issue