github.com/chipaca/snappy@v0.0.0-20210104084008-1f06296fe8ad/daemon/api_validate.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2020 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 daemon
    21  
    22  import (
    23  	"encoding/json"
    24  	"fmt"
    25  	"net/http"
    26  	"sort"
    27  	"strconv"
    28  	"strings"
    29  
    30  	"github.com/snapcore/snapd/asserts"
    31  	"github.com/snapcore/snapd/client"
    32  	"github.com/snapcore/snapd/overlord/assertstate"
    33  	"github.com/snapcore/snapd/overlord/auth"
    34  	"github.com/snapcore/snapd/overlord/state"
    35  )
    36  
    37  var (
    38  	validationSetsListCmd = &Command{
    39  		Path: "/v2/validation-sets",
    40  		GET:  listValidationSets,
    41  	}
    42  
    43  	validationSetsCmd = &Command{
    44  		Path: "/v2/validation-sets/{account}/{name}",
    45  		GET:  getValidationSet,
    46  		POST: applyValidationSet,
    47  	}
    48  )
    49  
    50  type validationSetResult struct {
    51  	AccountID string `json:"account-id"`
    52  	Name      string `json:"name"`
    53  	PinnedAt  int    `json:"pinned-at,omitempty"`
    54  	Mode      string `json:"mode"`
    55  	Sequence  int    `json:"sequence,omitempty"`
    56  	Valid     bool   `json:"valid"`
    57  	// TODO: attributes for Notes column
    58  }
    59  
    60  func modeString(mode assertstate.ValidationSetMode) (string, error) {
    61  	switch mode {
    62  	case assertstate.Monitor:
    63  		return "monitor", nil
    64  	case assertstate.Enforce:
    65  		return "enforce", nil
    66  	}
    67  	return "", fmt.Errorf("internal error: unhandled mode %d", mode)
    68  }
    69  
    70  func validationSetNotFound(accountID, name string, sequence int) Response {
    71  	v := map[string]interface{}{
    72  		"account-id": accountID,
    73  		"name":       name,
    74  	}
    75  	if sequence != 0 {
    76  		v["sequence"] = sequence
    77  	}
    78  	res := &errorResult{
    79  		Message: "validation set not found",
    80  		Kind:    client.ErrorKindValidationSetNotFound,
    81  		Value:   v,
    82  	}
    83  	return &resp{
    84  		Type:   ResponseTypeError,
    85  		Result: res,
    86  		Status: 404,
    87  	}
    88  }
    89  
    90  func validationSetAccountAndName(validationSet string) (accountID, name string, err error) {
    91  	parts := strings.Split(validationSet, "/")
    92  	if len(parts) != 2 {
    93  		return "", "", fmt.Errorf("invalid validation-set argument")
    94  	}
    95  	accountID = parts[0]
    96  	name = parts[1]
    97  	if !asserts.IsValidAccountID(accountID) {
    98  		return "", "", fmt.Errorf("invalid account ID %q", accountID)
    99  	}
   100  	if !asserts.IsValidValidationSetName(name) {
   101  		return "", "", fmt.Errorf("invalid validation set name %q", name)
   102  	}
   103  	return accountID, name, nil
   104  }
   105  
   106  func listValidationSets(c *Command, r *http.Request, _ *auth.UserState) Response {
   107  	st := c.d.overlord.State()
   108  	st.Lock()
   109  	defer st.Unlock()
   110  
   111  	validationSets, err := assertstate.ValidationSets(st)
   112  	if err != nil {
   113  		return InternalError("accessing validation sets failed: %v", err)
   114  	}
   115  
   116  	names := make([]string, 0, len(validationSets))
   117  	for k := range validationSets {
   118  		names = append(names, k)
   119  	}
   120  	sort.Strings(names)
   121  
   122  	results := make([]validationSetResult, len(names))
   123  	for i, vs := range names {
   124  		tr := validationSets[vs]
   125  		// TODO: evaluate against installed snaps
   126  		var valid bool
   127  		modeStr, err := modeString(tr.Mode)
   128  		if err != nil {
   129  			return InternalError(err.Error())
   130  		}
   131  		results[i] = validationSetResult{
   132  			AccountID: tr.AccountID,
   133  			Name:      tr.Name,
   134  			PinnedAt:  tr.PinnedAt,
   135  			Mode:      modeStr,
   136  			Sequence:  tr.Current,
   137  			Valid:     valid,
   138  		}
   139  	}
   140  
   141  	return SyncResponse(results, nil)
   142  }
   143  
   144  func getValidationSet(c *Command, r *http.Request, _ *auth.UserState) Response {
   145  	vars := muxVars(r)
   146  	accountID := vars["account"]
   147  	name := vars["name"]
   148  
   149  	if !asserts.IsValidAccountID(accountID) {
   150  		return BadRequest("invalid account ID %q", accountID)
   151  	}
   152  	if !asserts.IsValidValidationSetName(name) {
   153  		return BadRequest("invalid name %q", name)
   154  	}
   155  
   156  	query := r.URL.Query()
   157  
   158  	// sequence is optional
   159  	sequenceStr := query.Get("sequence")
   160  	var sequence int
   161  	if sequenceStr != "" {
   162  		var err error
   163  		sequence, err = strconv.Atoi(sequenceStr)
   164  		if err != nil {
   165  			return BadRequest("invalid sequence argument")
   166  		}
   167  		if sequence < 0 {
   168  			return BadRequest("invalid sequence argument: %d", sequence)
   169  		}
   170  	}
   171  
   172  	st := c.d.overlord.State()
   173  	st.Lock()
   174  	defer st.Unlock()
   175  
   176  	var tr assertstate.ValidationSetTracking
   177  	err := assertstate.GetValidationSet(st, accountID, name, &tr)
   178  	if err == state.ErrNoState || (err == nil && sequence != 0 && sequence != tr.PinnedAt) {
   179  		// TODO: not available locally, try to find in the store.
   180  		return validationSetNotFound(accountID, name, sequence)
   181  	}
   182  	if err != nil {
   183  		return InternalError("accessing validation sets failed: %v", err)
   184  	}
   185  
   186  	modeStr, err := modeString(tr.Mode)
   187  	if err != nil {
   188  		return InternalError(err.Error())
   189  	}
   190  	// TODO: evaluate against installed snaps
   191  	var valid bool
   192  	res := validationSetResult{
   193  		AccountID: tr.AccountID,
   194  		Name:      tr.Name,
   195  		PinnedAt:  tr.PinnedAt,
   196  		Mode:      modeStr,
   197  		Sequence:  tr.Current,
   198  		Valid:     valid,
   199  	}
   200  	return SyncResponse(res, nil)
   201  }
   202  
   203  type validationSetApplyRequest struct {
   204  	Action   string `json:"action"`
   205  	Mode     string `json:"mode"`
   206  	Sequence int    `json:"sequence,omitempty"`
   207  }
   208  
   209  func applyValidationSet(c *Command, r *http.Request, _ *auth.UserState) Response {
   210  	vars := muxVars(r)
   211  	accountID := vars["account"]
   212  	name := vars["name"]
   213  
   214  	if !asserts.IsValidAccountID(accountID) {
   215  		return BadRequest("invalid account ID %q", accountID)
   216  	}
   217  	if !asserts.IsValidValidationSetName(name) {
   218  		return BadRequest("invalid name %q", name)
   219  	}
   220  
   221  	var req validationSetApplyRequest
   222  	decoder := json.NewDecoder(r.Body)
   223  	if err := decoder.Decode(&req); err != nil {
   224  		return BadRequest("cannot decode request body into validation set action: %v", err)
   225  	}
   226  	if decoder.More() {
   227  		return BadRequest("extra content found in request body")
   228  	}
   229  	if req.Sequence < 0 {
   230  		return BadRequest("invalid sequence argument: %d", req.Sequence)
   231  	}
   232  
   233  	st := c.d.overlord.State()
   234  	st.Lock()
   235  	defer st.Unlock()
   236  
   237  	switch req.Action {
   238  	case "forget":
   239  		return forgetValidationSet(st, accountID, name, req.Sequence)
   240  	case "apply":
   241  		return updateValidationSet(st, accountID, name, req.Mode, req.Sequence)
   242  	default:
   243  		return BadRequest("unsupported action %q", req.Action)
   244  	}
   245  }
   246  
   247  func updateValidationSet(st *state.State, accountID, name string, reqMode string, sequence int) Response {
   248  	var mode assertstate.ValidationSetMode
   249  	switch reqMode {
   250  	case "monitor":
   251  		mode = assertstate.Monitor
   252  	case "enforce":
   253  		mode = assertstate.Enforce
   254  	default:
   255  		return BadRequest("invalid mode %q", reqMode)
   256  	}
   257  
   258  	// TODO: if pinned, check if we have the needed assertion locally;
   259  	// check with the store if there is something newer there;
   260  	// check what is the latest in the store if the assertion is not pinned.
   261  
   262  	tr := assertstate.ValidationSetTracking{
   263  		AccountID: accountID,
   264  		Name:      name,
   265  		Mode:      mode,
   266  		// note, Sequence may be 0, meaning not pinned.
   267  		PinnedAt: sequence,
   268  	}
   269  
   270  	// TODO: if the mode is enforced  check that the assertion is valid before
   271  	// saving it; for one from the store add it to the assertion db.
   272  
   273  	assertstate.UpdateValidationSet(st, &tr)
   274  	return SyncResponse(nil, nil)
   275  }
   276  
   277  // forgetValidationSet forgets the validation set.
   278  // The state needs to be locked by the caller.
   279  func forgetValidationSet(st *state.State, accountID, name string, sequence int) Response {
   280  	// check if it exists first
   281  	var tr assertstate.ValidationSetTracking
   282  	err := assertstate.GetValidationSet(st, accountID, name, &tr)
   283  	if err == state.ErrNoState || (err == nil && sequence != 0 && sequence != tr.PinnedAt) {
   284  		return validationSetNotFound(accountID, name, sequence)
   285  	}
   286  	if err != nil {
   287  		return InternalError("accessing validation sets failed: %v", err)
   288  	}
   289  	assertstate.DeleteValidationSet(st, accountID, name)
   290  	return SyncResponse(nil, nil)
   291  }