router.go 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230
  1. package router
  2. import (
  3. "net/http"
  4. "os"
  5. "path"
  6. "strings"
  7. "github.com/go-chi/chi"
  8. "github.com/porter-dev/porter/api/server/authn"
  9. "github.com/porter-dev/porter/api/server/authz"
  10. "github.com/porter-dev/porter/api/server/authz/policy"
  11. "github.com/porter-dev/porter/api/server/shared"
  12. "github.com/porter-dev/porter/api/server/shared/config"
  13. "github.com/porter-dev/porter/api/types"
  14. )
  15. func NewAPIRouter(config *config.Config) *chi.Mux {
  16. r := chi.NewRouter()
  17. endpointFactory := shared.NewAPIObjectEndpointFactory(config)
  18. baseRegisterer := NewBaseRegisterer()
  19. oauthCallbackRegisterer := NewOAuthCallbackRegisterer()
  20. releaseRegisterer := NewReleaseScopedRegisterer()
  21. namespaceRegisterer := NewNamespaceScopedRegisterer(releaseRegisterer)
  22. clusterRegisterer := NewClusterScopedRegisterer(namespaceRegisterer)
  23. infraRegisterer := NewInfraScopedRegisterer()
  24. gitInstallationRegisterer := NewGitInstallationScopedRegisterer()
  25. registryRegisterer := NewRegistryScopedRegisterer()
  26. helmRepoRegisterer := NewHelmRepoScopedRegisterer()
  27. inviteRegisterer := NewInviteScopedRegisterer()
  28. projectIntegrationRegisterer := NewProjectIntegrationScopedRegisterer()
  29. projectOAuthRegisterer := NewProjectOAuthScopedRegisterer()
  30. slackIntegrationRegisterer := NewSlackIntegrationScopedRegisterer()
  31. projRegisterer := NewProjectScopedRegisterer(
  32. clusterRegisterer,
  33. registryRegisterer,
  34. helmRepoRegisterer,
  35. inviteRegisterer,
  36. gitInstallationRegisterer,
  37. infraRegisterer,
  38. projectIntegrationRegisterer,
  39. projectOAuthRegisterer,
  40. slackIntegrationRegisterer,
  41. )
  42. userRegisterer := NewUserScopedRegisterer(projRegisterer)
  43. r.Route("/api", func(r chi.Router) {
  44. // set the content type for all API endpoints
  45. r.Use(ContentTypeJSON)
  46. baseRoutes := baseRegisterer.GetRoutes(
  47. r,
  48. config,
  49. &types.Path{
  50. RelativePath: "",
  51. },
  52. endpointFactory,
  53. )
  54. oauthCallbackRoutes := oauthCallbackRegisterer.GetRoutes(
  55. r,
  56. config,
  57. &types.Path{
  58. RelativePath: "",
  59. },
  60. endpointFactory,
  61. )
  62. userRoutes := userRegisterer.GetRoutes(
  63. r,
  64. config,
  65. &types.Path{
  66. RelativePath: "",
  67. },
  68. endpointFactory,
  69. userRegisterer.Children...,
  70. )
  71. routes := [][]*Route{
  72. baseRoutes,
  73. userRoutes,
  74. oauthCallbackRoutes,
  75. }
  76. var allRoutes []*Route
  77. for _, r := range routes {
  78. allRoutes = append(allRoutes, r...)
  79. }
  80. registerRoutes(config, allRoutes)
  81. })
  82. staticFilePath := config.ServerConf.StaticFilePath
  83. fs := http.FileServer(http.Dir(staticFilePath))
  84. r.Get("/*", func(w http.ResponseWriter, r *http.Request) {
  85. if _, err := os.Stat(staticFilePath + r.RequestURI); os.IsNotExist(err) {
  86. w.Header().Set("Cache-Control", "no-cache")
  87. http.StripPrefix(r.URL.Path, fs).ServeHTTP(w, r)
  88. } else {
  89. // Set static files involving html, js, or empty cache to "no-cache", which means they must be validated
  90. // for changes before the browser uses the cache
  91. if base := path.Base(r.URL.Path); strings.Contains(base, "html") || strings.Contains(base, "js") || base == "." || base == "/" {
  92. w.Header().Set("Cache-Control", "no-cache")
  93. }
  94. fs.ServeHTTP(w, r)
  95. }
  96. })
  97. return r
  98. }
  99. type Route struct {
  100. Endpoint *shared.APIEndpoint
  101. Handler http.Handler
  102. Router chi.Router
  103. }
  104. type Registerer struct {
  105. GetRoutes func(
  106. r chi.Router,
  107. config *config.Config,
  108. basePath *types.Path,
  109. factory shared.APIEndpointFactory,
  110. children ...*Registerer,
  111. ) []*Route
  112. Children []*Registerer
  113. }
  114. func registerRoutes(config *config.Config, routes []*Route) {
  115. // Create a new "user-scoped" factory which will create a new user-scoped request
  116. // after authentication. Each subsequent http.Handler can lookup the user in context.
  117. authNFactory := authn.NewAuthNFactory(config)
  118. // Create a new "project-scoped" factory which will create a new project-scoped request
  119. // after authorization. Each subsequent http.Handler can lookup the project in context.
  120. projFactory := authz.NewProjectScopedFactory(config)
  121. // Create a new "cluster-scoped" factory which will create a new cluster-scoped request
  122. // after authorization. Each subsequent http.Handler can lookup the cluster in context.
  123. clusterFactory := authz.NewClusterScopedFactory(config)
  124. // Create a new "namespace-scoped" factory which will create a new namespace-scoped request
  125. // after authorization. Each subsequent http.Handler can lookup the namespace in context.
  126. namespaceFactory := authz.NewNamespaceScopedFactory(config)
  127. // Create a new "helmrepo-scoped" factory which will create a new helmrepo-scoped request
  128. // after authorization. Each subsequent http.Handler can lookup the helm repo in context.
  129. helmRepoFactory := authz.NewHelmRepoScopedFactory(config)
  130. // Create a new "registry-scoped" factory which will create a new registry-scoped request
  131. // after authorization. Each subsequent http.Handler can lookup the registry in context.
  132. registryFactory := authz.NewRegistryScopedFactory(config)
  133. // Create a new "gitinstallation-scoped" factory which will create a new gitinstallation-scoped request
  134. // after authorization. Each subsequent http.Handler can lookup the gitinstallation in context.
  135. gitInstallationFactory := authz.NewGitInstallationScopedFactory(config)
  136. // Create a new "invite-scoped" factory which will create a new invite-scoped request
  137. // after authorization. Each subsequent http.Handler can lookup the invite in context.
  138. inviteFactory := authz.NewInviteScopedFactory(config)
  139. // Create a new "infra-scoped" factory which will create a new infra-scoped request
  140. // after authorization. Each subsequent http.Handler can lookup the infra in context.
  141. infraFactory := authz.NewInfraScopedFactory(config)
  142. // Create a new "release-scoped" factory which will create a new release-scoped request
  143. // after authorization. Each subsequent http.Handler can lookup the release in context.
  144. releaseFactory := authz.NewReleaseScopedFactory(config)
  145. // Policy doc loader loads the policy documents for a specific project.
  146. policyDocLoader := policy.NewBasicPolicyDocumentLoader(config.Repo.Project())
  147. for _, route := range routes {
  148. atomicGroup := route.Router.Group(nil)
  149. for _, scope := range route.Endpoint.Metadata.Scopes {
  150. switch scope {
  151. case types.UserScope:
  152. // if the endpoint should redirect when authn fails, attach redirect handler
  153. if route.Endpoint.Metadata.ShouldRedirect {
  154. atomicGroup.Use(authNFactory.NewAuthenticatedWithRedirect)
  155. } else {
  156. atomicGroup.Use(authNFactory.NewAuthenticated)
  157. }
  158. case types.ProjectScope:
  159. policyFactory := authz.NewPolicyMiddleware(config, *route.Endpoint.Metadata, policyDocLoader)
  160. atomicGroup.Use(policyFactory.Middleware)
  161. atomicGroup.Use(projFactory.Middleware)
  162. case types.ClusterScope:
  163. atomicGroup.Use(clusterFactory.Middleware)
  164. case types.NamespaceScope:
  165. atomicGroup.Use(namespaceFactory.Middleware)
  166. case types.HelmRepoScope:
  167. atomicGroup.Use(helmRepoFactory.Middleware)
  168. case types.RegistryScope:
  169. atomicGroup.Use(registryFactory.Middleware)
  170. case types.InviteScope:
  171. atomicGroup.Use(inviteFactory.Middleware)
  172. case types.GitInstallationScope:
  173. atomicGroup.Use(gitInstallationFactory.Middleware)
  174. case types.InfraScope:
  175. atomicGroup.Use(infraFactory.Middleware)
  176. case types.ReleaseScope:
  177. atomicGroup.Use(releaseFactory.Middleware)
  178. }
  179. }
  180. atomicGroup.Method(
  181. string(route.Endpoint.Metadata.Method),
  182. route.Endpoint.Metadata.Path.RelativePath,
  183. route.Handler,
  184. )
  185. }
  186. }
  187. // ContentTypeJSON sets the content type for requests to application/json
  188. func ContentTypeJSON(next http.Handler) http.Handler {
  189. return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  190. w.Header().Set("Content-Type", "application/json;charset=utf8")
  191. next.ServeHTTP(w, r)
  192. })
  193. }