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

     1  package intents
     2  
     3  import (
     4  	"encoding/json"
     5  	"errors"
     6  	"net/http"
     7  	"strings"
     8  
     9  	"github.com/cozy/cozy-stack/model/instance"
    10  	"github.com/cozy/cozy-stack/model/intent"
    11  	"github.com/cozy/cozy-stack/model/permission"
    12  	"github.com/cozy/cozy-stack/pkg/consts"
    13  	"github.com/cozy/cozy-stack/pkg/couchdb"
    14  	"github.com/cozy/cozy-stack/pkg/jsonapi"
    15  	"github.com/cozy/cozy-stack/web/middlewares"
    16  	"github.com/labstack/echo/v4"
    17  )
    18  
    19  type apiIntent struct {
    20  	doc *intent.Intent
    21  	ins *instance.Instance
    22  }
    23  
    24  func (i *apiIntent) ID() string                             { return i.doc.ID() }
    25  func (i *apiIntent) Rev() string                            { return i.doc.Rev() }
    26  func (i *apiIntent) DocType() string                        { return consts.Intents }
    27  func (i *apiIntent) Clone() couchdb.Doc                     { return i }
    28  func (i *apiIntent) SetID(id string)                        { i.doc.SetID(id) }
    29  func (i *apiIntent) SetRev(rev string)                      { i.doc.SetRev(rev) }
    30  func (i *apiIntent) Relationships() jsonapi.RelationshipMap { return nil }
    31  func (i *apiIntent) Included() []jsonapi.Object             { return nil }
    32  func (i *apiIntent) Links() *jsonapi.LinksList {
    33  	parts := strings.SplitN(i.doc.Client, "/", 2)
    34  	if len(parts) < 2 {
    35  		return nil
    36  	}
    37  	perms, err := permission.GetForWebapp(i.ins, parts[1])
    38  	if err != nil {
    39  		return nil
    40  	}
    41  	return &jsonapi.LinksList{
    42  		Self:  "/intents/" + i.ID(),
    43  		Perms: "/permissions/" + perms.ID(),
    44  	}
    45  }
    46  
    47  // In the JSON-API, the client is the domain of the client-side app that
    48  // asked the intent (it is used for postMessage)
    49  func (i *apiIntent) MarshalJSON() ([]byte, error) {
    50  	was := i.doc.Client
    51  	parts := strings.SplitN(i.doc.Client, "/", 2)
    52  	if len(parts) < 2 {
    53  		i.doc.Client = ""
    54  	} else {
    55  		u := i.ins.SubDomain(parts[1])
    56  		u.Path = ""
    57  		i.doc.Client = u.String()
    58  	}
    59  	res, err := json.Marshal(i.doc)
    60  	i.doc.Client = was
    61  	return res, err
    62  }
    63  
    64  func createIntent(c echo.Context) error {
    65  	pdoc, err := middlewares.GetPermission(c)
    66  	if err != nil {
    67  		return echo.NewHTTPError(http.StatusForbidden)
    68  	}
    69  	instance := middlewares.GetInstance(c)
    70  	intent := &intent.Intent{}
    71  	if _, err = jsonapi.Bind(c.Request().Body, intent); err != nil {
    72  		return jsonapi.BadRequest(err)
    73  	}
    74  	if intent.Action == "" {
    75  		return jsonapi.InvalidParameter("action", errors.New("Action is missing"))
    76  	}
    77  	if intent.Type == "" {
    78  		return jsonapi.InvalidParameter("type", errors.New("Type is missing"))
    79  	}
    80  	intent.Client = pdoc.SourceID
    81  	intent.SetID("")
    82  	intent.SetRev("")
    83  	intent.Services = nil
    84  	if err = intent.Save(instance); err != nil {
    85  		return wrapIntentsError(err)
    86  	}
    87  	if err = intent.FillServices(instance); err != nil {
    88  		return wrapIntentsError(err)
    89  	}
    90  	// Fill available webapps only if there are no services found
    91  	if len(intent.Services) == 0 {
    92  		if err = intent.FillAvailableWebapps(instance); err != nil {
    93  			return wrapIntentsError(err)
    94  		}
    95  	}
    96  	if err = intent.Save(instance); err != nil {
    97  		return wrapIntentsError(err)
    98  	}
    99  	api := &apiIntent{intent, instance}
   100  	return jsonapi.Data(c, http.StatusOK, api, nil)
   101  }
   102  
   103  func getIntent(c echo.Context) error {
   104  	instance := middlewares.GetInstance(c)
   105  	intent := &intent.Intent{}
   106  	id := c.Param("id")
   107  	pdoc, err := middlewares.GetPermission(c)
   108  	if err != nil {
   109  		return echo.NewHTTPError(http.StatusForbidden)
   110  	}
   111  	if err = couchdb.GetDoc(instance, consts.Intents, id, intent); err != nil {
   112  		return wrapIntentsError(err)
   113  	}
   114  	allowed := false
   115  	for _, service := range intent.Services {
   116  		if pdoc.SourceID == consts.Apps+"/"+service.Slug {
   117  			allowed = true
   118  		}
   119  	}
   120  	if !allowed {
   121  		return echo.NewHTTPError(http.StatusForbidden)
   122  	}
   123  	api := &apiIntent{intent, instance}
   124  	return jsonapi.Data(c, http.StatusOK, api, nil)
   125  }
   126  
   127  func wrapIntentsError(err error) error {
   128  	if couchdb.IsNotFoundError(err) {
   129  		return jsonapi.NotFound(err)
   130  	}
   131  	return jsonapi.InternalServerError(err)
   132  }
   133  
   134  // Routes sets the routing for the intents service
   135  func Routes(router *echo.Group) {
   136  	router.POST("", createIntent)
   137  	router.GET("/:id", getIntent)
   138  }