github.com/cozy/cozy-stack@v0.0.0-20240603063001-31110fa4cae1/web/registry/registry.go (about)

     1  package registry
     2  
     3  import (
     4  	"encoding/json"
     5  	"net/http"
     6  
     7  	"github.com/cozy/cozy-stack/model/app"
     8  	"github.com/cozy/cozy-stack/model/permission"
     9  	"github.com/cozy/cozy-stack/pkg/consts"
    10  	"github.com/cozy/cozy-stack/pkg/registry"
    11  	"github.com/cozy/cozy-stack/web/middlewares"
    12  	"github.com/labstack/echo/v4"
    13  	"github.com/labstack/echo/v4/middleware"
    14  )
    15  
    16  type authType int
    17  
    18  const (
    19  	authed authType = iota
    20  	perms
    21  )
    22  
    23  type clientCacheControl int
    24  
    25  const (
    26  	noClientCache clientCacheControl = iota
    27  	shortClientCache
    28  	permanentClientCache
    29  )
    30  
    31  func proxyReq(auth authType, clientCache clientCacheControl, proxyCacheControl registry.CacheControl) echo.HandlerFunc {
    32  	return func(c echo.Context) error {
    33  		i := middlewares.GetInstance(c)
    34  		switch auth {
    35  		case authed:
    36  			if !middlewares.IsLoggedIn(c) {
    37  				if err := middlewares.AllowWholeType(c, permission.GET, consts.Apps); err != nil {
    38  					return echo.NewHTTPError(http.StatusForbidden)
    39  				}
    40  			}
    41  		case perms:
    42  			pdoc, err := middlewares.GetPermission(c)
    43  			if err != nil {
    44  				return echo.NewHTTPError(http.StatusForbidden)
    45  			}
    46  			if pdoc.Type != permission.TypeWebapp && pdoc.Type != permission.TypeOauth {
    47  				return echo.NewHTTPError(http.StatusForbidden)
    48  			}
    49  		default:
    50  			panic("unknown authType")
    51  		}
    52  		req := c.Request()
    53  		proxyResp, err := registry.Proxy(req, i.Registries(), proxyCacheControl)
    54  		if err != nil {
    55  			return err
    56  		}
    57  		defer proxyResp.Body.Close()
    58  		switch clientCache {
    59  		case permanentClientCache:
    60  			c.Response().Header().Set("Cache-Control", "max-age=31536000, immutable")
    61  		case shortClientCache:
    62  			c.Response().Header().Set("Cache-Control", "max-age=86400")
    63  		}
    64  		contentType := proxyResp.Header.Get(echo.HeaderContentType)
    65  		return c.Stream(proxyResp.StatusCode, contentType, proxyResp.Body)
    66  	}
    67  }
    68  
    69  func proxyListReq(c echo.Context) error {
    70  	i := middlewares.GetInstance(c)
    71  	pdoc, err := middlewares.GetPermission(c)
    72  	if err != nil {
    73  		return err
    74  	}
    75  	if pdoc.Type != permission.TypeWebapp && pdoc.Type != permission.TypeOauth {
    76  		return echo.NewHTTPError(http.StatusForbidden)
    77  	}
    78  	req := c.Request()
    79  	list, err := registry.ProxyList(req, i.Registries())
    80  	if err != nil {
    81  		return err
    82  	}
    83  	maintenance, err := app.ListMaintenance()
    84  	if err != nil {
    85  		return err
    86  	}
    87  	for _, app := range list.Apps {
    88  		slug := registry.ParseSlug(app["slug"])
    89  		for _, item := range maintenance {
    90  			if item["slug"] == slug {
    91  				app["maintenance_activated"] = json.RawMessage("true")
    92  				if opts, err := json.Marshal(item["maintenance_options"]); err == nil {
    93  					app["maintenance_options"] = json.RawMessage(opts)
    94  				}
    95  			}
    96  		}
    97  	}
    98  	return c.JSON(http.StatusOK, list)
    99  }
   100  
   101  func proxyAppReq(c echo.Context) error {
   102  	i := middlewares.GetInstance(c)
   103  	pdoc, err := middlewares.GetPermission(c)
   104  	if err != nil {
   105  		return err
   106  	}
   107  	if pdoc.Type != permission.TypeWebapp && pdoc.Type != permission.TypeOauth {
   108  		return echo.NewHTTPError(http.StatusForbidden)
   109  	}
   110  	req := c.Request()
   111  	proxyResp, err := registry.Proxy(req, i.Registries(), registry.WithCache)
   112  	if err != nil {
   113  		return err
   114  	}
   115  	defer proxyResp.Body.Close()
   116  	var doc map[string]interface{}
   117  	if err := json.NewDecoder(proxyResp.Body).Decode(&doc); err != nil {
   118  		return err
   119  	}
   120  	opts, err := app.GetMaintenanceOptions(c.Param("app"))
   121  	if err != nil {
   122  		return err
   123  	}
   124  	if opts != nil {
   125  		doc["maintenance_activated"] = true
   126  		doc["maintenance_options"] = opts
   127  	}
   128  	return c.JSON(http.StatusOK, doc)
   129  }
   130  
   131  func proxyMaintenanceReq(c echo.Context) error {
   132  	i := middlewares.GetInstance(c)
   133  	pdoc, err := middlewares.GetPermission(c)
   134  	if err != nil {
   135  		return err
   136  	}
   137  	if pdoc.Type != permission.TypeWebapp && pdoc.Type != permission.TypeOauth {
   138  		return echo.NewHTTPError(http.StatusForbidden)
   139  	}
   140  	apps, err := registry.ListMaintenance(i.Registries())
   141  	if err != nil {
   142  		return err
   143  	}
   144  	maintenance, err := app.ListMaintenance()
   145  	if err != nil {
   146  		return err
   147  	}
   148  	list := make([]interface{}, 0, len(apps)+len(maintenance))
   149  	for _, item := range maintenance {
   150  		list = append(list, item)
   151  	}
   152  	for _, doc := range apps {
   153  		item, err := doc.MarshalJSON()
   154  		if err != nil {
   155  			return err
   156  		}
   157  		list = append(list, json.RawMessage(item))
   158  	}
   159  	return c.JSON(http.StatusOK, list)
   160  }
   161  
   162  // Routes sets the routing for the registry
   163  func Routes(router *echo.Group) {
   164  	gzip := middleware.Gzip()
   165  	router.GET("", proxyListReq, gzip)
   166  	router.GET("/", proxyListReq, gzip)
   167  	router.GET("/maintenance", proxyMaintenanceReq, gzip)
   168  	router.GET("/:app", proxyAppReq, gzip)
   169  	router.GET("/:app/icon", proxyReq(authed, shortClientCache, registry.NoCache))
   170  	router.GET("/:app/partnership_icon", proxyReq(authed, shortClientCache, registry.NoCache))
   171  	router.GET("/:app/screenshots/*", proxyReq(authed, shortClientCache, registry.NoCache))
   172  	router.GET("/:app/:version/icon", proxyReq(authed, permanentClientCache, registry.NoCache))
   173  	router.GET("/:app/:version/partnership_icon", proxyReq(authed, permanentClientCache, registry.NoCache))
   174  	router.GET("/:app/:version/screenshots/*", proxyReq(authed, permanentClientCache, registry.NoCache))
   175  	router.GET("/:app/:version", proxyReq(perms, permanentClientCache, registry.WithCache))
   176  	router.GET("/:app/:channel/latest", proxyReq(perms, noClientCache, registry.WithCache))
   177  }