Files
1Panel/core/middleware/demo_handle.go

195 lines
5.6 KiB
Go

package middleware
import (
"net/http"
"strings"
"sync"
"github.com/1Panel-dev/1Panel/core/app/dto"
"github.com/1Panel-dev/1Panel/core/buserr"
"github.com/gin-gonic/gin"
)
var (
demoRouteMu sync.RWMutex
demoReadOnlyPostRoutes = map[string]struct{}{
"/api/v2/dashboard/app/launcher/option": {},
"/api/v2/websites/config": {},
"/api/v2/websites/waf/config": {},
"/api/v2/websites/options": {},
"/api/v2/websites/rewrite": {},
"/api/v2/websites/dir": {},
"/api/v2/websites/proxies": {},
"/api/v2/websites/auths": {},
"/api/v2/websites/leech": {},
"/api/v2/websites/redirect": {},
"/api/v2/files/size": {},
"/api/v2/files/tree": {},
"/api/v2/toolbox/device/base": {},
"/api/v2/files/user/group": {},
"/api/v2/files/mount": {},
"/api/v2/hosts/ssh/log": {},
"/api/v2/toolbox/clam/base": {},
"/api/v2/backups/record/size": {},
"/api/v2/containers/info": {},
"/api/v2/containers/list": {},
"/api/v2/containers/list/byimage": {},
"/api/v2/containers/users": {},
"/api/v2/containers/files/size": {},
"/api/v2/logs/system/read": {},
"/api/v2/logs/tasks/read": {},
"/api/v2/core/auth/login": {},
"/api/v2/core/auth/mfalogin": {},
"/api/v2/core/auth/passkey/begin": {},
"/api/v2/core/auth/passkey/finish": {},
"/api/v2/core/logs/login": {},
"/api/v2/core/logs/operation": {},
"/api/v2/core/auth/logout": {},
"/api/v2/core/settings/search/base": {},
"/api/v2/apps/installed/loadport": {},
"/api/v2/apps/installed/check": {},
"/api/v2/apps/installed/conninfo": {},
"/api/v2/databases/common/info": {},
"/api/v2/databases/common/load/file": {},
"/api/v2/databases/load/file": {},
"/api/v2/databases/variables": {},
"/api/v2/databases/status": {},
"/api/v2/databases/baseinfo": {},
"/api/v2/backups/search/files": {},
"/api/v2/backups/record/search/bycronjob": {},
"/api/v2/cronjobs/search/records": {},
"/api/v2/xpack/waf/attack/stat": {},
"/api/v2/xpack/waf/config/website": {},
"/api/v2/xpack/waf/relation/stat": {},
"/api/v2/xpack/monitor/stat": {},
"/api/v2/xpack/monitor/visitors": {},
"/api/v2/xpack/monitor/visitors/loc": {},
"/api/v2/xpack/monitor/qps": {},
"/api/v2/xpack/monitor/logs/stat": {},
"/api/v2/xpack/monitor/websites": {},
"/api/v2/xpack/monitor/trend": {},
"/api/v2/xpack/monitor/rank": {},
"/api/v2/xpack/waf/cdn": {},
"/api/v2/xpack/tampers/search/log": {},
"/api/v2/xpack/tampers/search/file": {},
"/api/v2/core/nodes/list": {},
"/api/v2/core/xpack/nodes/search/upgrade/logs": {},
}
demoBlockedGetRoutes = map[string]struct{}{
"/api/v2/containers/exec": {},
"/api/v2/core/script/run": {},
"/api/v2/files/download": {},
"/api/v2/hosts/terminal/local": {},
"/api/v2/hosts/terminal/ssh": {},
"/api/v2/hosts/terminal/container": {},
"/api/v2/process/:pid": {},
}
)
const demoReadOnlyContextKey = "DEMO_READ_ONLY_REQUEST"
func RegisterDemoReadOnlyPostRoutes(paths ...string) {
demoRouteMu.Lock()
defer demoRouteMu.Unlock()
for _, routePath := range paths {
if routePath != "" {
demoReadOnlyPostRoutes[routePath] = struct{}{}
}
}
}
func RegisterDemoBlockedGetRoutes(paths ...string) {
demoRouteMu.Lock()
defer demoRouteMu.Unlock()
for _, routePath := range paths {
if routePath != "" {
demoBlockedGetRoutes[routePath] = struct{}{}
}
}
}
func demoRequestPath(c *gin.Context) string {
if routePath := c.FullPath(); routePath != "" {
return routePath
}
return c.Request.URL.Path
}
func isDemoSearchRoute(routePath string) bool {
return strings.HasSuffix(routePath, "/search") || strings.HasSuffix(routePath, "/ai-search")
}
func isDemoReadOnlyPost(routePath string) bool {
if isDemoSearchRoute(routePath) {
return true
}
demoRouteMu.RLock()
defer demoRouteMu.RUnlock()
for pattern := range demoReadOnlyPostRoutes {
if matchDemoRoute(pattern, routePath) {
return true
}
}
return false
}
func isDemoBlockedGet(routePath string) bool {
demoRouteMu.RLock()
defer demoRouteMu.RUnlock()
for pattern := range demoBlockedGetRoutes {
if matchDemoRoute(pattern, routePath) {
return true
}
}
return false
}
func matchDemoRoute(pattern, routePath string) bool {
if pattern == routePath {
return true
}
patternParts := strings.Split(strings.Trim(pattern, "/"), "/")
pathParts := strings.Split(strings.Trim(routePath, "/"), "/")
if len(patternParts) != len(pathParts) {
return false
}
for i := range patternParts {
if strings.HasPrefix(patternParts[i], ":") {
continue
}
if patternParts[i] != pathParts[i] {
return false
}
}
return true
}
func DemoHandle() gin.HandlerFunc {
return func(c *gin.Context) {
routePath := demoRequestPath(c)
if c.Request.Method == http.MethodGet && !isDemoBlockedGet(routePath) {
c.Set(demoReadOnlyContextKey, true)
c.Next()
return
}
if c.Request.Method == http.MethodPost && isDemoReadOnlyPost(routePath) {
c.Set(demoReadOnlyContextKey, true)
c.Next()
return
}
c.JSON(http.StatusInternalServerError, dto.Response{
Code: http.StatusInternalServerError,
Message: buserr.New("ErrDemoEnvironment").Error(),
})
c.Abort()
}
}