mirror of
https://github.com/fosrl/pangolin.git
synced 2025-05-12 13:20:35 +01:00
allow root path
This commit is contained in:
parent
e9cc48a3ae
commit
caded23b51
4 changed files with 96 additions and 12 deletions
|
@ -9,6 +9,10 @@ export function isValidIP(ip: string): boolean {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isValidUrlGlobPattern(pattern: string): boolean {
|
export function isValidUrlGlobPattern(pattern: string): boolean {
|
||||||
|
if (pattern === "/") {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
// Remove leading slash if present
|
// Remove leading slash if present
|
||||||
pattern = pattern.startsWith("/") ? pattern.slice(1) : pattern;
|
pattern = pattern.startsWith("/") ? pattern.slice(1) : pattern;
|
||||||
|
|
||||||
|
|
|
@ -1,61 +1,136 @@
|
||||||
import { isPathAllowed } from './verifySession';
|
|
||||||
import { assertEquals } from '@test/assert';
|
import { assertEquals } from '@test/assert';
|
||||||
|
|
||||||
|
function isPathAllowed(pattern: string, path: string): boolean {
|
||||||
|
|
||||||
|
// Normalize and split paths into segments
|
||||||
|
const normalize = (p: string) => p.split("/").filter(Boolean);
|
||||||
|
const patternParts = normalize(pattern);
|
||||||
|
const pathParts = normalize(path);
|
||||||
|
|
||||||
|
|
||||||
|
// Recursive function to try different wildcard matches
|
||||||
|
function matchSegments(patternIndex: number, pathIndex: number): boolean {
|
||||||
|
const indent = " ".repeat(pathIndex); // Indent based on recursion depth
|
||||||
|
const currentPatternPart = patternParts[patternIndex];
|
||||||
|
const currentPathPart = pathParts[pathIndex];
|
||||||
|
|
||||||
|
// If we've consumed all pattern parts, we should have consumed all path parts
|
||||||
|
if (patternIndex >= patternParts.length) {
|
||||||
|
const result = pathIndex >= pathParts.length;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we've consumed all path parts but still have pattern parts
|
||||||
|
if (pathIndex >= pathParts.length) {
|
||||||
|
// The only way this can match is if all remaining pattern parts are wildcards
|
||||||
|
const remainingPattern = patternParts.slice(patternIndex);
|
||||||
|
const result = remainingPattern.every((p) => p === "*");
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// For full segment wildcards, try consuming different numbers of path segments
|
||||||
|
if (currentPatternPart === "*") {
|
||||||
|
|
||||||
|
// Try consuming 0 segments (skip the wildcard)
|
||||||
|
if (matchSegments(patternIndex + 1, pathIndex)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try consuming current segment and recursively try rest
|
||||||
|
if (matchSegments(patternIndex, pathIndex + 1)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for in-segment wildcard (e.g., "prefix*" or "prefix*suffix")
|
||||||
|
if (currentPatternPart.includes("*")) {
|
||||||
|
// Convert the pattern segment to a regex pattern
|
||||||
|
const regexPattern = currentPatternPart
|
||||||
|
.replace(/\*/g, ".*") // Replace * with .* for regex wildcard
|
||||||
|
.replace(/\?/g, "."); // Replace ? with . for single character wildcard if needed
|
||||||
|
|
||||||
|
const regex = new RegExp(`^${regexPattern}$`);
|
||||||
|
|
||||||
|
if (regex.test(currentPathPart)) {
|
||||||
|
return matchSegments(patternIndex + 1, pathIndex + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// For regular segments, they must match exactly
|
||||||
|
if (currentPatternPart !== currentPathPart) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move to next segments in both pattern and path
|
||||||
|
return matchSegments(patternIndex + 1, pathIndex + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = matchSegments(0, 0);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
function runTests() {
|
function runTests() {
|
||||||
console.log('Running path matching tests...');
|
console.log('Running path matching tests...');
|
||||||
|
|
||||||
// Test exact matching
|
// Test exact matching
|
||||||
assertEquals(isPathAllowed('foo', 'foo'), true, 'Exact match should be allowed');
|
assertEquals(isPathAllowed('foo', 'foo'), true, 'Exact match should be allowed');
|
||||||
assertEquals(isPathAllowed('foo', 'bar'), false, 'Different segments should not match');
|
assertEquals(isPathAllowed('foo', 'bar'), false, 'Different segments should not match');
|
||||||
assertEquals(isPathAllowed('foo/bar', 'foo/bar'), true, 'Exact multi-segment match should be allowed');
|
assertEquals(isPathAllowed('foo/bar', 'foo/bar'), true, 'Exact multi-segment match should be allowed');
|
||||||
assertEquals(isPathAllowed('foo/bar', 'foo/baz'), false, 'Partial multi-segment match should not be allowed');
|
assertEquals(isPathAllowed('foo/bar', 'foo/baz'), false, 'Partial multi-segment match should not be allowed');
|
||||||
|
|
||||||
// Test with leading and trailing slashes
|
// Test with leading and trailing slashes
|
||||||
assertEquals(isPathAllowed('/foo', 'foo'), true, 'Pattern with leading slash should match');
|
assertEquals(isPathAllowed('/foo', 'foo'), true, 'Pattern with leading slash should match');
|
||||||
assertEquals(isPathAllowed('foo/', 'foo'), true, 'Pattern with trailing slash should match');
|
assertEquals(isPathAllowed('foo/', 'foo'), true, 'Pattern with trailing slash should match');
|
||||||
assertEquals(isPathAllowed('/foo/', 'foo'), true, 'Pattern with both leading and trailing slashes should match');
|
assertEquals(isPathAllowed('/foo/', 'foo'), true, 'Pattern with both leading and trailing slashes should match');
|
||||||
assertEquals(isPathAllowed('foo', '/foo/'), true, 'Path with leading and trailing slashes should match');
|
assertEquals(isPathAllowed('foo', '/foo/'), true, 'Path with leading and trailing slashes should match');
|
||||||
|
|
||||||
// Test simple wildcard matching
|
// Test simple wildcard matching
|
||||||
assertEquals(isPathAllowed('*', 'foo'), true, 'Single wildcard should match any single segment');
|
assertEquals(isPathAllowed('*', 'foo'), true, 'Single wildcard should match any single segment');
|
||||||
assertEquals(isPathAllowed('*', 'foo/bar'), true, 'Single wildcard should match multiple segments');
|
assertEquals(isPathAllowed('*', 'foo/bar'), true, 'Single wildcard should match multiple segments');
|
||||||
assertEquals(isPathAllowed('*/bar', 'foo/bar'), true, 'Wildcard prefix should match');
|
assertEquals(isPathAllowed('*/bar', 'foo/bar'), true, 'Wildcard prefix should match');
|
||||||
assertEquals(isPathAllowed('foo/*', 'foo/bar'), true, 'Wildcard suffix should match');
|
assertEquals(isPathAllowed('foo/*', 'foo/bar'), true, 'Wildcard suffix should match');
|
||||||
assertEquals(isPathAllowed('foo/*/baz', 'foo/bar/baz'), true, 'Wildcard in middle should match');
|
assertEquals(isPathAllowed('foo/*/baz', 'foo/bar/baz'), true, 'Wildcard in middle should match');
|
||||||
|
|
||||||
// Test multiple wildcards
|
// Test multiple wildcards
|
||||||
assertEquals(isPathAllowed('*/*', 'foo/bar'), true, 'Multiple wildcards should match corresponding segments');
|
assertEquals(isPathAllowed('*/*', 'foo/bar'), true, 'Multiple wildcards should match corresponding segments');
|
||||||
assertEquals(isPathAllowed('*/*/*', 'foo/bar/baz'), true, 'Three wildcards should match three segments');
|
assertEquals(isPathAllowed('*/*/*', 'foo/bar/baz'), true, 'Three wildcards should match three segments');
|
||||||
assertEquals(isPathAllowed('foo/*/*', 'foo/bar/baz'), true, 'Specific prefix with wildcards should match');
|
assertEquals(isPathAllowed('foo/*/*', 'foo/bar/baz'), true, 'Specific prefix with wildcards should match');
|
||||||
assertEquals(isPathAllowed('*/*/baz', 'foo/bar/baz'), true, 'Wildcards with specific suffix should match');
|
assertEquals(isPathAllowed('*/*/baz', 'foo/bar/baz'), true, 'Wildcards with specific suffix should match');
|
||||||
|
|
||||||
// Test wildcard consumption behavior
|
// Test wildcard consumption behavior
|
||||||
assertEquals(isPathAllowed('*', ''), true, 'Wildcard should optionally consume segments');
|
assertEquals(isPathAllowed('*', ''), true, 'Wildcard should optionally consume segments');
|
||||||
assertEquals(isPathAllowed('foo/*', 'foo'), true, 'Trailing wildcard should be optional');
|
assertEquals(isPathAllowed('foo/*', 'foo'), true, 'Trailing wildcard should be optional');
|
||||||
assertEquals(isPathAllowed('*/*', 'foo'), true, 'Multiple wildcards can match fewer segments');
|
assertEquals(isPathAllowed('*/*', 'foo'), true, 'Multiple wildcards can match fewer segments');
|
||||||
assertEquals(isPathAllowed('*/*/*', 'foo/bar'), true, 'Extra wildcards can be skipped');
|
assertEquals(isPathAllowed('*/*/*', 'foo/bar'), true, 'Extra wildcards can be skipped');
|
||||||
|
|
||||||
// Test complex nested paths
|
// Test complex nested paths
|
||||||
assertEquals(isPathAllowed('api/*/users', 'api/v1/users'), true, 'API versioning pattern should match');
|
assertEquals(isPathAllowed('api/*/users', 'api/v1/users'), true, 'API versioning pattern should match');
|
||||||
assertEquals(isPathAllowed('api/*/users/*', 'api/v1/users/123'), true, 'API resource pattern should match');
|
assertEquals(isPathAllowed('api/*/users/*', 'api/v1/users/123'), true, 'API resource pattern should match');
|
||||||
assertEquals(isPathAllowed('api/*/users/*/profile', 'api/v1/users/123/profile'), true, 'Nested API pattern should match');
|
assertEquals(isPathAllowed('api/*/users/*/profile', 'api/v1/users/123/profile'), true, 'Nested API pattern should match');
|
||||||
|
|
||||||
// Test for the requested padbootstrap* pattern
|
// Test for the requested padbootstrap* pattern
|
||||||
assertEquals(isPathAllowed('padbootstrap*', 'padbootstrap'), true, 'padbootstrap* should match padbootstrap');
|
assertEquals(isPathAllowed('padbootstrap*', 'padbootstrap'), true, 'padbootstrap* should match padbootstrap');
|
||||||
assertEquals(isPathAllowed('padbootstrap*', 'padbootstrapv1'), true, 'padbootstrap* should match padbootstrapv1');
|
assertEquals(isPathAllowed('padbootstrap*', 'padbootstrapv1'), true, 'padbootstrap* should match padbootstrapv1');
|
||||||
assertEquals(isPathAllowed('padbootstrap*', 'padbootstrap/files'), false, 'padbootstrap* should not match padbootstrap/files');
|
assertEquals(isPathAllowed('padbootstrap*', 'padbootstrap/files'), false, 'padbootstrap* should not match padbootstrap/files');
|
||||||
assertEquals(isPathAllowed('padbootstrap*/*', 'padbootstrap/files'), true, 'padbootstrap*/* should match padbootstrap/files');
|
assertEquals(isPathAllowed('padbootstrap*/*', 'padbootstrap/files'), true, 'padbootstrap*/* should match padbootstrap/files');
|
||||||
assertEquals(isPathAllowed('padbootstrap*/files', 'padbootstrapv1/files'), true, 'padbootstrap*/files should not match padbootstrapv1/files (wildcard is segment-based, not partial)');
|
assertEquals(isPathAllowed('padbootstrap*/files', 'padbootstrapv1/files'), true, 'padbootstrap*/files should not match padbootstrapv1/files (wildcard is segment-based, not partial)');
|
||||||
|
|
||||||
// Test wildcard edge cases
|
// Test wildcard edge cases
|
||||||
assertEquals(isPathAllowed('*/*/*/*/*/*', 'a/b'), true, 'Many wildcards can match few segments');
|
assertEquals(isPathAllowed('*/*/*/*/*/*', 'a/b'), true, 'Many wildcards can match few segments');
|
||||||
assertEquals(isPathAllowed('a/*/b/*/c', 'a/anything/b/something/c'), true, 'Multiple wildcards in pattern should match corresponding segments');
|
assertEquals(isPathAllowed('a/*/b/*/c', 'a/anything/b/something/c'), true, 'Multiple wildcards in pattern should match corresponding segments');
|
||||||
|
|
||||||
// Test patterns with partial segment matches
|
// Test patterns with partial segment matches
|
||||||
assertEquals(isPathAllowed('padbootstrap*', 'padbootstrap-123'), true, 'Wildcards in isPathAllowed should be segment-based, not character-based');
|
assertEquals(isPathAllowed('padbootstrap*', 'padbootstrap-123'), true, 'Wildcards in isPathAllowed should be segment-based, not character-based');
|
||||||
assertEquals(isPathAllowed('test*', 'testuser'), true, 'Asterisk as part of segment name is treated as a literal, not a wildcard');
|
assertEquals(isPathAllowed('test*', 'testuser'), true, 'Asterisk as part of segment name is treated as a literal, not a wildcard');
|
||||||
assertEquals(isPathAllowed('my*app', 'myapp'), true, 'Asterisk in middle of segment name is treated as a literal, not a wildcard');
|
assertEquals(isPathAllowed('my*app', 'myapp'), true, 'Asterisk in middle of segment name is treated as a literal, not a wildcard');
|
||||||
|
|
||||||
|
assertEquals(isPathAllowed('/', '/'), true, 'Root path should match root path');
|
||||||
|
assertEquals(isPathAllowed('/', '/test'), false, 'Root path should not match non-root path');
|
||||||
|
|
||||||
console.log('All tests passed!');
|
console.log('All tests passed!');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -64,4 +139,4 @@ try {
|
||||||
runTests();
|
runTests();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Test failed:', error);
|
console.error('Test failed:', error);
|
||||||
}
|
}
|
||||||
|
|
|
@ -798,6 +798,12 @@ export default function ReverseProxyTargets(props: {
|
||||||
type="submit"
|
type="submit"
|
||||||
variant="outlinePrimary"
|
variant="outlinePrimary"
|
||||||
className="mt-6"
|
className="mt-6"
|
||||||
|
disabled={
|
||||||
|
!(
|
||||||
|
addTargetForm.getValues("ip") &&
|
||||||
|
addTargetForm.getValues("port")
|
||||||
|
)
|
||||||
|
}
|
||||||
>
|
>
|
||||||
Add Target
|
Add Target
|
||||||
</Button>
|
</Button>
|
||||||
|
|
|
@ -64,7 +64,6 @@ import {
|
||||||
InfoSections,
|
InfoSections,
|
||||||
InfoSectionTitle
|
InfoSectionTitle
|
||||||
} from "@app/components/InfoSection";
|
} from "@app/components/InfoSection";
|
||||||
import { Separator } from "@app/components/ui/separator";
|
|
||||||
import { InfoPopup } from "@app/components/ui/info-popup";
|
import { InfoPopup } from "@app/components/ui/info-popup";
|
||||||
import {
|
import {
|
||||||
isValidCIDR,
|
isValidCIDR,
|
||||||
|
|
Loading…
Reference in a new issue