github.com/rigado/snapd@v2.42.5-go-mod+incompatible/store/errors.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2014-2018 Canonical Ltd
     5   *
     6   * This program is free software: you can redistribute it and/or modify
     7   * it under the terms of the GNU General Public License version 3 as
     8   * published by the Free Software Foundation.
     9   *
    10   * This program is distributed in the hope that it will be useful,
    11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
    12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    13   * GNU General Public License for more details.
    14   *
    15   * You should have received a copy of the GNU General Public License
    16   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
    17   *
    18   */
    19  
    20  package store
    21  
    22  import (
    23  	"errors"
    24  	"fmt"
    25  	"net/url"
    26  	"sort"
    27  	"strings"
    28  
    29  	"github.com/snapcore/snapd/snap/channel"
    30  	"github.com/snapcore/snapd/strutil"
    31  )
    32  
    33  var (
    34  	// ErrBadQuery is returned from Find when the query has special characters in strange places.
    35  	ErrBadQuery = errors.New("bad query")
    36  
    37  	// ErrSnapNotFound is returned when a snap can not be found
    38  	ErrSnapNotFound = errors.New("snap not found")
    39  
    40  	// ErrUnauthenticated is returned when authentication is needed to complete the query
    41  	ErrUnauthenticated = errors.New("you need to log in first")
    42  
    43  	// ErrAuthenticationNeeds2fa is returned if the authentication needs 2factor
    44  	ErrAuthenticationNeeds2fa = errors.New("two factor authentication required")
    45  
    46  	// Err2faFailed is returned when 2fa failed (e.g., a bad token was given)
    47  	Err2faFailed = errors.New("two factor authentication failed")
    48  
    49  	// ErrInvalidCredentials is returned on login error
    50  	// It can also be returned when refreshing the discharge
    51  	// macaroon if the user has changed their password.
    52  	ErrInvalidCredentials = errors.New("invalid credentials")
    53  
    54  	// ErrTOSNotAccepted is returned when the user has not accepted the store's terms of service.
    55  	ErrTOSNotAccepted = errors.New("terms of service not accepted")
    56  
    57  	// ErrNoPaymentMethods is returned when the user has no valid payment methods associated with their account.
    58  	ErrNoPaymentMethods = errors.New("no payment methods")
    59  
    60  	// ErrPaymentDeclined is returned when the user's payment method was declined by the upstream payment provider.
    61  	ErrPaymentDeclined = errors.New("payment declined")
    62  
    63  	// ErrLocalSnap is returned when an operation that only applies to snaps that come from a store was attempted on a local snap.
    64  	ErrLocalSnap = errors.New("cannot perform operation on local snap")
    65  
    66  	// ErrNoUpdateAvailable is returned when an update is attempetd for a snap that has no update available.
    67  	ErrNoUpdateAvailable = errors.New("snap has no updates available")
    68  )
    69  
    70  // RevisionNotAvailableError is returned when an install is attempted for a snap but the/a revision is not available (given install constraints).
    71  type RevisionNotAvailableError struct {
    72  	Action   string
    73  	Channel  string
    74  	Releases []channel.Channel
    75  }
    76  
    77  func (e *RevisionNotAvailableError) Error() string {
    78  	return "no snap revision available as specified"
    79  }
    80  
    81  // DownloadError represents a download error
    82  type DownloadError struct {
    83  	Code int
    84  	URL  *url.URL
    85  }
    86  
    87  func (e *DownloadError) Error() string {
    88  	return fmt.Sprintf("received an unexpected http response code (%v) when trying to download %s", e.Code, e.URL)
    89  }
    90  
    91  // PasswordPolicyError is returned in a few corner cases, most notably
    92  // when the password has been force-reset.
    93  type PasswordPolicyError map[string]stringList
    94  
    95  func (e PasswordPolicyError) Error() string {
    96  	var msg string
    97  
    98  	if reason, ok := e["reason"]; ok && len(reason) == 1 {
    99  		msg = reason[0]
   100  		if location, ok := e["location"]; ok && len(location) == 1 {
   101  			msg += "\nTo address this, go to: " + location[0] + "\n"
   102  		}
   103  	} else {
   104  		for k, vs := range e {
   105  			msg += fmt.Sprintf("%s: %s\n", k, strings.Join(vs, "  "))
   106  		}
   107  	}
   108  
   109  	return msg
   110  }
   111  
   112  // InvalidAuthDataError signals that the authentication data didn't pass validation.
   113  type InvalidAuthDataError map[string]stringList
   114  
   115  func (e InvalidAuthDataError) Error() string {
   116  	var es []string
   117  	for _, v := range e {
   118  		es = append(es, v...)
   119  	}
   120  	// XXX: confirm with server people that extra args are all
   121  	//      full sentences (with periods and capitalization)
   122  	//      (empirically this checks out)
   123  	return strings.Join(es, "  ")
   124  }
   125  
   126  // SnapActionError conveys errors that were reported on otherwise overall successful snap action (install/refresh) request.
   127  type SnapActionError struct {
   128  	// NoResults is set if the there were no results in the response
   129  	NoResults bool
   130  	// Refresh errors by snap name.
   131  	Refresh map[string]error
   132  	// Install errors by snap name.
   133  	Install map[string]error
   134  	// Download errors by snap name.
   135  	Download map[string]error
   136  	// Other errors.
   137  	Other []error
   138  }
   139  
   140  func (e SnapActionError) Error() string {
   141  	nRefresh := len(e.Refresh)
   142  	nInstall := len(e.Install)
   143  	nDownload := len(e.Download)
   144  	nOther := len(e.Other)
   145  
   146  	// single error
   147  	switch nRefresh + nInstall + nDownload + nOther {
   148  	case 0:
   149  		if e.NoResults {
   150  			// this is an atypical result
   151  			return "no install/refresh information results from the store"
   152  		}
   153  	case 1:
   154  		if nOther == 0 {
   155  			var op string
   156  			var errs map[string]error
   157  			switch {
   158  			case nRefresh > 0:
   159  				op = "refresh"
   160  				errs = e.Refresh
   161  			case nInstall > 0:
   162  				op = "install"
   163  				errs = e.Install
   164  			case nDownload > 0:
   165  				op = "download"
   166  				errs = e.Download
   167  			}
   168  			for name, e := range errs {
   169  				return fmt.Sprintf("cannot %s snap %q: %v", op, name, e)
   170  			}
   171  		} else {
   172  			return fmt.Sprintf("cannot refresh, install, or download: %v", e.Other[0])
   173  		}
   174  	}
   175  
   176  	header := "cannot refresh, install, or download:"
   177  	if nOther == 0 {
   178  		// at least one of nDownload, nInstall, or nRefresh is > 0
   179  		switch {
   180  		case nDownload == 0 && nRefresh == 0:
   181  			header = "cannot install:"
   182  		case nDownload == 0 && nInstall == 0:
   183  			header = "cannot refresh:"
   184  		case nRefresh == 0 && nInstall == 0:
   185  			header = "cannot download:"
   186  		case nDownload == 0:
   187  			header = "cannot refresh or install:"
   188  		case nInstall == 0:
   189  			header = "cannot refresh or download:"
   190  		case nRefresh == 0:
   191  			header = "cannot install or download:"
   192  		}
   193  	}
   194  
   195  	// reverse the "snap->error" map to "error->snap", as the
   196  	// common case is that all snaps fail with the same error
   197  	// (e.g. "no refresh available")
   198  	errToSnaps := map[string][]string{}
   199  	errKeys := []string{} // poorman's ordered map
   200  
   201  	for _, m := range []map[string]error{e.Refresh, e.Install, e.Download} {
   202  		for snapName, err := range m {
   203  			k := err.Error()
   204  			v, ok := errToSnaps[k]
   205  			if !ok {
   206  				errKeys = append(errKeys, k)
   207  			}
   208  			errToSnaps[k] = append(v, snapName)
   209  		}
   210  	}
   211  
   212  	es := make([]string, 1, 1+len(errToSnaps)+nOther)
   213  	es[0] = header
   214  	for _, k := range errKeys {
   215  		sort.Strings(errToSnaps[k])
   216  		es = append(es, fmt.Sprintf("%s: %s", k, strutil.Quoted(errToSnaps[k])))
   217  	}
   218  
   219  	for _, e := range e.Other {
   220  		es = append(es, e.Error())
   221  	}
   222  
   223  	if len(es) == 2 {
   224  		// header + 1 reason
   225  		return strings.Join(es, " ")
   226  	}
   227  
   228  	return strings.Join(es, "\n")
   229  }
   230  
   231  // Authorization soft-expiry errors that get handled automatically.
   232  var (
   233  	errUserAuthorizationNeedsRefresh   = errors.New("soft-expired user authorization needs refresh")
   234  	errDeviceAuthorizationNeedsRefresh = errors.New("soft-expired device authorization needs refresh")
   235  )
   236  
   237  func translateSnapActionError(action, snapChannel, code, message string, releases []snapRelease) error {
   238  	switch code {
   239  	case "revision-not-found":
   240  		e := &RevisionNotAvailableError{
   241  			Action:  action,
   242  			Channel: snapChannel,
   243  		}
   244  		if len(releases) != 0 {
   245  			parsedReleases := make([]channel.Channel, len(releases))
   246  			for i := 0; i < len(releases); i++ {
   247  				var err error
   248  				parsedReleases[i], err = channel.Parse(releases[i].Channel, releases[i].Architecture)
   249  				if err != nil {
   250  					// shouldn't happen, return error without Releases
   251  					return e
   252  				}
   253  			}
   254  			e.Releases = parsedReleases
   255  		}
   256  		return e
   257  	case "id-not-found", "name-not-found":
   258  		return ErrSnapNotFound
   259  	case "user-authorization-needs-refresh":
   260  		return errUserAuthorizationNeedsRefresh
   261  	case "device-authorization-needs-refresh":
   262  		return errDeviceAuthorizationNeedsRefresh
   263  	default:
   264  		return fmt.Errorf("%v", message)
   265  	}
   266  }