mirror of
https://github.com/coder/coder.git
synced 2025-07-03 16:13:58 +00:00
Fixes an issue where we extracted the `{user}` parameter from the URL and added it to the API Handler context regardless of whether the caller had permission to read the User.
110 lines
3.1 KiB
Go
110 lines
3.1 KiB
Go
package httpmw
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"net/http"
|
|
|
|
"github.com/go-chi/chi/v5"
|
|
"github.com/google/uuid"
|
|
|
|
"github.com/coder/coder/v2/coderd/database"
|
|
"github.com/coder/coder/v2/coderd/httpapi"
|
|
"github.com/coder/coder/v2/codersdk"
|
|
)
|
|
|
|
type userParamContextKey struct{}
|
|
|
|
const (
|
|
// userErrorMessage is a constant so that no information about the state
|
|
// of the queried user can be gained. We return the same error if the user
|
|
// does not exist, or if the input is just garbage.
|
|
userErrorMessage = "\"user\" must be an existing uuid or username."
|
|
)
|
|
|
|
// UserParam returns the user from the ExtractUserParam handler.
|
|
func UserParam(r *http.Request) database.User {
|
|
user, ok := r.Context().Value(userParamContextKey{}).(database.User)
|
|
if !ok {
|
|
panic("developer error: user parameter middleware not provided")
|
|
}
|
|
return user
|
|
}
|
|
|
|
// ExtractUserParam extracts a user from an ID/username in the {user} URL
|
|
// parameter.
|
|
func ExtractUserParam(db database.Store) func(http.Handler) http.Handler {
|
|
return func(next http.Handler) http.Handler {
|
|
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
|
|
ctx := r.Context()
|
|
user, ok := extractUserContext(ctx, db, rw, r)
|
|
if !ok {
|
|
// response already handled
|
|
return
|
|
}
|
|
ctx = context.WithValue(ctx, userParamContextKey{}, user)
|
|
next.ServeHTTP(rw, r.WithContext(ctx))
|
|
})
|
|
}
|
|
}
|
|
|
|
// extractUserContext queries the database for the parameterized `{user}` from the request URL.
|
|
func extractUserContext(ctx context.Context, db database.Store, rw http.ResponseWriter, r *http.Request) (user database.User, ok bool) {
|
|
// userQuery is either a uuid, a username, or 'me'
|
|
userQuery := chi.URLParam(r, "user")
|
|
if userQuery == "" {
|
|
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
|
|
Message: "\"user\" must be provided.",
|
|
})
|
|
return database.User{}, true
|
|
}
|
|
|
|
if userQuery == "me" {
|
|
apiKey, ok := APIKeyOptional(r)
|
|
if !ok {
|
|
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
|
|
Message: "Cannot use \"me\" without a valid session.",
|
|
})
|
|
return database.User{}, false
|
|
}
|
|
user, err := db.GetUserByID(ctx, apiKey.UserID)
|
|
if httpapi.Is404Error(err) {
|
|
httpapi.ResourceNotFound(rw)
|
|
return database.User{}, false
|
|
}
|
|
if err != nil {
|
|
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
|
|
Message: "Internal error fetching user.",
|
|
Detail: err.Error(),
|
|
})
|
|
return database.User{}, false
|
|
}
|
|
return user, true
|
|
}
|
|
|
|
if userID, err := uuid.Parse(userQuery); err == nil {
|
|
user, err = db.GetUserByID(ctx, userID)
|
|
if err != nil {
|
|
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
|
|
Message: userErrorMessage,
|
|
Detail: fmt.Sprintf("queried user=%q", userQuery),
|
|
})
|
|
return database.User{}, false
|
|
}
|
|
return user, true
|
|
}
|
|
|
|
// Try as a username last
|
|
user, err := db.GetUserByEmailOrUsername(ctx, database.GetUserByEmailOrUsernameParams{
|
|
Username: userQuery,
|
|
})
|
|
if err != nil {
|
|
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
|
|
Message: userErrorMessage,
|
|
Detail: fmt.Sprintf("queried user=%q", userQuery),
|
|
})
|
|
return database.User{}, false
|
|
}
|
|
return user, true
|
|
}
|