package api import ( "context" "fmt" "net/http" "forge.cadoles.com/Cadoles/emissary/internal/auth" "forge.cadoles.com/Cadoles/emissary/internal/auth/agent" "forge.cadoles.com/Cadoles/emissary/internal/auth/thirdparty" "forge.cadoles.com/Cadoles/emissary/internal/datastore" "github.com/pkg/errors" "gitlab.com/wpetit/goweb/api" "gitlab.com/wpetit/goweb/logger" ) var ErrCodeForbidden api.ErrorCode = "forbidden" func assertGlobalReadAccess(h http.Handler) http.Handler { fn := func(w http.ResponseWriter, r *http.Request) { reqUser, ok := assertRequestUser(w, r) if !ok { return } switch user := reqUser.(type) { case *thirdparty.User: role := user.Role() if role == thirdparty.RoleReader || role == thirdparty.RoleWriter { h.ServeHTTP(w, r) return } case *agent.User: // Agents dont have global read access default: logUnexpectedUserType(r.Context(), reqUser) } forbidden(w, r) } return http.HandlerFunc(fn) } func assertAgentWriteAccess(h http.Handler) http.Handler { fn := func(w http.ResponseWriter, r *http.Request) { reqUser, ok := assertRequestUser(w, r) if !ok { return } agentID, ok := getAgentID(w, r) if !ok { return } switch user := reqUser.(type) { case *thirdparty.User: role := user.Role() if role == thirdparty.RoleWriter { h.ServeHTTP(w, r) return } case *agent.User: if user.Agent().ID == agentID { h.ServeHTTP(w, r) return } default: logUnexpectedUserType(r.Context(), reqUser) } forbidden(w, r) } return http.HandlerFunc(fn) } func assertAgentReadAccess(h http.Handler) http.Handler { fn := func(w http.ResponseWriter, r *http.Request) { reqUser, ok := assertRequestUser(w, r) if !ok { return } agentID, ok := getAgentID(w, r) if !ok { return } switch user := reqUser.(type) { case *thirdparty.User: role := user.Role() if role == thirdparty.RoleReader || role == thirdparty.RoleWriter { h.ServeHTTP(w, r) return } case *agent.User: if user.Agent().ID == agentID { h.ServeHTTP(w, r) return } default: logUnexpectedUserType(r.Context(), reqUser) } forbidden(w, r) } return http.HandlerFunc(fn) } func assertRequestUser(w http.ResponseWriter, r *http.Request) (auth.User, bool) { ctx := r.Context() user, err := auth.CtxUser(ctx) if err != nil { err = errors.WithStack(err) logger.Error(ctx, "could not retrieve user", logger.CapturedE(err)) forbidden(w, r) return nil, false } if user == nil || user.Tenant() == "" { forbidden(w, r) return nil, false } return user, true } func (m *Mount) assertTenantOwns(w http.ResponseWriter, r *http.Request, agentID datastore.AgentID) bool { ctx := r.Context() user, ok := assertRequestUser(w, r) if !ok { return false } agent, err := m.agentRepo.Get(ctx, agentID) if err != nil { err = errors.WithStack(err) logger.Error(ctx, "could not get agent", logger.CapturedE(err)) api.ErrorResponse(w, http.StatusInternalServerError, ErrCodeUnknownError, nil) } if agent.TenantID != nil && *agent.TenantID == user.Tenant() { return true } api.ErrorResponse(w, http.StatusForbidden, ErrCodeForbidden, nil) return false } func forbidden(w http.ResponseWriter, r *http.Request) { logger.Warn(r.Context(), "forbidden", logger.F("path", r.URL.Path)) api.ErrorResponse(w, http.StatusForbidden, ErrCodeForbidden, nil) } func logUnexpectedUserType(ctx context.Context, user auth.User) { logger.Warn( ctx, "unexpected user type", logger.F("subject", user.Subject()), logger.F("type", fmt.Sprintf("%T", user)), ) }