Move debug information to the admin section - Closes #303

This commit is contained in:
Bubka 2024-02-22 14:12:47 +01:00
parent ee4b21eab2
commit 21fa77f348
5 changed files with 72 additions and 136 deletions

View File

@ -34,27 +34,15 @@ class SystemController extends Controller
$infos['common']['Operating system'] = PHP_OS;
$infos['common']['interface'] = PHP_SAPI;
// Auth & Security infos
if (! is_null($request->user())) {
$infos['common']['Auth guard'] = config('auth.defaults.guard');
if ($infos['common']['Auth guard'] === 'reverse-proxy-guard') {
$infos['common']['Auth proxy logout url'] = config('2fauth.config.proxyLogoutUrl');
$infos['common']['Auth proxy header for user'] = config('auth.auth_proxy_headers.user');
$infos['common']['Auth proxy header for email'] = config('auth.auth_proxy_headers.email');
}
$infos['common']['webauthn user verification'] = config('webauthn.user_verification');
$infos['common']['Trusted proxies'] = config('2fauth.config.trustedProxies') ?: 'none';
// Admin settings
if ($request->user()->isAdministrator()) {
$infos['admin_settings']['useEncryption'] = Settings::get('useEncryption');
$infos['admin_settings']['lastRadarScan'] = Carbon::parse(Settings::get('lastRadarScan'))->format('Y-m-d H:i:s');
$infos['admin_settings']['checkForUpdate'] = Settings::get('checkForUpdate');
}
}
// User info
if ($request->user()) {
$infos['user_preferences'] = $request->user()->preferences->toArray();
$infos['common']['Auth guard'] = config('auth.defaults.guard');
if ($infos['common']['Auth guard'] === 'reverse-proxy-guard') {
$infos['common']['Auth proxy logout url'] = config('2fauth.config.proxyLogoutUrl');
$infos['common']['Auth proxy header for user'] = config('auth.auth_proxy_headers.user');
$infos['common']['Auth proxy header for email'] = config('auth.auth_proxy_headers.email');
}
$infos['common']['webauthn user verification'] = config('webauthn.user_verification');
$infos['common']['Trusted proxies'] = config('2fauth.config.trustedProxies') ?: 'none';
$infos['common']['lastRadarScan'] = Carbon::parse(Settings::get('lastRadarScan'))->format('Y-m-d H:i:s');
return response()->json($infos);
}

View File

@ -1,35 +1,9 @@
<script setup>
import systemService from '@/services/systemService'
import { UseColorMode } from '@vueuse/components'
import CopyButton from '@/components/CopyButton.vue'
const $2fauth = inject('2fauth')
const router = useRouter()
const returnTo = router.options.history.state.back
const infos = ref()
const listInfos = ref(null)
const userPreferences = ref(false)
const listUserPreferences = ref(null)
const adminSettings = ref(false)
const listAdminSettings = ref(null)
onMounted(() => {
systemService.getSystemInfos({returnError: true}).then(response => {
infos.value = response.data.common
if (response.data.admin_settings) {
adminSettings.value = response.data.admin_settings
}
if (response.data.user_preferences) {
userPreferences.value = response.data.user_preferences
}
})
.catch(() => {
infos.value = null
})
})
</script>
<template>
@ -87,36 +61,6 @@
<li>{{ $t('commons.logos_by') }}&nbsp;<a href="https://2fa.directory/">2FA Directory</a>&nbsp;<a class="is-size-7" href="https://github.com/2factorauth/twofactorauth/blob/master/LICENSE.md">(MIT License)</a></li>
</ul>
</p>
<h2 class="title is-5 has-text-grey-light">
{{ $t('commons.environment') }}
</h2>
<div v-if="infos" class="about-debug box is-family-monospace is-size-7">
<CopyButton id="btnCopyEnvVars" :token="listInfos?.innerText" />
<ul ref="listInfos" id="listInfos">
<li v-for="(value, key) in infos" :value="value" :key="key"><b>{{key}}</b>: {{value}}</li>
</ul>
</div>
<div v-else-if="infos === null" class="about-debug box is-family-monospace is-size-7 has-text-warning-dark">
{{ $t('errors.error_during_data_fetching') }}
</div>
<h2 v-if="adminSettings" class="title is-5 has-text-grey-light">
{{ $t('settings.admin_settings') }}
</h2>
<div v-if="adminSettings" class="about-debug box is-family-monospace is-size-7">
<CopyButton id="btnCopyAdminSettings" :token="listAdminSettings?.innerText" />
<ul ref="listAdminSettings" id="listAdminSettings">
<li v-for="(value, setting) in adminSettings" :value="value" :key="setting"><b>{{setting}}</b>: {{value}}</li>
</ul>
</div>
<h2 v-if="userPreferences" class="title is-5 has-text-grey-light">
{{ $t('settings.user_preferences') }}
</h2>
<div v-if="userPreferences" class="about-debug box is-family-monospace is-size-7">
<CopyButton id="btnCopyUserPreferences" :token="listUserPreferences?.innerText" />
<ul ref="listUserPreferences" id="listUserPreferences">
<li v-for="(value, preference) in userPreferences" :value="value" :key="preference"><b>{{preference}}</b>: {{value}}</li>
</ul>
</div>
<!-- footer -->
<VueFooter :showButtons="true">
<ButtonBackCloseCancel :returnTo="{ path: returnTo }" action="back" />

View File

@ -1,15 +1,20 @@
<script setup>
import AdminTabs from '@/layouts/AdminTabs.vue'
import appSettingService from '@/services/appSettingService'
import systemService from '@/services/systemService'
import { useAppSettingsStore } from '@/stores/appSettings'
import { useNotifyStore } from '@/stores/notify'
import VersionChecker from '@/components/VersionChecker.vue'
import CopyButton from '@/components/CopyButton.vue'
const $2fauth = inject('2fauth')
const notify = useNotifyStore()
const appSettings = useAppSettingsStore()
const returnTo = useStorage($2fauth.prefix + 'returnTo', 'accounts')
const infos = ref()
const listInfos = ref(null)
/**
* Saves a setting on the backend
* @param {string} preference
@ -27,6 +32,15 @@
}
})
onMounted(() => {
systemService.getSystemInfos({returnError: true}).then(response => {
infos.value = response.data.common
})
.catch(() => {
infos.value = null
})
})
</script>
<template>
@ -48,6 +62,18 @@
<!-- disable SSO registration -->
<FormCheckbox v-model="appSettings.enableSso" @update:model-value="val => saveSetting('enableSso', val)" fieldName="enableSso" label="admin.forms.enable_sso.label" help="admin.forms.enable_sso.help" />
</form>
<h4 class="title is-4 pt-5 has-text-grey-light">{{ $t('commons.environment') }}</h4>
<div v-if="infos" class="about-debug box is-family-monospace is-size-7">
<CopyButton id="btnCopyEnvVars" :token="listInfos?.innerText" />
<ul ref="listInfos" id="listInfos">
<li v-for="(value, preference) in infos" :value="value" :key="preference">
<b>{{ preference }}</b>: <span class="has-text-grey">{{ value }}</span>
</li>
</ul>
</div>
<div v-else-if="infos === null" class="about-debug box is-family-monospace is-size-7 has-text-warning-dark">
{{ $t('errors.error_during_data_fetching') }}
</div>
</FormWrapper>
</div>
<VueFooter :showButtons="true">

View File

@ -77,7 +77,14 @@ Route::get('refresh-csrf', function () {
return csrf_token();
});
Route::get('infos', [SystemController::class, 'infos'])->name('system.infos');
/**
* Routes protected by an authentication guard and restricted to administrators
*/
Route::group(['middleware' => ['behind-auth', 'admin']], function () {
Route::get('infos', [SystemController::class, 'infos'])->name('system.infos');
});
Route::get('latestRelease', [SystemController::class, 'latestRelease'])->name('system.latestRelease');
/**

View File

@ -15,12 +15,12 @@ use Tests\FeatureTestCase;
#[CoversClass(SystemController::class)]
class SystemControllerTest extends FeatureTestCase
{
use WithoutMiddleware;
//use WithoutMiddleware;
/**
* @var \App\Models\User|\Illuminate\Contracts\Auth\Authenticatable
*/
protected $user;
protected $user, $admin;
/**
* @test
@ -30,6 +30,26 @@ class SystemControllerTest extends FeatureTestCase
parent::setUp();
$this->user = User::factory()->create();
$this->admin = User::factory()->administrator()->create();
}
/**
* @test
*/
public function test_infos_returns_unauthorized()
{
$response = $this->json('GET', '/infos')
->assertUnauthorized();
}
/**
* @test
*/
public function test_infos_returns_forbidden()
{
$response = $this->actingAs($this->user, 'api-guard')
->json('GET', '/infos')
->assertForbidden();
}
/**
@ -37,7 +57,8 @@ class SystemControllerTest extends FeatureTestCase
*/
public function test_infos_returns_only_base_collection()
{
$response = $this->json('GET', '/infos')
$response = $this->actingAs($this->admin, 'api-guard')
->json('GET', '/infos')
->assertOk()
->assertJsonStructure([
'common' => [
@ -54,61 +75,10 @@ class SystemControllerTest extends FeatureTestCase
'PHP version',
'Operating system',
'interface',
],
])
->assertJsonMissing([
'user_preferences',
'admin_settings',
]);
}
/**
* @test
*/
public function test_infos_returns_user_preferences_when_signed_in()
{
$response = $this->actingAs($this->user, 'api-guard')
->json('GET', '/infos')
->assertOk()
->assertJsonStructure([
'user_preferences' => [
'showOtpAsDot',
'closeOtpOnCopy',
'copyOtpOnDisplay',
'useBasicQrcodeReader',
'displayMode',
'showAccountsIcons',
'kickUserAfter',
'activeGroup',
'rememberActiveGroup',
'defaultGroup',
'defaultCaptureMode',
'useDirectCapture',
'useWebauthnOnly',
'getOfficialIcons',
'lang',
],
]);
}
/**
* @test
*/
public function test_infos_returns_admin_settings_when_signed_in_as_admin()
{
/**
* @var \App\Models\User|\Illuminate\Contracts\Auth\Authenticatable
*/
$admin = User::factory()->administrator()->create();
$response = $this->actingAs($admin, 'api-guard')
->json('GET', '/infos')
->assertOk()
->assertJsonStructure([
'admin_settings' => [
'useEncryption',
'lastRadarScan',
'checkForUpdate',
'Auth guard',
'webauthn user verification',
'Trusted proxies',
'lastRadarScan'
],
]);
}
@ -118,11 +88,12 @@ class SystemControllerTest extends FeatureTestCase
*/
public function test_infos_returns_proxy_collection_when_signed_in_behind_proxy()
{
$response = $this->actingAs($this->user, 'reverse-proxy-guard')
$response = $this->actingAs($this->admin, 'reverse-proxy-guard')
->json('GET', '/infos')
->assertOk()
->assertJsonStructure([
'common' => [
'Auth proxy logout url',
'Auth proxy header for user',
'Auth proxy header for email',
],