implement auth middleware

This commit is contained in:
Jakob Schrettenbrunner 2017-07-06 20:49:36 +02:00
parent 260c9d70ab
commit 0bceb409e5
4 changed files with 233 additions and 7 deletions

View File

@ -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()
}
}

View File

@ -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)
}
}
} }

View File

@ -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
}

View File

@ -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"))
})
}