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 () {
|
it('Footer contains promo', async function () {
|
||||||
const ftr = await webdriver.findElement(By.tagName('footer')).getText()
|
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 = 5;
|
||||||
string authenticated_user_provider = 6;
|
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 {}
|
message GetDashboardComponentsRequest {}
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,8 @@ type AuthenticatedUser struct {
|
||||||
SID string
|
SID string
|
||||||
|
|
||||||
Acls []string
|
Acls []string
|
||||||
|
|
||||||
|
EffectivePolicy *config.ConfigurationPolicy
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *AuthenticatedUser) IsGuest() bool {
|
func (u *AuthenticatedUser) IsGuest() bool {
|
||||||
|
|
@ -43,15 +45,22 @@ func logAclNotMatched(cfg *config.Config, aclFunction string, user *Authenticate
|
||||||
log.WithFields(log.Fields{
|
log.WithFields(log.Fields{
|
||||||
"User": user.Username,
|
"User": user.Username,
|
||||||
"Action": action.Title,
|
"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) {
|
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 {
|
if cfg.LogDebugOptions.AclMatched {
|
||||||
log.WithFields(log.Fields{
|
log.WithFields(log.Fields{
|
||||||
"User": user.Username,
|
"User": user.Username,
|
||||||
"Action": action.Title,
|
"Action": actionTitle,
|
||||||
"ACL": acl.Name,
|
"ACL": acl.Name,
|
||||||
}).Debugf("%v - Matched ACL", aclFunction)
|
}).Debugf("%v - Matched ACL", aclFunction)
|
||||||
}
|
}
|
||||||
|
|
@ -209,6 +218,8 @@ func buildUserAcls(cfg *config.Config, user *AuthenticatedUser) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
user.EffectivePolicy = getEffectivePolicy(cfg, user)
|
||||||
}
|
}
|
||||||
|
|
||||||
func hasGroupsMatch(matchUsergroups []string, usergroup string) bool {
|
func hasGroupsMatch(matchUsergroups []string, usergroup string) bool {
|
||||||
|
|
@ -249,3 +260,32 @@ func getRelevantAcls(cfg *config.Config, actionAcls []string, user *Authenticate
|
||||||
|
|
||||||
return ret
|
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
|
MatchUsergroups []string
|
||||||
MatchUsernames []string
|
MatchUsernames []string
|
||||||
Permissions PermissionsList
|
Permissions PermissionsList
|
||||||
|
Policy ConfigurationPolicy
|
||||||
|
}
|
||||||
|
|
||||||
|
// ConfigurationPolicy defines global settings which are overridden with an ACL.
|
||||||
|
type ConfigurationPolicy struct {
|
||||||
|
ShowDiagnostics bool
|
||||||
|
ShowLogList bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type PrometheusConfig struct {
|
type PrometheusConfig struct {
|
||||||
|
|
@ -123,6 +130,7 @@ type Config struct {
|
||||||
AuthOAuth2RedirectURL string
|
AuthOAuth2RedirectURL string
|
||||||
AuthOAuth2Providers map[string]*OAuth2Provider
|
AuthOAuth2Providers map[string]*OAuth2Provider
|
||||||
DefaultPermissions PermissionsList
|
DefaultPermissions PermissionsList
|
||||||
|
DefaultPolicy ConfigurationPolicy
|
||||||
AccessControlLists []*AccessControlList
|
AccessControlLists []*AccessControlList
|
||||||
WebUIDir string
|
WebUIDir string
|
||||||
CronSupportForSeconds bool
|
CronSupportForSeconds bool
|
||||||
|
|
@ -243,5 +251,8 @@ func DefaultConfigWithBasePort(basePort int) *Config {
|
||||||
config.ListenAddressWebUI = fmt.Sprintf("localhost:%d", basePort+3)
|
config.ListenAddressWebUI = fmt.Sprintf("localhost:%d", basePort+3)
|
||||||
config.ListenAddressPrometheus = fmt.Sprintf("localhost:%d", basePort+4)
|
config.ListenAddressPrometheus = fmt.Sprintf("localhost:%d", basePort+4)
|
||||||
|
|
||||||
|
config.DefaultPolicy.ShowDiagnostics = true
|
||||||
|
config.DefaultPolicy.ShowLogList = true
|
||||||
|
|
||||||
return &config
|
return &config
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import (
|
||||||
acl "github.com/OliveTin/OliveTin/internal/acl"
|
acl "github.com/OliveTin/OliveTin/internal/acl"
|
||||||
config "github.com/OliveTin/OliveTin/internal/config"
|
config "github.com/OliveTin/OliveTin/internal/config"
|
||||||
executor "github.com/OliveTin/OliveTin/internal/executor"
|
executor "github.com/OliveTin/OliveTin/internal/executor"
|
||||||
|
installationinfo "github.com/OliveTin/OliveTin/internal/installationinfo"
|
||||||
sv "github.com/OliveTin/OliveTin/internal/stringvariables"
|
sv "github.com/OliveTin/OliveTin/internal/stringvariables"
|
||||||
"sort"
|
"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
|
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 {
|
func buildAction(actionId string, actionBinding *executor.ActionBinding, user *acl.AuthenticatedUser) *apiv1.Action {
|
||||||
action := actionBinding.Action
|
action := actionBinding.Action
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -29,8 +29,6 @@ type webUISettings struct {
|
||||||
PageTitle string
|
PageTitle string
|
||||||
SectionNavigationStyle string
|
SectionNavigationStyle string
|
||||||
DefaultIconForBack string
|
DefaultIconForBack string
|
||||||
SshFoundKey string
|
|
||||||
SshFoundConfig string
|
|
||||||
EnableCustomJs bool
|
EnableCustomJs bool
|
||||||
AuthLoginUrl string
|
AuthLoginUrl string
|
||||||
AuthLocalLogin bool
|
AuthLocalLogin bool
|
||||||
|
|
@ -138,8 +136,6 @@ func generateWebUISettings(w http.ResponseWriter, r *http.Request) {
|
||||||
PageTitle: cfg.PageTitle,
|
PageTitle: cfg.PageTitle,
|
||||||
SectionNavigationStyle: cfg.SectionNavigationStyle,
|
SectionNavigationStyle: cfg.SectionNavigationStyle,
|
||||||
DefaultIconForBack: cfg.DefaultIconForBack,
|
DefaultIconForBack: cfg.DefaultIconForBack,
|
||||||
SshFoundKey: installationinfo.Runtime.SshFoundKey,
|
|
||||||
SshFoundConfig: installationinfo.Runtime.SshFoundConfig,
|
|
||||||
EnableCustomJs: cfg.EnableCustomJs,
|
EnableCustomJs: cfg.EnableCustomJs,
|
||||||
AuthLoginUrl: cfg.AuthLoginUrl,
|
AuthLoginUrl: cfg.AuthLoginUrl,
|
||||||
AuthLocalLogin: cfg.AuthLocalUsers.Enabled,
|
AuthLocalLogin: cfg.AuthLocalUsers.Enabled,
|
||||||
|
|
|
||||||
|
|
@ -35,12 +35,6 @@
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<ul id = "supplemental-links">
|
<ul id = "supplemental-links">
|
||||||
<li title = "Diagnostics">
|
|
||||||
<a id = "showDiagnostics">Diagnostics</a>
|
|
||||||
</li>
|
|
||||||
<li title = "Logs">
|
|
||||||
<a id = "showLogs">Logs</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
</ul>
|
||||||
</nav>
|
</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 './ActionButton.js' // To define action-button
|
||||||
|
import { NavigationBar } from './NavigationBar.js'
|
||||||
import { ExecutionDialog } from './ExecutionDialog.js'
|
import { ExecutionDialog } from './ExecutionDialog.js'
|
||||||
import { ActionStatusDisplay } from './ActionStatusDisplay.js'
|
import { ActionStatusDisplay } from './ActionStatusDisplay.js'
|
||||||
|
|
||||||
|
|
@ -76,6 +77,8 @@ function createAnnotation (key, val) {
|
||||||
* This is a weird function that just sets some globals.
|
* This is a weird function that just sets some globals.
|
||||||
*/
|
*/
|
||||||
export function initMarshaller () {
|
export function initMarshaller () {
|
||||||
|
window.navbar = new NavigationBar()
|
||||||
|
|
||||||
window.showSection = showSection
|
window.showSection = showSection
|
||||||
window.showSectionView = showSectionView
|
window.showSectionView = showSectionView
|
||||||
|
|
||||||
|
|
@ -124,6 +127,10 @@ export function marshalDashboardComponentsJsonToHtml (json) {
|
||||||
|
|
||||||
marshalActionsJsonToHtml(json)
|
marshalActionsJsonToHtml(json)
|
||||||
marshalDashboardStructureToHtml(json)
|
marshalDashboardStructureToHtml(json)
|
||||||
|
|
||||||
|
window.navbar.refreshSectionPolicyLinks(json.effectivePolicy)
|
||||||
|
|
||||||
|
refreshDiagnostics(json)
|
||||||
}
|
}
|
||||||
|
|
||||||
document.body.setAttribute('initial-marshal-complete', 'true')
|
document.body.setAttribute('initial-marshal-complete', 'true')
|
||||||
|
|
@ -342,8 +349,8 @@ export function setupSectionNavigation (style) {
|
||||||
}
|
}
|
||||||
|
|
||||||
registerSection('/', 'Actions', null, document.getElementById('showActions'))
|
registerSection('/', 'Actions', null, document.getElementById('showActions'))
|
||||||
registerSection('/diagnostics', 'Diagnostics', null, document.getElementById('showDiagnostics'))
|
registerSection('/diagnostics', 'Diagnostics', null, null)
|
||||||
registerSection('/logs', 'Logs', null, document.getElementById('showLogs'))
|
registerSection('/logs', 'Logs', null, null)
|
||||||
registerSection('/login', 'Login', null, null)
|
registerSection('/login', 'Login', null, null)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -368,9 +375,9 @@ function addLinkToSection (pathName, element) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function refreshDiagnostics () {
|
function refreshDiagnostics (json) {
|
||||||
document.getElementById('diagnostics-sshfoundkey').innerHTML = window.settings.SshFoundKey
|
document.getElementById('diagnostics-sshfoundkey').innerHTML = json.diagnostics.SshFoundKey
|
||||||
document.getElementById('diagnostics-sshfoundconfig').innerHTML = window.settings.SshFoundConfig
|
document.getElementById('diagnostics-sshfoundconfig').innerHTML = json.diagnostics.SshFoundConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
function getSystemTitle (title) {
|
function getSystemTitle (title) {
|
||||||
|
|
@ -400,17 +407,11 @@ function marshalSingleDashboard (dashboard, nav) {
|
||||||
oldLi.remove()
|
oldLi.remove()
|
||||||
}
|
}
|
||||||
|
|
||||||
const navigationA = document.createElement('a')
|
const systemTitleUrl = '/' + getSystemTitle(dashboard.title)
|
||||||
navigationA.title = dashboard.title
|
|
||||||
navigationA.innerText = dashboard.title
|
|
||||||
|
|
||||||
registerSection('/' + getSystemTitle(section.title), section.title, null, navigationA)
|
window.navbar.createLink(dashboard.title, systemTitleUrl, false)
|
||||||
|
|
||||||
const navigationLi = document.createElement('li')
|
registerSection(systemTitleUrl, section.title, null, null)
|
||||||
navigationLi.appendChild(navigationA)
|
|
||||||
navigationLi.title = dashboard.title
|
|
||||||
|
|
||||||
document.getElementById('navigation-links').appendChild(navigationLi)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function marshalDashboardStructureToHtml (json) {
|
function marshalDashboardStructureToHtml (json) {
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,6 @@ import {
|
||||||
marshalDashboardComponentsJsonToHtml,
|
marshalDashboardComponentsJsonToHtml,
|
||||||
marshalLogsJsonToHtml,
|
marshalLogsJsonToHtml,
|
||||||
refreshServerConnectionLabel,
|
refreshServerConnectionLabel,
|
||||||
refreshDiagnostics
|
|
||||||
} from './js/marshaller.js'
|
} from './js/marshaller.js'
|
||||||
import { checkWebsocketConnection } from './js/websocket.js'
|
import { checkWebsocketConnection } from './js/websocket.js'
|
||||||
|
|
||||||
|
|
@ -139,8 +138,6 @@ function processWebuiSettingsJson (settings) {
|
||||||
document.getElementsByTagName('main')[0].appendChild(loginForm)
|
document.getElementsByTagName('main')[0].appendChild(loginForm)
|
||||||
|
|
||||||
window.settings = settings
|
window.settings = settings
|
||||||
|
|
||||||
refreshDiagnostics()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function processAdditionalLinks (links) {
|
function processAdditionalLinks (links) {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue