implement auth middleware
This commit is contained in:
parent
260c9d70ab
commit
0bceb409e5
64
api/auth.go
64
api/auth.go
|
@ -1 +1,65 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/Pterodactyl/wings/config"
|
||||
"github.com/Pterodactyl/wings/control"
|
||||
"github.com/gin-gonic/gin"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
const (
|
||||
accessTokenHeader = "X-Access-Token"
|
||||
accessServerHeader = "X-Access-Server"
|
||||
)
|
||||
|
||||
type responseError struct {
|
||||
Error string `json:"error"`
|
||||
}
|
||||
|
||||
// AuthHandler returns a HandlerFunc that checks request authentication
|
||||
// permission is a permission string describing the required permission to access the route
|
||||
func AuthHandler(permission string) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
requestToken := c.Request.Header.Get(accessTokenHeader)
|
||||
requestServer := c.Request.Header.Get(accessServerHeader)
|
||||
|
||||
if requestToken != "" {
|
||||
// c: master controller, permissions not related to specific server
|
||||
if strings.HasPrefix(permission, "c:") {
|
||||
if config.Get().ContainsAuthKey(requestToken) {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
// All other permission strings not starting with c: require a server to be provided
|
||||
if requestServer != "" {
|
||||
server := control.GetServer(requestServer)
|
||||
if server != nil {
|
||||
if strings.HasPrefix(permission, "g:") {
|
||||
if config.Get().ContainsAuthKey(requestToken) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if strings.HasPrefix(permission, "s:") {
|
||||
if server.HasPermission(requestToken, permission) {
|
||||
return
|
||||
}
|
||||
}
|
||||
} else {
|
||||
c.JSON(http.StatusNotFound, responseError{"Server defined in " + accessServerHeader + " is not known."})
|
||||
}
|
||||
} else {
|
||||
c.JSON(http.StatusBadRequest, responseError{"No " + accessServerHeader + " header provided."})
|
||||
}
|
||||
}
|
||||
} else {
|
||||
log.Debug("Token missing in request.")
|
||||
c.JSON(http.StatusBadRequest, responseError{"No " + accessTokenHeader + " header provided."})
|
||||
}
|
||||
c.JSON(http.StatusForbidden, responseError{"You are do not have permission to perform this action."})
|
||||
c.Abort()
|
||||
}
|
||||
}
|
||||
|
|
141
api/auth_test.go
141
api/auth_test.go
|
@ -1,11 +1,148 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/Pterodactyl/wings/config"
|
||||
"github.com/Pterodactyl/wings/control"
|
||||
)
|
||||
|
||||
func TestFunction(t *testing.T) {
|
||||
assert.Equal(t, 1, 1)
|
||||
const configFile = "_testdata/config.json"
|
||||
|
||||
func TestAuthHandler(t *testing.T) {
|
||||
gin.SetMode(gin.ReleaseMode)
|
||||
|
||||
t.Run("rejects missing token", func(t *testing.T) {
|
||||
loadConfiguration(t, false)
|
||||
|
||||
responded, rec := requestMiddlewareWith("c:somepermission", "", "")
|
||||
|
||||
assert.False(t, responded)
|
||||
assert.Equal(t, rec.Code, http.StatusBadRequest)
|
||||
})
|
||||
|
||||
t.Run("rejects c:* with invalid key", func(t *testing.T) {
|
||||
loadConfiguration(t, false)
|
||||
|
||||
responded, rec := requestMiddlewareWith("c:somepermission", "invalidkey", "")
|
||||
|
||||
assert.False(t, responded)
|
||||
assert.Equal(t, rec.Code, http.StatusForbidden)
|
||||
})
|
||||
|
||||
t.Run("accepts existing c: key", func(t *testing.T) {
|
||||
loadConfiguration(t, false)
|
||||
|
||||
responded, rec := requestMiddlewareWith("c:somepermission", "existingkey", "") // TODO: working token
|
||||
|
||||
assert.True(t, responded)
|
||||
assert.Equal(t, rec.Code, http.StatusOK)
|
||||
})
|
||||
|
||||
t.Run("rejects missing server uuid", func(t *testing.T) {
|
||||
loadConfiguration(t, true)
|
||||
|
||||
responded, rec := requestMiddlewareWith("g:test", "existingkey", "")
|
||||
|
||||
assert.False(t, responded)
|
||||
assert.Equal(t, rec.Code, http.StatusBadRequest)
|
||||
})
|
||||
|
||||
t.Run("rejects not existing server", func(t *testing.T) {
|
||||
loadConfiguration(t, true)
|
||||
|
||||
responded, rec := requestMiddlewareWith("g:test", "existingkey", "notexistingserver")
|
||||
|
||||
assert.False(t, responded)
|
||||
assert.Equal(t, rec.Code, http.StatusNotFound)
|
||||
})
|
||||
|
||||
t.Run("accepts server with existing g: key", func(t *testing.T) {
|
||||
loadConfiguration(t, true)
|
||||
|
||||
responded, rec := requestMiddlewareWith("g:test", "existingkey", "existingserver")
|
||||
|
||||
assert.True(t, responded)
|
||||
assert.Equal(t, rec.Code, http.StatusOK)
|
||||
})
|
||||
|
||||
t.Run("rejects server with not existing g: key", func(t *testing.T) {
|
||||
loadConfiguration(t, true)
|
||||
|
||||
responded, rec := requestMiddlewareWith("g:test", "notexistingkey", "existingserver")
|
||||
|
||||
assert.False(t, responded)
|
||||
assert.Equal(t, rec.Code, http.StatusForbidden)
|
||||
})
|
||||
|
||||
t.Run("rejects server with not existing s: key", func(t *testing.T) {
|
||||
loadConfiguration(t, true)
|
||||
|
||||
responded, rec := requestMiddlewareWith("s:test", "notexistingskey", "existingserver")
|
||||
|
||||
assert.False(t, responded)
|
||||
assert.Equal(t, rec.Code, http.StatusForbidden)
|
||||
})
|
||||
|
||||
t.Run("accepts server with existing s: key with specific permission", func(t *testing.T) {
|
||||
loadConfiguration(t, true)
|
||||
|
||||
responded, rec := requestMiddlewareWith("s:test", "existingspecificskey", "existingserver")
|
||||
|
||||
assert.True(t, responded)
|
||||
assert.Equal(t, rec.Code, http.StatusOK)
|
||||
})
|
||||
|
||||
t.Run("accepts server with existing s: key with gloabl permission", func(t *testing.T) {
|
||||
loadConfiguration(t, true)
|
||||
|
||||
responded, rec := requestMiddlewareWith("s:test", "existingglobalskey", "existingserver")
|
||||
|
||||
assert.True(t, responded)
|
||||
assert.Equal(t, rec.Code, http.StatusOK)
|
||||
})
|
||||
|
||||
t.Run("rejects server with existing s: key without permission", func(t *testing.T) {
|
||||
loadConfiguration(t, true)
|
||||
|
||||
responded, rec := requestMiddlewareWith("s:without", "existingspecificskey", "existingserver")
|
||||
|
||||
assert.False(t, responded)
|
||||
assert.Equal(t, rec.Code, http.StatusForbidden)
|
||||
})
|
||||
}
|
||||
|
||||
func requestMiddlewareWith(neededPermission string, token string, serverUUID string) (responded bool, recorder *httptest.ResponseRecorder) {
|
||||
router := gin.New()
|
||||
responded = false
|
||||
recorder = httptest.NewRecorder()
|
||||
req, _ := http.NewRequest("GET", "/", nil)
|
||||
|
||||
router.GET("/", AuthHandler(neededPermission), func(c *gin.Context) {
|
||||
c.String(http.StatusOK, "Access granted.")
|
||||
responded = true
|
||||
})
|
||||
|
||||
req.Header.Set(accessTokenHeader, token)
|
||||
req.Header.Set(accessServerHeader, serverUUID)
|
||||
router.ServeHTTP(recorder, req)
|
||||
return
|
||||
}
|
||||
|
||||
func loadConfiguration(t *testing.T, serverConfig bool) {
|
||||
if err := config.LoadConfiguration(configFile); err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
if serverConfig {
|
||||
if err := control.LoadServerConfigurations("_testdata/servers/"); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -76,13 +76,16 @@ type Config struct {
|
|||
// If set to <= 0 logs are kept forever
|
||||
DeleteAfterDays int `mapstructure:"deleteAfterDays"`
|
||||
} `mapstructure:"log"`
|
||||
|
||||
AuthKeys []string `mapstructure:"authKeys"`
|
||||
}
|
||||
|
||||
var config *Config
|
||||
|
||||
func LoadConfiguration(path *string) error {
|
||||
if path != nil {
|
||||
viper.SetConfigFile(*path)
|
||||
// LoadConfiguration loads the configuration from a specified file
|
||||
func LoadConfiguration(path string) error {
|
||||
if path != "" {
|
||||
viper.SetConfigFile(path)
|
||||
} else {
|
||||
viper.AddConfigPath("./")
|
||||
viper.SetConfigName("config")
|
||||
|
@ -109,3 +112,13 @@ func Get() *Config {
|
|||
func setDefaults() {
|
||||
|
||||
}
|
||||
|
||||
// ContainsAuthKey checks wether the config contains a specified authentication key
|
||||
func (c *Config) ContainsAuthKey(key string) bool {
|
||||
for _, k := range c.AuthKeys {
|
||||
if k == key {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
|
|
@ -6,10 +6,22 @@ import (
|
|||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
var configFile = "../config.example.json"
|
||||
const configFile = "../config.example.json"
|
||||
|
||||
func TestLoadConfiguraiton(t *testing.T) {
|
||||
err := LoadConfiguration(&configFile)
|
||||
err := LoadConfiguration(configFile)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, Get().Web.ListenHost, "0.0.0.0")
|
||||
}
|
||||
|
||||
func TestContainsAuthKey(t *testing.T) {
|
||||
t.Run("key exists", func(t *testing.T) {
|
||||
LoadConfiguration(configFile)
|
||||
assert.True(t, Get().ContainsAuthKey("somekey"))
|
||||
})
|
||||
|
||||
t.Run("key doesn't exist", func(t *testing.T) {
|
||||
LoadConfiguration(configFile)
|
||||
assert.False(t, Get().ContainsAuthKey("someotherkey"))
|
||||
})
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user