| // Copyright 2016 The Upspin Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| package test |
| |
| import ( |
| "testing" |
| |
| "upspin.io/errors" |
| "upspin.io/test/testenv" |
| "upspin.io/upspin" |
| ) |
| |
| const writerName = readerName |
| |
| // testGetErrors checks that the client receives 'not exist', 'permission', or |
| // 'private' errors where appropriate. In particular, it makes sure that the |
| // DirServer implementations do not allow unauthorized users to probe the name |
| // space. The rule of thumb is that users should receive 'private' for any |
| // path to which they have no rights. |
| func testGetErrors(t *testing.T, r *testenv.Runner) { |
| const ( |
| base = ownerName + "/get-errors" |
| dir = base + "/dir" |
| file = dir + "/file" |
| missingFile = dir + "/missing" |
| access = dir + "/Access" |
| content = "hello, gophers" |
| ) |
| r.As(ownerName) |
| r.MakeDirectory(base) |
| r.MakeDirectory(dir) |
| r.Put(file, content) |
| r.Get(file) |
| if r.Failed() { |
| t.Fatal(r.Diag()) |
| } |
| r.Get(missingFile) |
| if !r.Match(errNotExist) { |
| t.Fatal(r.Diag()) |
| } |
| |
| // We expect a "private" error for a reader that has no rights |
| // to the file, as they cannot even know that it exists. |
| r.As(readerName) |
| r.Get(file) |
| if !r.Match(errPrivate) { |
| t.Fatal(r.Diag()) |
| } |
| |
| // Put in an access file for the owner only |
| // and try the same reader check again. |
| r.As(ownerName) |
| r.Put(access, "*:"+ownerName) |
| if r.Failed() { |
| t.Fatal(r.Diag()) |
| } |
| r.As(readerName) |
| r.Get(file) |
| if !r.Match(errPrivate) { |
| t.Fatal(r.Diag()) |
| } |
| |
| // Give the reader a specific (non-read) right and they should be able |
| // to get an incomplete DirEntry with Lookup and a "permission" error |
| // for a Get. A Get of a missing file should return "not exist". |
| for _, right := range []string{"list", "write", "create", "delete"} { |
| r.As(ownerName) |
| r.Put(access, right+":"+readerName) |
| if r.Failed() { |
| t.Fatal(r.Diag()) |
| } |
| r.As(readerName) |
| r.Get(file) |
| if !r.Match(errPermission) { |
| t.Fatalf("%s: %s", right, r.Diag()) |
| } |
| r.DirLookup(file) |
| if !r.GotIncompleteEntry(file) { |
| t.Fatalf(r.Diag()) |
| } |
| r.Get(file) |
| if !r.Match(errPermission) { |
| t.Fatal(r.Diag()) |
| } |
| r.Get(missingFile) |
| if !r.Match(errNotExist) { |
| t.Fatal(r.Diag()) |
| } |
| } |
| |
| // Give the reader the "list,read" rights and it works. |
| r.As(ownerName) |
| r.Put(access, "*:"+ownerName+"\nlist,read:"+readerName) |
| r.Put(file, content) // Put the file again to wrap the keys. |
| r.As(readerName) |
| r.Get(file) |
| if r.Failed() { |
| t.Fatal(r.Diag()) |
| } |
| r.Get(missingFile) |
| if !r.Match(errNotExist) { |
| t.Fatal(r.Diag()) |
| } |
| } |
| |
| // testGetLinkErrors is like testGetErrors but checks the |
| // behavior of Get when links are present. |
| func testGetLinkErrors(t *testing.T, r *testenv.Runner) { |
| const ( |
| base = ownerName + "/get-link-errors" |
| srcDir = base + "/src" |
| dstDir = base + "/dst" |
| link = srcDir + "/link" |
| file = dstDir + "/file" |
| srcAccess = srcDir + "/Access" |
| dstAccess = dstDir + "/Access" |
| content = "hello, gophers" |
| ) |
| r.As(ownerName) |
| r.MakeDirectory(base) |
| r.MakeDirectory(srcDir) |
| r.MakeDirectory(dstDir) |
| r.Put(file, content) |
| r.Get(file) |
| r.PutLink(file, link) |
| r.Get(link) |
| if r.Failed() { |
| t.Fatal(r.Diag()) |
| } |
| if r.Data != content { |
| t.Fatalf("link content = %q, want %q", r.Data, content) |
| } |
| |
| // Make sure we get ErrFollowLink when looking up the link. |
| r.DirLookup(link) |
| if !r.Match(upspin.ErrFollowLink) { |
| t.Fatal(r.Diag()) |
| } |
| |
| // As a user with no rights over the link, |
| // we should get a 'private' error. |
| r.As(readerName) |
| r.Get(link) |
| if !r.Match(errPrivate) { |
| t.Fatal(r.Diag()) |
| } |
| r.DirLookup(link) |
| if !r.Match(errPrivate) { |
| t.Fatal(r.Diag()) |
| } |
| |
| // Give the user rights over the link's target, |
| // but still no rights over the link. The user should |
| // be able to get the file, but still can't see the link. |
| r.As(ownerName) |
| r.Put(dstAccess, "*:"+ownerName+"\n*:"+readerName) |
| r.Put(file, content) // Put the file again to wrap the keys. |
| r.As(readerName) |
| r.Get(file) |
| if r.Failed() { |
| t.Fatal(r.Diag()) |
| } |
| r.Get(link) |
| if !r.Match(errors.E(errors.Private, upspin.PathName(link))) { |
| t.Fatal(r.Diag()) |
| } |
| r.DirLookup(link) |
| if !r.Match(errors.E(errors.Private, upspin.PathName(link))) { |
| t.Fatal(r.Diag()) |
| } |
| |
| // Give the user one right over the link and Get should work |
| // as the client will now be able to follow the link. |
| r.As(ownerName) |
| r.Put(srcAccess, "create:"+readerName) |
| r.As(readerName) |
| r.Get(link) |
| if r.Failed() { |
| t.Fatal(r.Diag()) |
| } |
| r.DirLookup(link) |
| if !r.Match(upspin.ErrFollowLink) { |
| t.Fatal(r.Diag()) |
| } |
| |
| // Remove the user's rights to the link target, |
| // now a get of the link should fail with 'private' for the target. |
| r.As(ownerName) |
| r.Delete(dstAccess) |
| r.As(readerName) |
| r.Get(link) |
| if !r.Match(errors.E(errors.Private, upspin.PathName(file))) { |
| t.Fatal(r.Diag()) |
| } |
| |
| // Add a non-read permission for the target, |
| // now a get of the link should fail with 'permission' for the target. |
| r.As(ownerName) |
| r.Put(dstAccess, "list:"+readerName) |
| r.As(readerName) |
| r.Get(link) |
| if !r.Match(errors.E(errors.Permission, upspin.PathName(file))) { |
| t.Fatal(r.Diag()) |
| } |
| |
| // Add list permission to reader and delete the target. |
| // Accessing the link should fail with a 'BrokenLink' error naming the link. |
| r.As(ownerName) |
| r.Put(dstAccess, "delete:"+ownerName+"\nlist:"+readerName) |
| r.Delete(file) |
| r.As(readerName) |
| r.Get(link) |
| if !r.Match(errors.E(errors.BrokenLink, upspin.PathName(link))) { |
| t.Fatal(r.Diag()) |
| } |
| } |
| |
| func testPutErrors(t *testing.T, r *testenv.Runner) { |
| const ( |
| base = ownerName + "/put-errors" |
| dir = base + "/dir" |
| file = dir + "/file" |
| access = dir + "/Access" |
| content = "hello, gophers" |
| ) |
| r.As(ownerName) |
| r.MakeDirectory(base) |
| r.MakeDirectory(dir) |
| r.Put(file, content) |
| r.Get(file) |
| r.Delete(file) |
| if r.Failed() { |
| t.Fatal(r.Diag()) |
| } |
| |
| // We expect a 'private' error for a writer that has no rights |
| // to the directory, as they cannot even know that it exists. |
| r.As(writerName) |
| r.Put(file, content) |
| if !r.Match(errPrivate) { |
| t.Fatal(r.Diag()) |
| } |
| |
| // Put in an access file for the owner only |
| // and try the same writer check again. |
| r.As(ownerName) |
| r.Put(access, "*:"+ownerName) |
| if r.Failed() { |
| t.Fatal(r.Diag()) |
| } |
| r.As(writerName) |
| r.Put(file, content) |
| if !r.Match(errPrivate) { |
| t.Fatal(r.Diag()) |
| } |
| |
| // Give the writer a specific (non-write/create) right and |
| // it should see a permission error. |
| for _, right := range []string{"list", "read", "delete"} { |
| r.As(ownerName) |
| r.Put(access, right+":"+writerName) |
| if r.Failed() { |
| t.Fatal(r.Diag()) |
| } |
| r.As(writerName) |
| r.Put(file, content) |
| if !r.Match(errPermission) { |
| t.Fatalf("%s: %s", right, r.Diag()) |
| } |
| } |
| |
| // Give the reader the "create" right and it works. |
| r.As(ownerName) |
| r.Put(access, "*:"+ownerName+"\ncreate:"+writerName) |
| r.As(writerName) |
| r.Put(file, content) |
| if r.Failed() { |
| t.Fatal(r.Diag()) |
| } |
| // Try to overwrite, and it should fail. |
| r.Put(file, content) |
| if !r.Match(errPermission) { |
| t.Fatal(r.Diag()) |
| } |
| |
| // Give the reader the "write" right and overwrite works. |
| r.As(ownerName) |
| r.Put(access, "*:"+ownerName+"\nwrite:"+writerName) |
| r.As(writerName) |
| r.Put(file, content) |
| if r.Failed() { |
| t.Fatal(r.Diag()) |
| } |
| |
| // Get should fail as the writer. |
| r.Get(file) |
| if !r.Match(errPermission) { |
| t.Fatal(r.Diag()) |
| } |
| |
| // But should succeed as the owner. |
| r.As(ownerName) |
| r.Get(file) |
| if r.Failed() { |
| t.Fatal(r.Diag()) |
| } |
| |
| // Give the writer read access, and Get should succeed. |
| r.As(ownerName) |
| r.Put(access, "*:"+ownerName+"\nread:"+writerName) |
| r.As(writerName) |
| r.Get(file) |
| if r.Failed() { |
| t.Fatal(r.Diag()) |
| } |
| } |
| |
| func testPutLinkErrors(t *testing.T, r *testenv.Runner) { |
| const ( |
| base = ownerName + "/put-link-errors" |
| srcDir = base + "/src" |
| dstDir = base + "/dst" |
| srcAccess = srcDir + "/Access" |
| dstAccess = dstDir + "/Access" |
| file = dstDir + "/file" |
| |
| fileLink = srcDir + "/file-link" |
| fileTarget = file |
| fileThroughFileLink = fileLink |
| |
| dirLink = srcDir + "/dir-link" |
| dirTarget = dstDir |
| fileThroughDirLink = dirLink + "/file" |
| |
| content = "hello, gophers" |
| ) |
| r.As(ownerName) |
| r.MakeDirectory(base) |
| r.MakeDirectory(srcDir) |
| r.MakeDirectory(dstDir) |
| r.PutLink(fileTarget, fileLink) |
| r.PutLink(dirTarget, dirLink) |
| r.Put(fileThroughFileLink, content) |
| r.Get(fileTarget) |
| r.Delete(fileTarget) |
| if r.Failed() { |
| t.Fatal(r.Diag()) |
| } |
| if r.Data != content { |
| t.Fatalf("link content = %q, want %q", r.Data, content) |
| } |
| r.Put(fileThroughDirLink, content) |
| r.Get(fileTarget) |
| r.Delete(fileTarget) |
| if r.Failed() { |
| t.Fatal(r.Diag()) |
| } |
| if r.Data != content { |
| t.Fatalf("link content = %q, want %q", r.Data, content) |
| } |
| |
| cases := []struct { |
| op string |
| link, fileThroughLink upspin.PathName |
| }{ |
| {"file link:", fileLink, fileThroughFileLink}, |
| {"dir link:", dirLink, fileThroughDirLink}, |
| } |
| for _, c := range cases { |
| // As a user with no rights over the link, |
| // we should get a 'private' error. |
| r.As(writerName) |
| r.Put(c.fileThroughLink, content) |
| if !r.Match(errPrivate) { |
| t.Fatal(c.op, r.Diag()) |
| } |
| |
| // Give the user rights over the destination file |
| // but still no rights over the link. The user should |
| // not be able to put the file, because the link is hidden. |
| r.As(ownerName) |
| r.Put(dstAccess, "*:"+ownerName+"\n*:"+writerName) |
| r.As(writerName) |
| r.Put(file, content) |
| r.Delete(file) |
| if r.Failed() { |
| t.Fatal(c.op, r.Diag()) |
| } |
| r.Put(c.fileThroughLink, content) |
| if !r.Match(errors.E(errors.Private, upspin.PathName(c.link))) { |
| t.Fatal(c.op, r.Diag()) |
| } |
| |
| // Give the user one right over the link and Put should work |
| // as the client will now be able to follow the link. |
| for _, right := range []string{"list", "create", "write", "read", "delete"} { |
| r.As(ownerName) |
| r.Put(srcAccess, right+":"+writerName) |
| r.As(writerName) |
| r.Put(c.fileThroughLink, content) |
| r.Delete(file) |
| if r.Failed() { |
| t.Fatalf("right = %q: %s", right, r.Diag()) |
| } |
| } |
| |
| // Remove the user's rights to the file, now a put through the |
| // link should fail with 'private' for the file. |
| r.As(ownerName) |
| r.Put(srcAccess, "list:"+writerName) |
| r.Delete(dstAccess) |
| r.As(writerName) |
| r.Put(c.fileThroughLink, content) |
| if !r.Match(errors.E(errors.Private, upspin.PathName(file))) { |
| t.Fatal(c.op, r.Diag()) |
| } |
| |
| // Add a non-read permission for the file, |
| // now a put of the link should fail with 'permission' for the file. |
| r.As(ownerName) |
| r.Put(dstAccess, "*:"+ownerName+"\nlist:"+writerName) |
| r.As(writerName) |
| r.Put(c.fileThroughLink, content) |
| if !r.Match(errors.E(errors.Permission, upspin.PathName(file))) { |
| t.Fatal(c.op, r.Diag()) |
| } |
| |
| // Clean up before next loop iteration. |
| r.As(ownerName) |
| r.Delete(srcAccess) |
| r.Delete(dstAccess) |
| if r.Failed() { |
| t.Fatal(c.op, r.Diag()) |
| } |
| } |
| } |
| |
| func testMakeDirectoryErrors(t *testing.T, r *testenv.Runner) { |
| const ( |
| base = ownerName + "/makedirectory-errors" |
| dir = base + "/dir" |
| subdir = dir + "/subdir" |
| access = dir + "/Access" |
| ) |
| r.As(ownerName) |
| r.MakeDirectory(base) |
| r.MakeDirectory(dir) |
| r.MakeDirectory(subdir) |
| r.DirLookup(subdir) |
| r.Delete(subdir) |
| if r.Failed() { |
| t.Fatal(r.Diag()) |
| } |
| |
| // Can't make an Access directory. |
| r.MakeDirectory(access) |
| if !r.Match(errors.E(errors.Invalid, upspin.PathName(access))) { |
| t.Error(r.Diag()) |
| } |
| |
| // We expect a 'private' error for a writer that has no rights |
| // to the directory, as they cannot even know that it exists. |
| r.As(writerName) |
| r.MakeDirectory(subdir) |
| if !r.Match(errPrivate) { |
| t.Fatal(r.Diag()) |
| } |
| |
| // Put in an access file for the owner only |
| // and try the same writer check again. |
| r.As(ownerName) |
| r.Put(access, "*:"+ownerName) |
| if r.Failed() { |
| t.Fatal(r.Diag()) |
| } |
| r.As(writerName) |
| r.MakeDirectory(subdir) |
| if !r.Match(errPrivate) { |
| t.Fatal(r.Diag()) |
| } |
| |
| // Give the writer a specific (non-create) right and |
| // it should see a permission error. |
| for _, right := range []string{"list", "read", "write", "delete"} { |
| r.As(ownerName) |
| r.Put(access, right+":"+writerName) |
| if r.Failed() { |
| t.Fatal(r.Diag()) |
| } |
| r.As(writerName) |
| r.MakeDirectory(subdir) |
| if !r.Match(errPermission) { |
| t.Fatalf("%s: %s", right, r.Diag()) |
| } |
| } |
| |
| // Give the reader the "create" right and it works. |
| r.As(ownerName) |
| r.Put(access, "*:"+ownerName+"\ncreate:"+writerName) |
| r.As(writerName) |
| r.MakeDirectory(subdir) |
| if r.Failed() { |
| t.Fatal(r.Diag()) |
| } |
| // Try to make it again, and it should fail. |
| r.MakeDirectory(subdir) |
| if !r.Match(errExist) { |
| t.Fatal(r.Diag()) |
| } |
| |
| // Lookup should return an incomplete entry as the writer. |
| r.DirLookup(subdir) |
| if !r.GotIncompleteEntry(subdir) { |
| t.Fatal(r.Diag()) |
| } |
| |
| // But should succeed as the owner. |
| r.As(ownerName) |
| r.DirLookup(subdir) |
| if r.Failed() { |
| t.Fatal(r.Diag()) |
| } |
| |
| // Give the writer read access, and lookup should succeed. |
| r.As(ownerName) |
| r.Put(access, "*:"+ownerName+"\nread:"+writerName) |
| r.As(writerName) |
| r.DirLookup(subdir) |
| if r.Failed() { |
| t.Fatal(r.Diag()) |
| } |
| } |
| |
| func testMakeDirectoryLinkErrors(t *testing.T, r *testenv.Runner) { |
| const ( |
| base = ownerName + "/makedirectory-link-errors" |
| srcDir = base + "/src" |
| dstDir = base + "/dst" |
| srcAccess = srcDir + "/Access" |
| dstAccess = dstDir + "/Access" |
| dir = dstDir + "/dir" |
| |
| directLink = srcDir + "/direct-link" |
| directTarget = dir |
| dirThroughDirectLink = directLink |
| |
| indirectLink = srcDir + "/indirect-link" |
| indirectTarget = dstDir |
| dirThroughIndirectLink = indirectLink + "/dir" |
| ) |
| r.As(ownerName) |
| r.MakeDirectory(base) |
| r.MakeDirectory(srcDir) |
| r.MakeDirectory(dstDir) |
| r.PutLink(directTarget, directLink) |
| r.PutLink(indirectTarget, indirectLink) |
| r.MakeDirectory(dirThroughDirectLink) |
| r.Delete(dir) |
| r.MakeDirectory(dirThroughIndirectLink) |
| r.Delete(dir) |
| if r.Failed() { |
| t.Fatal(r.Diag()) |
| } |
| |
| cases := []struct { |
| op string |
| link, dirThroughLink upspin.PathName |
| }{ |
| {"direct link:", directLink, dirThroughDirectLink}, |
| {"indirect link:", indirectLink, dirThroughIndirectLink}, |
| } |
| for _, c := range cases { |
| // As a user with no rights over the link, |
| // we should get a 'private' error. |
| r.As(writerName) |
| r.MakeDirectory(c.dirThroughLink) |
| if !r.Match(errPrivate) { |
| t.Fatal(c.op, r.Diag()) |
| } |
| |
| // Give the user rights over the destination directory |
| // but still no rights over the link. The user should |
| // not be able to make the directory, because the link is hidden. |
| r.As(ownerName) |
| r.Put(dstAccess, "*:"+ownerName+"\n*:"+writerName) |
| r.As(writerName) |
| r.MakeDirectory(dir) |
| r.Delete(dir) |
| if r.Failed() { |
| t.Fatal(c.op, r.Diag()) |
| } |
| r.MakeDirectory(c.dirThroughLink) |
| if !r.Match(errors.E(errors.Private, upspin.PathName(c.link))) { |
| t.Fatal(c.op, r.Diag()) |
| } |
| |
| // Give the user one right over the link and MakeDirectory should work |
| // as the client will now be able to follow the link. |
| for _, right := range []string{"list", "create", "write", "read", "delete"} { |
| r.As(ownerName) |
| r.Put(srcAccess, right+":"+writerName) |
| r.As(writerName) |
| r.MakeDirectory(c.dirThroughLink) |
| r.Delete(dir) |
| if r.Failed() { |
| t.Fatalf("%s right = %q: %s", c.op, right, r.Diag()) |
| } |
| } |
| |
| // Remove the user's rights to the directory, now a |
| // MakeDirectory through the link should fail with |
| // 'private' for the dir. |
| r.As(ownerName) |
| r.Put(srcAccess, "list:"+writerName) |
| r.Delete(dstAccess) |
| r.As(writerName) |
| r.MakeDirectory(c.dirThroughLink) |
| if !r.Match(errors.E(errors.Private, upspin.PathName(dir))) { |
| t.Fatal(c.op, r.Diag()) |
| } |
| |
| // Add a non-read permission for the directory, |
| // now a mkdir of the link should fail with 'permission' for the dir. |
| r.As(ownerName) |
| r.Put(dstAccess, "*:"+ownerName+"\nlist:"+writerName) |
| r.As(writerName) |
| r.MakeDirectory(c.dirThroughLink) |
| if !r.Match(errors.E(errors.Permission, upspin.PathName(dir))) { |
| t.Fatal(c.op, r.Diag()) |
| } |
| |
| // Clean up before next loop iteration. |
| r.As(ownerName) |
| r.Delete(srcAccess) |
| r.Delete(dstAccess) |
| if r.Failed() { |
| t.Fatal(c.op, r.Diag()) |
| } |
| } |
| } |
| |
| func testWhichAccessErrors(t *testing.T, r *testenv.Runner) { |
| const ( |
| base = ownerName + "/whichaccess-errors" |
| dir = base + "/dir" |
| file = dir + "/file" |
| baseAccess = base + "/Access" |
| dirAccess = dir + "/Access" |
| ) |
| r.As(ownerName) |
| r.MakeDirectory(base) |
| r.MakeDirectory(dir) |
| if r.Failed() { |
| t.Fatal(r.Diag()) |
| } |
| |
| // The owner should get nil and no error |
| // with no access file present. |
| r.DirWhichAccess(file) |
| if r.Failed() { |
| t.Fatal(r.Diag()) |
| } |
| if r.Entry != nil { |
| t.Fatalf("got entry %q, expected nil", r.Entry.Name) |
| } |
| |
| // A reader with no rights should get 'private' for the same. |
| r.As(readerName) |
| r.DirWhichAccess(file) |
| if !r.Match(errPrivate) { |
| t.Fatal(r.Diag()) |
| } |
| |
| // Put in an access file in the root, the owner should see it. |
| r.As(ownerName) |
| r.Put(baseAccess, "*:"+ownerName) |
| r.DirWhichAccess(file) |
| if !r.GotEntry(baseAccess) { |
| t.Fatal(r.Diag()) |
| } |
| |
| // While the reader should still get 'private'. |
| r.As(readerName) |
| r.DirWhichAccess(file) |
| if !r.Match(errPrivate) { |
| t.Fatal(r.Diag()) |
| } |
| |
| // Put an access file in dir, we should get that one. |
| r.As(ownerName) |
| r.Put(dirAccess, "*:"+ownerName) |
| r.DirWhichAccess(file) |
| if !r.GotEntry(dirAccess) { |
| t.Fatal(r.Diag()) |
| } |
| |
| // The reader still gets bupkis. |
| r.As(readerName) |
| r.DirWhichAccess(file) |
| if !r.Match(errPrivate) { |
| t.Fatal(r.Diag()) |
| } |
| |
| // Put the reader into (and take the owner out of) the dir access file. |
| // The owner should still see the access file (since they own it). |
| r.As(ownerName) |
| r.Put(dirAccess, "*:"+readerName) |
| r.DirWhichAccess(file) |
| if !r.GotEntry(dirAccess) { |
| t.Fatal(r.Diag()) |
| } |
| r.As(readerName) |
| r.DirWhichAccess(file) |
| if !r.GotEntry(dirAccess) { |
| t.Fatal(r.Diag()) |
| } |
| |
| // Do the same, but for the base access file. |
| r.As(ownerName) |
| r.Delete(dirAccess) |
| r.Put(baseAccess, "*:"+readerName) |
| r.DirWhichAccess(file) |
| if !r.GotEntry(baseAccess) { |
| t.Fatal(r.Diag()) |
| } |
| r.As(readerName) |
| r.DirWhichAccess(file) |
| if !r.GotEntry(baseAccess) { |
| t.Fatal(r.Diag()) |
| } |
| } |
| |
| func testWhichAccessLinkErrors(t *testing.T, r *testenv.Runner) { |
| const ( |
| base = ownerName + "/whichaccess-link-errors" |
| link = base + "/link" |
| linkTarget = "user@example.org/path" |
| access = base + "/Access" |
| ) |
| r.As(ownerName) |
| r.MakeDirectory(base) |
| r.PutLink(linkTarget, link) |
| if r.Failed() { |
| t.Fatal(r.Diag()) |
| } |
| |
| // The owner should see the link with no access file present. |
| r.DirWhichAccess(link) |
| if !r.Match(upspin.ErrFollowLink) { |
| t.Fatalf("expected ErrFollowLink, got: %v", r.Diag()) |
| } |
| |
| // A reader with no rights should get 'private'. |
| r.As(readerName) |
| r.DirWhichAccess(link) |
| if !r.Match(errPrivate) { |
| t.Fatal(r.Diag()) |
| } |
| |
| // Put an access file in the root; |
| // the owner should now be able to see the link. |
| r.As(ownerName) |
| r.Put(access, "*:"+ownerName) |
| r.DirWhichAccess(link) |
| if !r.Match(upspin.ErrFollowLink) { |
| t.Fatalf("expected ErrFollowLink, got: %v", r.Diag()) |
| } |
| |
| // The reader should still get 'private'. |
| r.As(readerName) |
| r.DirWhichAccess(link) |
| if !r.Match(errPrivate) { |
| t.Fatal(r.Diag()) |
| } |
| |
| // Put the reader into (and take the owner out of) the access file. |
| // The owner should still see the link and now the reader can also. |
| r.As(ownerName) |
| r.Put(access, "*:"+readerName) |
| r.DirWhichAccess(link) |
| if !r.Match(upspin.ErrFollowLink) { |
| t.Fatalf("expected ErrFollowLink, got: %v", r.Diag()) |
| } |
| r.As(readerName) |
| r.DirWhichAccess(link) |
| if !r.Match(upspin.ErrFollowLink) { |
| t.Fatalf("expected ErrFollowLink, got: %v", r.Diag()) |
| } |
| } |
| |
| func testGlobErrors(t *testing.T, r *testenv.Runner) { |
| const ( |
| base = ownerName + "/glob-errors" |
| dir = base + "/dir" |
| baseFile = base + "/file" |
| dirFile = dir + "/file" |
| baseAccess = base + "/Access" |
| dirAccess = dir + "/Access" |
| content = "hello, gophers" |
| ) |
| r.As(ownerName) |
| r.MakeDirectory(base) |
| r.MakeDirectory(dir) |
| r.Put(baseFile, content) |
| r.Put(dirFile, content) |
| if r.Failed() { |
| t.Fatal(r.Diag()) |
| } |
| |
| // Owner should be able to glob the root. |
| r.Glob(base + "/*") |
| // Check that we got the root entries, but ignore whether there are |
| // blocks in them because there may or may not be depending on whether |
| // a cache is in use. |
| if len(r.Entries) != 2 { |
| t.Fatalf("got %d entries, want 2", len(r.Entries)) |
| } |
| if got := r.Entries[0].Name; got != dir { |
| t.Fatalf("got entry %q, want %q", got, dir) |
| } |
| if got := r.Entries[1].Name; got != baseFile { |
| t.Fatalf("got entry %q, want %q", got, baseFile) |
| } |
| // Owner should be able to glob the directory. |
| r.Glob(base + "/*/*") |
| if !r.GotEntries(true, dirFile) { |
| t.Fatal(r.Diag()) |
| } |
| |
| // The reader should get a 'private' error |
| // because they can't see the base of the glob. |
| r.As(readerName) |
| r.Glob(base + "/*") |
| if !r.Match(errPrivate) { |
| t.Fatal(r.Diag()) |
| } |
| r.Glob(base + "/nonexistent/*") |
| if !r.Match(errPrivate) { |
| t.Fatal(r.Diag()) |
| } |
| r.Glob(base + "/*/*") |
| if !r.Match(errPrivate) { |
| t.Fatal(r.Diag()) |
| } |
| |
| // Give a reader list rights and they can see |
| // all files but no block data. |
| r.As(ownerName) |
| r.Put(baseAccess, "list:"+readerName) |
| r.As(readerName) |
| r.Glob(base + "/*") |
| if !r.GotEntries(false, baseAccess, dir, baseFile) { |
| t.Fatal(r.Diag()) |
| } |
| r.Glob(base + "/*/*") |
| if !r.GotEntries(false, dirFile) { |
| t.Fatal(r.Diag()) |
| } |
| |
| // With read rights they can see everything. |
| r.As(ownerName) |
| r.Put(baseAccess, "list,read:"+readerName) |
| r.As(readerName) |
| r.Glob(base + "/*") |
| if !r.GotEntries(true, baseAccess, dir, baseFile) { |
| t.Fatal(r.Diag()) |
| } |
| r.Glob(base + "/*/*") |
| if !r.GotEntries(true, dirFile) { |
| t.Fatal(r.Diag()) |
| } |
| |
| // Deny the reader access to the dir, |
| // they can still see the root but not the dir. |
| r.As(ownerName) |
| r.Put(dirAccess, "*:"+ownerName) |
| r.As(readerName) |
| r.Glob(base + "/*") |
| if !r.GotEntries(true, baseAccess, dir, baseFile) { |
| t.Fatal(r.Diag()) |
| } |
| r.Glob(base + "/*/*") |
| if !r.GotEntries(false) { |
| t.Fatal(r.Diag()) |
| } |
| |
| // Give the reader list access to the dir, they can still see the root |
| // (with blocks) and the dir (without blocks). |
| r.As(ownerName) |
| r.Put(dirAccess, "list:"+readerName) |
| r.As(readerName) |
| r.Glob(base + "/*") |
| if !r.GotEntries(true, baseAccess, dir, baseFile) { |
| t.Fatal(r.Diag()) |
| } |
| r.Glob(base + "/*/*") |
| if !r.GotEntries(false, dirAccess, dirFile) { |
| t.Fatal(r.Diag()) |
| } |
| |
| // Remove the root access file and the reader should |
| // still be able to glob the directory, given its name. |
| r.As(ownerName) |
| r.Delete(baseAccess) |
| r.As(readerName) |
| r.Glob(base + "/*") |
| if !r.Match(errPrivate) { |
| t.Fatal(r.Diag()) |
| } |
| r.Glob(base + "/*/*") |
| if !r.Match(errPrivate) { |
| t.Fatal(r.Diag()) |
| } |
| r.Glob(dir + "/*") |
| if !r.GotEntries(false, dirAccess, dirFile) { |
| t.Fatal(r.Diag()) |
| } |
| |
| // Now add a second directory, and give the reader list and read rights |
| // to it, and also give them list rights to the root. With one glob file |
| // that matches both directories, the reader should the file in dir with |
| // no blocks, and the file in dir2 with blocks. |
| const ( |
| dir2 = base + "/dir2" |
| dir2File = dir2 + "/file" |
| dir2Access = dir2 + "/Access" |
| ) |
| r.As(ownerName) |
| r.Put(baseAccess, "*:"+ownerName+"\nlist:"+readerName) |
| r.MakeDirectory(dir2) |
| r.Put(dir2File, content) |
| r.Put(dir2Access, "list,read:"+readerName) |
| if r.Failed() { |
| t.Fatal(r.Diag()) |
| } |
| r.As(readerName) |
| r.Glob(base + "/*/f*") |
| if r.Failed() { |
| t.Fatal(r.Diag()) |
| } |
| if n := len(r.Entries); n != 2 { |
| t.Fatalf("got %d files, want %d", n, 2) |
| } |
| if e := r.Entries[0]; e.Name != dirFile || len(e.Blocks) != 0 { |
| t.Fatalf("got %q with %d blocks, want %q with 0 blocks", e.Name, len(e.Blocks), dirFile) |
| } |
| if e := r.Entries[1]; e.Name != dir2File || len(e.Blocks) == 0 { |
| t.Fatalf("got %q with 0 blocks, want %q with blocks", e.Name, dirFile) |
| } |
| } |
| |
| func testGlobLinkErrors(t *testing.T, r *testenv.Runner) { |
| const ( |
| base = ownerName + "/glob-link-errors" |
| dir1 = base + "/dir1" |
| dir2 = base + "/dir2" |
| dir3 = base + "/dir3" |
| dir1access = dir1 + "/Access" |
| dir1file = dir1 + "/file" |
| dir2link = dir1 + "/dir2-link" |
| dir2filelink = dir1 + "/dir2-file-link" |
| dir3link = dir1 + "/dir3-link" |
| dir2access = dir2 + "/Access" |
| dir2file = dir2 + "/file" |
| dir3access = dir3 + "/Access" |
| dir3file = dir3 + "/file" |
| content = "hello, gophers" |
| ) |
| r.As(ownerName) |
| r.MakeDirectory(base) |
| r.MakeDirectory(dir1) |
| r.MakeDirectory(dir2) |
| r.MakeDirectory(dir3) |
| r.Put(dir1file, content) |
| r.PutLink(dir2, dir2link) |
| r.PutLink(dir2file, dir2filelink) |
| r.PutLink(dir3, dir3link) |
| r.Put(dir2file, content) |
| r.Put(dir3file, content) |
| if r.Failed() { |
| t.Fatal(r.Diag()) |
| } |
| |
| // The owner can glob everywhere. |
| r.Glob(base + "/*/file") |
| if !r.GotEntries(true, dir1file, dir2file, dir3file) { |
| t.Fatal(r.Diag()) |
| } |
| r.Glob(base + "/dir1/*") |
| if !r.GotEntries(true, dir2filelink, dir2link, dir3link, dir1file) { |
| t.Fatal(r.Diag()) |
| } |
| // Including traversing links. |
| r.Glob(base + "/dir1/*link/file") |
| if !r.GotEntries(true, dir2file, dir3file) { |
| t.Fatal(r.Diag()) |
| } |
| |
| // The reader can see nothing. |
| r.As(readerName) |
| r.Glob(base + "/*/file") |
| if !r.Match(errPrivate) { |
| t.Fatal(r.Diag()) |
| } |
| r.Glob(base + "/dir1/*") |
| if !r.Match(errPrivate) { |
| t.Fatal(r.Diag()) |
| } |
| r.Glob(base + "/dir1/*link/file") |
| if !r.Match(errPrivate) { |
| t.Fatal(r.Diag()) |
| } |
| |
| // Give the reader all rights to /dir1. |
| r.As(ownerName) |
| r.Put(dir1access, "*:"+readerName) |
| r.As(readerName) |
| // The reader cannot list the root, so they still get 'private'. |
| r.Glob(base + "/*/file") |
| if !r.Match(errPrivate) { |
| t.Fatal(r.Diag()) |
| } |
| // The reader can list /dir1, so they see its contents. |
| r.Glob(base + "/dir1/*") |
| if !r.GotEntries(true, dir1access, dir2filelink, dir2link, dir3link, dir1file) { |
| t.Fatal(r.Diag()) |
| } |
| // If the glob follows a link, but the reader doesn't have access to |
| // its target, the glob result does not include the target. |
| r.Glob(base + "/dir1/*link/file") |
| if !r.GotEntries(true) { |
| t.Fatal(r.Diag()) |
| } |
| |
| // Give the reader all rights to one of the target directories |
| // and they should see the matching file for which they have access. |
| r.As(ownerName) |
| r.Put(dir2access, "*:"+readerName) |
| r.As(readerName) |
| r.Glob(base + "/dir1/*link/file") |
| if !r.GotEntries(true, dir2file) { |
| t.Fatal(r.Diag()) |
| } |
| |
| // Give the reader list rights over both target directories |
| // and they can see all matching files. |
| r.As(ownerName) |
| r.Put(dir3access, "list:"+readerName) |
| r.As(readerName) |
| r.Glob(base + "/dir1/*link/file") |
| if len(r.Entries) != 2 { |
| for i, e := range r.Entries { |
| t.Logf("got entry %d: %v", i, e.Name) |
| } |
| t.Fatalf("got %d entries, want 2", len(r.Entries)) |
| } |
| if e0 := r.Entries[0]; e0.Name != dir2file { |
| t.Fatalf("entry 0 == %q, want %q", e0.Name, dir2file) |
| } else if len(e0.Blocks) == 0 { |
| t.Fatalf("entry 0 (%q) has 0 blocks, want some", e0.Name) |
| } |
| if e1 := r.Entries[1]; e1.Name != dir3file { |
| t.Fatalf("entry 1 == %q, want %q", e1.Name, dir3file) |
| } else if len(e1.Blocks) > 0 { |
| t.Fatalf("entry 1 (%q) has %d blocks, want 0", e1.Name, len(e1.Blocks)) |
| } |
| |
| // Test that a Glob through a link works. |
| r.As(readerName) |
| r.Glob(dir2link + "/*") |
| if !r.GotEntries(true, dir2access, dir2file) { |
| t.Fatal(r.Diag()) |
| } |
| } |
| |
| func testDeleteErrors(t *testing.T, r *testenv.Runner) { |
| const ( |
| base = ownerName + "/delete-errors" |
| file = base + "/file" |
| dir = base + "/dir" |
| fileInDir = dir + "/fileInDir" |
| accessFile = base + "/Access" |
| accessContent = "r,l:" + ownerName + "," + readerName |
| permissiveAccessContent = "d:" + ownerName + "," + readerName |
| ) |
| |
| r.As(ownerName) |
| r.MakeDirectory(base) |
| r.Put(file, "something") |
| r.MakeDirectory(dir) |
| r.Put(fileInDir, "in dir") |
| if r.Failed() { |
| t.Fatal(r.Diag()) |
| } |
| |
| // Reader has no right to see the file or the dir. |
| r.As(readerName) |
| r.Delete(file) |
| if !r.Match(errPrivate) { |
| t.Fatal(r.Diag()) |
| } |
| r.Delete(dir) |
| if !r.Match(errPrivate) { |
| t.Fatal(r.Diag()) |
| } |
| |
| // Owner permits reader to see everything, but not delete. |
| r.As(ownerName) |
| r.Put(accessFile, accessContent) |
| if r.Failed() { |
| t.Fatal(r.Diag()) |
| } |
| r.As(readerName) |
| r.Delete(file) |
| if !r.Match(errPermission) { |
| t.Fatal(r.Diag()) |
| } |
| r.Delete(dir) |
| if !r.Match(errPermission) { |
| t.Fatal(r.Diag()) |
| } |
| |
| // Owner grants delete right to reader. |
| r.As(ownerName) |
| r.Put(accessFile, permissiveAccessContent) |
| if r.Failed() { |
| t.Fatal(r.Diag()) |
| } |
| r.As(readerName) |
| r.Delete(file) |
| if r.Failed() { |
| t.Fatal(r.Diag()) |
| } |
| |
| // But reader cannot delete an Access file |
| r.Delete(accessFile) |
| if !r.Match(errPermission) { |
| t.Fatal(r.Diag()) |
| } |
| |
| // No one can delete a non-empty dir. |
| r.Delete(dir) |
| if !r.Match(errors.E(errors.NotEmpty, upspin.PathName(dir))) { |
| t.Fatal(r.Diag()) |
| } |
| r.As(ownerName) |
| r.Delete(dir) |
| if !r.Match(errors.E(errors.NotEmpty, upspin.PathName(dir))) { |
| t.Fatal(r.Diag()) |
| } |
| |
| // Owner can delete his own Access file and remaining entries. |
| r.Delete(accessFile) |
| // Even through a link. |
| r.PutLink(dir, base+"/link-to-dir") |
| r.Delete(base + "/link-to-dir/fileInDir") |
| r.Delete(dir) |
| if r.Failed() { |
| t.Fatal(r.Diag()) |
| } |
| } |