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

     1  package settings
     2  
     3  import (
     4  	"encoding/json"
     5  	"errors"
     6  	"fmt"
     7  	"net/http"
     8  	"net/url"
     9  	"strconv"
    10  	"time"
    11  
    12  	"github.com/cozy/cozy-stack/model/feature"
    13  	"github.com/cozy/cozy-stack/model/instance"
    14  	"github.com/cozy/cozy-stack/model/oauth"
    15  	"github.com/cozy/cozy-stack/model/permission"
    16  	"github.com/cozy/cozy-stack/pkg/consts"
    17  	"github.com/cozy/cozy-stack/pkg/couchdb"
    18  	"github.com/cozy/cozy-stack/pkg/jsonapi"
    19  	"github.com/cozy/cozy-stack/web/auth"
    20  	"github.com/cozy/cozy-stack/web/middlewares"
    21  	"github.com/labstack/echo/v4"
    22  )
    23  
    24  type apiOauthClient struct{ *oauth.Client }
    25  
    26  func (c *apiOauthClient) MarshalJSON() ([]byte, error) {
    27  	return json.Marshal(c.Client)
    28  }
    29  
    30  // Links is used to generate a JSON-API link for the client - see
    31  // jsonapi.Object interface
    32  func (c *apiOauthClient) Links() *jsonapi.LinksList {
    33  	return &jsonapi.LinksList{Self: "/settings/clients/" + c.ID()}
    34  }
    35  
    36  // Relationships is used to generate the parent relationship in JSON-API format
    37  // - see jsonapi.Object interface
    38  func (c *apiOauthClient) Relationships() jsonapi.RelationshipMap {
    39  	return jsonapi.RelationshipMap{}
    40  }
    41  
    42  // Included is part of the jsonapi.Object interface
    43  func (c *apiOauthClient) Included() []jsonapi.Object {
    44  	return []jsonapi.Object{}
    45  }
    46  
    47  func (h *HTTPHandler) listClients(c echo.Context) error {
    48  	instance := middlewares.GetInstance(c)
    49  
    50  	if err := middlewares.AllowWholeType(c, permission.GET, consts.OAuthClients); err != nil {
    51  		return err
    52  	}
    53  
    54  	bookmark := c.QueryParam("page[cursor]")
    55  	limit, err := strconv.ParseInt(c.QueryParam("page[limit]"), 10, 64)
    56  	if err != nil || limit < 0 || limit > consts.MaxItemsPerPageForMango {
    57  		limit = 100
    58  	}
    59  	clients, bookmark, err := oauth.GetAll(instance, int(limit), bookmark)
    60  	if err != nil {
    61  		return err
    62  	}
    63  
    64  	objs := make([]jsonapi.Object, len(clients))
    65  	for i, d := range clients {
    66  		objs[i] = jsonapi.Object(&apiOauthClient{d})
    67  	}
    68  
    69  	links := &jsonapi.LinksList{}
    70  	if bookmark != "" && len(objs) == int(limit) {
    71  		v := url.Values{}
    72  		v.Set("page[cursor]", bookmark)
    73  		if limit != 100 {
    74  			v.Set("page[limit]", fmt.Sprintf("%d", limit))
    75  		}
    76  		links.Next = "/settings/clients?" + v.Encode()
    77  	}
    78  	return jsonapi.DataList(c, http.StatusOK, objs, links)
    79  }
    80  
    81  func (h *HTTPHandler) revokeClient(c echo.Context) error {
    82  	instance := middlewares.GetInstance(c)
    83  
    84  	if err := middlewares.AllowWholeType(c, permission.DELETE, consts.OAuthClients); err != nil {
    85  		return err
    86  	}
    87  
    88  	clientID := c.Param("id")
    89  	defer auth.LockOAuthClient(instance, clientID)()
    90  
    91  	client, err := oauth.FindClient(instance, clientID)
    92  	if err != nil {
    93  		return err
    94  	}
    95  
    96  	if err := client.Delete(instance); err != nil {
    97  		return errors.New(err.Error)
    98  	}
    99  	return c.NoContent(http.StatusNoContent)
   100  }
   101  
   102  func (h *HTTPHandler) synchronized(c echo.Context) error {
   103  	instance := middlewares.GetInstance(c)
   104  
   105  	tok := middlewares.GetRequestToken(c)
   106  	if tok == "" {
   107  		return permission.ErrInvalidToken
   108  	}
   109  
   110  	claims, err := middlewares.ExtractClaims(c, instance, tok)
   111  	if err != nil {
   112  		return err
   113  	}
   114  
   115  	defer auth.LockOAuthClient(instance, claims.Subject)()
   116  
   117  	client, err := oauth.FindClient(instance, claims.Subject)
   118  	if err != nil {
   119  		return permission.ErrInvalidToken
   120  	}
   121  
   122  	client.SynchronizedAt = time.Now()
   123  	if err := couchdb.UpdateDoc(instance, client); err != nil {
   124  		return err
   125  	}
   126  	return c.NoContent(http.StatusNoContent)
   127  }
   128  
   129  func (h *HTTPHandler) limitExceeded(c echo.Context) error {
   130  	inst := middlewares.GetInstance(c)
   131  
   132  	if !middlewares.IsLoggedIn(c) {
   133  		return echo.NewHTTPError(http.StatusUnauthorized, "Error Must be authenticated")
   134  	}
   135  
   136  	redirect := c.QueryParam("redirect")
   137  	if redirect == "" {
   138  		redirect = inst.DefaultRedirection().String()
   139  	}
   140  
   141  	flags, err := feature.GetFlags(inst)
   142  	if err != nil {
   143  		return echo.NewHTTPError(http.StatusInternalServerError, fmt.Errorf("Could not get flags: %w", err))
   144  	}
   145  
   146  	if clientsLimit, ok := flags.M["cozy.oauthclients.max"].(float64); ok && clientsLimit >= 0 {
   147  		limit := int(clientsLimit)
   148  
   149  		clients, _, err := oauth.GetConnectedUserClients(inst, 100, "")
   150  		if err != nil {
   151  			return fmt.Errorf("Could not fetch connected OAuth clients: %s", err)
   152  		}
   153  		count := len(clients)
   154  
   155  		if count > limit {
   156  			isFlagship, _ := strconv.ParseBool(c.QueryParam("isFlagship"))
   157  
   158  			connectedDevicesURL := inst.SubDomain(consts.SettingsSlug)
   159  			connectedDevicesURL.Fragment = "/connectedDevices"
   160  
   161  			var premiumURL string
   162  			if inst.HasPremiumLinksEnabled() {
   163  				iapEnabled, _ := flags.M["flagship.iap.enabled"].(bool)
   164  				isIapAvailable, _ := strconv.ParseBool(c.QueryParam("isIapAvailable"))
   165  
   166  				if !isFlagship || (iapEnabled && isIapAvailable) {
   167  					var err error
   168  					if premiumURL, err = inst.ManagerURL(instance.ManagerPremiumURL); err != nil {
   169  						inst.Logger().Errorf("Could not get instance Premium Manager URL: %s", err.Error())
   170  					}
   171  				}
   172  			}
   173  
   174  			sess, _ := middlewares.GetSession(c)
   175  			settingsToken := inst.BuildAppToken(consts.SettingsSlug, sess.ID())
   176  			return c.Render(http.StatusOK, "oauth_clients_limit_exceeded.html", echo.Map{
   177  				"Domain":            inst.ContextualDomain(),
   178  				"ContextName":       inst.ContextName,
   179  				"Locale":            inst.Locale,
   180  				"Title":             inst.TemplateTitle(),
   181  				"Favicon":           middlewares.Favicon(inst),
   182  				"ClientsCount":      strconv.Itoa(count),
   183  				"ClientsLimit":      strconv.Itoa(limit),
   184  				"OpenLinksInNewTab": isFlagship,
   185  				"ManageDevicesURL":  connectedDevicesURL.String(),
   186  				"PremiumURL":        premiumURL,
   187  				"SettingsToken":     settingsToken,
   188  			})
   189  		}
   190  	}
   191  
   192  	return c.Redirect(http.StatusFound, redirect)
   193  }