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
|
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
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
|
"github.com/Pterodactyl/wings/config"
|
||||||
|
"github.com/Pterodactyl/wings/control"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestFunction(t *testing.T) {
|
const configFile = "_testdata/config.json"
|
||||||
assert.Equal(t, 1, 1)
|
|
||||||
|
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
|
// If set to <= 0 logs are kept forever
|
||||||
DeleteAfterDays int `mapstructure:"deleteAfterDays"`
|
DeleteAfterDays int `mapstructure:"deleteAfterDays"`
|
||||||
} `mapstructure:"log"`
|
} `mapstructure:"log"`
|
||||||
|
|
||||||
|
AuthKeys []string `mapstructure:"authKeys"`
|
||||||
}
|
}
|
||||||
|
|
||||||
var config *Config
|
var config *Config
|
||||||
|
|
||||||
func LoadConfiguration(path *string) error {
|
// LoadConfiguration loads the configuration from a specified file
|
||||||
if path != nil {
|
func LoadConfiguration(path string) error {
|
||||||
viper.SetConfigFile(*path)
|
if path != "" {
|
||||||
|
viper.SetConfigFile(path)
|
||||||
} else {
|
} else {
|
||||||
viper.AddConfigPath("./")
|
viper.AddConfigPath("./")
|
||||||
viper.SetConfigName("config")
|
viper.SetConfigName("config")
|
||||||
|
@ -109,3 +112,13 @@ func Get() *Config {
|
||||||
func setDefaults() {
|
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"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
var configFile = "../config.example.json"
|
const configFile = "../config.example.json"
|
||||||
|
|
||||||
func TestLoadConfiguraiton(t *testing.T) {
|
func TestLoadConfiguraiton(t *testing.T) {
|
||||||
err := LoadConfiguration(&configFile)
|
err := LoadConfiguration(configFile)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.Equal(t, Get().Web.ListenHost, "0.0.0.0")
|
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