mirror of
https://github.com/dolthub/dolt.git
synced 2026-01-28 18:59:00 -06:00
Validate type of new Root in root/ POST handler
In the server side of the Remote Databse, the handler for UpdateRoot now verifies that the new proposed Root is of a legal type: empty map OR Map<String, Ref<Commit-like>> Fixes #2116
This commit is contained in:
@@ -91,6 +91,7 @@ func (ds *databaseCommon) datasetsFromRef(datasetsRef hash.Hash) *types.Map {
|
||||
}
|
||||
|
||||
func (ds *databaseCommon) commit(datasetID string, commit types.Struct) error {
|
||||
d.PanicIfTrue(!IsCommitType(commit.Type()), "Can't commit a non-Commit struct to dataset %s", datasetID)
|
||||
return ds.doCommit(datasetID, commit)
|
||||
}
|
||||
|
||||
|
||||
@@ -218,9 +218,10 @@ func (suite *HTTPBatchStoreSuite) TestPutChunksBackpressure() {
|
||||
}
|
||||
|
||||
func (suite *HTTPBatchStoreSuite) TestRoot() {
|
||||
c := chunks.NewChunk([]byte("abc"))
|
||||
suite.True(suite.cs.UpdateRoot(c.Hash(), hash.Hash{}))
|
||||
suite.Equal(c.Hash(), suite.store.Root())
|
||||
c := types.EncodeValue(types.NewMap(), nil)
|
||||
suite.cs.Put(c)
|
||||
suite.True(suite.store.UpdateRoot(c.Hash(), hash.Hash{}))
|
||||
suite.Equal(c.Hash(), suite.cs.Root())
|
||||
}
|
||||
|
||||
func (suite *HTTPBatchStoreSuite) TestVersionMismatch() {
|
||||
@@ -230,7 +231,8 @@ func (suite *HTTPBatchStoreSuite) TestVersionMismatch() {
|
||||
}
|
||||
|
||||
func (suite *HTTPBatchStoreSuite) TestUpdateRoot() {
|
||||
c := chunks.NewChunk([]byte("abc"))
|
||||
c := types.EncodeValue(types.NewMap(), nil)
|
||||
suite.cs.Put(c)
|
||||
suite.True(suite.store.UpdateRoot(c.Hash(), hash.Hash{}))
|
||||
suite.Equal(c.Hash(), suite.cs.Root())
|
||||
}
|
||||
@@ -238,7 +240,8 @@ func (suite *HTTPBatchStoreSuite) TestUpdateRoot() {
|
||||
func (suite *HTTPBatchStoreSuite) TestUpdateRootWithParams() {
|
||||
u := fmt.Sprintf("http://localhost:9000?access_token=%s&other=19", testAuthToken)
|
||||
store := newAuthenticatingHTTPBatchStoreForTest(suite, u)
|
||||
c := chunks.NewChunk([]byte("abc"))
|
||||
c := types.EncodeValue(types.NewMap(), nil)
|
||||
suite.cs.Put(c)
|
||||
suite.True(store.UpdateRoot(c.Hash(), hash.Hash{}))
|
||||
suite.Equal(c.Hash(), suite.cs.Root())
|
||||
}
|
||||
|
||||
@@ -223,7 +223,7 @@ func handleRootGet(w http.ResponseWriter, req *http.Request, ps URLParams, rt ch
|
||||
w.Header().Add("content-type", "text/plain")
|
||||
}
|
||||
|
||||
func handleRootPost(w http.ResponseWriter, req *http.Request, ps URLParams, rt chunks.ChunkStore) {
|
||||
func handleRootPost(w http.ResponseWriter, req *http.Request, ps URLParams, cs chunks.ChunkStore) {
|
||||
d.PanicIfTrue(req.Method != "POST", "Expected post method.")
|
||||
|
||||
params := req.URL.Query()
|
||||
@@ -234,8 +234,38 @@ func handleRootPost(w http.ResponseWriter, req *http.Request, ps URLParams, rt c
|
||||
d.PanicIfTrue(len(tokens) != 1, `Expected "current" query param value`)
|
||||
current := hash.Parse(tokens[0])
|
||||
|
||||
if !rt.UpdateRoot(current, last) {
|
||||
// Ensure that proposed new Root is present in cs
|
||||
c := cs.Get(current)
|
||||
d.PanicIfTrue(c.IsEmpty(), "Can't set Root to a non-present Chunk")
|
||||
|
||||
// Ensure that proposed new Root is a Map and, if it has anything in it, that it's <String, RefOfCommit>
|
||||
v := types.DecodeValue(c, nil)
|
||||
d.PanicIfTrue(v.Type().Kind() != types.MapKind, "Root of a Database must be a Map")
|
||||
m := v.(types.Map)
|
||||
if !m.Empty() && !isMapOfStringToRefOfCommit(m) {
|
||||
panic(d.Wrap(fmt.Errorf("Root of a Database must be a Map<String, RefOfCommit>, not %s", m.Type().Describe())))
|
||||
}
|
||||
|
||||
if !cs.UpdateRoot(current, last) {
|
||||
w.WriteHeader(http.StatusConflict)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func isMapOfStringToRefOfCommit(m types.Map) bool {
|
||||
mapTypes := m.Type().Desc.(types.CompoundDesc).ElemTypes
|
||||
keyType, valType := mapTypes[0], mapTypes[1]
|
||||
return keyType.Kind() == types.StringKind && (isRefOfCommitType(valType) || isUnionOfRefOfCommitType(valType))
|
||||
}
|
||||
|
||||
func isUnionOfRefOfCommitType(t *types.Type) bool {
|
||||
if t.Kind() != types.UnionKind {
|
||||
return false
|
||||
}
|
||||
for _, et := range t.Desc.(types.CompoundDesc).ElemTypes {
|
||||
if !isRefOfCommitType(et) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -252,10 +252,13 @@ func TestHandleGetRoot(t *testing.T) {
|
||||
func TestHandlePostRoot(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
cs := chunks.NewTestStore()
|
||||
input1, input2 := "abc", "def"
|
||||
vs := types.NewValueStore(types.NewBatchStoreAdaptor(cs))
|
||||
|
||||
commit := NewCommit(types.String("head"), types.NewSet(), types.NewStruct("Meta", types.StructData{}))
|
||||
newHead := types.NewMap(types.String("dataset1"), vs.WriteValue(commit))
|
||||
chnx := []chunks.Chunk{
|
||||
chunks.NewChunk([]byte(input1)),
|
||||
chunks.NewChunk([]byte(input2)),
|
||||
chunks.NewChunk([]byte("abc")),
|
||||
types.EncodeValue(newHead, nil),
|
||||
}
|
||||
err := cs.PutMany(chnx)
|
||||
assert.NoError(err)
|
||||
@@ -279,6 +282,27 @@ func TestHandlePostRoot(t *testing.T) {
|
||||
assert.Equal(http.StatusOK, w.Code, "Handler error:\n%s", string(w.Body.Bytes()))
|
||||
}
|
||||
|
||||
func TestRejectPostRoot(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
cs := chunks.NewTestStore()
|
||||
|
||||
newHead := types.NewMap(types.String("dataset1"), types.String("Not a Head"))
|
||||
chunk := types.EncodeValue(newHead, nil)
|
||||
cs.Put(chunk)
|
||||
|
||||
// First attempt should fail, as 'last' won't match.
|
||||
u := &url.URL{}
|
||||
queryParams := url.Values{}
|
||||
queryParams.Add("last", chunks.EmptyChunk.Hash().String())
|
||||
queryParams.Add("current", chunk.Hash().String())
|
||||
u.RawQuery = queryParams.Encode()
|
||||
url := u.String()
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
HandleRootPost(w, newRequest("POST", "", url, nil, nil), params{}, cs)
|
||||
assert.Equal(http.StatusBadRequest, w.Code, "Handler error:\n%s", string(w.Body.Bytes()))
|
||||
}
|
||||
|
||||
type params map[string]string
|
||||
|
||||
func (p params) ByName(k string) string {
|
||||
|
||||
@@ -75,12 +75,18 @@ func TestWriteValue(t *testing.T) {
|
||||
lastRoot := w.Body
|
||||
assert.Equal(http.StatusOK, w.Code)
|
||||
|
||||
tval := types.Bool(true)
|
||||
wval := types.String(testString)
|
||||
craftCommit := func(v types.Value) types.Struct {
|
||||
return datas.NewCommit(v, types.NewSet(), types.NewStruct("Meta", types.StructData{}))
|
||||
}
|
||||
|
||||
tval := craftCommit(types.Bool(true))
|
||||
wval := craftCommit(types.String(testString))
|
||||
chunk1 := types.EncodeValue(tval, nil)
|
||||
chunk2 := types.EncodeValue(wval, nil)
|
||||
refList := types.NewList(types.NewRef(tval), types.NewRef(wval))
|
||||
chunk3 := types.EncodeValue(refList, nil)
|
||||
refMap := types.NewMap(
|
||||
types.String("ds1"), types.NewRef(tval),
|
||||
types.String("ds2"), types.NewRef(wval))
|
||||
chunk3 := types.EncodeValue(refMap, nil)
|
||||
|
||||
body := &bytes.Buffer{}
|
||||
// we would use this func, but it's private so use next line instead: serializeHints(body, map[ref.Ref]struct{}{hint: struct{}{}})
|
||||
@@ -98,10 +104,10 @@ func TestWriteValue(t *testing.T) {
|
||||
assert.Equal(http.StatusCreated, w.Code)
|
||||
|
||||
w = httptest.NewRecorder()
|
||||
args := fmt.Sprintf("&last=%s¤t=%s", lastRoot, types.NewRef(refList).TargetHash())
|
||||
args := fmt.Sprintf("&last=%s¤t=%s", lastRoot, types.NewRef(refMap).TargetHash())
|
||||
r, _ = newRequest("POST", dbName+constants.RootPath+"?access_token="+authKey+args, ioutil.NopCloser(body))
|
||||
router.ServeHTTP(w, r)
|
||||
assert.Equal(http.StatusOK, w.Code)
|
||||
assert.Equal(http.StatusOK, w.Code, string(w.Body.Bytes()))
|
||||
|
||||
whash := wval.Hash()
|
||||
hints := map[hash.Hash]struct{}{whash: struct{}{}}
|
||||
@@ -109,12 +115,12 @@ func TestWriteValue(t *testing.T) {
|
||||
r, _ = newRequest("POST", dbName+constants.GetRefsPath, rdr)
|
||||
r.Header.Add("Content-Type", "application/x-www-form-urlencoded")
|
||||
router.ServeHTTP(w, r)
|
||||
assert.Equal(http.StatusOK, w.Code)
|
||||
assert.Equal(http.StatusOK, w.Code, string(w.Body.Bytes()))
|
||||
|
||||
ms := chunks.NewMemoryStore()
|
||||
chunks.Deserialize(w.Body, ms, nil)
|
||||
v := types.DecodeValue(ms.Get(whash), datas.NewDatabase(ms))
|
||||
assert.Equal(testString, string(v.(types.String)))
|
||||
assert.Equal(testString, string(v.(types.Struct).Get(datas.ValueField).(types.String)))
|
||||
}
|
||||
|
||||
func newRequest(method, url string, body io.Reader) (req *http.Request, err error) {
|
||||
|
||||
Reference in New Issue
Block a user