package router import ( "net/http" "github.com/go-chi/chi" "github.com/porter-dev/porter/api/server/authn" "github.com/porter-dev/porter/api/server/authz" "github.com/porter-dev/porter/api/server/authz/policy" "github.com/porter-dev/porter/api/server/shared" "github.com/porter-dev/porter/api/types" ) func NewAPIRouter(config *shared.Config) *chi.Mux { r := chi.NewRouter() // set the content type for all API endpoints r.Use(ContentTypeJSON) endpointFactory := shared.NewAPIObjectEndpointFactory(config) baseRegisterer := NewBaseRegisterer() clusterRegisterer := NewClusterScopedRegisterer() projRegisterer := NewProjectScopedRegisterer(clusterRegisterer) userRegisterer := NewUserScopedRegisterer(projRegisterer) r.Route("/api", func(r chi.Router) { baseRoutes := baseRegisterer.GetRoutes( r, config, &types.Path{ RelativePath: "", }, endpointFactory, ) userRoutes := userRegisterer.GetRoutes( r, config, &types.Path{ RelativePath: "", }, endpointFactory, userRegisterer.Children..., ) routes := append(baseRoutes, userRoutes...) registerRoutes(config, routes) }) return r } type Route struct { Endpoint *shared.APIEndpoint Handler http.Handler Router chi.Router } type Registerer struct { GetRoutes func( r chi.Router, config *shared.Config, basePath *types.Path, factory shared.APIEndpointFactory, children ...*Registerer, ) []*Route Children []*Registerer } func registerRoutes(config *shared.Config, routes []*Route) { // Create a new "user-scoped" factory which will create a new user-scoped request // after authentication. Each subsequent http.Handler can lookup the user in context. authNFactory := authn.NewAuthNFactory(config) // Create a new "project-scoped" factory which will create a new project-scoped request // after authorization. Each subsequent http.Handler can lookup the project in context. projFactory := authz.NewProjectScopedFactory(config) // Create a new "cluster-scoped" factory which will create a new cluster-scoped request // after authorization. Each subsequent http.Handler can lookup the cluster in context. clusterFactory := authz.NewClusterScopedFactory(config) // Policy doc loader loads the policy documents for a specific project. policyDocLoader := policy.NewBasicPolicyDocumentLoader(config.Repo.Project()) for _, route := range routes { atomicGroup := route.Router.Group(nil) for _, scope := range route.Endpoint.Metadata.Scopes { switch scope { case types.UserScope: // if the endpoint should redirect when authn fails, attach redirect handler if route.Endpoint.Metadata.ShouldRedirect { atomicGroup.Use(authNFactory.NewAuthenticatedWithRedirect) } else { atomicGroup.Use(authNFactory.NewAuthenticated) } case types.ProjectScope: policyFactory := authz.NewPolicyMiddleware(config, *route.Endpoint.Metadata, policyDocLoader) atomicGroup.Use(policyFactory.Middleware) atomicGroup.Use(projFactory.Middleware) case types.ClusterScope: atomicGroup.Use(clusterFactory.Middleware) } } atomicGroup.Method( string(route.Endpoint.Metadata.Method), route.Endpoint.Metadata.Path.RelativePath, route.Handler, ) } } // ContentTypeJSON sets the content type for requests to application/json func ContentTypeJSON(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json;charset=utf8") next.ServeHTTP(w, r) }) }