gopkg.in/ubuntu-core/snappy.v0@v0.0.0-20210902073436-25a8614f10a6/daemon/api_asserts.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2016-2019 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 "errors" 24 "fmt" 25 "net/http" 26 "net/url" 27 "strconv" 28 29 "github.com/snapcore/snapd/asserts" 30 "github.com/snapcore/snapd/overlord/assertstate" 31 "github.com/snapcore/snapd/overlord/auth" 32 ) 33 34 var ( 35 // TODO: allow to post assertions for UserOK? they are verified anyway 36 assertsCmd = &Command{ 37 Path: "/v2/assertions", 38 GET: getAssertTypeNames, 39 POST: doAssert, 40 ReadAccess: openAccess{}, 41 WriteAccess: authenticatedAccess{}, 42 } 43 44 assertsFindManyCmd = &Command{ 45 Path: "/v2/assertions/{assertType}", 46 GET: assertsFindMany, 47 ReadAccess: openAccess{}, 48 } 49 ) 50 51 // a helper type for parsing the options specified to /v2/assertions and other 52 // such endpoints that can either do JSON or assertion depending on the value 53 // of the the URL query parameters 54 type daemonAssertOptions struct { 55 jsonResult bool 56 headersOnly bool 57 remote bool 58 headers map[string]string 59 } 60 61 // helper for parsing url query options into formatting option vars 62 func parseHeadersFormatOptionsFromURL(q url.Values) (*daemonAssertOptions, error) { 63 res := daemonAssertOptions{} 64 res.headers = make(map[string]string) 65 for k := range q { 66 v := q.Get(k) 67 switch k { 68 case "remote": 69 switch v { 70 case "true", "false": 71 res.remote, _ = strconv.ParseBool(v) 72 default: 73 return nil, errors.New(`"remote" query parameter when used must be set to "true" or "false" or left unset`) 74 } 75 case "json": 76 switch v { 77 case "false": 78 res.jsonResult = false 79 case "headers": 80 res.headersOnly = true 81 fallthrough 82 case "true": 83 res.jsonResult = true 84 default: 85 return nil, errors.New(`"json" query parameter when used must be set to "true" or "headers"`) 86 } 87 default: 88 res.headers[k] = v 89 } 90 } 91 92 return &res, nil 93 } 94 95 func getAssertTypeNames(c *Command, r *http.Request, user *auth.UserState) Response { 96 return SyncResponse(map[string][]string{ 97 "types": asserts.TypeNames(), 98 }) 99 100 } 101 102 func doAssert(c *Command, r *http.Request, user *auth.UserState) Response { 103 batch := asserts.NewBatch(nil) 104 _, err := batch.AddStream(r.Body) 105 if err != nil { 106 return BadRequest("cannot decode request body into assertions: %v", err) 107 } 108 109 state := c.d.overlord.State() 110 state.Lock() 111 defer state.Unlock() 112 113 if err := assertstate.AddBatch(state, batch, &asserts.CommitOptions{ 114 Precheck: true, 115 }); err != nil { 116 return BadRequest("assert failed: %v", err) 117 } 118 119 return SyncResponse(nil) 120 } 121 122 func assertsFindOneRemote(c *Command, at *asserts.AssertionType, headers map[string]string, user *auth.UserState) ([]asserts.Assertion, error) { 123 primaryKeys, err := asserts.PrimaryKeyFromHeaders(at, headers) 124 if err != nil { 125 return nil, fmt.Errorf("cannot query remote assertion: %v", err) 126 } 127 sto := storeFrom(c.d) 128 as, err := sto.Assertion(at, primaryKeys, user) 129 if err != nil { 130 return nil, err 131 } 132 133 return []asserts.Assertion{as}, nil 134 } 135 136 func assertsFindManyInState(c *Command, at *asserts.AssertionType, headers map[string]string, opts *daemonAssertOptions) ([]asserts.Assertion, error) { 137 state := c.d.overlord.State() 138 state.Lock() 139 db := assertstate.DB(state) 140 state.Unlock() 141 142 return db.FindMany(at, opts.headers) 143 } 144 145 func assertsFindMany(c *Command, r *http.Request, user *auth.UserState) Response { 146 assertTypeName := muxVars(r)["assertType"] 147 assertType := asserts.Type(assertTypeName) 148 if assertType == nil { 149 return BadRequest("invalid assert type: %q", assertTypeName) 150 } 151 opts, err := parseHeadersFormatOptionsFromURL(r.URL.Query()) 152 if err != nil { 153 return BadRequest(err.Error()) 154 } 155 156 var assertions []asserts.Assertion 157 if opts.remote { 158 assertions, err = assertsFindOneRemote(c, assertType, opts.headers, user) 159 } else { 160 assertions, err = assertsFindManyInState(c, assertType, opts.headers, opts) 161 } 162 if err != nil && !asserts.IsNotFound(err) { 163 return InternalError("searching assertions failed: %v", err) 164 } 165 166 if opts.jsonResult { 167 assertsJSON := make([]struct { 168 Headers map[string]interface{} `json:"headers,omitempty"` 169 Body string `json:"body,omitempty"` 170 }, len(assertions)) 171 for i := range assertions { 172 assertsJSON[i].Headers = assertions[i].Headers() 173 if !opts.headersOnly { 174 assertsJSON[i].Body = string(assertions[i].Body()) 175 } 176 } 177 return SyncResponse(assertsJSON) 178 } 179 180 return AssertResponse(assertions, true) 181 }