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

     1  // Package settings regroups some API methods to facilitate the usage of the
     2  // io.cozy settings documents.
     3  package settings
     4  
     5  import (
     6  	"encoding/json"
     7  	"net/http"
     8  	"strings"
     9  
    10  	"github.com/cozy/cozy-stack/model/instance"
    11  	"github.com/cozy/cozy-stack/model/instance/lifecycle"
    12  	"github.com/cozy/cozy-stack/model/permission"
    13  	"github.com/cozy/cozy-stack/pkg/consts"
    14  	"github.com/cozy/cozy-stack/pkg/couchdb"
    15  	"github.com/cozy/cozy-stack/pkg/jsonapi"
    16  	"github.com/cozy/cozy-stack/web/middlewares"
    17  	"github.com/labstack/echo/v4"
    18  )
    19  
    20  type apiInstance struct {
    21  	doc *couchdb.JSONDoc
    22  }
    23  
    24  func (i *apiInstance) ID() string                             { return i.doc.ID() }
    25  func (i *apiInstance) Rev() string                            { return i.doc.Rev() }
    26  func (i *apiInstance) DocType() string                        { return consts.Settings }
    27  func (i *apiInstance) Clone() couchdb.Doc                     { return i }
    28  func (i *apiInstance) SetID(id string)                        { i.doc.SetID(id) }
    29  func (i *apiInstance) SetRev(rev string)                      { i.doc.SetRev(rev) }
    30  func (i *apiInstance) Relationships() jsonapi.RelationshipMap { return nil }
    31  func (i *apiInstance) Included() []jsonapi.Object             { return nil }
    32  func (i *apiInstance) Links() *jsonapi.LinksList {
    33  	return &jsonapi.LinksList{Self: "/settings/instance"}
    34  }
    35  
    36  func (i *apiInstance) MarshalJSON() ([]byte, error) {
    37  	return json.Marshal(i.doc)
    38  }
    39  
    40  func (h *HTTPHandler) getInstance(c echo.Context) error {
    41  	inst := middlewares.GetInstance(c)
    42  
    43  	doc, err := inst.SettingsDocument()
    44  	if err != nil {
    45  		return err
    46  	}
    47  
    48  	doc.M["locale"] = inst.Locale
    49  	doc.M["onboarding_finished"] = inst.OnboardingFinished
    50  	if inst.PasswordDefined != nil {
    51  		doc.M["password_defined"] = *inst.PasswordDefined
    52  	}
    53  	doc.M["auto_update"] = !inst.NoAutoUpdate
    54  	doc.M["auth_mode"] = instance.AuthModeToString(inst.AuthMode)
    55  	doc.M["tos"] = inst.TOSSigned
    56  	doc.M["tos_latest"] = inst.TOSLatest
    57  	doc.M["uuid"] = inst.UUID
    58  	doc.M["oidc_id"] = inst.OIDCID
    59  	doc.M["context"] = inst.ContextName
    60  	if len(inst.Sponsorships) > 0 {
    61  		doc.M["sponsorships"] = inst.Sponsorships
    62  	}
    63  	// XXX we had a bug where the default_redirection was filled by a full URL
    64  	// instead of slug+path, and we fix it when this endpoint is called.
    65  	if value, ok := doc.M["default_redirection"].(string); !ok || strings.HasPrefix(value, "http") {
    66  		doc.M["default_redirection"] = inst.DefaultAppAndPath()
    67  	}
    68  
    69  	// Allow any application with a token
    70  	if _, err = middlewares.GetPermission(c); err != nil {
    71  		return err
    72  	}
    73  
    74  	return jsonapi.Data(c, http.StatusOK, &apiInstance{doc}, nil)
    75  }
    76  
    77  func (h *HTTPHandler) updateInstance(c echo.Context) error {
    78  	inst := middlewares.GetInstance(c)
    79  
    80  	doc := &couchdb.JSONDoc{}
    81  	obj, err := jsonapi.Bind(c.Request().Body, doc)
    82  	if err != nil {
    83  		return err
    84  	}
    85  
    86  	if doc.M == nil {
    87  		return jsonapi.BadJSON()
    88  	}
    89  
    90  	doc.Type = consts.Settings
    91  	doc.SetID(consts.InstanceSettingsID)
    92  	doc.SetRev(obj.Meta.Rev)
    93  
    94  	if err = middlewares.Allow(c, permission.PUT, doc); err != nil {
    95  		return err
    96  	}
    97  
    98  	pdoc, err := middlewares.GetPermission(c)
    99  	if err != nil || pdoc.Type != permission.TypeCLI {
   100  		delete(doc.M, "auth_mode")
   101  		delete(doc.M, "tos")
   102  		delete(doc.M, "tos_latest")
   103  		delete(doc.M, "uuid")
   104  		delete(doc.M, "context")
   105  		delete(doc.M, "sponsorships")
   106  		delete(doc.M, "oidc_id")
   107  	}
   108  
   109  	if err := lifecycle.Patch(inst, &lifecycle.Options{SettingsObj: doc}); err != nil {
   110  		return err
   111  	}
   112  
   113  	doc.M["locale"] = inst.Locale
   114  	doc.M["onboarding_finished"] = inst.OnboardingFinished
   115  	doc.M["auto_update"] = !inst.NoAutoUpdate
   116  	doc.M["auth_mode"] = instance.AuthModeToString(inst.AuthMode)
   117  	doc.M["tos"] = inst.TOSSigned
   118  	doc.M["tos_latest"] = inst.TOSLatest
   119  	doc.M["uuid"] = inst.UUID
   120  	doc.M["oidc_id"] = inst.OIDCID
   121  	doc.M["context"] = inst.ContextName
   122  
   123  	return jsonapi.Data(c, http.StatusOK, &apiInstance{doc}, nil)
   124  }
   125  
   126  func (h *HTTPHandler) updateInstanceTOS(c echo.Context) error {
   127  	inst := middlewares.GetInstance(c)
   128  
   129  	// Allow any request from OAuth tokens to use this route
   130  	pdoc, err := middlewares.GetPermission(c)
   131  	if err != nil {
   132  		return err
   133  	}
   134  	if pdoc.Type != permission.TypeOauth && pdoc.Type != permission.TypeCLI {
   135  		return echo.NewHTTPError(http.StatusForbidden)
   136  	}
   137  
   138  	if err := lifecycle.ManagerSignTOS(inst, c.Request()); err != nil {
   139  		return err
   140  	}
   141  
   142  	return c.NoContent(http.StatusNoContent)
   143  }
   144  
   145  func (h *HTTPHandler) updateInstanceAuthMode(c echo.Context) error {
   146  	inst := middlewares.GetInstance(c)
   147  
   148  	if err := middlewares.AllowWholeType(c, permission.PUT, consts.Settings); err != nil {
   149  		return err
   150  	}
   151  
   152  	args := struct {
   153  		AuthMode                string `json:"auth_mode"`
   154  		TwoFactorActivationCode string `json:"two_factor_activation_code"`
   155  	}{}
   156  	if err := c.Bind(&args); err != nil {
   157  		return err
   158  	}
   159  
   160  	authMode, err := instance.StringToAuthMode(args.AuthMode)
   161  	if err != nil {
   162  		return jsonapi.BadRequest(err)
   163  	}
   164  	if inst.HasAuthMode(authMode) {
   165  		return c.NoContent(http.StatusNoContent)
   166  	}
   167  
   168  	switch authMode {
   169  	case instance.Basic:
   170  	case instance.TwoFactorMail:
   171  		if args.TwoFactorActivationCode == "" {
   172  			if err = lifecycle.SendMailConfirmationCode(inst); err != nil {
   173  				return err
   174  			}
   175  			return c.NoContent(http.StatusNoContent)
   176  		}
   177  		if ok := inst.ValidateMailConfirmationCode(args.TwoFactorActivationCode); !ok {
   178  			return c.NoContent(http.StatusUnprocessableEntity)
   179  		}
   180  	}
   181  
   182  	err = lifecycle.Patch(inst, &lifecycle.Options{AuthMode: args.AuthMode})
   183  	if err != nil {
   184  		return err
   185  	}
   186  
   187  	return c.NoContent(http.StatusNoContent)
   188  }
   189  
   190  func (h *HTTPHandler) askInstanceDeletion(c echo.Context) error {
   191  	if err := middlewares.RequireSettingsApp(c); err != nil {
   192  		return err
   193  	}
   194  
   195  	inst := middlewares.GetInstance(c)
   196  	if err := lifecycle.AskDeletion(inst); err != nil {
   197  		return err
   198  	}
   199  	return c.NoContent(http.StatusNoContent)
   200  }
   201  
   202  func (h *HTTPHandler) clearMovedFrom(c echo.Context) error {
   203  	if !middlewares.IsLoggedIn(c) {
   204  		return echo.NewHTTPError(http.StatusForbidden)
   205  	}
   206  
   207  	inst := middlewares.GetInstance(c)
   208  	doc, err := inst.SettingsDocument()
   209  	if err != nil {
   210  		return err
   211  	}
   212  
   213  	if doc.M["moved_from"] != nil {
   214  		delete(doc.M, "moved_from")
   215  		if err := couchdb.UpdateDoc(inst, doc); err != nil {
   216  			return err
   217  		}
   218  	}
   219  
   220  	return c.NoContent(http.StatusNoContent)
   221  }