func TestAccessFunction(t *testing.T) { db := setupTestDB(t) defer tearDownTestDB(t, db) authenticator := auth.NewAuthenticator(db.Bucket, db) var err error db.ChannelMapper, err = channels.NewChannelMapper(`function(doc){access(doc.users,doc.userChannels);}`) assertNoError(t, err, "Couldn't create channel mapper") user, _ := authenticator.NewUser("naomi", "letmein", channels.SetOf("Netflix")) user.SetRoleNames([]string{"animefan", "tumblr"}) authenticator.Save(user) body := Body{"users": []string{"naomi"}, "userChannels": []string{"Hulu"}} _, err = db.Put("doc1", body) assertNoError(t, err, "") body = Body{"users": []string{"role:animefan"}, "userChannels": []string{"CrunchyRoll"}} _, err = db.Put("doc2", body) assertNoError(t, err, "") // Create the role _after_ creating the documents, to make sure the previously-indexed access // privileges are applied. role, _ := authenticator.NewRole("animefan", nil) authenticator.Save(role) user, _ = authenticator.GetUser("naomi") expected := channels.SetOf("Hulu", "Netflix").AtSequence(1) assert.DeepEquals(t, user.Channels(), expected) expected.AddChannel("CrunchyRoll", 2) assert.DeepEquals(t, user.InheritedChannels(), expected) }
func TestRebuildRoleChannels(t *testing.T) { computer := mockComputer{channels: ch.AtSequence(ch.SetOf("derived1", "derived2"), 1)} auth := NewAuthenticator(gTestBucket, &computer) role, _ := auth.NewRole("testRole", ch.SetOf("explicit1")) err := auth.InvalidateChannels(role) assert.Equals(t, err, nil) role2, err := auth.GetRole("testRole") assert.Equals(t, err, nil) assert.DeepEquals(t, role2.Channels(), ch.AtSequence(ch.SetOf("explicit1", "derived1", "derived2"), 1)) }
func TestRebuildUserChannels(t *testing.T) { computer := mockComputer{channels: ch.AtSequence(ch.SetOf("derived1", "derived2"), 1)} auth := NewAuthenticator(gTestBucket, &computer) user, _ := auth.NewUser("testUser", "password", ch.SetOf("explicit1")) user.setChannels(nil) err := auth.Save(user) assert.Equals(t, err, nil) // user2, err := auth.GetUser("testUser") assert.Equals(t, err, nil) log.Printf("Channels = %s", user2.Channels()) assert.DeepEquals(t, user2.Channels(), ch.AtSequence(ch.SetOf("explicit1", "derived1", "derived2"), 1)) }
func TestLogin(t *testing.T) { var rt restTester a := auth.NewAuthenticator(rt.bucket(), nil) user, err := a.GetUser("") assert.Equals(t, err, nil) user.SetDisabled(true) err = a.Save(user) assert.Equals(t, err, nil) user, err = a.GetUser("") assert.Equals(t, err, nil) assert.True(t, user.Disabled()) response := rt.sendRequest("PUT", "/db/doc", `{"hi": "there"}`) assertStatus(t, response, 401) user, err = a.NewUser("pupshaw", "letmein", channels.SetOf("*")) a.Save(user) assertStatus(t, rt.sendRequest("GET", "/db/_session", ""), 200) response = rt.sendRequest("POST", "/db/_session", `{"name":"pupshaw", "password":"******"}`) assertStatus(t, response, 200) log.Printf("Set-Cookie: %s", response.Header().Get("Set-Cookie")) assert.True(t, response.Header().Get("Set-Cookie") != "") }
// Updates the Channels property of a document object with current & past channels. // Returns the set of channels that have changed (document joined or left in this revision) func (doc *document) updateChannels(newChannels base.Set) (changedChannels base.Set) { var changed []string oldChannels := doc.Channels if oldChannels == nil { oldChannels = ChannelMap{} doc.Channels = oldChannels } else { // Mark every no-longer-current channel as unsubscribed: curSequence := doc.Sequence for channel, removal := range oldChannels { if removal == nil && !newChannels.Contains(channel) { oldChannels[channel] = &ChannelRemoval{ Seq: curSequence, RevID: doc.CurrentRev, Deleted: doc.Deleted} changed = append(changed, channel) } } } // Mark every current channel as subscribed: for channel, _ := range newChannels { if value, exists := oldChannels[channel]; value != nil || !exists { oldChannels[channel] = nil changed = append(changed, channel) } } if changed != nil { base.LogTo("CRUD", "\tDoc %q in channels %q", doc.ID, newChannels) changedChannels = channels.SetOf(changed...) } return }
func (auth *Authenticator) defaultGuestUser() User { user := &userImpl{ roleImpl: roleImpl{ ExplicitChannels_: ch.SetOf("*").AtSequence(1), }, auth: auth, } user.Channels_ = user.ExplicitChannels_.Copy() return user }
func TestSaveRoles(t *testing.T) { auth := NewAuthenticator(gTestBucket, nil) role, _ := auth.NewRole("testRole", ch.SetOf("test")) err := auth.Save(role) assert.Equals(t, err, nil) role2, err := auth.GetRole("testRole") assert.Equals(t, err, nil) assert.DeepEquals(t, role2, role) }
func TestSaveUsers(t *testing.T) { auth := NewAuthenticator(gTestBucket, nil) user, _ := auth.NewUser("testUser", "password", ch.SetOf("test")) err := auth.Save(user) assert.Equals(t, err, nil) user2, err := auth.GetUser("testUser") assert.Equals(t, err, nil) assert.DeepEquals(t, user2, user) }
// Creates a Go-channel of all the changes made on a channel. // Does NOT handle the Wait option. Does NOT check authorization. func (db *Database) changesFeed(channel string, options ChangesOptions) (<-chan *ChangeEntry, error) { dbExpvars.Add("channelChangesFeeds", 1) log, err := db.changeCache.GetChangesInChannel(channel, options) if err != nil { return nil, err } if len(log) == 0 { // There are no entries newer than 'since'. Return an empty feed: feed := make(chan *ChangeEntry) close(feed) return feed, nil } feed := make(chan *ChangeEntry, 1) go func() { defer close(feed) // Now write each log entry to the 'feed' channel in turn: for _, logEntry := range log { if !options.Conflicts && (logEntry.Flags&channels.Hidden) != 0 { //continue // FIX: had to comment this out. // This entry is shadowed by a conflicting one. We would like to skip it. // The problem is that if this is the newest revision of this doc, then the // doc will appear under this sequence # in the changes view, which means // we won't emit the doc at all because we already stopped emitting entries // from the view before this point. } if logEntry.Sequence >= options.Since.TriggeredBy { options.Since.TriggeredBy = 0 } seqID := SequenceID{ Seq: logEntry.Sequence, TriggeredBy: options.Since.TriggeredBy, } change := ChangeEntry{ Seq: seqID, ID: logEntry.DocID, Deleted: (logEntry.Flags & channels.Deleted) != 0, Changes: []ChangeRev{{"rev": logEntry.RevID}}, branched: (logEntry.Flags & channels.Branched) != 0, } if logEntry.Flags&channels.Removed != 0 { change.Removed = channels.SetOf(channel) } select { case <-options.Terminator: base.LogTo("Changes+", "Aborting changesFeed") return case feed <- &change: } } }() return feed, nil }
// Unit test for bug #314 func TestChangesAfterChannelAdded(t *testing.T) { base.LogKeys["Cache"] = true base.LogKeys["Changes"] = true base.LogKeys["Changes+"] = true db := setupTestDB(t) defer tearDownTestDB(t, db) db.ChannelMapper = channels.NewDefaultChannelMapper() // Create a user with access to channel ABC authenticator := db.Authenticator() user, _ := authenticator.NewUser("naomi", "letmein", channels.SetOf("ABC")) authenticator.Save(user) // Create a doc on two channels (sequence 1): revid, _ := db.Put("doc1", Body{"channels": []string{"ABC", "PBS"}}) // Modify user to have access to both channels (sequence 2): userInfo, err := db.GetPrincipal("naomi", true) assert.True(t, userInfo != nil) userInfo.ExplicitChannels = base.SetOf("ABC", "PBS") _, err = db.UpdatePrincipal(*userInfo, true, true) assertNoError(t, err, "UpdatePrincipal failed") // Check the _changes feed: db.changeCache.waitForSequence(1) db.user, _ = authenticator.GetUser("naomi") changes, err := db.GetChanges(base.SetOf("*"), ChangesOptions{Since: SequenceID{Seq: 1}}) assertNoError(t, err, "Couldn't GetChanges") assert.Equals(t, len(changes), 2) assert.DeepEquals(t, changes[0], &ChangeEntry{ Seq: SequenceID{Seq: 1, TriggeredBy: 2}, ID: "doc1", Changes: []ChangeRev{{"rev": revid}}}) assert.DeepEquals(t, changes[1], &ChangeEntry{ Seq: SequenceID{Seq: 2}, ID: "_user/naomi", Changes: []ChangeRev{}}) // Add a new doc (sequence 3): revid, _ = db.Put("doc2", Body{"channels": []string{"PBS"}}) // Check the _changes feed -- this is to make sure the changeCache properly received // sequence 2 (the user doc) and isn't stuck waiting for it. db.changeCache.waitForSequence(3) changes, err = db.GetChanges(base.SetOf("*"), ChangesOptions{Since: SequenceID{Seq: 2}}) assertNoError(t, err, "Couldn't GetChanges (2nd)") assert.Equals(t, len(changes), 1) assert.DeepEquals(t, changes[0], &ChangeEntry{ Seq: SequenceID{Seq: 3}, ID: "doc2", Changes: []ChangeRev{{"rev": revid}}}) }
func TestUpdateDesignDoc(t *testing.T) { db := setupTestDB(t) defer tearDownTestDB(t, db) _, err := db.Put("_design/official", Body{}) assertNoError(t, err, "add design doc as admin") authenticator := auth.NewAuthenticator(db.Bucket, db) db.user, _ = authenticator.NewUser("naomi", "letmein", channels.SetOf("Netflix")) _, err = db.Put("_design/pwn3d", Body{}) assertHTTPError(t, err, 403) }
// Top-level handler for _changes feed requests. func (h *handler) handleChanges() error { // http://wiki.apache.org/couchdb/HTTP_database_API#Changes // http://docs.couchdb.org/en/latest/api/database/changes.html var options db.ChangesOptions options.Since = channels.TimedSetFromString(h.getQuery("since")) options.Limit = int(h.getIntQuery("limit", 0)) options.Conflicts = (h.getQuery("style") == "all_docs") options.IncludeDocs = (h.getBoolQuery("include_docs")) options.Terminator = make(chan bool) defer close(options.Terminator) // Get the channels as parameters to an imaginary "bychannel" filter. // The default is all channels the user can access. userChannels := channels.SetOf("*") filter := h.getQuery("filter") if filter != "" { if filter != "sync_gateway/bychannel" { return base.HTTPErrorf(http.StatusBadRequest, "Unknown filter; try sync_gateway/bychannel") } channelsParam := h.getQuery("channels") if channelsParam == "" { return base.HTTPErrorf(http.StatusBadRequest, "Missing 'channels' filter parameter") } var err error userChannels, err = channels.SetFromArray(strings.Split(channelsParam, ","), channels.ExpandStar) if err != nil { return err } if len(userChannels) == 0 { return base.HTTPErrorf(http.StatusBadRequest, "Empty channel list") } } h.db.ChangesClientStats.Increment() defer h.db.ChangesClientStats.Decrement() switch h.getQuery("feed") { case "normal", "": return h.sendSimpleChanges(userChannels, options) case "longpoll": options.Wait = true return h.sendSimpleChanges(userChannels, options) case "continuous": return h.sendContinuousChangesByHTTP(userChannels, options) case "websocket": return h.sendContinuousChangesByWebSocket(userChannels, options) default: return base.HTTPErrorf(http.StatusBadRequest, "Unknown feed type") } }
func TestRebuildChannelsError(t *testing.T) { computer := mockComputer{} auth := NewAuthenticator(gTestBucket, &computer) role, err := auth.NewRole("testRole2", ch.SetOf("explicit1")) assert.Equals(t, err, nil) assert.Equals(t, auth.InvalidateChannels(role), nil) computer.err = fmt.Errorf("I'm sorry, Dave.") role2, err := auth.GetRole("testRole2") assert.Equals(t, role2, nil) assert.DeepEquals(t, err, computer.err) }
func TestSerializeRole(t *testing.T) { auth := NewAuthenticator(gTestBucket, nil) role, _ := auth.NewRole("froods", ch.SetOf("hoopy", "public")) encoded, _ := json.Marshal(role) assert.True(t, encoded != nil) log.Printf("Marshaled Role as: %s", encoded) elor := &roleImpl{} err := json.Unmarshal(encoded, elor) assert.True(t, err == nil) assert.DeepEquals(t, elor.Name(), role.Name()) assert.DeepEquals(t, elor.ExplicitChannels(), role.ExplicitChannels()) }
func (auth *Authenticator) defaultGuestUser() User { user := &userImpl{ roleImpl: roleImpl{ ExplicitChannels_: ch.AtSequence(ch.SetOf(), 1), }, userImplBody: userImplBody{ Disabled_: true, }, auth: auth, } user.Channels_ = user.ExplicitChannels_.Copy() return user }
func TestSessionExtension(t *testing.T) { var rt restTester a := auth.NewAuthenticator(rt.bucket(), nil) user, err := a.GetUser("") assert.Equals(t, err, nil) user.SetDisabled(true) err = a.Save(user) assert.Equals(t, err, nil) user, err = a.GetUser("") assert.Equals(t, err, nil) assert.True(t, user.Disabled()) log.Printf("hello") response := rt.sendRequest("PUT", "/db/doc", `{"hi": "there"}`) assertStatus(t, response, 401) user, err = a.NewUser("pupshaw", "letmein", channels.SetOf("*")) a.Save(user) assertStatus(t, rt.sendAdminRequest("GET", "/db/_session", ""), 200) response = rt.sendAdminRequest("POST", "/db/_session", `{"name":"pupshaw", "ttl":10}`) assertStatus(t, response, 200) var body db.Body json.Unmarshal(response.Body.Bytes(), &body) sessionId := body["session_id"].(string) sessionExpiration := body["expires"].(string) assert.True(t, sessionId != "") assert.True(t, sessionExpiration != "") assert.True(t, body["cookie_name"].(string) == "SyncGatewaySession") reqHeaders := map[string]string{ "Cookie": "SyncGatewaySession=" + body["session_id"].(string), } response = rt.sendRequestWithHeaders("PUT", "/db/doc1", `{"hi": "there"}`, reqHeaders) assertStatus(t, response, 201) assert.True(t, response.Header().Get("Set-Cookie") == "") //Sleep for 2 seconds, this will ensure 10% of the 100 seconds session ttl has elapsed and //should cause a new Cookie to be sent by the server with the same session ID and an extended expiration date time.Sleep(2 * time.Second) response = rt.sendRequestWithHeaders("PUT", "/db/doc2", `{"hi": "there"}`, reqHeaders) assertStatus(t, response, 201) assert.True(t, response.Header().Get("Set-Cookie") != "") }
func TestRoleInheritance(t *testing.T) { // Create some roles: auth := NewAuthenticator(gTestBucket, nil) role, _ := auth.NewRole("square", ch.SetOf("dull", "duller", "dullest")) assert.Equals(t, auth.Save(role), nil) role, _ = auth.NewRole("frood", ch.SetOf("hoopy", "hoopier", "hoopiest")) assert.Equals(t, auth.Save(role), nil) user, _ := auth.NewUser("arthur", "password", ch.SetOf("britain")) user.(*userImpl).setRolesSince(ch.TimedSet{"square": 0x3, "nonexistent": 0x42, "frood": 0x4}) assert.DeepEquals(t, user.RoleNames(), ch.TimedSet{"square": 0x3, "nonexistent": 0x42, "frood": 0x4}) auth.Save(user) user2, err := auth.GetUser("arthur") assert.Equals(t, err, nil) log.Printf("Channels = %s", user2.Channels()) assert.DeepEquals(t, user2.Channels(), ch.AtSequence(ch.SetOf("britain"), 1)) assert.DeepEquals(t, user2.InheritedChannels(), ch.TimedSet{"britain": 0x1, "dull": 0x3, "duller": 0x3, "dullest": 0x3, "hoopy": 0x4, "hoopier": 0x4, "hoopiest": 0x4}) assert.True(t, user2.CanSeeChannel("britain")) assert.True(t, user2.CanSeeChannel("duller")) assert.True(t, user2.CanSeeChannel("hoopy")) assert.Equals(t, user2.AuthorizeAllChannels(ch.SetOf("britain", "dull", "hoopiest")), nil) }
func TestSerializeUser(t *testing.T) { auth := NewAuthenticator(gTestBucket, nil) user, _ := auth.NewUser("me", "letmein", ch.SetOf("me", "public")) user.SetEmail("*****@*****.**") encoded, _ := json.Marshal(user) assert.True(t, encoded != nil) log.Printf("Marshaled User as: %s", encoded) resu := &userImpl{} err := json.Unmarshal(encoded, resu) assert.True(t, err == nil) assert.DeepEquals(t, resu.Name(), user.Name()) assert.DeepEquals(t, resu.Email(), user.Email()) assert.DeepEquals(t, resu.ExplicitChannels(), user.ExplicitChannels()) assert.True(t, resu.Authenticate("letmein")) assert.False(t, resu.Authenticate("123456")) }
func (h *handler) handleChanges() error { // http://wiki.apache.org/couchdb/HTTP_database_API#Changes var options db.ChangesOptions options.Since = h.getIntQuery("since", 0) options.Limit = int(h.getIntQuery("limit", 0)) options.Conflicts = (h.getQuery("style") == "all_docs") options.IncludeDocs = (h.getBoolQuery("include_docs")) // Get the channels as parameters to an imaginary "bychannel" filter. // The default is all channels the user can access. userChannels := channels.SetOf("*") filter := h.getQuery("filter") if filter != "" { if filter != "sync_gateway/bychannel" { return &base.HTTPError{http.StatusBadRequest, "Unknown filter; try sync_gateway/bychannel"} } channelsParam := h.getQuery("channels") if channelsParam == "" { return &base.HTTPError{http.StatusBadRequest, "Missing 'channels' filter parameter"} } var err error userChannels, err = channels.SetFromArray(strings.Split(channelsParam, ","), channels.ExpandStar) if err != nil { return err } if len(userChannels) == 0 { return &base.HTTPError{http.StatusBadRequest, "Empty channel list"} } } switch h.getQuery("feed") { case "continuous": return h.handleContinuousChanges(userChannels, options) case "longpoll": options.Wait = true } return h.handleSimpleChanges(userChannels, options) }
func TestLogin(t *testing.T) { bucket, _ := db.ConnectToBucket("walrus:", "default", "test") a := auth.NewAuthenticator(bucket, nil) user, err := a.GetUser("") assert.Equals(t, err, nil) user.SetDisabled(true) err = a.Save(user) assert.Equals(t, err, nil) user, err = a.GetUser("") assert.Equals(t, err, nil) assert.True(t, user.Disabled()) response := callRESTOn(bucket, "PUT", "/db/doc", `{"hi": "there"}`) assertStatus(t, response, 401) user, err = a.NewUser("pupshaw", "letmein", channels.SetOf("*")) a.Save(user) response = callRESTOn(bucket, "POST", "/db/_session", `{"name":"pupshaw", "password":"******"}`) assertStatus(t, response, 200) log.Printf("Set-Cookie: %s", response.Header().Get("Set-Cookie")) assert.True(t, response.Header().Get("Set-Cookie") != "") }
func TestConflicts(t *testing.T) { AlwaysCompactChangeLog = true // Makes examining the change log deterministic defer func() { AlwaysCompactChangeLog = false }() db := setupTestDB(t) defer tearDownTestDB(t, db) db.ChannelMapper = channels.NewDefaultChannelMapper() // base.LogKeys["CRUD"] = true // base.LogKeys["Changes"] = true // Create rev 1 of "doc": body := Body{"n": 1, "channels": []string{"all", "1"}} assertNoError(t, db.PutExistingRev("doc", body, []string{"1-a"}), "add 1-a") log, _ := db.GetChangeLog("all", 0) assert.Equals(t, len(log.Entries), 1) assert.Equals(t, int(log.Since), 0) // Create two conflicting changes: body["n"] = 2 body["channels"] = []string{"all", "2b"} assertNoError(t, db.PutExistingRev("doc", body, []string{"2-b", "1-a"}), "add 2-b") body["n"] = 3 body["channels"] = []string{"all", "2a"} assertNoError(t, db.PutExistingRev("doc", body, []string{"2-a", "1-a"}), "add 2-a") // Verify the change with the higher revid won: gotBody, err := db.Get("doc") assert.DeepEquals(t, gotBody, Body{"_id": "doc", "_rev": "2-b", "n": int64(2), "channels": []interface{}{"all", "2b"}}) // Verify we can still get the other two revisions: gotBody, err = db.GetRev("doc", "1-a", false, nil) assert.DeepEquals(t, gotBody, Body{"_id": "doc", "_rev": "1-a", "n": int64(1), "channels": []interface{}{"all", "1"}}) gotBody, err = db.GetRev("doc", "2-a", false, nil) assert.DeepEquals(t, gotBody, Body{"_id": "doc", "_rev": "2-a", "n": int64(3), "channels": []interface{}{"all", "2a"}}) // Verify the change-log of the "all" channel: log, _ = db.GetChangeLog("all", 0) assert.Equals(t, len(log.Entries), 3) assert.Equals(t, int(log.Since), 0) assert.DeepEquals(t, log.Entries[0], &channels.LogEntry{Sequence: 1}) assert.DeepEquals(t, log.Entries[1], &channels.LogEntry{Sequence: 2, DocID: "doc", RevID: "2-b"}) assert.DeepEquals(t, log.Entries[2], &channels.LogEntry{Sequence: 3, DocID: "doc", RevID: "2-a", Flags: channels.Hidden}) // Verify the _changes feed: changes, err := db.GetChanges(channels.SetOf("all"), ChangesOptions{Conflicts: true}) assertNoError(t, err, "Couldn't GetChanges") assert.Equals(t, len(changes), 2) // (CouchDB would merge these into one entry, but the gateway doesn't.) assert.DeepEquals(t, changes[0], &ChangeEntry{ Seq: "all:2", ID: "doc", Changes: []ChangeRev{{"rev": "2-b"}}}) assert.DeepEquals(t, changes[1], &ChangeEntry{ Seq: "all:3", ID: "doc", Changes: []ChangeRev{{"rev": "2-a"}}}) changes, err = db.GetChanges(channels.SetOf("all"), ChangesOptions{Conflicts: false}) assertNoError(t, err, "Couldn't GetChanges") assert.Equals(t, len(changes), 1) assert.DeepEquals(t, changes[0], &ChangeEntry{ Seq: "all:2", ID: "doc", Changes: []ChangeRev{{"rev": "2-b"}}}) // Delete 2-b; verify this makes 2-a current: _, err = db.DeleteDoc("doc", "2-b") assertNoError(t, err, "delete 2-b") gotBody, err = db.Get("doc") assert.DeepEquals(t, gotBody, Body{"_id": "doc", "_rev": "2-a", "n": int64(3), "channels": []interface{}{"all", "2a"}}) // Verify channel assignments are correct for channels defined by 2-a: doc, _ := db.getDoc("doc") chan2a, found := doc.Channels["2a"] assert.True(t, found) assert.True(t, chan2a == nil) // currently in 2a assert.True(t, doc.Channels["2b"] != nil) // has been removed from 2b }
func TestAllDocs(t *testing.T) { AlwaysCompactChangeLog = true // Makes examining the change log deterministic defer func() { AlwaysCompactChangeLog = false }() db := setupTestDB(t) defer tearDownTestDB(t, db) // Lower the log capacity to 50 to ensure the test will overflow, causing logs to be truncated, // so the changes feed will have to backfill from its view. oldMaxLogLength := MaxChangeLogLength MaxChangeLogLength = 50 defer func() { MaxChangeLogLength = oldMaxLogLength }() base.LogKeys["Changes"] = true defer func() { base.LogKeys["Changes"] = false }() db.ChannelMapper = channels.NewDefaultChannelMapper() ids := make([]IDAndRev, 100) for i := 0; i < 100; i++ { channels := []string{"all"} if i%10 == 0 { channels = append(channels, "KFJC") } body := Body{"serialnumber": i, "channels": channels} ids[i].DocID = fmt.Sprintf("alldoc-%02d", i) revid, err := db.Put(ids[i].DocID, body) ids[i].RevID = revid assertNoError(t, err, "Couldn't create document") } alldocs, err := db.AllDocIDs() assertNoError(t, err, "AllDocIDs failed") assert.Equals(t, len(alldocs), 100) for i, entry := range alldocs { assert.DeepEquals(t, entry, ids[i]) } // Now delete one document and try again: _, err = db.DeleteDoc(ids[23].DocID, ids[23].RevID) assertNoError(t, err, "Couldn't delete doc 23") alldocs, err = db.AllDocIDs() assertNoError(t, err, "AllDocIDs failed") assert.Equals(t, len(alldocs), 99) for i, entry := range alldocs { j := i if i >= 23 { j++ } assert.DeepEquals(t, entry, ids[j]) } // Inspect the channel log to confirm that it's only got the last 50 sequences. // There are 101 sequences overall, so the 1st one it has should be #52. log, err := db.GetChangeLog("all", 0) assertNoError(t, err, "GetChangeLog") assert.Equals(t, log.Since, uint64(51)) assert.Equals(t, len(log.Entries), 50) assert.Equals(t, int(log.Entries[0].Sequence), 52) // Now check the changes feed: var options ChangesOptions changes, err := db.GetChanges(channels.SetOf("all"), options) assertNoError(t, err, "Couldn't GetChanges") assert.Equals(t, len(changes), 100) for i, change := range changes { seq := i + 1 if i >= 23 { seq++ } assert.Equals(t, change.Seq, fmt.Sprintf("all:%d", seq)) assert.Equals(t, change.Deleted, i == 99) var removed base.Set if i == 99 { removed = channels.SetOf("all") } assert.DeepEquals(t, change.Removed, removed) } options.IncludeDocs = true changes, err = db.GetChanges(channels.SetOf("KFJC"), options) assertNoError(t, err, "Couldn't GetChanges") assert.Equals(t, len(changes), 10) for i, change := range changes { assert.Equals(t, change.Seq, fmt.Sprintf("KFJC:%d", 10*i+1)) assert.Equals(t, change.ID, ids[10*i].DocID) assert.Equals(t, change.Deleted, false) assert.DeepEquals(t, change.Removed, base.Set(nil)) assert.Equals(t, change.Doc["serialnumber"], int64(10*i)) } // Trying to add the existing log should fail with no error added, err := db.AddChangeLog("all", log) assertNoError(t, err, "add channel log") assert.False(t, added) // Delete the channel log to test if it can be rebuilt: assertNoError(t, db.Bucket.Delete(channelLogDocID("all")), "delete channel log") // Get the changes feed; result should still be correct: changes, err = db.GetChanges(channels.SetOf("all"), options) assertNoError(t, err, "Couldn't GetChanges") assert.Equals(t, len(changes), 100) // Verify it was rebuilt log, err = db.GetChangeLog("all", 0) assertNoError(t, err, "GetChangeLog") assert.Equals(t, len(log.Entries), 50) assert.Equals(t, int(log.Entries[0].Sequence), 52) }
func TestRoleAccessChanges(t *testing.T) { base.LogKeys["Access"] = true base.LogKeys["CRUD"] = true base.LogKeys["Changes+"] = true rt := restTester{syncFn: `function(doc) {role(doc.user, doc.role);channel(doc.channel)}`} a := rt.ServerContext().Database("db").Authenticator() guest, err := a.GetUser("") assert.Equals(t, err, nil) guest.SetDisabled(false) err = a.Save(guest) assert.Equals(t, err, nil) // Create users: alice, err := a.NewUser("alice", "letmein", channels.SetOf("alpha")) a.Save(alice) zegpold, err := a.NewUser("zegpold", "letmein", channels.SetOf("beta")) a.Save(zegpold) hipster, err := a.NewRole("hipster", channels.SetOf("gamma")) a.Save(hipster) // Create some docs in the channels: response := rt.send(request("PUT", "/db/fashion", `{"user":"******","role":["role:hipster","role:bogus"]}`)) // seq=1 assertStatus(t, response, 201) var body db.Body json.Unmarshal(response.Body.Bytes(), &body) assert.Equals(t, body["ok"], true) fashionRevID := body["rev"].(string) assertStatus(t, rt.send(request("PUT", "/db/g1", `{"channel":"gamma"}`)), 201) // seq=2 assertStatus(t, rt.send(request("PUT", "/db/a1", `{"channel":"alpha"}`)), 201) // seq=3 assertStatus(t, rt.send(request("PUT", "/db/b1", `{"channel":"beta"}`)), 201) // seq=4 assertStatus(t, rt.send(request("PUT", "/db/d1", `{"channel":"delta"}`)), 201) // seq=5 // Check user access: alice, _ = a.GetUser("alice") assert.DeepEquals(t, alice.InheritedChannels(), channels.TimedSet{"alpha": 0x1, "gamma": 0x1}) assert.DeepEquals(t, alice.RoleNames(), channels.TimedSet{"bogus": 0x1, "hipster": 0x1}) zegpold, _ = a.GetUser("zegpold") assert.DeepEquals(t, zegpold.InheritedChannels(), channels.TimedSet{"beta": 0x1}) assert.DeepEquals(t, zegpold.RoleNames(), channels.TimedSet{}) // Check the _changes feed: var changes struct { Results []db.ChangeEntry Last_Seq interface{} } response = rt.send(requestByUser("GET", "/db/_changes", "", "alice")) log.Printf("1st _changes looks like: %s", response.Body.Bytes()) json.Unmarshal(response.Body.Bytes(), &changes) assert.Equals(t, len(changes.Results), 2) since := changes.Last_Seq assert.Equals(t, since, "3") response = rt.send(requestByUser("GET", "/db/_changes", "", "zegpold")) log.Printf("2nd _changes looks like: %s", response.Body.Bytes()) json.Unmarshal(response.Body.Bytes(), &changes) assert.Equals(t, len(changes.Results), 1) since = changes.Last_Seq assert.Equals(t, since, "4") // Update "fashion" doc to grant zegpold the role "hipster" and take it away from alice: str := fmt.Sprintf(`{"user":"******", "role":"role:hipster", "_rev":%q}`, fashionRevID) assertStatus(t, rt.send(request("PUT", "/db/fashion", str)), 201) // seq=6 // Check user access again: alice, _ = a.GetUser("alice") assert.DeepEquals(t, alice.InheritedChannels(), channels.TimedSet{"alpha": 0x1}) zegpold, _ = a.GetUser("zegpold") assert.DeepEquals(t, zegpold.InheritedChannels(), channels.TimedSet{"beta": 0x1, "gamma": 0x6}) // The complete _changes feed for zegpold contains docs g1 and b1: changes.Results = nil response = rt.send(requestByUser("GET", "/db/_changes", "", "zegpold")) log.Printf("3rd _changes looks like: %s", response.Body.Bytes()) json.Unmarshal(response.Body.Bytes(), &changes) assert.Equals(t, len(changes.Results), 2) assert.Equals(t, changes.Last_Seq, "6:2") assert.Equals(t, changes.Results[0].ID, "b1") assert.Equals(t, changes.Results[1].ID, "g1") // Changes feed with since=4 would ordinarily be empty, but zegpold got access to channel // gamma after sequence 4, so the pre-existing docs in that channel are included: base.LogKeys["Changes"] = true base.LogKeys["Cache"] = true response = rt.send(requestByUser("GET", "/db/_changes?since=4", "", "zegpold")) log.Printf("4th _changes looks like: %s", response.Body.Bytes()) changes.Results = nil json.Unmarshal(response.Body.Bytes(), &changes) assert.Equals(t, len(changes.Results), 1) assert.Equals(t, changes.Results[0].ID, "g1") base.LogKeys["Cache"] = false }
func TestChannelAccessChanges(t *testing.T) { // base.ParseLogFlags([]string{"Cache", "Changes+", "CRUD"}) rt := restTester{syncFn: `function(doc) {access(doc.owner, doc._id);channel(doc.channel)}`} a := rt.ServerContext().Database("db").Authenticator() guest, err := a.GetUser("") assert.Equals(t, err, nil) guest.SetDisabled(false) err = a.Save(guest) assert.Equals(t, err, nil) // Create users: alice, err := a.NewUser("alice", "letmein", channels.SetOf("zero")) a.Save(alice) zegpold, err := a.NewUser("zegpold", "letmein", channels.SetOf("zero")) a.Save(zegpold) // Create some docs that give users access: response := rt.send(request("PUT", "/db/alpha", `{"owner":"alice"}`)) // seq=1 assertStatus(t, response, 201) var body db.Body json.Unmarshal(response.Body.Bytes(), &body) assert.Equals(t, body["ok"], true) alphaRevID := body["rev"].(string) assertStatus(t, rt.send(request("PUT", "/db/beta", `{"owner":"boadecia"}`)), 201) // seq=2 assertStatus(t, rt.send(request("PUT", "/db/delta", `{"owner":"alice"}`)), 201) // seq=3 assertStatus(t, rt.send(request("PUT", "/db/gamma", `{"owner":"zegpold"}`)), 201) // seq=4 assertStatus(t, rt.send(request("PUT", "/db/a1", `{"channel":"alpha"}`)), 201) // seq=5 assertStatus(t, rt.send(request("PUT", "/db/b1", `{"channel":"beta"}`)), 201) // seq=6 assertStatus(t, rt.send(request("PUT", "/db/d1", `{"channel":"delta"}`)), 201) // seq=7 assertStatus(t, rt.send(request("PUT", "/db/g1", `{"channel":"gamma"}`)), 201) // seq=8 // Check the _changes feed: var changes struct { Results []db.ChangeEntry } response = rt.send(requestByUser("GET", "/db/_changes", "", "zegpold")) log.Printf("_changes looks like: %s", response.Body.Bytes()) err = json.Unmarshal(response.Body.Bytes(), &changes) assert.Equals(t, err, nil) assert.Equals(t, len(changes.Results), 1) since := changes.Results[0].Seq assert.Equals(t, changes.Results[0].ID, "g1") assert.Equals(t, since, db.SequenceID{Seq: 8}) // Check user access: alice, _ = a.GetUser("alice") assert.DeepEquals(t, alice.Channels(), channels.TimedSet{"zero": 0x1, "alpha": 0x1, "delta": 0x3}) zegpold, _ = a.GetUser("zegpold") assert.DeepEquals(t, zegpold.Channels(), channels.TimedSet{"zero": 0x1, "gamma": 0x4}) // Update a document to revoke access to alice and grant it to zegpold: str := fmt.Sprintf(`{"owner":"zegpold", "_rev":%q}`, alphaRevID) assertStatus(t, rt.send(request("PUT", "/db/alpha", str)), 201) // seq=9 // Check user access again: alice, _ = a.GetUser("alice") assert.DeepEquals(t, alice.Channels(), channels.TimedSet{"zero": 0x1, "delta": 0x3}) zegpold, _ = a.GetUser("zegpold") assert.DeepEquals(t, zegpold.Channels(), channels.TimedSet{"zero": 0x1, "alpha": 0x9, "gamma": 0x4}) // Look at alice's _changes feed: changes.Results = nil response = rt.send(requestByUser("GET", "/db/_changes", "", "alice")) log.Printf("//////// _changes for alice looks like: %s", response.Body.Bytes()) json.Unmarshal(response.Body.Bytes(), &changes) assert.Equals(t, len(changes.Results), 1) assert.Equals(t, changes.Results[0].ID, "d1") // The complete _changes feed for zegpold contains docs a1 and g1: changes.Results = nil response = rt.send(requestByUser("GET", "/db/_changes", "", "zegpold")) log.Printf("//////// _changes for zegpold looks like: %s", response.Body.Bytes()) json.Unmarshal(response.Body.Bytes(), &changes) assert.Equals(t, len(changes.Results), 2) assert.Equals(t, changes.Results[0].ID, "g1") assert.Equals(t, changes.Results[0].Seq, db.SequenceID{Seq: 8}) assert.Equals(t, changes.Results[1].ID, "a1") assert.Equals(t, changes.Results[1].Seq, db.SequenceID{Seq: 5, TriggeredBy: 9}) // Changes feed with since=gamma:8 would ordinarily be empty, but zegpold got access to channel // alpha after sequence 8, so the pre-existing docs in that channel are included: response = rt.send(requestByUser("GET", fmt.Sprintf("/db/_changes?since=%s", since), "", "zegpold")) log.Printf("_changes looks like: %s", response.Body.Bytes()) changes.Results = nil json.Unmarshal(response.Body.Bytes(), &changes) assert.Equals(t, len(changes.Results), 1) assert.Equals(t, changes.Results[0].ID, "a1") // What happens if we call access() with a nonexistent username? assertStatus(t, rt.send(request("PUT", "/db/epsilon", `{"owner":"waldo"}`)), 201) // Finally, throw a wrench in the works by changing the sync fn. Note that normally this wouldn't // be changed while the database is in use (only when it's re-opened) but for testing purposes // we do it now because we can't close and re-open an ephemeral Walrus database. dbc := rt.ServerContext().Database("db") db, _ := db.GetDatabase(dbc, nil) changed, err := db.UpdateSyncFun(`function(doc) {access("alice", "beta");channel("beta");}`) assert.Equals(t, err, nil) assert.True(t, changed) changeCount, err := db.UpdateAllDocChannels(true, false) assert.Equals(t, err, nil) assert.Equals(t, changeCount, 9) changes.Results = nil response = rt.send(requestByUser("GET", "/db/_changes", "", "alice")) log.Printf("_changes looks like: %s", response.Body.Bytes()) json.Unmarshal(response.Body.Bytes(), &changes) expectedIDs := []string{"beta", "delta", "gamma", "a1", "b1", "d1", "g1", "alpha", "epsilon"} assert.Equals(t, len(changes.Results), len(expectedIDs)) for i, expectedID := range expectedIDs { assert.Equals(t, changes.Results[i].ID, expectedID) } // Check accumulated statistics: assert.Equals(t, db.ChangesClientStats.TotalCount(), uint32(5)) assert.Equals(t, db.ChangesClientStats.MaxCount(), uint32(1)) db.ChangesClientStats.Reset() assert.Equals(t, db.ChangesClientStats.TotalCount(), uint32(0)) assert.Equals(t, db.ChangesClientStats.MaxCount(), uint32(0)) }
func TestAccessControl(t *testing.T) { type allDocsRow struct { ID string `json:"id"` Key string `json:"key"` Value struct { Rev string `json:"rev"` Channels []string `json:"channels,omitempty"` Access map[string]base.Set `json:"access,omitempty"` // for admins only } `json:"value"` Doc db.Body `json:"doc,omitempty"` Error string `json:"error"` } var allDocsResult struct { TotalRows int `json:"total_rows"` Offset int `json:"offset"` Rows []allDocsRow `json:"rows"` } // Create some docs: var rt restTester a := auth.NewAuthenticator(rt.bucket(), nil) guest, err := a.GetUser("") assert.Equals(t, err, nil) guest.SetDisabled(false) err = a.Save(guest) assert.Equals(t, err, nil) assertStatus(t, rt.sendRequest("PUT", "/db/doc1", `{"channels":[]}`), 201) assertStatus(t, rt.sendRequest("PUT", "/db/doc2", `{"channels":["CBS"]}`), 201) assertStatus(t, rt.sendRequest("PUT", "/db/doc3", `{"channels":["CBS", "Cinemax"]}`), 201) assertStatus(t, rt.sendRequest("PUT", "/db/doc4", `{"channels":["WB", "Cinemax"]}`), 201) guest.SetDisabled(true) err = a.Save(guest) assert.Equals(t, err, nil) // Create a user: alice, err := a.NewUser("alice", "letmein", channels.SetOf("Cinemax")) a.Save(alice) // Get a single doc the user has access to: request, _ := http.NewRequest("GET", "/db/doc3", nil) request.SetBasicAuth("alice", "letmein") response := rt.send(request) assertStatus(t, response, 200) // Get a single doc the user doesn't have access to: request, _ = http.NewRequest("GET", "/db/doc2", nil) request.SetBasicAuth("alice", "letmein") response = rt.send(request) assertStatus(t, response, 403) // Check that _all_docs only returns the docs the user has access to: request, _ = http.NewRequest("GET", "/db/_all_docs?channels=true", nil) request.SetBasicAuth("alice", "letmein") response = rt.send(request) assertStatus(t, response, 200) log.Printf("Response = %s", response.Body.Bytes()) err = json.Unmarshal(response.Body.Bytes(), &allDocsResult) assert.Equals(t, err, nil) assert.Equals(t, len(allDocsResult.Rows), 2) assert.Equals(t, allDocsResult.Rows[0].ID, "doc3") assert.DeepEquals(t, allDocsResult.Rows[0].Value.Channels, []string{"Cinemax"}) assert.Equals(t, allDocsResult.Rows[1].ID, "doc4") assert.DeepEquals(t, allDocsResult.Rows[1].Value.Channels, []string{"Cinemax"}) // Check _all_docs with include_docs option: request, _ = http.NewRequest("GET", "/db/_all_docs?include_docs=true", nil) request.SetBasicAuth("alice", "letmein") response = rt.send(request) assertStatus(t, response, 200) log.Printf("Response = %s", response.Body.Bytes()) err = json.Unmarshal(response.Body.Bytes(), &allDocsResult) assert.Equals(t, err, nil) assert.Equals(t, len(allDocsResult.Rows), 2) assert.Equals(t, allDocsResult.Rows[0].ID, "doc3") assert.Equals(t, allDocsResult.Rows[1].ID, "doc4") // Check POST to _all_docs: body := `{"keys": ["doc4", "doc1", "doc3", "b0gus"]}` request, _ = http.NewRequest("POST", "/db/_all_docs?channels=true", bytes.NewBufferString(body)) request.SetBasicAuth("alice", "letmein") response = rt.send(request) assertStatus(t, response, 200) log.Printf("Response from POST _all_docs = %s", response.Body.Bytes()) err = json.Unmarshal(response.Body.Bytes(), &allDocsResult) assert.Equals(t, err, nil) assert.Equals(t, len(allDocsResult.Rows), 4) assert.Equals(t, allDocsResult.Rows[0].Key, "doc4") assert.Equals(t, allDocsResult.Rows[0].ID, "doc4") assert.DeepEquals(t, allDocsResult.Rows[0].Value.Channels, []string{"Cinemax"}) assert.Equals(t, allDocsResult.Rows[1].Key, "doc1") assert.Equals(t, allDocsResult.Rows[1].Error, "forbidden") assert.Equals(t, allDocsResult.Rows[2].ID, "doc3") assert.DeepEquals(t, allDocsResult.Rows[2].Value.Channels, []string{"Cinemax"}) assert.Equals(t, allDocsResult.Rows[3].Key, "b0gus") assert.Equals(t, allDocsResult.Rows[3].Error, "not_found") // Check _all_docs as admin: response = rt.sendAdminRequest("GET", "/db/_all_docs", "") assertStatus(t, response, 200) log.Printf("Admin response = %s", response.Body.Bytes()) err = json.Unmarshal(response.Body.Bytes(), &allDocsResult) assert.Equals(t, err, nil) assert.Equals(t, len(allDocsResult.Rows), 4) assert.Equals(t, allDocsResult.Rows[0].ID, "doc1") assert.Equals(t, allDocsResult.Rows[1].ID, "doc2") }
func TestDocDeletionFromChannel(t *testing.T) { // See https://github.com/couchbase/couchbase-lite-ios/issues/59 // base.LogKeys["Changes"] = true // base.LogKeys["Cache"] = true rt := restTester{syncFn: `function(doc) {channel(doc.channel)}`} a := rt.ServerContext().Database("db").Authenticator() // Create user: alice, _ := a.NewUser("alice", "letmein", channels.SetOf("zero")) a.Save(alice) // Create a doc Alice can see: response := rt.send(request("PUT", "/db/alpha", `{"channel":"zero"}`)) // Check the _changes feed: rt.ServerContext().Database("db").WaitForPendingChanges() var changes struct { Results []db.ChangeEntry } response = rt.send(requestByUser("GET", "/db/_changes", "", "alice")) log.Printf("_changes looks like: %s", response.Body.Bytes()) json.Unmarshal(response.Body.Bytes(), &changes) assert.Equals(t, len(changes.Results), 1) since := changes.Results[0].Seq assert.Equals(t, since, db.SequenceID{Seq: 1}) assert.Equals(t, changes.Results[0].ID, "alpha") rev1 := changes.Results[0].Changes[0]["rev"] // Delete the document: assertStatus(t, rt.send(request("DELETE", "/db/alpha?rev="+rev1, "")), 200) // Get the updates from the _changes feed: time.Sleep(100 * time.Millisecond) response = rt.send(requestByUser("GET", fmt.Sprintf("/db/_changes?since=%s", since), "", "alice")) log.Printf("_changes looks like: %s", response.Body.Bytes()) changes.Results = nil json.Unmarshal(response.Body.Bytes(), &changes) assert.Equals(t, len(changes.Results), 1) assert.Equals(t, changes.Results[0].ID, "alpha") assert.Equals(t, changes.Results[0].Deleted, true) assert.DeepEquals(t, changes.Results[0].Removed, base.SetOf("zero")) rev2 := changes.Results[0].Changes[0]["rev"] // Now get the deleted revision: response = rt.send(requestByUser("GET", "/db/alpha?rev="+rev2, "", "alice")) assert.Equals(t, response.Code, 200) log.Printf("Deletion looks like: %s", response.Body.Bytes()) var docBody db.Body json.Unmarshal(response.Body.Bytes(), &docBody) assert.DeepEquals(t, docBody, db.Body{"_id": "alpha", "_rev": rev2, "_deleted": true}) // Access without deletion revID shouldn't be allowed (since doc is not in Alice's channels): response = rt.send(requestByUser("GET", "/db/alpha", "", "alice")) assert.Equals(t, response.Code, 403) // A bogus rev ID should return a 404: response = rt.send(requestByUser("GET", "/db/alpha?rev=bogus", "", "alice")) assert.Equals(t, response.Code, 404) // Get the old revision, which should still be accessible: response = rt.send(requestByUser("GET", "/db/alpha?rev="+rev1, "", "alice")) assert.Equals(t, response.Code, 200) }
// Returns a list of all the changes made on a channel. // Does NOT handle the Wait option. Does NOT check authorization. func (db *Database) changesFeed(channel string, options ChangesOptions) (<-chan *ChangeEntry, error) { since := options.Since[channel] channelLog, err := db.changesWriter.getChangeLog(channel, since) if err != nil { base.Warn("Error reading channel-log %q (using view instead): %v", channel, err) channelLog = nil } rebuildLog := channelLog == nil && err == nil && (EnableStarChannelLog || channel != "*") var log []*channels.LogEntry if channelLog != nil { log = channelLog.Entries } var viewFeed <-chan *ChangeEntry if channelLog == nil || channelLog.Since > since { var upToSeq uint64 if channelLog != nil { upToSeq = channelLog.Since } // Channel log may not go back far enough, so also fetch view-based change feed: viewFeed, err = db.changesFeedFromView(channel, options, upToSeq) if err != nil { return nil, err } } feed := make(chan *ChangeEntry, 5) go func() { defer close(feed) // First, if we need to backfill from the view, write its early entries to the channel: if viewFeed != nil { newLog := channels.ChangeLog{Since: since} for change := range viewFeed { if channelLog != nil && change.seqNo > channelLog.Since { // TODO: Close the view-based feed somehow break } select { case <-options.Terminator: base.LogTo("Changes+", "Aborting changesFeed (reading from view)") return case feed <- change: } if rebuildLog { // If there wasn't any channel log, build up a new one from the view: entry := channels.LogEntry{ Sequence: change.seqNo, DocID: change.ID, RevID: change.Changes[0]["rev"], } if change.Deleted { entry.Flags |= channels.Deleted } if change.Removed != nil { entry.Flags |= channels.Removed } newLog.Add(entry) newLog.TruncateTo(MaxChangeLogLength) } } if rebuildLog { // Save the missing channel log we just rebuilt: base.LogTo("Changes", "Saving rebuilt channel log %q with %d sequences", channel, len(newLog.Entries)) db.changesWriter.addChangeLog(channel, &newLog) } } // Now write each log entry to the 'feed' channel in turn: for _, logEntry := range log { if !options.Conflicts && (logEntry.Flags&channels.Hidden) != 0 { //continue // FIX: had to comment this out. // This entry is shadowed by a conflicting one. We would like to skip it. // The problem is that if this is the newest revision of this doc, then the // doc will appear under this sequence # in the changes view, which means // we won't emit the doc at all because we already stopped emitting entries // from the view before this point. } change := ChangeEntry{ seqNo: logEntry.Sequence, ID: logEntry.DocID, Deleted: (logEntry.Flags & channels.Deleted) != 0, Changes: []ChangeRev{{"rev": logEntry.RevID}}, } if logEntry.Flags&channels.Removed != 0 { change.Removed = channels.SetOf(channel) } else if options.IncludeDocs || options.Conflicts { doc, _ := db.GetDoc(logEntry.DocID) db.addDocToChangeEntry(doc, &change, options.IncludeDocs, false) } select { case <-options.Terminator: base.LogTo("Changes+", "Aborting changesFeed") return case feed <- &change: } if options.Limit > 0 { options.Limit-- if options.Limit == 0 { break } } } }() return feed, nil }
// Returns a list of all the changes made on a channel, reading from a view instead of the // channel log. This will include all historical changes, but may omit very recent ones. func (db *Database) changesFeedFromView(channel string, options ChangesOptions, upToSeq uint64) (<-chan *ChangeEntry, error) { base.LogTo("Changes", "Getting 'changes' view for channel %q %#v", channel, options) since := options.Since[channel] endkey := []interface{}{channel, upToSeq} if upToSeq == 0 { endkey[1] = map[string]interface{}{} // infinity } totalLimit := options.Limit usingDocs := options.Conflicts || options.IncludeDocs opts := Body{"stale": false, "update_seq": true, "endkey": endkey, "include_docs": usingDocs} feed := make(chan *ChangeEntry, kChangesViewPageSize) // Generate the output in a new goroutine, writing to 'feed': go func() { defer close(feed) for { // Query the 'channels' view: opts["startkey"] = []interface{}{channel, since + 1} limit := totalLimit if limit == 0 || limit > kChangesViewPageSize { limit = kChangesViewPageSize } opts["limit"] = limit var waiter *changeWaiter if options.Wait { waiter = db.tapListener.NewWaiterWithChannels(base.SetOf(channel), nil) } var vres ViewResult var err error for len(vres.Rows) == 0 { base.LogTo("Changes+", "Querying 'changes' for channel %q %#v", channel, opts) vres = ViewResult{} err = db.Bucket.ViewCustom("sync_gateway", "channels", opts, &vres) if err != nil { base.Log("Error from 'channels' view: %v", err) return } if len(vres.Rows) == 0 { if waiter == nil || !waiter.Wait() { return } } } for _, row := range vres.Rows { key := row.Key.([]interface{}) since = uint64(key[1].(float64)) value := row.Value.([]interface{}) docID := value[0].(string) revID := value[1].(string) entry := &ChangeEntry{ seqNo: since, ID: docID, Changes: []ChangeRev{{"rev": revID}}, Deleted: (len(value) >= 3 && value[2].(bool)), } if len(value) >= 4 && value[3].(bool) { entry.Removed = channels.SetOf(channel) } else if usingDocs { doc, _ := unmarshalDocument(docID, row.Doc.Json) db.addDocToChangeEntry(doc, entry, options.IncludeDocs, options.Conflicts) } select { case <-options.Terminator: base.LogTo("Changes+", "Aborting changesFeedFromView") return case feed <- entry: } } // Step to the next page of results: nRows := len(vres.Rows) if nRows < kChangesViewPageSize || options.Wait { break } if totalLimit > 0 { totalLimit -= nRows if totalLimit <= 0 { break } } delete(opts, "stale") // we only need to update the index once } }() return feed, nil }
func TestConflicts(t *testing.T) { db := setupTestDB(t) defer tearDownTestDB(t, db) db.ChannelMapper = channels.NewDefaultChannelMapper() //base.LogKeys["Cache"] = true // base.LogKeys["CRUD"] = true // base.LogKeys["Changes"] = true // Create rev 1 of "doc": body := Body{"n": 1, "channels": []string{"all", "1"}} assertNoError(t, db.PutExistingRev("doc", body, []string{"1-a"}), "add 1-a") time.Sleep(50 * time.Millisecond) // Wait for tap feed to catch up log := db.GetChangeLog("all", 0) assert.Equals(t, len(log), 1) // Create two conflicting changes: body["n"] = 2 body["channels"] = []string{"all", "2b"} assertNoError(t, db.PutExistingRev("doc", body, []string{"2-b", "1-a"}), "add 2-b") body["n"] = 3 body["channels"] = []string{"all", "2a"} assertNoError(t, db.PutExistingRev("doc", body, []string{"2-a", "1-a"}), "add 2-a") time.Sleep(50 * time.Millisecond) // Wait for tap feed to catch up // Verify the change with the higher revid won: gotBody, err := db.Get("doc") assert.DeepEquals(t, gotBody, Body{"_id": "doc", "_rev": "2-b", "n": int64(2), "channels": []interface{}{"all", "2b"}}) // Verify we can still get the other two revisions: gotBody, err = db.GetRev("doc", "1-a", false, nil) assert.DeepEquals(t, gotBody, Body{"_id": "doc", "_rev": "1-a", "n": 1, "channels": []string{"all", "1"}}) gotBody, err = db.GetRev("doc", "2-a", false, nil) assert.DeepEquals(t, gotBody, Body{"_id": "doc", "_rev": "2-a", "n": 3, "channels": []string{"all", "2a"}}) // Verify the change-log of the "all" channel: db.changeCache.waitForSequence(3) log = db.GetChangeLog("all", 0) assert.Equals(t, len(log), 1) assert.Equals(t, log[0].Sequence, uint64(3)) assert.Equals(t, log[0].DocID, "doc") assert.Equals(t, log[0].RevID, "2-b") assert.Equals(t, log[0].Flags, uint8(channels.Hidden|channels.Branched|channels.Conflict)) // Verify the _changes feed: options := ChangesOptions{ Conflicts: true, } changes, err := db.GetChanges(channels.SetOf("all"), options) assertNoError(t, err, "Couldn't GetChanges") assert.Equals(t, len(changes), 1) assert.DeepEquals(t, changes[0], &ChangeEntry{ Seq: SequenceID{Seq: 3}, ID: "doc", Changes: []ChangeRev{{"rev": "2-b"}, {"rev": "2-a"}}, branched: true}) // Delete 2-b; verify this makes 2-a current: rev3, err := db.DeleteDoc("doc", "2-b") assertNoError(t, err, "delete 2-b") gotBody, err = db.Get("doc") assert.DeepEquals(t, gotBody, Body{"_id": "doc", "_rev": "2-a", "n": int64(3), "channels": []interface{}{"all", "2a"}}) // Verify channel assignments are correct for channels defined by 2-a: doc, _ := db.GetDoc("doc") chan2a, found := doc.Channels["2a"] assert.True(t, found) assert.True(t, chan2a == nil) // currently in 2a assert.True(t, doc.Channels["2b"] != nil) // has been removed from 2b // Verify the _changes feed: db.changeCache.waitForSequence(4) changes, err = db.GetChanges(channels.SetOf("all"), options) assertNoError(t, err, "Couldn't GetChanges") assert.Equals(t, len(changes), 1) assert.DeepEquals(t, changes[0], &ChangeEntry{ Seq: SequenceID{Seq: 4}, ID: "doc", Changes: []ChangeRev{{"rev": "2-a"}, {"rev": rev3}}, branched: true}) }
func TestAccessControl(t *testing.T) { type viewRow struct { ID string `json:"id"` Key string `json:"key"` Value map[string]string `json:"value"` Doc db.Body `json:"doc,omitempty"` } var viewResult struct { TotalRows int `json:"total_rows"` Offset int `json:"offset"` Rows []viewRow `json:"rows"` } // Create some docs: var rt restTester a := auth.NewAuthenticator(rt.bucket(), nil) guest, err := a.GetUser("") assert.Equals(t, err, nil) guest.SetDisabled(false) err = a.Save(guest) assert.Equals(t, err, nil) assertStatus(t, rt.sendRequest("PUT", "/db/doc1", `{"channels":[]}`), 201) assertStatus(t, rt.sendRequest("PUT", "/db/doc2", `{"channels":["CBS"]}`), 201) assertStatus(t, rt.sendRequest("PUT", "/db/doc3", `{"channels":["CBS", "Cinemax"]}`), 201) assertStatus(t, rt.sendRequest("PUT", "/db/doc4", `{"channels":["WB", "Cinemax"]}`), 201) guest.SetDisabled(true) err = a.Save(guest) assert.Equals(t, err, nil) // Create a user: alice, err := a.NewUser("alice", "letmein", channels.SetOf("Cinemax")) a.Save(alice) // Get a single doc the user has access to: request, _ := http.NewRequest("GET", "/db/doc3", nil) request.SetBasicAuth("alice", "letmein") response := rt.send(request) assertStatus(t, response, 200) // Get a single doc the user doesn't have access to: request, _ = http.NewRequest("GET", "/db/doc2", nil) request.SetBasicAuth("alice", "letmein") response = rt.send(request) assertStatus(t, response, 403) // Check that _all_docs only returns the docs the user has access to: request, _ = http.NewRequest("GET", "/db/_all_docs", nil) request.SetBasicAuth("alice", "letmein") response = rt.send(request) assertStatus(t, response, 200) log.Printf("Response = %s", response.Body.Bytes()) err = json.Unmarshal(response.Body.Bytes(), &viewResult) assert.Equals(t, err, nil) assert.Equals(t, len(viewResult.Rows), 2) assert.Equals(t, viewResult.Rows[0].ID, "doc3") assert.Equals(t, viewResult.Rows[1].ID, "doc4") // Check _all_docs with include_docs option: request, _ = http.NewRequest("GET", "/db/_all_docs?include_docs=true", nil) request.SetBasicAuth("alice", "letmein") response = rt.send(request) assertStatus(t, response, 200) log.Printf("Response = %s", response.Body.Bytes()) err = json.Unmarshal(response.Body.Bytes(), &viewResult) assert.Equals(t, err, nil) assert.Equals(t, len(viewResult.Rows), 2) assert.Equals(t, viewResult.Rows[0].ID, "doc3") assert.Equals(t, viewResult.Rows[1].ID, "doc4") }