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