mirror of
https://github.com/safing/portbase
synced 2025-09-01 10:09:50 +00:00
Implement review suggestions
This commit is contained in:
parent
5daeac8cf7
commit
3244fefd43
10 changed files with 118 additions and 111 deletions
|
@ -3,24 +3,20 @@ package api
|
||||||
import "net/http"
|
import "net/http"
|
||||||
|
|
||||||
// WrapInAuthHandler wraps a simple http.HandlerFunc into a handler that
|
// WrapInAuthHandler wraps a simple http.HandlerFunc into a handler that
|
||||||
// exposes the given API permissions.
|
// exposes the required API permissions for this handler.
|
||||||
func WrapInAuthHandler(fn http.HandlerFunc, read, write Permission) http.Handler {
|
func WrapInAuthHandler(fn http.HandlerFunc, read, write Permission) http.Handler {
|
||||||
return &wrappedAuthenticatedHandler{
|
return &wrappedAuthenticatedHandler{
|
||||||
handleFunc: fn,
|
HandlerFunc: fn,
|
||||||
read: read,
|
read: read,
|
||||||
write: write,
|
write: write,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type wrappedAuthenticatedHandler struct {
|
type wrappedAuthenticatedHandler struct {
|
||||||
handleFunc http.HandlerFunc
|
http.HandlerFunc
|
||||||
read Permission
|
|
||||||
write Permission
|
|
||||||
}
|
|
||||||
|
|
||||||
// ServeHTTP handles the http request.
|
read Permission
|
||||||
func (wah *wrappedAuthenticatedHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
write Permission
|
||||||
wah.handleFunc(w, r)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReadPermission returns the read permission for the handler.
|
// ReadPermission returns the read permission for the handler.
|
||||||
|
|
|
@ -49,8 +49,10 @@ const (
|
||||||
PermitSelf Permission = 4
|
PermitSelf Permission = 4
|
||||||
)
|
)
|
||||||
|
|
||||||
// Authenticator is a function that can be set as the authenticator for the API endpoint. If none is set, all requests will be permitted.
|
// AuthenticatorFunc is a function that can be set as the authenticator for the
|
||||||
type Authenticator func(ctx context.Context, s *http.Server, r *http.Request) (*AuthToken, error)
|
// API endpoint. If none is set, all requests will have full access.
|
||||||
|
// The returned AuthToken represents the permissions that the request has.
|
||||||
|
type AuthenticatorFunc func(r *http.Request, s *http.Server) (*AuthToken, error)
|
||||||
|
|
||||||
// AuthToken represents either a set of required or granted permissions.
|
// AuthToken represents either a set of required or granted permissions.
|
||||||
// All attributes must be set when the struct is built and must not be changed
|
// All attributes must be set when the struct is built and must not be changed
|
||||||
|
@ -81,7 +83,8 @@ func (token *AuthToken) Refresh(ttl time.Duration) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// AuthenticatedHandler defines the handler interface to specify custom
|
// AuthenticatedHandler defines the handler interface to specify custom
|
||||||
// permission for an API handler.
|
// permission for an API handler. The returned permission is the required
|
||||||
|
// permission for the request to proceed.
|
||||||
type AuthenticatedHandler interface {
|
type AuthenticatedHandler interface {
|
||||||
ReadPermission(*http.Request) Permission
|
ReadPermission(*http.Request) Permission
|
||||||
WritePermission(*http.Request) Permission
|
WritePermission(*http.Request) Permission
|
||||||
|
@ -94,34 +97,35 @@ const (
|
||||||
|
|
||||||
var (
|
var (
|
||||||
authFnSet = abool.New()
|
authFnSet = abool.New()
|
||||||
authFn Authenticator
|
authFn AuthenticatorFunc
|
||||||
|
|
||||||
authTokens = make(map[string]*AuthToken)
|
authTokens = make(map[string]*AuthToken)
|
||||||
authTokensLock sync.Mutex
|
authTokensLock sync.Mutex
|
||||||
|
|
||||||
// ErrAPIAccessDeniedMessage should be returned by Authenticator functions in
|
// ErrAPIAccessDeniedMessage should be wrapped by errors returned by
|
||||||
// order to signify a blocked request, including a error message for the user.
|
// AuthenticatorFunc in order to signify a blocked request, including a error
|
||||||
|
// message for the user. This is an empty message on purpose, as to allow the
|
||||||
|
// function to define the full text of the error shown to the user.
|
||||||
ErrAPIAccessDeniedMessage = errors.New("")
|
ErrAPIAccessDeniedMessage = errors.New("")
|
||||||
)
|
)
|
||||||
|
|
||||||
// SetAuthenticator sets an authenticator function for the API endpoint. If none is set, all requests will be permitted.
|
// SetAuthenticator sets an authenticator function for the API endpoint. If none is set, all requests will be permitted.
|
||||||
func SetAuthenticator(fn Authenticator) error {
|
func SetAuthenticator(fn AuthenticatorFunc) error {
|
||||||
if module.Online() {
|
if module.Online() {
|
||||||
return ErrAuthenticationImmutable
|
return ErrAuthenticationImmutable
|
||||||
}
|
}
|
||||||
|
|
||||||
if authFnSet.IsSet() {
|
if !authFnSet.SetToIf(false, true) {
|
||||||
return ErrAuthenticationAlreadySet
|
return ErrAuthenticationAlreadySet
|
||||||
}
|
}
|
||||||
|
|
||||||
authFn = fn
|
authFn = fn
|
||||||
authFnSet.Set()
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func authMiddleware(next http.Handler) http.Handler {
|
func authMiddleware(next http.Handler) http.Handler {
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
token := authenticateRequest(w, r, nil)
|
token := authenticateRequest(w, r, next)
|
||||||
if token != nil {
|
if token != nil {
|
||||||
if _, apiRequest := getAPIContext(r); apiRequest != nil {
|
if _, apiRequest := getAPIContext(r); apiRequest != nil {
|
||||||
apiRequest.AuthToken = token
|
apiRequest.AuthToken = token
|
||||||
|
@ -136,7 +140,7 @@ func authenticateRequest(w http.ResponseWriter, r *http.Request, targetHandler h
|
||||||
|
|
||||||
// Check if authenticator is set.
|
// Check if authenticator is set.
|
||||||
if !authFnSet.IsSet() {
|
if !authFnSet.IsSet() {
|
||||||
// Return highest available permissions.
|
// Return highest available permissions for the request.
|
||||||
return &AuthToken{
|
return &AuthToken{
|
||||||
Read: PermitSelf,
|
Read: PermitSelf,
|
||||||
Write: PermitSelf,
|
Write: PermitSelf,
|
||||||
|
@ -180,7 +184,8 @@ func authenticateRequest(w http.ResponseWriter, r *http.Request, targetHandler h
|
||||||
requiredPermission = PermitAnyone
|
requiredPermission = PermitAnyone
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for valid permission after handling the specials.
|
// The required permission must match the request permission values after
|
||||||
|
// handling the specials.
|
||||||
if requiredPermission < PermitAnyone || requiredPermission > PermitSelf {
|
if requiredPermission < PermitAnyone || requiredPermission > PermitSelf {
|
||||||
tracer.Warningf(
|
tracer.Warningf(
|
||||||
"api: handler returned invalid permission: %s (%d)",
|
"api: handler returned invalid permission: %s (%d)",
|
||||||
|
@ -197,11 +202,11 @@ func authenticateRequest(w http.ResponseWriter, r *http.Request, targetHandler h
|
||||||
// Get auth token from authenticator if none was in the request.
|
// Get auth token from authenticator if none was in the request.
|
||||||
if token == nil {
|
if token == nil {
|
||||||
var err error
|
var err error
|
||||||
token, err = authFn(r.Context(), server, r)
|
token, err = authFn(r, server)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// Check for internal error.
|
// Check for internal error.
|
||||||
if !errors.Is(err, ErrAPIAccessDeniedMessage) {
|
if !errors.Is(err, ErrAPIAccessDeniedMessage) {
|
||||||
tracer.Warningf("api: authenticator failed: %s", err)
|
tracer.Errorf("api: authenticator failed: %s", err)
|
||||||
http.Error(w, "Internal server error during authentication.", http.StatusInternalServerError)
|
http.Error(w, "Internal server error during authentication.", http.StatusInternalServerError)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -254,7 +259,13 @@ func authenticateRequest(w http.ResponseWriter, r *http.Request, targetHandler h
|
||||||
}
|
}
|
||||||
|
|
||||||
tracer.Tracef("api: granted %s access to authenticated handler", r.RemoteAddr)
|
tracer.Tracef("api: granted %s access to authenticated handler", r.RemoteAddr)
|
||||||
return token
|
|
||||||
|
// Make a copy of the AuthToken in order mitigate the handler poisoning the
|
||||||
|
// token, as changes would apply to future requests.
|
||||||
|
return &AuthToken{
|
||||||
|
Read: token.Read,
|
||||||
|
Write: token.Write,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkAuthToken(r *http.Request) *AuthToken {
|
func checkAuthToken(r *http.Request) *AuthToken {
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
@ -14,7 +13,7 @@ var (
|
||||||
testToken = new(AuthToken)
|
testToken = new(AuthToken)
|
||||||
)
|
)
|
||||||
|
|
||||||
func testAuthenticator(ctx context.Context, s *http.Server, r *http.Request) (*AuthToken, error) {
|
func testAuthenticator(r *http.Request, s *http.Server) (*AuthToken, error) {
|
||||||
switch {
|
switch {
|
||||||
case testToken.Read == -127 || testToken.Write == -127:
|
case testToken.Read == -127 || testToken.Write == -127:
|
||||||
return nil, errors.New("test error")
|
return nil, errors.New("test error")
|
||||||
|
|
10
api/doc.go
Normal file
10
api/doc.go
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
/*
|
||||||
|
Package api provides an API for integration with other components of the same software package and also third party components.
|
||||||
|
|
||||||
|
It provides direct database access as well as a simpler way to register API endpoints. You can of course also register raw `http.Handler`s directly.
|
||||||
|
|
||||||
|
Optional authentication guards registered handlers. This is achieved by attaching functions to the `http.Handler`s that are registered, which allow them to specify the required permissions for the handler.
|
||||||
|
|
||||||
|
The permissions are divided into the roles and assume a single user per host. The Roles are User, Admin and Self. User roles are expected to have mostly read access and react to notifications or system events, like a system tray program. The Admin role is meant for advanced components that also change settings, but are restricted so they cannot break the software. Self is reserved for internal use with full access.
|
||||||
|
*/
|
||||||
|
package api
|
|
@ -7,6 +7,7 @@ import (
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/safing/portbase/database/record"
|
"github.com/safing/portbase/database/record"
|
||||||
|
@ -29,36 +30,36 @@ type Endpoint struct {
|
||||||
// Order int
|
// Order int
|
||||||
// ExpertiseLevel config.ExpertiseLevel
|
// ExpertiseLevel config.ExpertiseLevel
|
||||||
|
|
||||||
// ActionFn is for simple actions with a return message for the user.
|
// ActionFunc is for simple actions with a return message for the user.
|
||||||
ActionFn ActionFn `json:"-"`
|
ActionFunc ActionFunc `json:"-"`
|
||||||
|
|
||||||
// DataFn is for returning raw data that the caller for further processing.
|
// DataFunc is for returning raw data that the caller for further processing.
|
||||||
DataFn DataFn `json:"-"`
|
DataFunc DataFunc `json:"-"`
|
||||||
|
|
||||||
// StructFn is for returning any kind of struct.
|
// StructFunc is for returning any kind of struct.
|
||||||
StructFn StructFn `json:"-"`
|
StructFunc StructFunc `json:"-"`
|
||||||
|
|
||||||
// RecordFn is for returning a database record. It will be properly locked
|
// RecordFunc is for returning a database record. It will be properly locked
|
||||||
// and marshalled including metadata.
|
// and marshalled including metadata.
|
||||||
RecordFn RecordFn `json:"-"`
|
RecordFunc RecordFunc `json:"-"`
|
||||||
|
|
||||||
// HandlerFn is the raw http handler.
|
// HandlerFunc is the raw http handler.
|
||||||
HandlerFn http.HandlerFunc `json:"-"`
|
HandlerFunc http.HandlerFunc `json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type (
|
type (
|
||||||
// ActionFn is for simple actions with a return message for the user.
|
// ActionFunc is for simple actions with a return message for the user.
|
||||||
ActionFn func(ar *Request) (msg string, err error)
|
ActionFunc func(ar *Request) (msg string, err error)
|
||||||
|
|
||||||
// DataFn is for returning raw data that the caller for further processing.
|
// DataFunc is for returning raw data that the caller for further processing.
|
||||||
DataFn func(ar *Request) (data []byte, err error)
|
DataFunc func(ar *Request) (data []byte, err error)
|
||||||
|
|
||||||
// StructFn is for returning any kind of struct.
|
// StructFunc is for returning any kind of struct.
|
||||||
StructFn func(ar *Request) (i interface{}, err error)
|
StructFunc func(ar *Request) (i interface{}, err error)
|
||||||
|
|
||||||
// RecordFn is for returning a database record. It will be properly locked
|
// RecordFunc is for returning a database record. It will be properly locked
|
||||||
// and marshalled including metadata.
|
// and marshalled including metadata.
|
||||||
RecordFn func(ar *Request) (r record.Record, err error)
|
RecordFunc func(ar *Request) (r record.Record, err error)
|
||||||
)
|
)
|
||||||
|
|
||||||
// MIME Types
|
// MIME Types
|
||||||
|
@ -135,7 +136,7 @@ func RegisterEndpoint(e Endpoint) error {
|
||||||
|
|
||||||
func (e *Endpoint) check() error {
|
func (e *Endpoint) check() error {
|
||||||
// Check path.
|
// Check path.
|
||||||
if e.Path == "" {
|
if strings.TrimSpace(e.Path) == "" {
|
||||||
return errors.New("path is missing")
|
return errors.New("path is missing")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -150,23 +151,23 @@ func (e *Endpoint) check() error {
|
||||||
// Check functions.
|
// Check functions.
|
||||||
var defaultMimeType string
|
var defaultMimeType string
|
||||||
fnCnt := 0
|
fnCnt := 0
|
||||||
if e.ActionFn != nil {
|
if e.ActionFunc != nil {
|
||||||
fnCnt++
|
fnCnt++
|
||||||
defaultMimeType = MimeTypeText
|
defaultMimeType = MimeTypeText
|
||||||
}
|
}
|
||||||
if e.DataFn != nil {
|
if e.DataFunc != nil {
|
||||||
fnCnt++
|
fnCnt++
|
||||||
defaultMimeType = MimeTypeText
|
defaultMimeType = MimeTypeText
|
||||||
}
|
}
|
||||||
if e.StructFn != nil {
|
if e.StructFunc != nil {
|
||||||
fnCnt++
|
fnCnt++
|
||||||
defaultMimeType = MimeTypeJSON
|
defaultMimeType = MimeTypeJSON
|
||||||
}
|
}
|
||||||
if e.RecordFn != nil {
|
if e.RecordFunc != nil {
|
||||||
fnCnt++
|
fnCnt++
|
||||||
defaultMimeType = MimeTypeJSON
|
defaultMimeType = MimeTypeJSON
|
||||||
}
|
}
|
||||||
if e.HandlerFn != nil {
|
if e.HandlerFunc != nil {
|
||||||
fnCnt++
|
fnCnt++
|
||||||
defaultMimeType = MimeTypeText
|
defaultMimeType = MimeTypeText
|
||||||
}
|
}
|
||||||
|
@ -214,7 +215,7 @@ func (eh *endpointHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
switch r.Method {
|
switch r.Method {
|
||||||
case http.MethodHead:
|
case http.MethodHead:
|
||||||
http.Error(w, "", http.StatusOK)
|
w.WriteHeader(http.StatusOK)
|
||||||
return
|
return
|
||||||
case http.MethodPost, http.MethodPut:
|
case http.MethodPost, http.MethodPut:
|
||||||
// Read body data.
|
// Read body data.
|
||||||
|
@ -235,32 +236,32 @@ func (eh *endpointHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
case apiEndpoint.ActionFn != nil:
|
case apiEndpoint.ActionFunc != nil:
|
||||||
var msg string
|
var msg string
|
||||||
msg, err = apiEndpoint.ActionFn(apiRequest)
|
msg, err = apiEndpoint.ActionFunc(apiRequest)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
responseData = []byte(msg)
|
responseData = []byte(msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
case apiEndpoint.DataFn != nil:
|
case apiEndpoint.DataFunc != nil:
|
||||||
responseData, err = apiEndpoint.DataFn(apiRequest)
|
responseData, err = apiEndpoint.DataFunc(apiRequest)
|
||||||
|
|
||||||
case apiEndpoint.StructFn != nil:
|
case apiEndpoint.StructFunc != nil:
|
||||||
var v interface{}
|
var v interface{}
|
||||||
v, err = apiEndpoint.StructFn(apiRequest)
|
v, err = apiEndpoint.StructFunc(apiRequest)
|
||||||
if err == nil && v != nil {
|
if err == nil && v != nil {
|
||||||
responseData, err = json.Marshal(v)
|
responseData, err = json.Marshal(v)
|
||||||
}
|
}
|
||||||
|
|
||||||
case apiEndpoint.RecordFn != nil:
|
case apiEndpoint.RecordFunc != nil:
|
||||||
var rec record.Record
|
var rec record.Record
|
||||||
rec, err = apiEndpoint.RecordFn(apiRequest)
|
rec, err = apiEndpoint.RecordFunc(apiRequest)
|
||||||
if err == nil && r != nil {
|
if err == nil && r != nil {
|
||||||
responseData, err = marshalRecord(rec, false)
|
responseData, err = marshalRecord(rec, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
case apiEndpoint.HandlerFn != nil:
|
case apiEndpoint.HandlerFunc != nil:
|
||||||
apiEndpoint.HandlerFn(w, r)
|
apiEndpoint.HandlerFunc(w, r)
|
||||||
return
|
return
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
@ -287,7 +288,7 @@ func (eh *endpointHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
func readBody(w http.ResponseWriter, r *http.Request) (inputData []byte, ok bool) {
|
func readBody(w http.ResponseWriter, r *http.Request) (inputData []byte, ok bool) {
|
||||||
// Check for too long content in order to prevent death.
|
// Check for too long content in order to prevent death.
|
||||||
if r.ContentLength > 20000000 { // 20MB
|
if r.ContentLength > 20000000 { // 20MB
|
||||||
http.Error(w, "Too much input data.", http.StatusBadRequest)
|
http.Error(w, "Too much input data.", http.StatusRequestEntityTooLarge)
|
||||||
return nil, false
|
return nil, false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -297,6 +298,5 @@ func readBody(w http.ResponseWriter, r *http.Request) (inputData []byte, ok bool
|
||||||
http.Error(w, "Failed to read body: "+err.Error(), http.StatusInternalServerError)
|
http.Error(w, "Failed to read body: "+err.Error(), http.StatusInternalServerError)
|
||||||
return nil, false
|
return nil, false
|
||||||
}
|
}
|
||||||
r.Body.Close()
|
|
||||||
return inputData, true
|
return inputData, true
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,25 +11,25 @@ import (
|
||||||
|
|
||||||
func registerDebugEndpoints() error {
|
func registerDebugEndpoints() error {
|
||||||
if err := RegisterEndpoint(Endpoint{
|
if err := RegisterEndpoint(Endpoint{
|
||||||
Path: "debug/stack",
|
Path: "debug/stack",
|
||||||
Read: PermitAnyone,
|
|
||||||
DataFn: getStack,
|
|
||||||
}); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := RegisterEndpoint(Endpoint{
|
|
||||||
Path: "debug/stack/print",
|
|
||||||
Read: PermitAnyone,
|
Read: PermitAnyone,
|
||||||
ActionFn: printStack,
|
DataFunc: getStack,
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := RegisterEndpoint(Endpoint{
|
if err := RegisterEndpoint(Endpoint{
|
||||||
Path: "debug/info",
|
Path: "debug/stack/print",
|
||||||
Read: PermitAnyone,
|
Read: PermitAnyone,
|
||||||
DataFn: debugInfo,
|
ActionFunc: printStack,
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := RegisterEndpoint(Endpoint{
|
||||||
|
Path: "debug/info",
|
||||||
|
Read: PermitAnyone,
|
||||||
|
DataFunc: debugInfo,
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -70,7 +70,7 @@ func debugInfo(ar *Request) (data []byte, err error) {
|
||||||
|
|
||||||
// Add debug information.
|
// Add debug information.
|
||||||
di.AddVersionInfo()
|
di.AddVersionInfo()
|
||||||
di.AddPlatformInfo(ar.Ctx())
|
di.AddPlatformInfo(ar.Context())
|
||||||
di.AddLastReportedModuleError()
|
di.AddLastReportedModuleError()
|
||||||
di.AddLastUnexpectedLogs()
|
di.AddLastUnexpectedLogs()
|
||||||
di.AddGoroutineStack()
|
di.AddGoroutineStack()
|
||||||
|
|
|
@ -10,23 +10,23 @@ func registerMetaEndpoints() error {
|
||||||
Path: "endpoints",
|
Path: "endpoints",
|
||||||
Read: PermitAnyone,
|
Read: PermitAnyone,
|
||||||
MimeType: MimeTypeJSON,
|
MimeType: MimeTypeJSON,
|
||||||
DataFn: listEndpoints,
|
DataFunc: listEndpoints,
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := RegisterEndpoint(Endpoint{
|
if err := RegisterEndpoint(Endpoint{
|
||||||
Path: "permission",
|
Path: "permission",
|
||||||
Read: Require,
|
Read: Require,
|
||||||
StructFn: permissions,
|
StructFunc: permissions,
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := RegisterEndpoint(Endpoint{
|
if err := RegisterEndpoint(Endpoint{
|
||||||
Path: "ping",
|
Path: "ping",
|
||||||
Read: PermitAnyone,
|
Read: PermitAnyone,
|
||||||
ActionFn: ping,
|
ActionFunc: ping,
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,7 +30,7 @@ func TestEndpoints(t *testing.T) {
|
||||||
assert.NoError(t, RegisterEndpoint(Endpoint{
|
assert.NoError(t, RegisterEndpoint(Endpoint{
|
||||||
Path: "test/action",
|
Path: "test/action",
|
||||||
Read: PermitAnyone,
|
Read: PermitAnyone,
|
||||||
ActionFn: func(_ *Request) (msg string, err error) {
|
ActionFunc: func(_ *Request) (msg string, err error) {
|
||||||
return successMsg, nil
|
return successMsg, nil
|
||||||
},
|
},
|
||||||
}))
|
}))
|
||||||
|
@ -39,7 +39,7 @@ func TestEndpoints(t *testing.T) {
|
||||||
assert.NoError(t, RegisterEndpoint(Endpoint{
|
assert.NoError(t, RegisterEndpoint(Endpoint{
|
||||||
Path: "test/action-err",
|
Path: "test/action-err",
|
||||||
Read: PermitAnyone,
|
Read: PermitAnyone,
|
||||||
ActionFn: func(_ *Request) (msg string, err error) {
|
ActionFunc: func(_ *Request) (msg string, err error) {
|
||||||
return "", errors.New(failedMsg)
|
return "", errors.New(failedMsg)
|
||||||
},
|
},
|
||||||
}))
|
}))
|
||||||
|
@ -50,7 +50,7 @@ func TestEndpoints(t *testing.T) {
|
||||||
assert.NoError(t, RegisterEndpoint(Endpoint{
|
assert.NoError(t, RegisterEndpoint(Endpoint{
|
||||||
Path: "test/data",
|
Path: "test/data",
|
||||||
Read: PermitAnyone,
|
Read: PermitAnyone,
|
||||||
DataFn: func(_ *Request) (data []byte, err error) {
|
DataFunc: func(_ *Request) (data []byte, err error) {
|
||||||
return []byte(successMsg), nil
|
return []byte(successMsg), nil
|
||||||
},
|
},
|
||||||
}))
|
}))
|
||||||
|
@ -59,7 +59,7 @@ func TestEndpoints(t *testing.T) {
|
||||||
assert.NoError(t, RegisterEndpoint(Endpoint{
|
assert.NoError(t, RegisterEndpoint(Endpoint{
|
||||||
Path: "test/data-err",
|
Path: "test/data-err",
|
||||||
Read: PermitAnyone,
|
Read: PermitAnyone,
|
||||||
DataFn: func(_ *Request) (data []byte, err error) {
|
DataFunc: func(_ *Request) (data []byte, err error) {
|
||||||
return nil, errors.New(failedMsg)
|
return nil, errors.New(failedMsg)
|
||||||
},
|
},
|
||||||
}))
|
}))
|
||||||
|
@ -70,7 +70,7 @@ func TestEndpoints(t *testing.T) {
|
||||||
assert.NoError(t, RegisterEndpoint(Endpoint{
|
assert.NoError(t, RegisterEndpoint(Endpoint{
|
||||||
Path: "test/struct",
|
Path: "test/struct",
|
||||||
Read: PermitAnyone,
|
Read: PermitAnyone,
|
||||||
StructFn: func(_ *Request) (i interface{}, err error) {
|
StructFunc: func(_ *Request) (i interface{}, err error) {
|
||||||
return &actionTestRecord{
|
return &actionTestRecord{
|
||||||
Msg: successMsg,
|
Msg: successMsg,
|
||||||
}, nil
|
}, nil
|
||||||
|
@ -81,7 +81,7 @@ func TestEndpoints(t *testing.T) {
|
||||||
assert.NoError(t, RegisterEndpoint(Endpoint{
|
assert.NoError(t, RegisterEndpoint(Endpoint{
|
||||||
Path: "test/struct-err",
|
Path: "test/struct-err",
|
||||||
Read: PermitAnyone,
|
Read: PermitAnyone,
|
||||||
StructFn: func(_ *Request) (i interface{}, err error) {
|
StructFunc: func(_ *Request) (i interface{}, err error) {
|
||||||
return nil, errors.New(failedMsg)
|
return nil, errors.New(failedMsg)
|
||||||
},
|
},
|
||||||
}))
|
}))
|
||||||
|
@ -92,7 +92,7 @@ func TestEndpoints(t *testing.T) {
|
||||||
assert.NoError(t, RegisterEndpoint(Endpoint{
|
assert.NoError(t, RegisterEndpoint(Endpoint{
|
||||||
Path: "test/record",
|
Path: "test/record",
|
||||||
Read: PermitAnyone,
|
Read: PermitAnyone,
|
||||||
RecordFn: func(_ *Request) (r record.Record, err error) {
|
RecordFunc: func(_ *Request) (r record.Record, err error) {
|
||||||
r = &actionTestRecord{
|
r = &actionTestRecord{
|
||||||
Msg: successMsg,
|
Msg: successMsg,
|
||||||
}
|
}
|
||||||
|
@ -105,7 +105,7 @@ func TestEndpoints(t *testing.T) {
|
||||||
assert.NoError(t, RegisterEndpoint(Endpoint{
|
assert.NoError(t, RegisterEndpoint(Endpoint{
|
||||||
Path: "test/record-err",
|
Path: "test/record-err",
|
||||||
Read: PermitAnyone,
|
Read: PermitAnyone,
|
||||||
RecordFn: func(_ *Request) (r record.Record, err error) {
|
RecordFunc: func(_ *Request) (r record.Record, err error) {
|
||||||
return nil, errors.New(failedMsg)
|
return nil, errors.New(failedMsg)
|
||||||
},
|
},
|
||||||
}))
|
}))
|
||||||
|
@ -139,17 +139,17 @@ func TestActionRegistration(t *testing.T) {
|
||||||
|
|
||||||
assert.Error(t, RegisterEndpoint(Endpoint{
|
assert.Error(t, RegisterEndpoint(Endpoint{
|
||||||
Path: "test/err",
|
Path: "test/err",
|
||||||
ActionFn: func(_ *Request) (msg string, err error) {
|
ActionFunc: func(_ *Request) (msg string, err error) {
|
||||||
return successMsg, nil
|
return successMsg, nil
|
||||||
},
|
},
|
||||||
DataFn: func(_ *Request) (data []byte, err error) {
|
DataFunc: func(_ *Request) (data []byte, err error) {
|
||||||
return []byte(successMsg), nil
|
return []byte(successMsg), nil
|
||||||
},
|
},
|
||||||
}))
|
}))
|
||||||
|
|
||||||
assert.NoError(t, RegisterEndpoint(Endpoint{
|
assert.NoError(t, RegisterEndpoint(Endpoint{
|
||||||
Path: "test/err",
|
Path: "test/err",
|
||||||
ActionFn: func(_ *Request) (msg string, err error) {
|
ActionFunc: func(_ *Request) (msg string, err error) {
|
||||||
return successMsg, nil
|
return successMsg, nil
|
||||||
},
|
},
|
||||||
}))
|
}))
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
|
@ -10,7 +9,7 @@ import (
|
||||||
// Request is a support struct to pool more request related information.
|
// Request is a support struct to pool more request related information.
|
||||||
type Request struct {
|
type Request struct {
|
||||||
// Request is the http request.
|
// Request is the http request.
|
||||||
Request *http.Request
|
*http.Request
|
||||||
|
|
||||||
// InputData contains the request body for write operations.
|
// InputData contains the request body for write operations.
|
||||||
InputData []byte
|
InputData []byte
|
||||||
|
@ -28,11 +27,6 @@ type Request struct {
|
||||||
HandlerCache interface{}
|
HandlerCache interface{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ctx is a shortcut to access the request context.
|
|
||||||
func (ar *Request) Ctx() context.Context {
|
|
||||||
return ar.Request.Context()
|
|
||||||
}
|
|
||||||
|
|
||||||
// apiRequestContextKey is a key used for the context key/value storage.
|
// apiRequestContextKey is a key used for the context key/value storage.
|
||||||
type apiRequestContextKey struct{}
|
type apiRequestContextKey struct{}
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,7 @@ package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"net/http"
|
"net/http"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
@ -127,21 +128,17 @@ func (mh *mainHandler) handle(w http.ResponseWriter, r *http.Request) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check authentication.
|
// Check authentication.
|
||||||
token := authenticateRequest(lrw, r, handler)
|
apiRequest.AuthToken = authenticateRequest(lrw, r, handler)
|
||||||
if token == nil {
|
if apiRequest.AuthToken == nil {
|
||||||
// Authenticator already replied.
|
// Authenticator already replied.
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
apiRequest.AuthToken = &AuthToken{
|
|
||||||
Read: token.Read,
|
|
||||||
Write: token.Write,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle request.
|
// Handle request.
|
||||||
switch {
|
switch {
|
||||||
case handler != nil:
|
case handler != nil:
|
||||||
handler.ServeHTTP(lrw, r)
|
handler.ServeHTTP(lrw, r)
|
||||||
case match.MatchErr == mux.ErrMethodMismatch:
|
case errors.Is(match.MatchErr, mux.ErrMethodMismatch):
|
||||||
http.Error(lrw, "Method not allowed.", http.StatusMethodNotAllowed)
|
http.Error(lrw, "Method not allowed.", http.StatusMethodNotAllowed)
|
||||||
default: // handler == nil or other error
|
default: // handler == nil or other error
|
||||||
http.Error(lrw, "Not found.", http.StatusNotFound)
|
http.Error(lrw, "Not found.", http.StatusNotFound)
|
||||||
|
|
Loading…
Add table
Reference in a new issue