Moved v3 code from NginxProxyManager/nginx-proxy-manager-3 to NginxProxyManager/nginx-proxy-manager

This commit is contained in:
Jamie Curnow
2022-05-12 08:47:31 +10:00
parent 4db34f5894
commit 2110ecc382
830 changed files with 38168 additions and 36635 deletions

View File

@ -0,0 +1,13 @@
package middleware
import (
"net/http"
)
// AccessControl sets http headers for responses
func AccessControl(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Access-Control-Allow-Origin", "*")
next.ServeHTTP(w, r)
})
}

View File

@ -0,0 +1,94 @@
package middleware
import (
"context"
"fmt"
"net/http"
c "npm/internal/api/context"
h "npm/internal/api/http"
"npm/internal/config"
"npm/internal/entity/user"
njwt "npm/internal/jwt"
"npm/internal/logger"
"npm/internal/util"
"github.com/go-chi/jwtauth"
)
// DecodeAuth decodes an auth header
func DecodeAuth() func(http.Handler) http.Handler {
privateKey, privateKeyParseErr := njwt.GetPrivateKey()
if privateKeyParseErr != nil && privateKey == nil {
logger.Error("PrivateKeyParseError", privateKeyParseErr)
}
publicKey, publicKeyParseErr := njwt.GetPublicKey()
if publicKeyParseErr != nil && publicKey == nil {
logger.Error("PublicKeyParseError", publicKeyParseErr)
}
tokenAuth := jwtauth.New("RS256", privateKey, publicKey)
return jwtauth.Verifier(tokenAuth)
}
// Enforce is a authentication middleware to enforce access from the
// jwtauth.Verifier middleware request context values. The Authenticator sends a 401 Unauthorised
// response for any unverified tokens and passes the good ones through.
func Enforce(permission string) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
if config.IsSetup {
token, claims, err := jwtauth.FromContext(ctx)
if err != nil {
h.ResultErrorJSON(w, r, http.StatusUnauthorized, err.Error(), nil)
return
}
userID := int(claims["uid"].(float64))
_, enabled := user.IsEnabled(userID)
if token == nil || !token.Valid || !enabled {
h.ResultErrorJSON(w, r, http.StatusUnauthorized, "Unauthorised", nil)
return
}
// Check if permissions exist for this user
if permission != "" {
// Since the permission that we require is not on the token, we have to get it from the DB
// So we don't go crazy with hits, we will use a memory cache
cacheKey := fmt.Sprintf("userCapabilties.%v", userID)
cacheItem, found := AuthCache.Get(cacheKey)
var userCapabilities []string
if found {
userCapabilities = cacheItem.([]string)
} else {
// Get from db and store it
userCapabilities, err = user.GetCapabilities(userID)
if err != nil {
AuthCacheSet(cacheKey, userCapabilities)
}
}
// Now check that they have the permission in their admin capabilities
// full-admin can do anything
if !util.SliceContainsItem(userCapabilities, user.CapabilityFullAdmin) && !util.SliceContainsItem(userCapabilities, permission) {
// Access denied
logger.Debug("User has: %+v but needs %s", userCapabilities, permission)
h.ResultErrorJSON(w, r, http.StatusForbidden, "Forbidden", nil)
return
}
}
// Add claims to context
ctx = context.WithValue(ctx, c.UserIDCtxKey, userID)
}
// Token is authenticated, continue as normal
next.ServeHTTP(w, r.WithContext(ctx))
})
}
}

View File

@ -0,0 +1,23 @@
package middleware
import (
"time"
"npm/internal/logger"
cache "github.com/patrickmn/go-cache"
)
// AuthCache is a cache item that stores the Admin API data for each admin that has been requesting endpoints
var AuthCache *cache.Cache
// AuthCacheInit will create a new Memory Cache
func AuthCacheInit() {
logger.Debug("Creating a new AuthCache")
AuthCache = cache.New(1*time.Minute, 5*time.Minute)
}
// AuthCacheSet will store the item in memory for the expiration time
func AuthCacheSet(k string, x interface{}) {
AuthCache.Set(k, x, cache.DefaultExpiration)
}

View File

@ -0,0 +1,26 @@
package middleware
import (
"context"
"io/ioutil"
"net/http"
c "npm/internal/api/context"
)
// BodyContext simply adds the body data to a context item
func BodyContext() func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Grab the Body Data
var body []byte
if r.Body != nil {
body, _ = ioutil.ReadAll(r.Body)
}
// Add it to the context
ctx := r.Context()
ctx = context.WithValue(ctx, c.BodyCtxKey, body)
next.ServeHTTP(w, r.WithContext(ctx))
})
}
}

View File

@ -0,0 +1,88 @@
package middleware
import (
"fmt"
"net/http"
"strings"
"github.com/go-chi/chi"
)
var methodMap = []string{
http.MethodGet,
http.MethodHead,
http.MethodPost,
http.MethodPut,
http.MethodPatch,
http.MethodDelete,
http.MethodConnect,
http.MethodTrace,
}
func getRouteMethods(routes chi.Router, path string) []string {
var methods []string
tctx := chi.NewRouteContext()
for _, method := range methodMap {
if routes.Match(tctx, method, path) {
methods = append(methods, method)
}
}
return methods
}
var headersAllowedByCORS = []string{
"Authorization",
"Host",
"Content-Type",
"Connection",
"User-Agent",
"Cache-Control",
"Accept-Encoding",
"X-Jumbo-AppKey",
"X-Jumbo-SKey",
"X-Jumbo-SV",
"X-Jumbo-Timestamp",
"X-Jumbo-Version",
"X-Jumbo-Customer-Id",
}
// Cors handles cors headers
func Cors(routes chi.Router) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
methods := getRouteMethods(routes, r.URL.Path)
if len(methods) == 0 {
// no route no cors
next.ServeHTTP(w, r)
return
}
methods = append(methods, http.MethodOptions)
w.Header().Set("Access-Control-Allow-Methods", strings.Join(methods, ","))
w.Header().Set("Access-Control-Allow-Headers",
strings.Join(headersAllowedByCORS, ","),
)
next.ServeHTTP(w, r)
})
}
}
// Options handles options requests
func Options(routes chi.Router) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
methods := getRouteMethods(routes, r.URL.Path)
if len(methods) == 0 {
// no route shouldn't have options
next.ServeHTTP(w, r)
return
}
if r.Method == http.MethodOptions {
w.Header().Set("Access-Control-Allow-Origin", "*")
w.Header().Set("Content-Type", "application/json")
fmt.Fprint(w, "{}")
return
}
next.ServeHTTP(w, r)
})
}
}

View File

@ -0,0 +1,28 @@
package middleware
import (
"fmt"
"net/http"
h "npm/internal/api/http"
"npm/internal/config"
)
// EnforceSetup will error if the config setup doesn't match what is required
func EnforceSetup(shouldBeSetup bool) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if config.IsSetup != shouldBeSetup {
state := "during"
if config.IsSetup {
state = "after"
}
h.ResultErrorJSON(w, r, http.StatusForbidden, fmt.Sprintf("Not available %s setup phase", state), nil)
return
}
// All good
next.ServeHTTP(w, r)
})
}
}

View File

@ -0,0 +1,24 @@
package middleware
import (
"context"
"net/http"
"strings"
c "npm/internal/api/context"
)
// Expansion will determine whether the request should have objects expanded
// with ?expand=1 or ?expand=true
func Expansion(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
expandStr := r.URL.Query().Get("expand")
if expandStr != "" {
ctx := r.Context()
ctx = context.WithValue(ctx, c.ExpansionCtxKey, strings.Split(expandStr, ","))
next.ServeHTTP(w, r.WithContext(ctx))
} else {
next.ServeHTTP(w, r)
}
})
}

View File

@ -0,0 +1,115 @@
package middleware
import (
"context"
"encoding/json"
"fmt"
"net/http"
c "npm/internal/api/context"
h "npm/internal/api/http"
"npm/internal/model"
"npm/internal/util"
"strings"
"github.com/qri-io/jsonschema"
)
// Filters will accept a pre-defined schemaData to validate against the GET query params
// passed in to this endpoint. This will ensure that the filters are not injecting SQL.
// After we have determined what the Filters are to be, they are saved on the Context
// to be used later in other endpoints.
func Filters(schemaData string) func(http.Handler) http.Handler {
reservedFilterKeys := []string{
"limit",
"offset",
"sort",
"order",
"expand",
"t", // This is used as a timestamp paramater in some clients and can be ignored
}
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
var filters []model.Filter
for key, val := range r.URL.Query() {
key = strings.ToLower(key)
// Split out the modifier from the field name and set a default modifier
var keyParts []string
keyParts = strings.Split(key, ":")
if len(keyParts) == 1 {
// Default modifier
keyParts = append(keyParts, "equals")
}
// Only use this filter if it's not a reserved get param
if !util.SliceContainsItem(reservedFilterKeys, keyParts[0]) {
for _, valItem := range val {
// Check that the val isn't empty
if len(strings.TrimSpace(valItem)) > 0 {
valSlice := []string{valItem}
if keyParts[1] == "in" || keyParts[1] == "notin" {
valSlice = strings.Split(valItem, ",")
}
filters = append(filters, model.Filter{
Field: keyParts[0],
Modifier: keyParts[1],
Value: valSlice,
})
}
}
}
}
// Only validate schema if there are filters to validate
if len(filters) > 0 {
ctx := r.Context()
// Marshal the Filters in to a JSON string so that the Schema Validation works against it
filterData, marshalErr := json.MarshalIndent(filters, "", " ")
if marshalErr != nil {
h.ResultErrorJSON(w, r, http.StatusInternalServerError, fmt.Sprintf("Schema Fatal: %v", marshalErr), nil)
return
}
// Create root schema
rs := &jsonschema.Schema{}
if err := json.Unmarshal([]byte(schemaData), rs); err != nil {
h.ResultErrorJSON(w, r, http.StatusInternalServerError, fmt.Sprintf("Schema Fatal: %v", err), nil)
return
}
// Validate it
errors, jsonError := rs.ValidateBytes(ctx, filterData)
if jsonError != nil {
h.ResultErrorJSON(w, r, http.StatusBadRequest, jsonError.Error(), nil)
return
}
if len(errors) > 0 {
h.ResultErrorJSON(w, r, http.StatusBadRequest, "Invalid Filters", errors)
return
}
ctx = context.WithValue(ctx, c.FiltersCtxKey, filters)
next.ServeHTTP(w, r.WithContext(ctx))
} else {
next.ServeHTTP(w, r)
}
})
}
}
// GetFiltersFromContext returns the Filters
func GetFiltersFromContext(r *http.Request) []model.Filter {
filters, ok := r.Context().Value(c.FiltersCtxKey).([]model.Filter)
if !ok {
// the assertion failed
var emptyFilters []model.Filter
return emptyFilters
}
return filters
}

View File

@ -0,0 +1,23 @@
package middleware
import (
"context"
"net/http"
c "npm/internal/api/context"
)
// PrettyPrint will determine whether the request should be pretty printed in output
// with ?pretty=1 or ?pretty=true
func PrettyPrint(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
prettyStr := r.URL.Query().Get("pretty")
if prettyStr == "1" || prettyStr == "true" {
ctx := r.Context()
ctx = context.WithValue(ctx, c.PrettyPrintCtxKey, true)
next.ServeHTTP(w, r.WithContext(ctx))
} else {
next.ServeHTTP(w, r)
}
})
}

View File

@ -0,0 +1,55 @@
package middleware
import (
"context"
"encoding/json"
"fmt"
"net/http"
c "npm/internal/api/context"
h "npm/internal/api/http"
"github.com/qri-io/jsonschema"
)
// CheckRequestSchema checks the payload against schema
func CheckRequestSchema(ctx context.Context, schemaData string, payload []byte) ([]jsonschema.KeyError, error) {
// Create root schema
rs := &jsonschema.Schema{}
if err := json.Unmarshal([]byte(schemaData), rs); err != nil {
return nil, fmt.Errorf("Schema Fatal: %v", err)
}
// Validate it
schemaErrors, jsonError := rs.ValidateBytes(ctx, payload)
if jsonError != nil {
return nil, jsonError
}
return schemaErrors, nil
}
// EnforceRequestSchema accepts a schema and validates the request body against it
func EnforceRequestSchema(schemaData string) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Get content from context
bodyBytes, _ := r.Context().Value(c.BodyCtxKey).([]byte)
schemaErrors, err := CheckRequestSchema(r.Context(), schemaData, bodyBytes)
if err != nil {
h.ResultErrorJSON(w, r, http.StatusInternalServerError, err.Error(), nil)
return
}
if len(schemaErrors) > 0 {
h.ResultSchemaErrorJSON(w, r, schemaErrors)
return
}
// All good
next.ServeHTTP(w, r)
})
}
}