init push
This commit is contained in:
172
internal/transport/http/middleware/wopi_middleware.go
Normal file
172
internal/transport/http/middleware/wopi_middleware.go
Normal file
@@ -0,0 +1,172 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user