Skip to content

Commit

Permalink
Merge pull request #1437 from bnevis-i/issue-1435
Browse files Browse the repository at this point in the history
feat(security)!: Add authentication hooks to routes
  • Loading branch information
bnevis-i committed Jul 13, 2023
2 parents a0fbb63 + fa33c88 commit 1ff3b26
Show file tree
Hide file tree
Showing 8 changed files with 128 additions and 13 deletions.
13 changes: 13 additions & 0 deletions internal/app/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -124,12 +124,25 @@ func (svc *Service) AppContext() context.Context {

// AddRoute allows you to leverage the existing webserver to add routes.
func (svc *Service) AddRoute(route string, handler func(nethttp.ResponseWriter, *nethttp.Request), methods ...string) error {
// Legacy behavior is to add unauthenticated route
return svc.AddCustomRoute(route, interfaces.Unauthenticated, handler, methods...)
}

// AddCustomRoute allows you to leverage the existing webserver to add routes.
func (svc *Service) AddCustomRoute(route string, authentication interfaces.Authentication, handler func(nethttp.ResponseWriter, *nethttp.Request), methods ...string) error {
if route == commonConstants.ApiPingRoute ||
route == commonConstants.ApiConfigRoute ||
route == commonConstants.ApiVersionRoute ||
route == internal.ApiTriggerRoute {
return errors.New("route is reserved")
}
if authentication == interfaces.Authenticated {
lc := bootstrapContainer.LoggingClientFrom(svc.dic.Get)
secretProvider := bootstrapContainer.SecretProviderExtFrom(svc.dic.Get)
authenticationHook := bootstrapHandlers.AutoConfigAuthenticationFunc(secretProvider, lc)

return svc.webserver.AddRoute(route, authenticationHook(svc.addContext(handler)), methods...)
}
return svc.webserver.AddRoute(route, svc.addContext(handler), methods...)
}

Expand Down
41 changes: 41 additions & 0 deletions internal/app/service_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,47 @@ func TestAddRoute(t *testing.T) {

}

func TestAddCustomRouteUnauthenticated(t *testing.T) {
router := mux.NewRouter()

ws := webserver.NewWebServer(dic, router, uuid.NewString())

sdk := Service{
webserver: ws,
}
_ = sdk.AddCustomRoute("/test", interfaces.Unauthenticated, func(http.ResponseWriter, *http.Request) {}, http.MethodGet)
_ = router.Walk(func(route *mux.Route, router *mux.Router, ancestors []*mux.Route) error {
path, err := route.GetPathTemplate()
if err != nil {
return err
}
assert.Equal(t, "/test", path)
return nil
})

}

func TestAddCustomRouteAuthenticated(t *testing.T) {
router := mux.NewRouter()

ws := webserver.NewWebServer(dic, router, uuid.NewString())

sdk := Service{
webserver: ws,
dic: di.NewContainer(di.ServiceConstructorMap{}),
}
_ = sdk.AddCustomRoute("/test", interfaces.Authenticated, func(http.ResponseWriter, *http.Request) {}, http.MethodGet)
_ = router.Walk(func(route *mux.Route, router *mux.Router, ancestors []*mux.Route) error {
path, err := route.GetPathTemplate()
if err != nil {
return err
}
assert.Equal(t, "/test", path)
return nil
})

}

func TestAddBackgroundPublisherNoTopic(t *testing.T) {
sdk := Service{
config: &common.ConfigurationStruct{},
Expand Down
17 changes: 13 additions & 4 deletions internal/webserver/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import (
"github.com/edgexfoundry/app-functions-sdk-go/v3/pkg/interfaces"

bootstrapContainer "github.com/edgexfoundry/go-mod-bootstrap/v3/bootstrap/container"
bootstrapHandlers "github.com/edgexfoundry/go-mod-bootstrap/v3/bootstrap/handlers"
"github.com/edgexfoundry/go-mod-bootstrap/v3/di"
"github.com/edgexfoundry/go-mod-core-contracts/v3/clients/logger"
"github.com/edgexfoundry/go-mod-core-contracts/v3/common"
Expand Down Expand Up @@ -73,6 +74,7 @@ func (webserver *WebServer) SetCustomConfigInfo(customConfig interfaces.Updatabl

// AddRoute enables support to leverage the existing webserver to add routes.
func (webserver *WebServer) AddRoute(routePath string, handler func(http.ResponseWriter, *http.Request), methods ...string) error {
// If authentication is required, caller's handler should implement it
route := webserver.router.HandleFunc(routePath, handler).Methods(methods...)
if routeErr := route.GetError(); routeErr != nil {
return routeErr
Expand All @@ -85,12 +87,16 @@ func (webserver *WebServer) ConfigureStandardRoutes() {
router := webserver.router
controller := webserver.controller

lc := bootstrapContainer.LoggingClientFrom(webserver.dic.Get)
secretProvider := bootstrapContainer.SecretProviderExtFrom(webserver.dic.Get)
authenticationHook := bootstrapHandlers.AutoConfigAuthenticationFunc(secretProvider, lc)

webserver.lc.Info("Registering standard routes...")

router.HandleFunc(common.ApiPingRoute, controller.Ping).Methods(http.MethodGet)
router.HandleFunc(common.ApiVersionRoute, controller.Version).Methods(http.MethodGet)
router.HandleFunc(common.ApiConfigRoute, controller.Config).Methods(http.MethodGet)
router.HandleFunc(internal.ApiAddSecretRoute, controller.AddSecret).Methods(http.MethodPost)
router.HandleFunc(common.ApiVersionRoute, authenticationHook(controller.Version)).Methods(http.MethodGet)
router.HandleFunc(common.ApiConfigRoute, authenticationHook(controller.Config)).Methods(http.MethodGet)
router.HandleFunc(internal.ApiAddSecretRoute, authenticationHook(controller.AddSecret)).Methods(http.MethodPost)

router.Use(handlers.ProcessCORS(webserver.config.Service.CORSConfiguration))

Expand All @@ -105,7 +111,10 @@ func (webserver *WebServer) ConfigureStandardRoutes() {

// SetupTriggerRoute adds a route to handle trigger pipeline from REST request
func (webserver *WebServer) SetupTriggerRoute(path string, handlerForTrigger func(http.ResponseWriter, *http.Request)) {
webserver.router.HandleFunc(path, handlerForTrigger)
lc := bootstrapContainer.LoggingClientFrom(webserver.dic.Get)
secretProvider := bootstrapContainer.SecretProviderExtFrom(webserver.dic.Get)
authenticationHook := bootstrapHandlers.AutoConfigAuthenticationFunc(secretProvider, lc)
webserver.router.HandleFunc(path, authenticationHook(handlerForTrigger))
}

// StartWebServer starts the web server
Expand Down
9 changes: 8 additions & 1 deletion internal/webserver/server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,13 @@
package webserver

import (
"github.com/google/uuid"
"net/http"
"net/http/httptest"
"os"
"testing"

"github.com/google/uuid"

bootstrapContainer "github.com/edgexfoundry/go-mod-bootstrap/v3/bootstrap/container"
"github.com/edgexfoundry/go-mod-bootstrap/v3/bootstrap/interfaces/mocks"
"github.com/edgexfoundry/go-mod-bootstrap/v3/di"
Expand Down Expand Up @@ -69,6 +70,12 @@ func TestAddRoute(t *testing.T) {
}

func TestSetupTriggerRoute(t *testing.T) {
envDisableSecurity := os.Getenv("EDGEX_DISABLE_JWT_VALIDATION")
os.Setenv("EDGEX_DISABLE_JWT_VALIDATION", "true")
defer func() {
os.Setenv("EDGEX_DISABLE_JWT_VALIDATION", envDisableSecurity)
}()

webserver := NewWebServer(dic, mux.NewRouter(), uuid.NewString())

handlerFunctionNotCalled := true
Expand Down
17 changes: 13 additions & 4 deletions pkg/interfaces/mocks/AppFunctionContext.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

23 changes: 22 additions & 1 deletion pkg/interfaces/mocks/ApplicationService.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 5 additions & 2 deletions pkg/interfaces/mocks/Trigger.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 13 additions & 1 deletion pkg/interfaces/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,15 +74,27 @@ type UpdatableConfig interface {
bootstrapInterfaces.UpdatableConfig
}

// Authentication is a typed boolean for AddRoute calls
type Authentication bool

const (
Unauthenticated Authentication = false
Authenticated Authentication = true
)

// ApplicationService defines the interface for an edgex Application Service
type ApplicationService interface {
// AppContext returns the application service context used to detect cancelled context when the service is terminating.
// Used by custom app service to appropriately exit any long-running functions.
AppContext() context.Context
// AddRoute a custom REST route to the application service's internal webserver
// AddRoute adds a custom REST route to the application service's internal webserver
// A reference to this ApplicationService is add the the context that is passed to the handler, which
// can be retrieved using the `AppService` key
AddRoute(route string, handler func(http.ResponseWriter, *http.Request), methods ...string) error
// AddCustomRoute adds a custom REST route to the application service's internal webserver
// A reference to this ApplicationService is add the the context that is passed to the handler, which
// can be retrieved using the `AppService` key
AddCustomRoute(route string, authentication Authentication, handler func(http.ResponseWriter, *http.Request), methods ...string) error
// RequestTimeout returns the configured request timeout value from [Service] section.
RequestTimeout() time.Duration
// ApplicationSettings returns the key/value map of custom settings
Expand Down

0 comments on commit 1ff3b26

Please sign in to comment.