Merge pull request #17 from matthewpi/gin
Port over remaining transfer/archive code to gin, delete http.go
This commit is contained in:
commit
0a13cfe236
|
@ -21,6 +21,7 @@ func Configure() *gin.Engine {
|
||||||
protected.GET("/api/system", getSystemInformation)
|
protected.GET("/api/system", getSystemInformation)
|
||||||
protected.GET("/api/servers", getAllServers)
|
protected.GET("/api/servers", getAllServers)
|
||||||
protected.POST("/api/servers", postCreateServer)
|
protected.POST("/api/servers", postCreateServer)
|
||||||
|
protected.POST("/api/transfer", postTransfer)
|
||||||
|
|
||||||
// These are server specific routes, and require that the request be authorized, and
|
// These are server specific routes, and require that the request be authorized, and
|
||||||
// that the server exist on the Daemon.
|
// that the server exist on the Daemon.
|
||||||
|
|
|
@ -130,7 +130,7 @@ func patchServer(c *gin.Context) {
|
||||||
c.Status(http.StatusNoContent)
|
c.Status(http.StatusNoContent)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Performs a server installation in a backgrounded thread.
|
// Performs a server installation in a background thread.
|
||||||
func postServerInstall(c *gin.Context) {
|
func postServerInstall(c *gin.Context) {
|
||||||
s := GetServer(c.Param("server"))
|
s := GetServer(c.Param("server"))
|
||||||
|
|
||||||
|
|
|
@ -2,12 +2,9 @@ package router
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/pkg/errors"
|
|
||||||
"github.com/pterodactyl/wings/api"
|
|
||||||
"github.com/pterodactyl/wings/server"
|
"github.com/pterodactyl/wings/server"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Backs up a server.
|
// Backs up a server.
|
||||||
|
@ -28,37 +25,3 @@ func postServerBackup(c *gin.Context) {
|
||||||
|
|
||||||
c.Status(http.StatusAccepted)
|
c.Status(http.StatusAccepted)
|
||||||
}
|
}
|
||||||
|
|
||||||
func getServerArchive(c *gin.Context) {
|
|
||||||
}
|
|
||||||
|
|
||||||
func postServerArchive(c *gin.Context) {
|
|
||||||
s := GetServer(c.Param("server"))
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
start := time.Now()
|
|
||||||
|
|
||||||
if err := s.Archiver.Archive(); err != nil {
|
|
||||||
zap.S().Errorw("failed to get archive for server", zap.String("server", s.Uuid), zap.Error(err))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
zap.S().Debugw("successfully created archive for server", zap.String("server", s.Uuid), zap.Duration("time", time.Now().Sub(start).Round(time.Microsecond)))
|
|
||||||
|
|
||||||
r := api.NewRequester()
|
|
||||||
rerr, err := r.SendArchiveStatus(s.Uuid, true)
|
|
||||||
if rerr != nil || err != nil {
|
|
||||||
if err != nil {
|
|
||||||
zap.S().Errorw("failed to notify panel with archive status", zap.String("server", s.Uuid), zap.Error(err))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
zap.S().Errorw("panel returned an error when sending the archive status", zap.String("server", s.Uuid), zap.Error(errors.New(rerr.String())))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
zap.S().Debugw("successfully notified panel about archive status", zap.String("server", s.Uuid))
|
|
||||||
}()
|
|
||||||
|
|
||||||
c.Status(http.StatusAccepted)
|
|
||||||
}
|
|
|
@ -1,18 +1,18 @@
|
||||||
package main
|
package router
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"bytes"
|
"bytes"
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
|
"errors"
|
||||||
"github.com/buger/jsonparser"
|
"github.com/buger/jsonparser"
|
||||||
"github.com/gorilla/websocket"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/julienschmidt/httprouter"
|
|
||||||
"github.com/mholt/archiver/v3"
|
"github.com/mholt/archiver/v3"
|
||||||
"github.com/pkg/errors"
|
|
||||||
"github.com/pterodactyl/wings/api"
|
"github.com/pterodactyl/wings/api"
|
||||||
"github.com/pterodactyl/wings/config"
|
"github.com/pterodactyl/wings/config"
|
||||||
"github.com/pterodactyl/wings/installer"
|
"github.com/pterodactyl/wings/installer"
|
||||||
|
"github.com/pterodactyl/wings/router/tokens"
|
||||||
"github.com/pterodactyl/wings/server"
|
"github.com/pterodactyl/wings/server"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
"io"
|
"io"
|
||||||
|
@ -22,143 +22,122 @@ import (
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Retrieves a server out of the collection by UUID.
|
func getServerArchive(c *gin.Context) {
|
||||||
func (rt *Router) GetServer(uuid string) *server.Server {
|
auth := strings.SplitN(c.GetHeader("Authorization"), " ", 2)
|
||||||
return server.GetServers().Find(func(i *server.Server) bool {
|
|
||||||
return i.Uuid == uuid
|
if len(auth) != 2 || auth[0] != "Bearer" {
|
||||||
|
c.Header("WWW-Authenticate", "Bearer")
|
||||||
|
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{
|
||||||
|
"error": "The required authorization heads were not present in the request.",
|
||||||
})
|
})
|
||||||
}
|
|
||||||
|
|
||||||
type Router struct {
|
|
||||||
upgrader websocket.Upgrader
|
|
||||||
|
|
||||||
// The authentication token defined in the config.yml file that allows
|
|
||||||
// a request to perform any action against the daemon.
|
|
||||||
token string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (rt *Router) AuthenticateRequest(h httprouter.Handle) httprouter.Handle {
|
|
||||||
return rt.AuthenticateToken(rt.AuthenticateServer(h))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Middleware to protect server specific routes. This will ensure that the server exists and
|
|
||||||
// is in a state that allows it to be exposed to the API.
|
|
||||||
func (rt *Router) AuthenticateServer(h httprouter.Handle) httprouter.Handle {
|
|
||||||
return func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
|
|
||||||
if rt.GetServer(ps.ByName("server")) != nil {
|
|
||||||
h(w, r, ps)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
http.NotFound(w, r)
|
token := tokens.TransferPayload{}
|
||||||
}
|
if err := tokens.ParseToken([]byte(c.Query("token")), &token); err != nil {
|
||||||
}
|
TrackedError(err).AbortWithServerError(c)
|
||||||
|
|
||||||
// Attaches required access control headers to all of the requests.
|
|
||||||
func (rt *Router) AttachAccessControlHeaders(w http.ResponseWriter, r *http.Request, ps httprouter.Params) (http.ResponseWriter, *http.Request, httprouter.Params) {
|
|
||||||
w.Header().Set("Access-Control-Allow-Origin", config.Get().PanelLocation)
|
|
||||||
w.Header().Set("Access-Control-Allow-Headers", "Accept, Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization")
|
|
||||||
|
|
||||||
return w, r, ps
|
|
||||||
}
|
|
||||||
|
|
||||||
// Authenticates the request token against the given permission string, ensuring that
|
|
||||||
// if it is a server permission, the token has control over that server. If it is a global
|
|
||||||
// token, this will ensure that the request is using a properly signed global token.
|
|
||||||
func (rt *Router) AuthenticateToken(h httprouter.Handle) httprouter.Handle {
|
|
||||||
return func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
|
|
||||||
// Adds support for using this middleware on the websocket routes for servers. Those
|
|
||||||
// routes don't support Authorization headers, per the spec, so we abuse the socket
|
|
||||||
// protocol header and use that to pass the authorization token along to Wings without
|
|
||||||
// exposing the token in the URL directly. Neat. 📸
|
|
||||||
auth := strings.SplitN(r.Header.Get("Authorization"), " ", 2)
|
|
||||||
|
|
||||||
if len(auth) != 2 || auth[0] != "Bearer" {
|
|
||||||
w.Header().Set("WWW-Authenticate", "Bearer")
|
|
||||||
http.Error(w, "authorization failed", http.StatusUnauthorized)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try to match the request against the global token for the Daemon, regardless
|
if token.Subject != c.Param("server") {
|
||||||
// of the permission type. If nothing is matched we will fall through to the Panel
|
c.AbortWithStatusJSON(http.StatusForbidden, gin.H{
|
||||||
// API to try and validate permissions for a server.
|
"error": "You are not authorized to access this endpoint.",
|
||||||
if auth[1] == rt.token {
|
})
|
||||||
h(rt.AttachAccessControlHeaders(w, r, ps))
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Happens because we don't have any of the server handling code here.
|
s := GetServer(c.Param("server"))
|
||||||
http.Error(w, "not implemented", http.StatusNotImplemented)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (rt *Router) routeGetServerArchive(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
|
|
||||||
auth := strings.SplitN(r.Header.Get("Authorization"), " ", 2)
|
|
||||||
|
|
||||||
if len(auth) != 2 || auth[0] != "Bearer" {
|
|
||||||
w.Header().Set("WWW-Authenticate", "Bearer")
|
|
||||||
http.Error(w, "authorization failed", http.StatusUnauthorized)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
token, err := ParseArchiveJWT([]byte(auth[1]))
|
|
||||||
if err != nil {
|
|
||||||
http.Error(w, "authorization failed", http.StatusUnauthorized)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if token.Subject != ps.ByName("server") {
|
|
||||||
http.Error(w, "forbidden", http.StatusForbidden)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
s := rt.GetServer(ps.ByName("server"))
|
|
||||||
|
|
||||||
st, err := s.Archiver.Stat()
|
st, err := s.Archiver.Stat()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if !os.IsNotExist(err) {
|
if !os.IsNotExist(err) {
|
||||||
zap.S().Errorw("failed to stat archive for reading", zap.String("server", s.Uuid), zap.Error(err))
|
// zap.S().Errorw("failed to stat archive for reading", zap.String("server", s.Uuid), zap.Error(err))
|
||||||
http.Error(w, "failed to stat archive", http.StatusInternalServerError)
|
TrackedServerError(err, s).SetMessage("failed to stat archive").AbortWithServerError(c)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
http.NotFound(w, r)
|
c.AbortWithStatus(http.StatusNotFound)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
checksum, err := s.Archiver.Checksum()
|
checksum, err := s.Archiver.Checksum()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
zap.S().Errorw("failed to calculate checksum", zap.String("server", s.Uuid), zap.Error(err))
|
// zap.S().Errorw("failed to calculate checksum", zap.String("server", s.Uuid), zap.Error(err))
|
||||||
http.Error(w, "failed to calculate checksum", http.StatusInternalServerError)
|
TrackedServerError(err, s).SetMessage("failed to calculate checksum").AbortWithServerError(c)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
file, err := os.Open(s.Archiver.ArchivePath())
|
file, err := os.Open(s.Archiver.ArchivePath())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
tserr := TrackedServerError(err, s)
|
||||||
if !os.IsNotExist(err) {
|
if !os.IsNotExist(err) {
|
||||||
zap.S().Errorw("failed to open archive for reading", zap.String("server", s.Uuid), zap.Error(err))
|
tserr.SetMessage("failed to open archive for reading")
|
||||||
|
// zap.S().Errorw("failed to open archive for reading", zap.String("server", s.Uuid), zap.Error(err))
|
||||||
|
} else {
|
||||||
|
tserr.SetMessage("failed to open archive")
|
||||||
}
|
}
|
||||||
|
|
||||||
http.Error(w, "failed to open archive", http.StatusInternalServerError)
|
tserr.AbortWithServerError(c)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer file.Close()
|
defer file.Close()
|
||||||
|
|
||||||
w.Header().Set("X-Checksum", checksum)
|
c.Header("X-Checksum", checksum)
|
||||||
w.Header().Set("X-Mime-Type", st.Mimetype)
|
c.Header("X-Mime-Type", st.Mimetype)
|
||||||
w.Header().Set("Content-Length", strconv.Itoa(int(st.Info.Size())))
|
c.Header("Content-Length", strconv.Itoa(int(st.Info.Size())))
|
||||||
w.Header().Set("Content-Disposition", "attachment; filename="+s.Archiver.ArchiveName())
|
c.Header("Content-Disposition", "attachment; filename="+s.Archiver.ArchiveName())
|
||||||
w.Header().Set("Content-Type", "application/octet-stream")
|
c.Header("Content-Type", "application/octet-stream")
|
||||||
|
|
||||||
bufio.NewReader(file).WriteTo(w)
|
bufio.NewReader(file).WriteTo(c.Writer)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rt *Router) routeIncomingTransfer(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
|
func postServerArchive(c *gin.Context) {
|
||||||
zap.S().Debug("incoming transfer from panel!")
|
s := GetServer(c.Param("server"))
|
||||||
defer r.Body.Close()
|
|
||||||
|
go func(server *server.Server) {
|
||||||
|
start := time.Now()
|
||||||
|
|
||||||
|
if err := server.Archiver.Archive(); err != nil {
|
||||||
|
zap.S().Errorw("failed to get archive for server", zap.String("server", s.Uuid), zap.Error(err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
zap.S().Debugw(
|
||||||
|
"successfully created archive for server",
|
||||||
|
zap.String("server", server.Uuid),
|
||||||
|
zap.Duration("time", time.Now().Sub(start).Round(time.Microsecond)),
|
||||||
|
)
|
||||||
|
|
||||||
|
r := api.NewRequester()
|
||||||
|
rerr, err := r.SendArchiveStatus(server.Uuid, true)
|
||||||
|
if rerr != nil || err != nil {
|
||||||
|
if err != nil {
|
||||||
|
zap.S().Errorw("failed to notify panel with archive status", zap.String("server", server.Uuid), zap.Error(err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
zap.S().Errorw(
|
||||||
|
"panel returned an error when sending the archive status",
|
||||||
|
zap.String("server", server.Uuid),
|
||||||
|
zap.Error(errors.New(rerr.String())),
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
zap.S().Debugw("successfully notified panel about archive status", zap.String("server", server.Uuid))
|
||||||
|
}(s)
|
||||||
|
|
||||||
|
c.Status(http.StatusAccepted)
|
||||||
|
}
|
||||||
|
|
||||||
|
func postTransfer(c *gin.Context) {
|
||||||
|
zap.S().Debug("incoming transfer from panel")
|
||||||
|
|
||||||
|
buf := bytes.Buffer{}
|
||||||
|
buf.ReadFrom(c.Request.Body)
|
||||||
|
|
||||||
go func(data []byte) {
|
go func(data []byte) {
|
||||||
serverID, _ := jsonparser.GetString(data, "server_id")
|
serverID, _ := jsonparser.GetString(data, "server_id")
|
||||||
|
@ -323,25 +302,7 @@ func (rt *Router) routeIncomingTransfer(w http.ResponseWriter, r *http.Request,
|
||||||
|
|
||||||
zap.S().Debugw("successfully notified panel about transfer success", zap.String("server", serverID))
|
zap.S().Debugw("successfully notified panel about transfer success", zap.String("server", serverID))
|
||||||
hasError = false
|
hasError = false
|
||||||
}(rt.ReaderToBytes(r.Body))
|
}(buf.Bytes())
|
||||||
|
|
||||||
w.WriteHeader(202)
|
c.Status(http.StatusAccepted)
|
||||||
}
|
|
||||||
|
|
||||||
func (rt *Router) ReaderToBytes(r io.Reader) []byte {
|
|
||||||
buf := bytes.Buffer{}
|
|
||||||
buf.ReadFrom(r)
|
|
||||||
|
|
||||||
return buf.Bytes()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Configures the router and all of the associated routes.
|
|
||||||
func (rt *Router) ConfigureRouter() *httprouter.Router {
|
|
||||||
router := httprouter.New()
|
|
||||||
|
|
||||||
router.GET("/api/servers/:server/archive", rt.AuthenticateServer(rt.routeGetServerArchive))
|
|
||||||
|
|
||||||
router.POST("/api/transfer", rt.AuthenticateToken(rt.routeIncomingTransfer))
|
|
||||||
|
|
||||||
return router
|
|
||||||
}
|
}
|
|
@ -1,25 +1,25 @@
|
||||||
package tokens
|
package tokens
|
||||||
|
|
||||||
import (
|
import (
|
||||||
cache2 "github.com/patrickmn/go-cache"
|
"github.com/patrickmn/go-cache"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
type TokenStore struct {
|
type TokenStore struct {
|
||||||
cache *cache2.Cache
|
cache *cache.Cache
|
||||||
mutex *sync.Mutex
|
mutex *sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
var _tokens *TokenStore
|
var _tokens *TokenStore
|
||||||
|
|
||||||
// Returns the global unqiue token store cache. This is used to validate
|
// Returns the global unique token store cache. This is used to validate
|
||||||
// one time token usage by storing any received tokens in a local memory
|
// one time token usage by storing any received tokens in a local memory
|
||||||
// cache until they are ready to expire.
|
// cache until they are ready to expire.
|
||||||
func getTokenStore() *TokenStore {
|
func getTokenStore() *TokenStore {
|
||||||
if _tokens == nil {
|
if _tokens == nil {
|
||||||
_tokens = &TokenStore{
|
_tokens = &TokenStore{
|
||||||
cache: cache2.New(time.Minute*60, time.Minute*5),
|
cache: cache.New(time.Minute*60, time.Minute*5),
|
||||||
mutex: &sync.Mutex{},
|
mutex: &sync.Mutex{},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
14
router/tokens/transfer.go
Normal file
14
router/tokens/transfer.go
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
package tokens
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gbrlsnchs/jwt/v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TransferPayload struct {
|
||||||
|
jwt.Payload
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetPayload returns the JWT payload.
|
||||||
|
func (p *TransferPayload) GetPayload() *jwt.Payload {
|
||||||
|
return &p.Payload
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user