handler.go 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181
  1. package authn
  2. import (
  3. "context"
  4. "fmt"
  5. "net/http"
  6. "strings"
  7. "github.com/gorilla/sessions"
  8. "github.com/porter-dev/porter/api/server/shared/apierrors"
  9. "github.com/porter-dev/porter/api/server/shared/config"
  10. "github.com/porter-dev/porter/api/types"
  11. "github.com/porter-dev/porter/internal/auth/token"
  12. )
  13. // AuthNFactory generates a middleware handler `AuthN`
  14. type AuthNFactory struct {
  15. config *config.Config
  16. }
  17. // NewAuthNFactory returns an `AuthNFactory` that uses the passed-in server
  18. // config
  19. func NewAuthNFactory(
  20. config *config.Config,
  21. ) *AuthNFactory {
  22. return &AuthNFactory{config}
  23. }
  24. // NewAuthenticated creates a new instance of `AuthN` that implements the http.Handler
  25. // interface.
  26. func (f *AuthNFactory) NewAuthenticated(next http.Handler) http.Handler {
  27. return &AuthN{next, f.config, false}
  28. }
  29. // NewAuthenticatedWithRedirect creates a new instance of `AuthN` that implements the http.Handler
  30. // interface. This handler redirects the user to login if the user is not attached, and stores a
  31. // redirect URI in the session, if the session exists.
  32. func (f *AuthNFactory) NewAuthenticatedWithRedirect(next http.Handler) http.Handler {
  33. return &AuthN{next, f.config, true}
  34. }
  35. // AuthN implements the authentication middleware
  36. type AuthN struct {
  37. next http.Handler
  38. config *config.Config
  39. redirect bool
  40. }
  41. // ServeHTTP attaches an authenticated subject to the request context,
  42. // or serves a forbidden error. If authenticated, it calls the next handler.
  43. //
  44. // A token can either be issued for a specific project id or for a user. In the case
  45. // of a project id, we attach a service account to the context. In the case of a
  46. // user, we attach that user to the context.
  47. func (authn *AuthN) ServeHTTP(w http.ResponseWriter, r *http.Request) {
  48. // first check for a bearer token
  49. tok, err := authn.getTokenFromRequest(r)
  50. // if the error is not an invalid auth error, the token was invalid, and we throw error
  51. // forbidden. If the error was an invalid auth error, we look for a cookie.
  52. if err != nil && err != errInvalidAuthHeader {
  53. authn.sendForbiddenError(err, w, r)
  54. return
  55. } else if err == nil && tok != nil {
  56. authn.nextWithToken(w, r, tok)
  57. return
  58. }
  59. // if the bearer token is not found, look for a request cookie
  60. session, err := authn.config.Store.Get(r, authn.config.ServerConf.CookieName)
  61. if err != nil {
  62. session.Values["authenticated"] = false
  63. // we attempt to save the session, but do not catch the error since we send the
  64. // forbidden error regardless
  65. session.Save(r, w)
  66. authn.sendForbiddenError(err, w, r)
  67. return
  68. }
  69. if auth, ok := session.Values["authenticated"].(bool); !auth || !ok {
  70. authn.handleForbiddenForSession(w, r, fmt.Errorf("stored cookie was not authenticated"), session)
  71. return
  72. }
  73. // read the user id in the token
  74. userID, ok := session.Values["user_id"].(uint)
  75. if !ok {
  76. authn.handleForbiddenForSession(w, r, fmt.Errorf("could not cast user_id to uint"), session)
  77. return
  78. }
  79. authn.nextWithUserID(w, r, userID)
  80. }
  81. func (authn *AuthN) handleForbiddenForSession(
  82. w http.ResponseWriter,
  83. r *http.Request,
  84. err error,
  85. session *sessions.Session,
  86. ) {
  87. if authn.redirect {
  88. // need state parameter to validate when redirected
  89. if r.URL.RawQuery == "" {
  90. session.Values["redirect_uri"] = r.URL.Path
  91. } else {
  92. session.Values["redirect_uri"] = r.URL.Path + "?" + r.URL.RawQuery
  93. }
  94. session.Save(r, w)
  95. http.Redirect(w, r, "/dashboard", 302)
  96. } else {
  97. authn.sendForbiddenError(err, w, r)
  98. }
  99. return
  100. }
  101. // nextWithToken calls the next handler with either the service account or user corresponding
  102. // to the token set in context.
  103. func (authn *AuthN) nextWithToken(w http.ResponseWriter, r *http.Request, tok *token.Token) {
  104. // TODO: add section to get service account for server-side token
  105. // for now, we just use nextWithUser using the `iby` field for the token
  106. authn.nextWithUserID(w, r, tok.IBy)
  107. }
  108. // nextWithUserID calls the next handler with the user set in the context with key
  109. // `types.UserScope`.
  110. func (authn *AuthN) nextWithUserID(w http.ResponseWriter, r *http.Request, userID uint) {
  111. // search for the user
  112. user, err := authn.config.Repo.User().ReadUser(userID)
  113. if err != nil {
  114. authn.sendForbiddenError(fmt.Errorf("user with id %d not found in database", userID), w, r)
  115. return
  116. }
  117. // add the user to the context
  118. ctx := r.Context()
  119. ctx = context.WithValue(ctx, types.UserScope, user)
  120. r = r.Clone(ctx)
  121. authn.next.ServeHTTP(w, r)
  122. }
  123. // sendForbiddenError sends a 403 Forbidden error to the end user while logging a
  124. // specific error
  125. func (authn *AuthN) sendForbiddenError(err error, w http.ResponseWriter, r *http.Request) {
  126. reqErr := apierrors.NewErrForbidden(err)
  127. apierrors.HandleAPIError(authn.config.Logger, authn.config.Alerter, w, r, reqErr, true)
  128. }
  129. var errInvalidToken = fmt.Errorf("authorization header exists, but token is not valid")
  130. var errInvalidAuthHeader = fmt.Errorf("invalid authorization header in request")
  131. // getTokenFromRequest finds an `Authorization` header of the form `Bearer <token>`,
  132. // and returns a valid token if it exists.
  133. func (authn *AuthN) getTokenFromRequest(r *http.Request) (*token.Token, error) {
  134. reqToken := r.Header.Get("Authorization")
  135. splitToken := strings.Split(reqToken, "Bearer")
  136. if len(splitToken) != 2 {
  137. return nil, errInvalidAuthHeader
  138. }
  139. reqToken = strings.TrimSpace(splitToken[1])
  140. tok, err := token.GetTokenFromEncoded(reqToken, authn.config.TokenConf)
  141. if err != nil {
  142. return nil, errInvalidToken
  143. }
  144. return tok, nil
  145. }