func TestShareSearchSerialization(t *testing.T) { signer := blob.MustParse("yyy-5678") q := &search.SearchQuery{ Expression: "is:image", Limit: 42, } bb := schema.NewShareRef(schema.ShareHaveRef, true) bb.SetShareSearch(q) bb = bb.SetSigner(signer) bb = bb.SetClaimDate(time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC)) s := bb.Blob().JSON() want := `{"camliVersion": 1, "authType": "haveref", "camliSigner": "yyy-5678", "camliType": "claim", "claimDate": "2009-11-10T23:00:00Z", "claimType": "share", "search": { "expression": "is:image", "limit": 42, "around": null }, "transitive": true }` if want != s { t.Errorf("Incorrect serialization of shared search. Wanted:\n %s\nGot:\n%s\n", want, s) } }
func (c *shareCmd) RunCommand(args []string) error { unsigned := schema.NewShareRef(schema.ShareHaveRef, c.transitive) if c.search != "" { if len(args) != 0 { return cmdmain.UsageError("when using the -search flag, share takes zero arguments") } var q search.SearchQuery if err := json.Unmarshal([]byte(c.search), &q); err != nil { return cmdmain.UsageError(fmt.Sprintf("invalid search: %s", err)) } unsigned.SetShareSearch(&q) } else { if len(args) != 1 { return cmdmain.UsageError("share takes at most one argument") } target, ok := blob.Parse(args[0]) if !ok { return cmdmain.UsageError("invalid blobref") } unsigned.SetShareTarget(target) } if c.duration != 0 { unsigned.SetShareExpiration(time.Now().Add(c.duration)) } pr, err := getUploader().UploadAndSignBlob(unsigned) handleResult("share", pr, err) return nil }
// Issue 228: only follow transitive blobref links in known trusted schema fields. func TestSharingTransitiveSafety(t *testing.T) { st := newShareTester(t) defer st.done() content := "the secret" contentRef := blob.SHA1FromString(content) // User-injected blob, somehow. evilClaim := fmt.Sprintf("Some payload containing the ref: %v", contentRef) evilClaimRef := blob.SHA1FromString(evilClaim) share := schema.NewShareRef(schema.ShareHaveRef, false). SetShareTarget(evilClaimRef). SetShareIsTransitive(true). SetSigner(blob.SHA1FromString("irrelevant")). SetRawStringField("camliSig", "alsounused") shareRef := func() blob.Ref { return share.Blob().BlobRef() } st.put(share.Blob()) st.putRaw(contentRef, content) st.putRaw(evilClaimRef, evilClaim) st.testGet(shareRef().String(), noError) st.testGet(fmt.Sprintf("%s?via=%s", evilClaimRef, shareRef()), noError) st.testGet(fmt.Sprintf("%s?via=%s,%s", contentRef, shareRef(), evilClaimRef), viaChainInvalidLink) if !st.slept() { t.Error("expected sleep after miss") } }
func TestHandleGetViaSharing(t *testing.T) { st := newShareTester(t) defer st.done() content := "monkey" // the secret contentRef := blob.SHA1FromString(content) link := fmt.Sprintf(`{"camliVersion": 1, "camliType": "file", "parts": [ {"blobRef": "%v", "size": %d} ]}`, contentRef, len(content)) linkRef := blob.SHA1FromString(link) share := schema.NewShareRef(schema.ShareHaveRef, false). SetShareTarget(linkRef). SetSigner(blob.SHA1FromString("irrelevant")). SetRawStringField("camliSig", "alsounused") shareRef := func() blob.Ref { return share.Blob().BlobRef() } t.Logf("Checking share blob doesn't yet exist...") st.testGet(shareRef().String(), shareFetchFailed) if !st.slept() { t.Error("expected sleep after miss") } st.put(share.Blob()) t.Logf("Checking share blob now exists...") st.testGet(shareRef().String(), noError) t.Logf("Checking we can't get the content directly via the share...") st.testGet(fmt.Sprintf("%s?via=%s", contentRef, shareRef()), shareTargetInvalid) t.Logf("Checking we can't get the link (file) blob directly...") st.putRaw(linkRef, link) st.testGet(linkRef.String(), shareBlobInvalid) t.Logf("Checking we can get the link (file) blob via the share...") st.testGet(fmt.Sprintf("%s?via=%s", linkRef, shareRef()), noError) t.Logf("Checking we can't get the content via the non-transitive share...") st.testGet(fmt.Sprintf("%s?via=%s,%s", contentRef, shareRef(), linkRef), shareNotTransitive) // TODO: new test? share.SetShareIsTransitive(true) st.put(share.Blob()) st.testGet(fmt.Sprintf("%s?via=%s,%s", linkRef, shareRef(), linkRef), viaChainInvalidLink) st.putRaw(contentRef, content) st.testGet(fmt.Sprintf("%s?via=%s,%s", contentRef, shareRef(), linkRef), noError) // new test? share.SetShareExpiration(time.Now().Add(-time.Duration(10) * time.Minute)) st.put(share.Blob()) st.testGet(fmt.Sprintf("%s?via=%s,%s", contentRef, shareRef(), linkRef), shareExpired) share.SetShareExpiration(time.Now().Add(time.Duration(10) * time.Minute)) st.put(share.Blob()) st.testGet(fmt.Sprintf("%s?via=%s,%s", contentRef, shareRef(), linkRef), noError) }
func (c *shareCmd) RunCommand(args []string) error { if len(args) != 1 { return cmdmain.UsageError("share takes exactly one argument, a blobref") } target, ok := blob.Parse(args[0]) if !ok { return cmdmain.UsageError("invalid blobref") } unsigned := schema.NewShareRef(schema.ShareHaveRef, target, c.transitive) if c.duration != 0 { unsigned.SetShareExpiration(time.Now().Add(c.duration)) } pr, err := getUploader().UploadAndSignBlob(unsigned) handleResult("share", pr, err) return nil }
func (up *Uploader) UploadShare(target *blobref.BlobRef, transitive bool) (*client.PutResult, error) { unsigned := schema.NewShareRef(schema.ShareHaveRef, target, transitive) return up.UploadAndSignMap(unsigned) }
func TestHandleGetViaSharing(t *testing.T) { // TODO(aa): It would be good if we could test that we are failing for // the right reason for all of these (some kind of internal error code). sto := &test.Fetcher{} handler := &httputil.PrefixHandler{"/", &shareHandler{sto}} wr := &httptest.ResponseRecorder{} get := func(path string) *httptest.ResponseRecorder { wr = httptest.NewRecorder() req, _ := http.NewRequest("GET", "http://unused/"+path, nil) handler.ServeHTTP(wr, req) return wr } content := "monkey" contentRef := blob.SHA1FromString(content) // For the purposes of following the via chain, the only thing that // matters is that the content of each link contains the name of the // next link. link := contentRef.String() linkRef := blob.SHA1FromString(link) share := schema.NewShareRef(schema.ShareHaveRef, linkRef, false). SetSigner(blob.SHA1FromString("irrelevant")). SetRawStringField("camliSig", "alsounused") log.Print("Should fail because first link does not exist") get(share.Blob().BlobRef().String()) ExpectInt(t, 401, wr.Code, "") log.Print("Should fail because share target does not match next link") sto.ReceiveBlob(share.Blob().BlobRef(), strings.NewReader(share.Blob().JSON())) get(contentRef.String() + "?via=" + share.Blob().BlobRef().String()) ExpectInt(t, 401, wr.Code, "") log.Print("Should fail because first link is not a share") sto.ReceiveBlob(linkRef, strings.NewReader(link)) get(linkRef.String()) ExpectInt(t, 401, wr.Code, "") log.Print("Should successfully fetch share") get(share.Blob().BlobRef().String()) ExpectInt(t, 200, wr.Code, "") log.Print("Should successfully fetch link via share") get(linkRef.String() + "?via=" + share.Blob().BlobRef().String()) ExpectInt(t, 200, wr.Code, "") log.Print("Should fail because share is not transitive") get(contentRef.String() + "?via=" + share.Blob().BlobRef().String() + "," + linkRef.String()) ExpectInt(t, 401, wr.Code, "") log.Print("Should fail because link content does not contain target") share.SetShareIsTransitive(true) sto.ReceiveBlob(share.Blob().BlobRef(), strings.NewReader(share.Blob().JSON())) get(linkRef.String() + "?via=" + share.Blob().BlobRef().String() + "," + linkRef.String()) ExpectInt(t, 401, wr.Code, "") log.Print("Should successfully fetch content via link via share") sto.ReceiveBlob(contentRef, strings.NewReader(content)) get(contentRef.String() + "?via=" + share.Blob().BlobRef().String() + "," + linkRef.String()) ExpectInt(t, 200, wr.Code, "") log.Print("Should fail because share is expired") share.SetShareExpiration(time.Now().Add(-time.Duration(10) * time.Minute)) sto.ReceiveBlob(share.Blob().BlobRef(), strings.NewReader(share.Blob().JSON())) get(contentRef.String() + "?via=" + share.Blob().BlobRef().String() + "," + linkRef.String()) ExpectInt(t, 401, wr.Code, "") log.Print("Should succeed because share has not expired") share.SetShareExpiration(time.Now().Add(time.Duration(10) * time.Minute)) sto.ReceiveBlob(share.Blob().BlobRef(), strings.NewReader(share.Blob().JSON())) get(contentRef.String() + "?via=" + share.Blob().BlobRef().String() + "," + linkRef.String()) ExpectInt(t, 200, wr.Code, "") // TODO(aa): assemble }
func TestHandleGetViaSharing(t *testing.T) { sto := &test.Fetcher{} handler := &shareHandler{fetcher: sto} var wr *httptest.ResponseRecorder putRaw := func(ref blob.Ref, data string) { if _, err := blobserver.Receive(sto, ref, strings.NewReader(data)); err != nil { t.Fatal(err) } } put := func(blob *schema.Blob) { putRaw(blob.BlobRef(), blob.JSON()) } get := func(path string) *shareError { wr = httptest.NewRecorder() req, _ := http.NewRequest("GET", "http://unused/"+path, nil) err := handler.serveHTTP(wr, req) if err != nil { return err.(*shareError) } return nil } content := "monkey" contentRef := blob.SHA1FromString(content) // For the purposes of following the via chain, the only thing that // matters is that the content of each link contains the name of the // next link. link := contentRef.String() linkRef := blob.SHA1FromString(link) share := schema.NewShareRef(schema.ShareHaveRef, linkRef, false). SetSigner(blob.SHA1FromString("irrelevant")). SetRawStringField("camliSig", "alsounused") var err *shareError if err = get(share.Blob().BlobRef().String()); err == nil || err.code != shareFetchFailed { t.Error("Expected missing blob error") } put(share.Blob()) if err = get(fmt.Sprintf("%s?via=%s", contentRef, share.Blob().BlobRef())); err == nil || err.code != shareTargetInvalid { t.Error("Expected invalid target error") } putRaw(linkRef, link) if err = get(linkRef.String()); err == nil || err.code != shareReadFailed { t.Error("Expected invalid share blob error") } if err = get(share.Blob().BlobRef().String()); err != nil { t.Error("Expected to successfully fetch share, but got: %s", err) } if err = get(fmt.Sprintf("%s?via=%s", linkRef, share.Blob().BlobRef())); err != nil { t.Error("Expected to successfully fetch link via share, but got: %s", err) } if err = get(fmt.Sprintf("%s?via=%s,%s", contentRef, share.Blob().BlobRef(), linkRef)); err == nil || err.code != shareNotTransitive { t.Error("Expected share not transitive error") } share.SetShareIsTransitive(true) put(share.Blob()) if err = get(fmt.Sprintf("%s?via=%s,%s", linkRef, share.Blob().BlobRef(), linkRef)); err == nil || err.code != viaChainInvalidLink { t.Error("Expected via chain invalid link err") } putRaw(contentRef, content) if err = get(fmt.Sprintf("%s?via=%s,%s", contentRef, share.Blob().BlobRef(), linkRef)); err != nil { t.Error("Expected to succesfully fetch via link via share, but got: %s", err) } share.SetShareExpiration(time.Now().Add(-time.Duration(10) * time.Minute)) put(share.Blob()) if err = get(fmt.Sprintf("%s?via=%s,%s", contentRef, share.Blob().BlobRef(), linkRef)); err == nil || err.code != shareExpired { t.Error("Expected share expired error") } share.SetShareExpiration(time.Now().Add(time.Duration(10) * time.Minute)) put(share.Blob()) if err = get(fmt.Sprintf("%s?via=%s,%s", contentRef, share.Blob().BlobRef(), linkRef)); err != nil { t.Error("Expected to successfully fetch unexpired share, but got: %s", err) } // TODO(aa): assemble }
func TestHandleGetViaSharing(t *testing.T) { sto := &test.Fetcher{} handler := &shareHandler{fetcher: sto} var wr *httptest.ResponseRecorder putRaw := func(ref blob.Ref, data string) { if _, err := blobserver.Receive(sto, ref, strings.NewReader(data)); err != nil { t.Fatal(err) } } put := func(blob *schema.Blob) { putRaw(blob.BlobRef(), blob.JSON()) } get := func(path string) *shareError { wr = httptest.NewRecorder() req, _ := http.NewRequest("GET", "http://unused/"+path, nil) err := handler.serveHTTP(wr, req) if err != nil { return err.(*shareError) } return nil } testGet := func(path string, expectedError errorCode) { err := get(path) if expectedError != noError { if err == nil || err.code != expectedError { t.Errorf("Fetching %s, expected error %#v, but got %#v", path, expectedError, err) } } else { if err != nil { t.Errorf("Fetching %s, expected success but got %#v", path, err) } } if wr.HeaderMap.Get("Access-Control-Allow-Origin") != "*" { t.Errorf("Fetching %s, share response did not contain expected CORS header", path) } } content := "monkey" contentRef := blob.SHA1FromString(content) // For the purposes of following the via chain, the only thing that // matters is that the content of each link contains the name of the // next link. link := contentRef.String() linkRef := blob.SHA1FromString(link) share := schema.NewShareRef(schema.ShareHaveRef, false). SetShareTarget(linkRef). SetSigner(blob.SHA1FromString("irrelevant")). SetRawStringField("camliSig", "alsounused") testGet(share.Blob().BlobRef().String(), shareFetchFailed) put(share.Blob()) testGet(fmt.Sprintf("%s?via=%s", contentRef, share.Blob().BlobRef()), shareTargetInvalid) putRaw(linkRef, link) testGet(linkRef.String(), shareReadFailed) testGet(share.Blob().BlobRef().String(), noError) testGet(fmt.Sprintf("%s?via=%s", linkRef, share.Blob().BlobRef()), noError) testGet(fmt.Sprintf("%s?via=%s,%s", contentRef, share.Blob().BlobRef(), linkRef), shareNotTransitive) share.SetShareIsTransitive(true) put(share.Blob()) testGet(fmt.Sprintf("%s?via=%s,%s", linkRef, share.Blob().BlobRef(), linkRef), viaChainInvalidLink) putRaw(contentRef, content) testGet(fmt.Sprintf("%s?via=%s,%s", contentRef, share.Blob().BlobRef(), linkRef), noError) share.SetShareExpiration(time.Now().Add(-time.Duration(10) * time.Minute)) put(share.Blob()) testGet(fmt.Sprintf("%s?via=%s,%s", contentRef, share.Blob().BlobRef(), linkRef), shareExpired) share.SetShareExpiration(time.Now().Add(time.Duration(10) * time.Minute)) put(share.Blob()) testGet(fmt.Sprintf("%s?via=%s,%s", contentRef, share.Blob().BlobRef(), linkRef), noError) // TODO(aa): assemble }