package httpmw import ( "context" "net/http" "github.com/coder/coder/v2/coderd/database" "github.com/coder/coder/v2/coderd/database/dbauthz" "github.com/coder/coder/v2/coderd/httpapi" "github.com/coder/coder/v2/codersdk" ) type ( organizationParamContextKey struct{} organizationMemberParamContextKey struct{} ) // OrganizationParam returns the organization from the ExtractOrganizationParam handler. func OrganizationParam(r *http.Request) database.Organization { organization, ok := r.Context().Value(organizationParamContextKey{}).(database.Organization) if !ok { panic("developer error: organization param middleware not provided") } return organization } // OrganizationMemberParam returns the organization membership that allowed the query // from the ExtractOrganizationParam handler. func OrganizationMemberParam(r *http.Request) OrganizationMember { organizationMember, ok := r.Context().Value(organizationMemberParamContextKey{}).(OrganizationMember) if !ok { panic("developer error: organization member param middleware not provided") } return organizationMember } // ExtractOrganizationParam grabs an organization from the "organization" URL parameter. // This middleware requires the API key middleware higher in the call stack for authentication. func ExtractOrganizationParam(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() orgID, ok := ParseUUIDParam(rw, r, "organization") if !ok { return } organization, err := db.GetOrganizationByID(ctx, orgID) if httpapi.Is404Error(err) { httpapi.ResourceNotFound(rw) return } if err != nil { httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error fetching organization.", Detail: err.Error(), }) return } ctx = context.WithValue(ctx, organizationParamContextKey{}, organization) next.ServeHTTP(rw, r.WithContext(ctx)) }) } } // OrganizationMember is the database object plus the Username. Including the Username in this // middleware is preferable to a join at the SQL layer so that we can keep the autogenerated // database types as they are. type OrganizationMember struct { database.OrganizationMember Username string } // ExtractOrganizationMemberParam grabs a user membership from the "organization" and "user" URL parameter. // This middleware requires the ExtractUser and ExtractOrganization middleware higher in the stack func ExtractOrganizationMemberParam(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() // We need to resolve the `{user}` URL parameter so that we can get the userID and // username. We do this as SystemRestricted since the caller might have permission // to access the OrganizationMember object, but *not* the User object. So, it is // very important that we do not add the User object to the request context or otherwise // leak it to the API handler. // nolint:gocritic user, ok := extractUserContext(dbauthz.AsSystemRestricted(ctx), db, rw, r) if !ok { return } organization := OrganizationParam(r) organizationMember, err := db.GetOrganizationMemberByUserID(ctx, database.GetOrganizationMemberByUserIDParams{ OrganizationID: organization.ID, UserID: user.ID, }) if httpapi.Is404Error(err) { httpapi.ResourceNotFound(rw) return } if err != nil { httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error fetching organization member.", Detail: err.Error(), }) return } ctx = context.WithValue(ctx, organizationMemberParamContextKey{}, OrganizationMember{ OrganizationMember: organizationMember, // Here we're making one exception to the rule about not leaking data about the user // to the API handler, which is to include the username. If the caller has permission // to read the OrganizationMember, then we're explicitly saying here that they also // have permission to see the member's username, which is itself uncontroversial. // // API handlers need this information for audit logging and returning the owner's // username in response to creating a workspace. Username: user.Username, }) next.ServeHTTP(rw, r.WithContext(ctx)) }) } }