mirror of
https://github.com/goauthentik/authentik.git
synced 2026-06-17 19:09:11 +03:00
admin/files: support %(theme)s variable in media file paths (#19108)
* admin/files: support %(theme)s variable in media file paths * wip * Apply suggestion from @rissson Co-authored-by: Marc 'risson' Schmitt <marc.schmitt@risson.space> Signed-off-by: Dominic R <dominic@sdko.org> --------- Signed-off-by: Dominic R <dominic@sdko.org> Co-authored-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
This commit is contained in:
+36
-5
@@ -5,6 +5,7 @@ import (
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/go-http-utils/etag"
|
||||
@@ -17,11 +18,44 @@ import (
|
||||
staticWeb "goauthentik.io/web"
|
||||
)
|
||||
|
||||
// Theme variable placeholder that can be used in file paths
|
||||
// This allows for theme-specific files like logo-%(theme)s.png
|
||||
const themeVariable = "%(theme)s"
|
||||
|
||||
// Valid themes that can be substituted for %(theme)s
|
||||
var validThemes = []string{"light", "dark"}
|
||||
|
||||
type StorageClaims struct {
|
||||
jwt.RegisteredClaims
|
||||
Path string `json:"path,omitempty"`
|
||||
}
|
||||
|
||||
// pathMatchesWithTheme checks if the requested path matches the JWT path,
|
||||
// accounting for theme variable substitution.
|
||||
// If the JWT path contains %(theme)s, it will match the requested path
|
||||
// if substituting %(theme)s with any valid theme produces the requested path.
|
||||
func pathMatchesWithTheme(jwtPath, requestedPath string) bool {
|
||||
// Direct match (no theme variable)
|
||||
if jwtPath == requestedPath {
|
||||
return true
|
||||
}
|
||||
|
||||
// Check if JWT path contains theme variable
|
||||
if !strings.Contains(jwtPath, themeVariable) {
|
||||
return false
|
||||
}
|
||||
|
||||
// Try substituting each valid theme and check for a match
|
||||
for _, theme := range validThemes {
|
||||
substituted := strings.ReplaceAll(jwtPath, themeVariable, theme)
|
||||
if substituted == requestedPath {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func storageTokenIsValid(usage string, r *http.Request) bool {
|
||||
tokenString := r.URL.Query().Get("token")
|
||||
if tokenString == "" {
|
||||
@@ -51,11 +85,8 @@ func storageTokenIsValid(usage string, r *http.Request) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
if claims.Path != fmt.Sprintf("%s/%s", usage, r.URL.Path) {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
requestedPath := fmt.Sprintf("%s/%s", usage, r.URL.Path)
|
||||
return pathMatchesWithTheme(claims.Path, requestedPath)
|
||||
}
|
||||
|
||||
func (ws *WebServer) configureStatic() {
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
package web
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestPathMatchesWithTheme(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
jwtPath string
|
||||
requestedPath string
|
||||
want bool
|
||||
}{
|
||||
{
|
||||
name: "exact match without theme variable",
|
||||
jwtPath: "media/public/logo.png",
|
||||
requestedPath: "media/public/logo.png",
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "no match without theme variable",
|
||||
jwtPath: "media/public/logo.png",
|
||||
requestedPath: "media/public/other.png",
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "theme variable matches light theme",
|
||||
jwtPath: "media/public/logo-%(theme)s.png",
|
||||
requestedPath: "media/public/logo-light.png",
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "theme variable matches dark theme",
|
||||
jwtPath: "media/public/logo-%(theme)s.png",
|
||||
requestedPath: "media/public/logo-dark.png",
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "theme variable does not match invalid theme",
|
||||
jwtPath: "media/public/logo-%(theme)s.png",
|
||||
requestedPath: "media/public/logo-blue.png",
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "theme variable in directory path",
|
||||
jwtPath: "media/%(theme)s/logo.png",
|
||||
requestedPath: "media/light/logo.png",
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "multiple theme variables",
|
||||
jwtPath: "media/%(theme)s/logo-%(theme)s.png",
|
||||
requestedPath: "media/light/logo-light.png",
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "multiple theme variables with dark",
|
||||
jwtPath: "media/%(theme)s/logo-%(theme)s.png",
|
||||
requestedPath: "media/dark/logo-dark.png",
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "multiple theme variables mixed themes should not match",
|
||||
jwtPath: "media/%(theme)s/logo-%(theme)s.png",
|
||||
requestedPath: "media/light/logo-dark.png",
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "theme variable with nested path",
|
||||
jwtPath: "media/public/brand/logo-%(theme)s.svg",
|
||||
requestedPath: "media/public/brand/logo-dark.svg",
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "empty paths",
|
||||
jwtPath: "",
|
||||
requestedPath: "",
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "theme variable only",
|
||||
jwtPath: "%(theme)s",
|
||||
requestedPath: "light",
|
||||
want: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := pathMatchesWithTheme(tt.jwtPath, tt.requestedPath)
|
||||
if got != tt.want {
|
||||
t.Errorf("pathMatchesWithTheme(%q, %q) = %v, want %v",
|
||||
tt.jwtPath, tt.requestedPath, got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user