From 05f661e0e5790095b80dd13c21fb6625094cde8b Mon Sep 17 00:00:00 2001 From: Eli Bosley Date: Fri, 31 Jan 2025 15:10:49 -0500 Subject: [PATCH] feat: download fixtures from the web --- api/.gitignore | 3 + api/.prettierignore | 4 + .../unraid-file-modifier/file-modification.ts | 95 +- .../__fixtures__/DefaultPageLayout.php | 1208 ----------------- .../__fixtures__/Notifications.page | 286 ---- .../__test__/generic-modification.spec.ts | 35 +- ...efaultPageLayout.php.modified.snapshot.php | 33 +- .../DefaultPageLayout.php.original.php | 33 +- .../DefaultPageLayout.php.snapshot.patch | 60 - .../auth-request.modification.ts | 8 +- .../default-page-layout.modification.ts | 10 +- .../modifications/log-rotate.modification.ts | 10 +- .../notifications-page.modification.ts | 12 +- .../default-page-layout.patch | 12 +- .../notifications-page.patch} | 6 +- .../modifications/patches/sso.patch | 59 + .../modifications/sso.modification.ts | 12 +- .../unraid-file-modifier.spec.ts | 9 +- api/vite.config.ts | 2 +- 19 files changed, 226 insertions(+), 1671 deletions(-) create mode 100644 api/.prettierignore delete mode 100644 api/src/unraid-api/unraid-file-modifier/modifications/__fixtures__/DefaultPageLayout.php delete mode 100644 api/src/unraid-api/unraid-file-modifier/modifications/__fixtures__/Notifications.page delete mode 100644 api/src/unraid-api/unraid-file-modifier/modifications/__test__/snapshots/DefaultPageLayout.php.snapshot.patch rename api/src/unraid-api/unraid-file-modifier/modifications/{__fixtures__ => patches}/default-page-layout.patch (93%) rename api/src/unraid-api/unraid-file-modifier/modifications/{__test__/snapshots/Notifications.page.snapshot.patch => patches/notifications-page.patch} (89%) create mode 100644 api/src/unraid-api/unraid-file-modifier/modifications/patches/sso.patch diff --git a/api/.gitignore b/api/.gitignore index 50aadee2f..d8be9bf2e 100644 --- a/api/.gitignore +++ b/api/.gitignore @@ -80,3 +80,6 @@ deploy/* # IDE Settings Files .idea + +# Downloaded Fixtures (For File Modifications) +src/unraid-api/unraid-file-modifier/modifications/__fixtures__/downloaded/* diff --git a/api/.prettierignore b/api/.prettierignore new file mode 100644 index 000000000..29efe68b2 --- /dev/null +++ b/api/.prettierignore @@ -0,0 +1,4 @@ +!src/* + +# Downloaded Fixtures (For File Modifications) +src/unraid-api/unraid-file-modifier/modifications/__fixtures__/downloaded/* \ No newline at end of file diff --git a/api/src/unraid-api/unraid-file-modifier/file-modification.ts b/api/src/unraid-api/unraid-file-modifier/file-modification.ts index 839d32f9a..a4c83ad92 100644 --- a/api/src/unraid-api/unraid-file-modifier/file-modification.ts +++ b/api/src/unraid-api/unraid-file-modifier/file-modification.ts @@ -1,13 +1,9 @@ import { Logger } from '@nestjs/common'; -import { readFile, writeFile, access, unlink } from 'fs/promises'; import { constants } from 'fs'; -import { join, dirname, basename } from 'path'; -import { applyPatch, parsePatch, reversePatch } from 'diff'; +import { access, readFile, unlink, writeFile } from 'fs/promises'; +import { basename, dirname, join } from 'path'; -export interface PatchResult { - targetFile: string; - patch: string; -} +import { applyPatch, parsePatch, reversePatch } from 'diff'; export interface ShouldApplyWithReason { shouldApply: boolean; @@ -17,11 +13,12 @@ export interface ShouldApplyWithReason { // Convert interface to abstract class with default implementations export abstract class FileModification { abstract id: string; - + public abstract readonly filePath: string; + protected constructor(protected readonly logger: Logger) {} // This is the main method that child classes need to implement - protected abstract generatePatch(): Promise; + protected abstract generatePatch(): Promise; private getPatchFilePath(targetFile: string): string { const dir = dirname(targetFile); @@ -29,9 +26,9 @@ export abstract class FileModification { return join(dir, filename); } - private async savePatch(patchResult: PatchResult): Promise { - const patchFile = this.getPatchFilePath(patchResult.targetFile); - await writeFile(patchFile, patchResult.patch, 'utf8'); + private async savePatch(patchResult: string): Promise { + const patchFile = this.getPatchFilePath(this.filePath); + await writeFile(patchFile, patchResult, 'utf8'); } private async loadSavedPatch(targetFile: string): Promise { @@ -44,38 +41,72 @@ export abstract class FileModification { } } - // Default implementation of apply that uses the patch - async apply(): Promise { - const patchResult = await this.generatePatch(); - const { targetFile, patch } = patchResult; - const currentContent = await readFile(targetFile, 'utf8'); - const parsedPatch = parsePatch(patch)[0]; - const results = applyPatch(currentContent, parsedPatch); - if (results === false) { - throw new Error(`Failed to apply patch to ${targetFile}`); + private async getPregeneratedPatch(): Promise { + const patchResults = await import.meta.glob('./modifications/patches/*.patch', { + query: '?raw', + import: 'default', + }); + + if (patchResults[`./modifications/patches/${this.id}.patch`]) { + const loader = Object.values(patchResults)[0]; + const fileContents = await loader(); + this.logger.debug(`Loaded pregenerated patch for ${this.id}`); + if (typeof fileContents !== 'string') { + this.logger.error('Invalid patch format on patch: ' + this.id); + return null; + } + return fileContents; } - await writeFile(targetFile, results); - await this.savePatch(patchResult); + return null; + } + + private async applyPatch(patchContents: string): Promise { + const currentContent = await readFile(this.filePath, 'utf8'); + const parsedPatch = parsePatch(patchContents)[0]; + const results = applyPatch(currentContent, parsedPatch); + if (results === false) { + throw new Error(`Failed to apply patch to ${this.filePath}`); + } + await writeFile(this.filePath, results); + } + + // Default implementation of apply that uses the patch + async apply(): Promise { + // First attempt to apply the patch that was generated + const staticPatch = await this.getPregeneratedPatch(); + if (staticPatch) { + try { + await this.applyPatch(staticPatch); + await this.savePatch(staticPatch); + return; + } catch (error) { + this.logger.error( + `Failed to apply static patch to ${this.filePath}, continuing with dynamic patch` + ); + } + } + const patchContents = await this.generatePatch(); + await this.applyPatch(patchContents); + await this.savePatch(patchContents); } // Update rollback to use the shared utility async rollback(): Promise { - const { targetFile } = await this.generatePatch(); let patch: string; // Try to load saved patch first - const savedPatch = await this.loadSavedPatch(targetFile); + const savedPatch = await this.loadSavedPatch(this.filePath); if (savedPatch) { this.logger.debug(`Using saved patch file for ${this.id}`); patch = savedPatch; } else { this.logger.debug(`No saved patch found for ${this.id}, generating new patch`); - const patchResult = await this.generatePatch(); - patch = patchResult.patch; + const patchContents = await this.generatePatch(); + patch = patchContents; } - const currentContent = await readFile(targetFile, 'utf8'); + const currentContent = await readFile(this.filePath, 'utf8'); const parsedPatch = parsePatch(patch)[0]; if (!parsedPatch || !parsedPatch.hunks || parsedPatch.hunks.length === 0) { @@ -86,14 +117,14 @@ export abstract class FileModification { const results = applyPatch(currentContent, reversedPatch); if (results === false) { - throw new Error(`Failed to rollback patch from ${targetFile}`); + throw new Error(`Failed to rollback patch from ${this.filePath}`); } - await writeFile(targetFile, results); - + await writeFile(this.filePath, results); + // Clean up the patch file after successful rollback try { - const patchFile = this.getPatchFilePath(targetFile); + const patchFile = this.getPatchFilePath(this.filePath); await access(patchFile, constants.W_OK); await unlink(patchFile); } catch { diff --git a/api/src/unraid-api/unraid-file-modifier/modifications/__fixtures__/DefaultPageLayout.php b/api/src/unraid-api/unraid-file-modifier/modifications/__fixtures__/DefaultPageLayout.php deleted file mode 100644 index 2214f524e..000000000 --- a/api/src/unraid-api/unraid-file-modifier/modifications/__fixtures__/DefaultPageLayout.php +++ /dev/null @@ -1,1208 +0,0 @@ - -/dev/null &"); - -function annotate($text) {echo "\n\n";} -?> - -lang=""> - -<?=_var($var,'NAME')?>/<?=_var($myPage,'name')?> - - - - - - - - -"> -"> -"> -"> -"> -"> -"> -"> - - - - - - - - - - - -
- - - -"; -if ($themes2) echo "
"; -foreach ($buttons as $button) { - annotate($button['file']); - // include page specific stylesheets (if existing) - $css = "/{$button['root']}/sheets/{$button['name']}"; - $css_stock = "$css.css"; - $css_theme = "$css-$theme.css"; - if (is_file($docroot.$css_stock)) echo '',"\n"; - if (is_file($docroot.$css_theme)) echo '',"\n"; - // create page content - eval('?>'.parse_text($button['text'])); -} -unset($buttons,$button); - -// Build page content -// Reload page every X minutes during extended viewing? -if (isset($myPage['Load']) && $myPage['Load']>0) echo "\n\n"; -echo "
"; -$tab = 1; -$pages = []; -if (!empty($myPage['text'])) $pages[$myPage['name']] = $myPage; -if (_var($myPage,'Type')=='xmenu') $pages = array_merge($pages, find_pages($myPage['name'])); -if (isset($myPage['Tabs'])) $display['tabs'] = strtolower($myPage['Tabs'])=='true' ? 0 : 1; -$tabbed = $display['tabs']==0 && count($pages)>1; - -foreach ($pages as $page) { - $close = false; - if (isset($page['Title'])) { - eval("\$title=\"".htmlspecialchars($page['Title'])."\";"); - if ($tabbed) { - echo "
"; - $close = true; - } else { - if ($tab==1) echo "
"; - echo "
"; - echo tab_title($title,$page['root'],_var($page,'Tag',false)); - echo "
"; - } - $tab++; - } - if (isset($page['Type']) && $page['Type']=='menu') { - $pgs = find_pages($page['name']); - foreach ($pgs as $pg) { - @eval("\$title=\"".htmlspecialchars($pg['Title'])."\";"); - $icon = _var($pg,'Icon',""); - if (substr($icon,-4)=='.png') { - $root = $pg['root']; - if (file_exists("$docroot/$root/images/$icon")) { - $icon = ""; - } elseif (file_exists("$docroot/$root/$icon")) { - $icon = ""; - } else { - $icon = ""; - } - } elseif (substr($icon,0,5)=='icon-') { - $icon = ""; - } elseif ($icon[0]!='<') { - if (substr($icon,0,3)!='fa-') $icon = "fa-$icon"; - $icon = ""; - } - echo ""; - } - } - // create list of nchan scripts to be started - if (isset($page['Nchan'])) nchan_merge($page['root'], $page['Nchan']); - annotate($page['file']); - // include page specific stylesheets (if existing) - $css = "/{$page['root']}/sheets/{$page['name']}"; - $css_stock = "$css.css"; - $css_theme = "$css-$theme.css"; - if (is_file($docroot.$css_stock)) echo '',"\n"; - if (is_file($docroot.$css_theme)) echo '',"\n"; - // create page content - empty($page['Markdown']) || $page['Markdown']=='true' ? eval('?>'.Markdown(parse_text($page['text']))) : eval('?>'.parse_text($page['text'])); - if ($close) echo "
"; -} -if (count($pages)) { - $running = file_exists($nchan_pid) ? file($nchan_pid,FILE_IGNORE_NEW_LINES|FILE_SKIP_EMPTY_LINES) : []; - $start = array_diff($nchan, $running); // returns any new scripts to be started - $stop = array_diff($running, $nchan); // returns any old scripts to be stopped - $running = array_merge($start, $running); // update list of current running nchan scripts - // start nchan scripts which are new - foreach ($start as $row) { - $script = explode(':',$row)[0]; - exec("$docroot/$script &>/dev/null &"); - } - // stop nchan scripts with the :stop option - foreach ($stop as $row) { - [$script,$opt] = my_explode(':',$row); - if ($opt == 'stop') { - exec("pkill -f $docroot/$script &>/dev/null &"); - array_splice($running,array_search($row,$running),1); - } - } - if (count($running)) file_put_contents($nchan_pid,implode("\n",$running)."\n"); else @unlink($nchan_pid); -} -unset($pages,$page,$pgs,$pg,$icon,$nchan,$running,$start,$stop,$row,$script,$opt,$nchan_run); -?> -
-
-
- -'; -$progress = (_var($var,'fsProgress')!='') ? "•{$var['fsProgress']}" : ""; -switch (_var($var,'fsState')) { -case 'Stopped': - echo " ",_('Array Stopped'),"$progress"; break; -case 'Starting': - echo " ",_('Array Starting'),"$progress"; break; -case 'Stopping': - echo " ",_('Array Stopping'),"$progress"; break; -default: - echo " ",_('Array Started'),"$progress"; break; -} -echo ""; -echo "Unraid® webGui ©2024, Lime Technology, Inc."; -echo " "._('manual').""; -echo "
"; -?> - - - - diff --git a/api/src/unraid-api/unraid-file-modifier/modifications/__fixtures__/Notifications.page b/api/src/unraid-api/unraid-file-modifier/modifications/__fixtures__/Notifications.page deleted file mode 100644 index 22e485de7..000000000 --- a/api/src/unraid-api/unraid-file-modifier/modifications/__fixtures__/Notifications.page +++ /dev/null @@ -1,286 +0,0 @@ -Menu="UserPreferences" -Type="xmenu" -Title="Notification Settings" -Icon="icon-notifications" -Tag="phone-square" ---- - - - -
- - - - - - - - - - - - - -_(Notifications display)_: -: - -:notifications_display_help: - -_(Display position)_: -: - -:notifications_display_position_help: - -_(Auto-close)_ (_(seconds)_): -: _(a value of zero means no automatic closure)_ - -:notifications_auto_close_help: - -_(Date format)_: -: - -:notifications_date_format_help: - -_(Time format)_: -: - -:notifications_time_format_help: - -_(Store notifications to flash)_: -: - -:notifications_store_flash_help: - -_(System notifications)_: -: - -:notifications_system_help: - -_(Unraid OS update notification)_: -: - -:notifications_os_update_help: - -_(Plugins update notification)_: -: - -:notifications_plugins_update_help: - -_(Docker update notification)_: -: - -:notifications_docker_update_help: - -_(Language update notification)_: -: - -_(Array status notification)_: -: - -:notifications_array_status_help: - - -: - - -: - - -: - - -: - - -: - -:notifications_agent_selection_help: - -_(Notification entity)_: -: _(Notices)_ - >_(Browser)_   - >_(Email)_   - >_(Agents)_   - -  -: _(Warnings)_ - >_(Browser)_   - >_(Email)_   - >_(Agents)_   - -  -: _(Alerts)_ - >_(Browser)_   - >_(Email)_   - >_(Agents)_   - -:notifications_classification_help: - - -: -
diff --git a/api/src/unraid-api/unraid-file-modifier/modifications/__test__/generic-modification.spec.ts b/api/src/unraid-api/unraid-file-modifier/modifications/__test__/generic-modification.spec.ts index 33ec1c04b..30242d9e2 100644 --- a/api/src/unraid-api/unraid-file-modifier/modifications/__test__/generic-modification.spec.ts +++ b/api/src/unraid-api/unraid-file-modifier/modifications/__test__/generic-modification.spec.ts @@ -1,6 +1,6 @@ import { Logger } from '@nestjs/common'; -import { readFile } from 'fs/promises'; -import { resolve } from 'path'; +import { readFile, writeFile } from 'fs/promises'; +import { basename, resolve } from 'path'; import { describe, expect, test } from 'vitest'; import DefaultPageLayoutModification from '@app/unraid-api/unraid-file-modifier/modifications/default-page-layout.modification'; @@ -9,32 +9,33 @@ import { FileModification } from '@app/unraid-api/unraid-file-modifier/file-modi import SSOFileModification from '@app/unraid-api/unraid-file-modifier/modifications/sso.modification'; interface ModificationTestCase { - name: string; ModificationClass: new (logger: Logger) => FileModification; - fileName: string; + fileUrl: string; } const testCases: ModificationTestCase[] = [ { - name: 'DefaultPageLayout.php', ModificationClass: DefaultPageLayoutModification, - fileName: 'DefaultPageLayout.php' + fileUrl: 'https://github.com/unraid/webgui/raw/refs/heads/master/emhttp/plugins/dynamix/include/DefaultPageLayout.php', }, { - name: 'Notifications.page', ModificationClass: NotificationsPageModification, - fileName: 'Notifications.page' + fileUrl: "https://github.com/unraid/webgui/raw/refs/heads/master/emhttp/plugins/dynamix/Notifications.page", }, { - name: '.login.php', + fileUrl: 'https://github.com/unraid/webgui/raw/refs/heads/master/emhttp/plugins/dynamix/include/.login.php', ModificationClass: SSOFileModification, - fileName: '.login.php' } ]; async function testModification(testCase: ModificationTestCase) { - const path = resolve(__dirname, `../__fixtures__/${testCase.fileName}`); - const fileContent = await readFile(path, 'utf-8'); + // First download the file from Github + const fileName = basename(testCase.fileUrl); + + const path = resolve(__dirname, `../__fixtures__/downloaded/${fileName}`); + const fileContent = await fetch(testCase.fileUrl).then(response => response.text()); + await writeFile(path, fileContent); + expect(fileContent.length).toBeGreaterThan(0); const logger = new Logger(); @@ -44,23 +45,23 @@ async function testModification(testCase: ModificationTestCase) { // @ts-ignore - Ignore for testing purposes const patch = await patcher.generatePatch(); - + // Test patch matches snapshot - await expect(patch.patch).toMatchFileSnapshot( - `snapshots/${testCase.fileName}.snapshot.patch` + await expect(patch).toMatchFileSnapshot( + `../patches/${patcher.id}.patch` ); // Apply patch and verify modified file await patcher.apply(); await expect(await readFile(path, 'utf-8')).toMatchFileSnapshot( - `snapshots/${testCase.fileName}.modified.snapshot.php` + `snapshots/${fileName}.modified.snapshot.php` ); // Rollback and verify original state await patcher.rollback(); const revertedContent = await readFile(path, 'utf-8'); await expect(revertedContent).toMatchFileSnapshot( - `snapshots/${testCase.fileName}.original.php` + `snapshots/${fileName}.original.php` ); } diff --git a/api/src/unraid-api/unraid-file-modifier/modifications/__test__/snapshots/DefaultPageLayout.php.modified.snapshot.php b/api/src/unraid-api/unraid-file-modifier/modifications/__test__/snapshots/DefaultPageLayout.php.modified.snapshot.php index 8cdf8fe88..612c4a1f8 100644 --- a/api/src/unraid-api/unraid-file-modifier/modifications/__test__/snapshots/DefaultPageLayout.php.modified.snapshot.php +++ b/api/src/unraid-api/unraid-file-modifier/modifications/__test__/snapshots/DefaultPageLayout.php.modified.snapshot.php @@ -200,11 +200,11 @@ function settab(tab) { $.cookie('one','tab1'); - + $.cookie('one',tab); - $.cookie(($.cookie('one')==null?'tab':'one'),tab); + $.cookie('one',tab); } function done(key) { @@ -577,8 +577,26 @@ function flashReport() { }); } $(function() { - var tab = $.cookie('one')||$.cookie('tab')||'tab1'; - if (tab=='tab0') tab = 'tab'+$('input[name$="tabs"]').length; else if ($('#'+tab).length==0) {initab(); tab = 'tab1';} + let tab; + + + tab = $.cookie('tab')||'tab1'; + + + tab = $.cookie('one')||'tab1'; + + + tab = $.cookie('one')||'tab1'; + + /* Check if the tab is 'tab0' */ + if (tab === 'tab0') { + /* Set tab to the last available tab based on input[name$="tabs"] length */ + tab = 'tab' + $('input[name$="tabs"]').length; + } else if ($('#' + tab).length === 0) { + /* If the tab element does not exist, initialize a tab and set to 'tab1' */ + initab(); + tab = 'tab1'; + } $('#'+tab).attr('checked', true); updateTime(); $.jGrowl.defaults.closeTemplate = ''; @@ -693,7 +711,7 @@ if (isset($myPage['Load']) && $myPage['Load']>0) echo "\n - diff --git a/api/src/unraid-api/unraid-file-modifier/modifications/__test__/snapshots/DefaultPageLayout.php.snapshot.patch b/api/src/unraid-api/unraid-file-modifier/modifications/__test__/snapshots/DefaultPageLayout.php.snapshot.patch deleted file mode 100644 index 32d063efe..000000000 --- a/api/src/unraid-api/unraid-file-modifier/modifications/__test__/snapshots/DefaultPageLayout.php.snapshot.patch +++ /dev/null @@ -1,60 +0,0 @@ -Index: /app/src/unraid-api/unraid-file-modifier/modifications/__fixtures__/DefaultPageLayout.php -=================================================================== ---- /app/src/unraid-api/unraid-file-modifier/modifications/__fixtures__/DefaultPageLayout.php -+++ /app/src/unraid-api/unraid-file-modifier/modifications/__fixtures__/DefaultPageLayout.php -@@ -557,14 +557,5 @@ - $.post('/webGui/include/Notify.php',{cmd:'get',csrf_token:csrf_token},function(msg) { - $.each($.parseJSON(msg), function(i, notify){ -- $.jGrowl(notify.subject+'
'+notify.description,{ -- group: notify.importance, -- header: notify.event+': '+notify.timestamp, -- theme: notify.file, -- sticky: true, -- beforeOpen: function(e,m,o){if ($('div.jGrowl-notification').hasClass(notify.file)) return(false);}, -- afterOpen: function(e,m,o){if (notify.link) $(e).css('cursor','pointer');}, -- click: function(e,m,o){if (notify.link) location.replace(notify.link);}, -- close: function(e,m,o){$.post('/webGui/include/Notify.php',{cmd:'archive',file:notify.file,csrf_token:csrf_token});} -- }); -+ - }); - }); -@@ -680,6 +671,6 @@ - } - --echo ""; - -+ - if ($themes2) echo ""; - echo ""; -@@ -886,20 +877,12 @@ - - if (notify.show) { -- $.jGrowl(notify.subject+'
'+notify.description,{ -- group: notify.importance, -- header: notify.event+': '+notify.timestamp, -- theme: notify.file, -- beforeOpen: function(e,m,o){if ($('div.jGrowl-notification').hasClass(notify.file)) return(false);}, -- afterOpen: function(e,m,o){if (notify.link) $(e).css('cursor','pointer');}, -- click: function(e,m,o){if (notify.link) location.replace(notify.link);}, -- close: function(e,m,o){$.post('/webGui/include/Notify.php',{cmd:'hide',file:""+notify.file,csrf_token:csrf_token},function(){$.post('/webGui/include/Notify.php',{cmd:'archive',file:notify.file,csrf_token:csrf_token});});} -- }); -+ - } - - }); -- $('#bell').removeClass('red-orb yellow-orb green-orb').prop('title'," ["+bell1+']\n'+" ["+bell2+']\n'+" ["+bell3+']'); -- if (bell1) $('#bell').addClass('red-orb'); else -- if (bell2) $('#bell').addClass('yellow-orb'); else -- if (bell3) $('#bell').addClass('green-orb'); -+ -+ -+ -+ - break; - } -@@ -1204,4 +1187,5 @@ - }); - -+ - - diff --git a/api/src/unraid-api/unraid-file-modifier/modifications/auth-request.modification.ts b/api/src/unraid-api/unraid-file-modifier/modifications/auth-request.modification.ts index 722475e01..5956ac72b 100644 --- a/api/src/unraid-api/unraid-file-modifier/modifications/auth-request.modification.ts +++ b/api/src/unraid-api/unraid-file-modifier/modifications/auth-request.modification.ts @@ -6,7 +6,6 @@ import { createPatch } from 'diff'; import { FileModification, - PatchResult, ShouldApplyWithReason, } from '@app/unraid-api/unraid-file-modifier/file-modification'; @@ -26,7 +25,7 @@ export default class AuthRequestModification extends FileModification { super(logger); } - protected async generatePatch(): Promise { + protected async generatePatch(): Promise { const JS_FILES = await getJsFiles(WEB_COMPS_DIR); this.logger.debug(`Found ${JS_FILES.length} .js files in ${WEB_COMPS_DIR}`); @@ -54,10 +53,7 @@ export default class AuthRequestModification extends FileModification { context: 3, }); - return { - targetFile: AUTH_REQUEST_FILE, - patch, - }; + return patch; } async shouldApply(): Promise { diff --git a/api/src/unraid-api/unraid-file-modifier/modifications/default-page-layout.modification.ts b/api/src/unraid-api/unraid-file-modifier/modifications/default-page-layout.modification.ts index 0be5c79d3..99a820a4e 100644 --- a/api/src/unraid-api/unraid-file-modifier/modifications/default-page-layout.modification.ts +++ b/api/src/unraid-api/unraid-file-modifier/modifications/default-page-layout.modification.ts @@ -5,13 +5,12 @@ import { createPatch } from 'diff'; import { FileModification, - PatchResult, ShouldApplyWithReason, } from '@app/unraid-api/unraid-file-modifier/file-modification'; export default class DefaultPageLayoutModification extends FileModification { id: string = 'default-page-layout'; - private readonly filePath: string = + public readonly filePath: string = '/usr/local/emhttp/plugins/dynamix/include/DefaultPageLayout.php'; constructor(logger: Logger) { @@ -47,7 +46,7 @@ export default class DefaultPageLayoutModification extends FileModification { return transformers.reduce((content, fn) => fn(content), fileContent); } - protected async generatePatch(): Promise { + protected async generatePatch(): Promise { const fileContent = await readFile(this.filePath, 'utf-8'); @@ -57,10 +56,7 @@ export default class DefaultPageLayoutModification extends FileModification { context: 2, }); - return { - targetFile: this.filePath, - patch, - }; + return patch; } async shouldApply(): Promise { diff --git a/api/src/unraid-api/unraid-file-modifier/modifications/log-rotate.modification.ts b/api/src/unraid-api/unraid-file-modifier/modifications/log-rotate.modification.ts index 8bc52d613..7bb39acce 100644 --- a/api/src/unraid-api/unraid-file-modifier/modifications/log-rotate.modification.ts +++ b/api/src/unraid-api/unraid-file-modifier/modifications/log-rotate.modification.ts @@ -7,13 +7,12 @@ import { execa } from 'execa'; import { fileExists } from '@app/core/utils/files/file-exists'; import { FileModification, - PatchResult, ShouldApplyWithReason, } from '@app/unraid-api/unraid-file-modifier/file-modification'; export class LogRotateModification extends FileModification { id: string = 'log-rotate'; - private readonly filePath: string = '/etc/logrotate.d/unraid-api' as const; + public readonly filePath: string = '/etc/logrotate.d/unraid-api' as const; private readonly logRotateConfig: string = ` /var/log/unraid-api/*.log { rotate 1 @@ -31,7 +30,7 @@ export class LogRotateModification extends FileModification { super(logger); } - protected async generatePatch(): Promise { + protected async generatePatch(): Promise { const currentContent = (await fileExists(this.filePath)) ? await readFile(this.filePath, 'utf8') : ''; @@ -50,10 +49,7 @@ export class LogRotateModification extends FileModification { // After applying patch, ensure file permissions are correct await execa('chown', ['root:root', this.filePath]).catch((err) => this.logger.error(err)); - return { - targetFile: this.filePath, - patch, - }; + return patch; } async shouldApply(): Promise { diff --git a/api/src/unraid-api/unraid-file-modifier/modifications/notifications-page.modification.ts b/api/src/unraid-api/unraid-file-modifier/modifications/notifications-page.modification.ts index 73386137f..1425d8186 100644 --- a/api/src/unraid-api/unraid-file-modifier/modifications/notifications-page.modification.ts +++ b/api/src/unraid-api/unraid-file-modifier/modifications/notifications-page.modification.ts @@ -5,19 +5,18 @@ import { createPatch } from 'diff'; import { FileModification, - PatchResult, ShouldApplyWithReason, } from '@app/unraid-api/unraid-file-modifier/file-modification'; export default class NotificationsPageModification extends FileModification { - id: string = 'Notifications.page'; - private readonly filePath: string = '/usr/local/emhttp/plugins/dynamix/Notifications.page'; + id: string = 'notifications-page'; + public readonly filePath: string = '/usr/local/emhttp/plugins/dynamix/Notifications.page'; constructor(logger: Logger) { super(logger); } - protected async generatePatch(): Promise { + protected async generatePatch(): Promise { const fileContent = await readFile(this.filePath, 'utf-8'); const newContent = NotificationsPageModification.applyToSource(fileContent); @@ -26,10 +25,7 @@ export default class NotificationsPageModification extends FileModification { context: 3, }); - return { - targetFile: this.filePath, - patch, - }; + return patch; } async shouldApply(): Promise { diff --git a/api/src/unraid-api/unraid-file-modifier/modifications/__fixtures__/default-page-layout.patch b/api/src/unraid-api/unraid-file-modifier/modifications/patches/default-page-layout.patch similarity index 93% rename from api/src/unraid-api/unraid-file-modifier/modifications/__fixtures__/default-page-layout.patch rename to api/src/unraid-api/unraid-file-modifier/modifications/patches/default-page-layout.patch index 32d063efe..2fe02a66d 100644 --- a/api/src/unraid-api/unraid-file-modifier/modifications/__fixtures__/default-page-layout.patch +++ b/api/src/unraid-api/unraid-file-modifier/modifications/patches/default-page-layout.patch @@ -1,7 +1,7 @@ -Index: /app/src/unraid-api/unraid-file-modifier/modifications/__fixtures__/DefaultPageLayout.php +Index: /app/src/unraid-api/unraid-file-modifier/modifications/__fixtures__/downloaded/DefaultPageLayout.php =================================================================== ---- /app/src/unraid-api/unraid-file-modifier/modifications/__fixtures__/DefaultPageLayout.php -+++ /app/src/unraid-api/unraid-file-modifier/modifications/__fixtures__/DefaultPageLayout.php +--- /app/src/unraid-api/unraid-file-modifier/modifications/__fixtures__/downloaded/DefaultPageLayout.php ++++ /app/src/unraid-api/unraid-file-modifier/modifications/__fixtures__/downloaded/DefaultPageLayout.php @@ -557,14 +557,5 @@ $.post('/webGui/include/Notify.php',{cmd:'get',csrf_token:csrf_token},function(msg) { $.each($.parseJSON(msg), function(i, notify){ @@ -18,7 +18,7 @@ Index: /app/src/unraid-api/unraid-file-modifier/modifications/__fixtures__/Defau + }); }); -@@ -680,6 +671,6 @@ +@@ -698,6 +689,6 @@ } -echo ""; @@ -26,7 +26,7 @@ Index: /app/src/unraid-api/unraid-file-modifier/modifications/__fixtures__/Defau + if ($themes2) echo ""; echo ""; -@@ -886,20 +877,12 @@ +@@ -904,20 +895,12 @@ if (notify.show) { - $.jGrowl(notify.subject+'
'+notify.description,{ @@ -52,7 +52,7 @@ Index: /app/src/unraid-api/unraid-file-modifier/modifications/__fixtures__/Defau + break; } -@@ -1204,4 +1187,5 @@ +@@ -1222,4 +1205,5 @@ }); + diff --git a/api/src/unraid-api/unraid-file-modifier/modifications/__test__/snapshots/Notifications.page.snapshot.patch b/api/src/unraid-api/unraid-file-modifier/modifications/patches/notifications-page.patch similarity index 89% rename from api/src/unraid-api/unraid-file-modifier/modifications/__test__/snapshots/Notifications.page.snapshot.patch rename to api/src/unraid-api/unraid-file-modifier/modifications/patches/notifications-page.patch index 62f67bc5d..d108eda03 100644 --- a/api/src/unraid-api/unraid-file-modifier/modifications/__test__/snapshots/Notifications.page.snapshot.patch +++ b/api/src/unraid-api/unraid-file-modifier/modifications/patches/notifications-page.patch @@ -1,7 +1,7 @@ -Index: /app/src/unraid-api/unraid-file-modifier/modifications/__fixtures__/Notifications.page +Index: /app/src/unraid-api/unraid-file-modifier/modifications/__fixtures__/downloaded/Notifications.page =================================================================== ---- /app/src/unraid-api/unraid-file-modifier/modifications/__fixtures__/Notifications.page -+++ /app/src/unraid-api/unraid-file-modifier/modifications/__fixtures__/Notifications.page +--- /app/src/unraid-api/unraid-file-modifier/modifications/__fixtures__/downloaded/Notifications.page ++++ /app/src/unraid-api/unraid-file-modifier/modifications/__fixtures__/downloaded/Notifications.page @@ -135,23 +135,7 @@ :notifications_auto_close_help: diff --git a/api/src/unraid-api/unraid-file-modifier/modifications/patches/sso.patch b/api/src/unraid-api/unraid-file-modifier/modifications/patches/sso.patch new file mode 100644 index 000000000..8e9d44265 --- /dev/null +++ b/api/src/unraid-api/unraid-file-modifier/modifications/patches/sso.patch @@ -0,0 +1,59 @@ +Index: /app/src/unraid-api/unraid-file-modifier/modifications/__fixtures__/downloaded/.login.php +=================================================================== +--- /app/src/unraid-api/unraid-file-modifier/modifications/__fixtures__/downloaded/.login.php original ++++ /app/src/unraid-api/unraid-file-modifier/modifications/__fixtures__/downloaded/.login.php modified +@@ -1,5 +1,33 @@ + 800) { ++ $safePassword = escapeshellarg($password); ++ if (!preg_match('/^[A-Za-z0-9-_]+.[A-Za-z0-9-_]+.[A-Za-z0-9-_]+$/', $password)) { ++ my_logger("SSO Login Attempt Failed: Invalid token format"); ++ return false; ++ } ++ $safePassword = escapeshellarg($password); ++ $response = exec("/usr/local/bin/unraid-api sso validate-token $safePassword", $output, $code); ++ my_logger("SSO Login Attempt: $response"); ++ if ($code === 0 && $response && strpos($response, '"valid":true') !== false) { ++ return true; ++ } ++ } ++ return false; ++} + // Included in login.php + + // Only start a session to check if they have a cookie that looks like our session + $server_name = strtok($_SERVER['HTTP_HOST'],":"); +@@ -203,9 +231,9 @@ + throw new Exception(_('Too many invalid login attempts')); + } + + // Bail if username + password combo doesn't work +- if (!verifyUsernamePassword($username, $password)) throw new Exception(_('Invalid username or password')); ++ if (!verifyUsernamePasswordAndSSO($username, $password)) throw new Exception(_('Invalid username or password')); + + // Bail if we need a token but it's invalid + if (isWildcardCert() && $twoFactorRequired && !verifyTwoFactorToken($username, $token)) throw new Exception(_('Invalid 2FA token')); + +@@ -537,8 +565,9 @@ + document.body.appendChild(errorElement); + } + + ++ + + +