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:
Chris Masone
2016-08-01 15:13:45 -07:00
parent 8fee5ca80b
commit 4f073aee3d
5 changed files with 82 additions and 18 deletions

View File

@@ -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)
}

View File

@@ -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())
}

View File

@@ -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
}

View File

@@ -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 {

View File

@@ -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&current=%s", lastRoot, types.NewRef(refList).TargetHash())
args := fmt.Sprintf("&last=%s&current=%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) {