173 lines
4.5 KiB
Go
173 lines
4.5 KiB
Go
package middleware
|
|
|
|
import (
|
|
"net/http"
|
|
"net/url"
|
|
"strings"
|
|
|
|
"github.com/gofiber/fiber/v2"
|
|
|
|
"wucher/internal/service"
|
|
"wucher/internal/shared/pkg/apperrorsx"
|
|
"wucher/internal/shared/pkg/uuidv7"
|
|
)
|
|
|
|
const (
|
|
wopiAccessTokenQuery = "access_token"
|
|
wopiFileIDParam = "fileId"
|
|
)
|
|
|
|
func WOPIRequired(svc *service.WOPIService) fiber.Handler {
|
|
return func(c *fiber.Ctx) error {
|
|
if svc == nil {
|
|
return apperrorsx.Write(c, apperrorsx.New(apperrorsx.ErrFileManagerWOPIServiceUnavailable))
|
|
}
|
|
|
|
fileID, err := parseWOPIFileID(c)
|
|
if err != nil {
|
|
return apperrorsx.Write(c, apperrorsx.New(apperrorsx.ErrFileManagerWOPIInvalidFileID))
|
|
}
|
|
|
|
token := firstNonEmptyWOPIQueryValue(c, wopiAccessTokenQuery)
|
|
if token == "" {
|
|
token = firstNonEmptyWOPISrcQueryValue(c, wopiAccessTokenQuery)
|
|
}
|
|
if token == "" {
|
|
authz := strings.TrimSpace(c.Get("Authorization"))
|
|
if strings.HasPrefix(strings.ToLower(authz), "bearer ") {
|
|
token = strings.TrimSpace(authz[7:])
|
|
}
|
|
}
|
|
claims, err := svc.VerifyAccessToken(token, fileID)
|
|
if err != nil {
|
|
return apperrorsx.Write(c, apperrorsx.New(apperrorsx.ErrFileManagerWOPIInvalidToken).WithCause(err))
|
|
}
|
|
|
|
if err := svc.VerifyProof(c.Method(), c.Path(), token, c.Get("X-WOPI-TimeStamp"), c.Get("X-WOPI-Proof"), c.Get("X-WOPI-ProofOld")); err != nil {
|
|
return apperrorsx.Write(c, apperrorsx.New(apperrorsx.ErrFileManagerWOPIInvalidProof).WithCause(err))
|
|
}
|
|
|
|
userID, _ := uuidv7.ParsePathLikeString(claims.UserID)
|
|
c.Locals("user_id", userID)
|
|
c.Locals("wopi_claims", claims)
|
|
c.Locals("wopi_permission", strings.ToLower(strings.TrimSpace(claims.Permission)))
|
|
c.Locals("wopi_takeover_id", claims.TakeoverID)
|
|
c.Locals("wopi_access_token", token)
|
|
return c.Next()
|
|
}
|
|
}
|
|
|
|
func firstNonEmptyWOPIQueryValue(c *fiber.Ctx, key string) string {
|
|
if c == nil || strings.TrimSpace(key) == "" {
|
|
return ""
|
|
}
|
|
found := ""
|
|
c.Context().QueryArgs().VisitAll(func(k, v []byte) {
|
|
if found != "" {
|
|
return
|
|
}
|
|
if !strings.EqualFold(string(k), key) {
|
|
return
|
|
}
|
|
value := strings.TrimSpace(string(v))
|
|
if value != "" {
|
|
found = value
|
|
}
|
|
})
|
|
if found != "" {
|
|
return found
|
|
}
|
|
return strings.TrimSpace(c.Query(key))
|
|
}
|
|
|
|
func firstNonEmptyWOPISrcQueryValue(c *fiber.Ctx, key string) string {
|
|
if c == nil || strings.TrimSpace(key) == "" {
|
|
return ""
|
|
}
|
|
src := strings.TrimSpace(c.Query("WOPISrc"))
|
|
if src == "" {
|
|
return ""
|
|
}
|
|
decoded := src
|
|
if unescaped, err := url.QueryUnescape(src); err == nil && strings.TrimSpace(unescaped) != "" {
|
|
decoded = strings.TrimSpace(unescaped)
|
|
}
|
|
parsed, err := url.Parse(decoded)
|
|
if err != nil {
|
|
return ""
|
|
}
|
|
query := parsed.Query()
|
|
if value := strings.TrimSpace(query.Get(key)); value != "" {
|
|
return value
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func parseWOPIFileID(c *fiber.Ctx) ([]byte, error) {
|
|
if c == nil {
|
|
return nil, fiber.ErrBadRequest
|
|
}
|
|
candidates := []string{
|
|
c.Params(wopiFileIDParam),
|
|
c.Path(),
|
|
c.OriginalURL(),
|
|
c.Query("WOPISrc"),
|
|
}
|
|
for _, candidate := range candidates {
|
|
if fileID, err := uuidv7.ParsePathLikeString(candidate); err == nil {
|
|
return fileID, nil
|
|
}
|
|
}
|
|
return nil, fiber.ErrBadRequest
|
|
}
|
|
|
|
func WOPIRequestBodyLimit(maxBytes int) fiber.Handler {
|
|
return func(c *fiber.Ctx) error {
|
|
if maxBytes <= 0 {
|
|
return c.Next()
|
|
}
|
|
if c.Method() == http.MethodPost || c.Method() == http.MethodPut {
|
|
if c.Request().Header.ContentLength() > maxBytes {
|
|
return apperrorsx.Write(c, apperrorsx.New(apperrorsx.ErrFileManagerWOPIValidationFailed))
|
|
}
|
|
}
|
|
return c.Next()
|
|
}
|
|
}
|
|
|
|
func WOPIPermissionRequired(required service.WOPIPermission) fiber.Handler {
|
|
return func(c *fiber.Ctx) error {
|
|
if strings.TrimSpace(string(required)) == "" {
|
|
return c.Next()
|
|
}
|
|
raw := c.Locals("wopi_permission")
|
|
current, _ := raw.(string)
|
|
if !wopiPermissionAllowed(current, string(required)) {
|
|
return apperrorsx.Write(c, apperrorsx.New(apperrorsx.ErrFileManagerWOPIPermissionDenied))
|
|
}
|
|
return c.Next()
|
|
}
|
|
}
|
|
|
|
func wopiPermissionAllowed(current, required string) bool {
|
|
switch strings.ToLower(strings.TrimSpace(required)) {
|
|
case string(service.WOPIPermissionRead):
|
|
return wopiPermissionRank(current) >= wopiPermissionRank(string(service.WOPIPermissionRead))
|
|
case string(service.WOPIPermissionWrite):
|
|
return wopiPermissionRank(current) >= wopiPermissionRank(string(service.WOPIPermissionWrite))
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
func wopiPermissionRank(v string) int {
|
|
switch strings.ToLower(strings.TrimSpace(v)) {
|
|
case string(service.WOPIPermissionRead):
|
|
return 1
|
|
case string(service.WOPIPermissionWrite):
|
|
return 2
|
|
default:
|
|
return 0
|
|
}
|
|
}
|