CasaOS/route/v2.go

264 lines
8.0 KiB
Go

package route
import (
"crypto/ecdsa"
"log"
"net/http"
"net/url"
"path"
"path/filepath"
"strconv"
"strings"
"github.com/IceWhaleTech/CasaOS/codegen"
"github.com/IceWhaleTech/CasaOS/pkg/config"
"github.com/IceWhaleTech/CasaOS/pkg/utils/file"
"github.com/IceWhaleTech/CasaOS-Common/external"
"github.com/IceWhaleTech/CasaOS-Common/utils/jwt"
v2Route "github.com/IceWhaleTech/CasaOS/route/v2"
"github.com/deepmap/oapi-codegen/pkg/middleware"
"github.com/getkin/kin-openapi/openapi3"
"github.com/getkin/kin-openapi/openapi3filter"
"github.com/labstack/echo/v4"
echo_middleware "github.com/labstack/echo/v4/middleware"
)
var (
_swagger *openapi3.T
V2APIPath string
V2DocPath string
V3FilePath string
)
func init() {
swagger, err := codegen.GetSwagger()
if err != nil {
panic(err)
}
_swagger = swagger
u, err := url.Parse(_swagger.Servers[0].URL)
if err != nil {
panic(err)
}
V2APIPath = strings.TrimRight(u.Path, "/")
V2DocPath = "/doc" + V2APIPath
V3FilePath = "/v3/file"
}
func InitV2Router() http.Handler {
appManagement := v2Route.NewCasaOS()
e := echo.New()
e.Use((echo_middleware.CORSWithConfig(echo_middleware.CORSConfig{
AllowOrigins: []string{"*"},
AllowMethods: []string{echo.POST, echo.GET, echo.OPTIONS, echo.PUT, echo.DELETE},
AllowHeaders: []string{echo.HeaderAuthorization, echo.HeaderContentLength, echo.HeaderXCSRFToken, echo.HeaderContentType, echo.HeaderAccessControlAllowOrigin, echo.HeaderAccessControlAllowHeaders, echo.HeaderAccessControlAllowMethods, echo.HeaderConnection, echo.HeaderOrigin, echo.HeaderXRequestedWith},
ExposeHeaders: []string{echo.HeaderContentLength, echo.HeaderAccessControlAllowOrigin, echo.HeaderAccessControlAllowHeaders},
MaxAge: 172800,
AllowCredentials: true,
})))
e.Use(echo_middleware.Gzip())
e.Use(echo_middleware.Logger())
e.Use(echo_middleware.JWTWithConfig(echo_middleware.JWTConfig{
Skipper: func(c echo.Context) bool {
return c.RealIP() == "::1" || c.RealIP() == "127.0.0.1"
//return true
},
ParseTokenFunc: func(token string, c echo.Context) (interface{}, error) {
valid, claims, err := jwt.Validate(token, func() (*ecdsa.PublicKey, error) { return external.GetPublicKey(config.CommonInfo.RuntimePath) })
if err != nil || !valid {
return nil, echo.ErrUnauthorized
}
c.Request().Header.Set("user_id", strconv.Itoa(claims.ID))
return claims, nil
},
TokenLookupFuncs: []echo_middleware.ValuesExtractor{
func(c echo.Context) ([]string, error) {
if len(c.Request().Header.Get(echo.HeaderAuthorization)) > 0 {
return []string{c.Request().Header.Get(echo.HeaderAuthorization)}, nil
}
return []string{c.QueryParam("token")}, nil
},
},
}))
// e.Use(func(next echo.HandlerFunc) echo.HandlerFunc {
// return func(c echo.Context) error {
// switch c.Request().Header.Get(echo.HeaderContentType) {
// case common.MIMEApplicationYAML: // in case request contains a compose content in YAML
// return middleware.OapiRequestValidatorWithOptions(_swagger, &middleware.Options{
// Options: openapi3filter.Options{
// AuthenticationFunc: openapi3filter.NoopAuthenticationFunc,
// // ExcludeRequestBody: true,
// // ExcludeResponseBody: true,
// },
// })(next)(c)
// default:
// return middleware.OapiRequestValidatorWithOptions(_swagger, &middleware.Options{
// Options: openapi3filter.Options{
// AuthenticationFunc: openapi3filter.NoopAuthenticationFunc,
// },
// })(next)(c)
// }
// }
// })
e.Use(middleware.OapiRequestValidatorWithOptions(_swagger, &middleware.Options{
Skipper: func(c echo.Context) bool {
// jump validate when upload file
// because file upload can't pass validate
// issue: https://github.com/deepmap/oapi-codegen/issues/514
return strings.Contains(c.Request().Header[echo.HeaderContentType][0], "multipart/form-data")
},
Options: openapi3filter.Options{AuthenticationFunc: openapi3filter.NoopAuthenticationFunc},
}))
codegen.RegisterHandlersWithBaseURL(e, appManagement, V2APIPath)
return e
}
func InitV2DocRouter(docHTML string, docYAML string) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path == V2DocPath {
if _, err := w.Write([]byte(docHTML)); err != nil {
w.WriteHeader(http.StatusInternalServerError)
}
return
}
if r.URL.Path == V2DocPath+"/openapi.yaml" {
if _, err := w.Write([]byte(docYAML)); err != nil {
w.WriteHeader(http.StatusInternalServerError)
}
}
})
}
func InitFile() http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
token := r.URL.Query().Get("token")
if len(token) == 0 {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusUnauthorized)
w.Write([]byte(`{"message": "token not found"}`))
return
}
valid, _, errs := jwt.Validate(token, func() (*ecdsa.PublicKey, error) { return external.GetPublicKey(config.CommonInfo.RuntimePath) })
if errs != nil || !valid {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusUnauthorized)
w.Write([]byte(`{"message": "validation failure"}`))
return
}
filePath := r.URL.Query().Get("path")
fileName := path.Base(filePath)
w.Header().Add("Content-Disposition", "attachment; filename*=utf-8''"+url.PathEscape(fileName))
http.ServeFile(w, r, filePath)
//http.ServeFile(w, r, filePath)
})
}
func InitDir() http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
token := r.URL.Query().Get("token")
if len(token) == 0 {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusUnauthorized)
w.Write([]byte(`{"message": "token not found"}`))
return
}
valid, _, errs := jwt.Validate(token, func() (*ecdsa.PublicKey, error) { return external.GetPublicKey(config.CommonInfo.RuntimePath) })
if errs != nil || !valid {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusUnauthorized)
w.Write([]byte(`{"message": "validation failure"}`))
return
}
t := r.URL.Query().Get("format")
files := r.URL.Query().Get("files")
if len(files) == 0 {
// w.JSON(common_err.CLIENT_ERROR, model.Result{
// Success: common_err.INVALID_PARAMS,
// Message: common_err.GetMsg(common_err.INVALID_PARAMS),
// })
return
}
list := strings.Split(files, ",")
for _, v := range list {
if !file.Exists(v) {
// c.JSON(common_err.SERVICE_ERROR, model.Result{
// Success: common_err.FILE_DOES_NOT_EXIST,
// Message: common_err.GetMsg(common_err.FILE_DOES_NOT_EXIST),
// })
return
}
}
w.Header().Add("Content-Type", "application/octet-stream")
w.Header().Add("Content-Transfer-Encoding", "binary")
w.Header().Add("Cache-Control", "no-cache")
// handles only single files not folders and multiple files
// if len(list) == 1 {
// filePath := list[0]
// info, err := os.Stat(filePath)
// if err != nil {
// w.JSON(http.StatusOK, model.Result{
// Success: common_err.FILE_DOES_NOT_EXIST,
// Message: common_err.GetMsg(common_err.FILE_DOES_NOT_EXIST),
// })
//return
// }
//}
extension, ar, err := file.GetCompressionAlgorithm(t)
if err != nil {
// w.JSON(common_err.CLIENT_ERROR, model.Result{
// Success: common_err.INVALID_PARAMS,
// Message: common_err.GetMsg(common_err.INVALID_PARAMS),
// })
return
}
err = ar.Create(w)
if err != nil {
// c.JSON(common_err.SERVICE_ERROR, model.Result{
// Success: common_err.SERVICE_ERROR,
// Message: common_err.GetMsg(common_err.SERVICE_ERROR),
// Data: err.Error(),
// })
return
}
defer ar.Close()
commonDir := file.CommonPrefix(filepath.Separator, list...)
currentPath := filepath.Base(commonDir)
name := "_" + currentPath
name += extension
w.Header().Add("Content-Disposition", "attachment; filename*=utf-8''"+url.PathEscape(name))
for _, fname := range list {
err = file.AddFile(ar, fname, commonDir)
if err != nil {
log.Printf("Failed to archive %s: %v", fname, err)
}
}
})
}