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)) }
// Adds a database to the ServerContext given its configuration. func (sc *ServerContext) AddDatabaseFromConfig(config *DbConfig) (*db.DatabaseContext, error) { server := "http://localhost:8091" pool := "default" bucketName := config.name if config.Server != nil { server = *config.Server } if config.Pool != nil { pool = *config.Pool } if config.Bucket != nil { bucketName = *config.Bucket } dbName := config.name if dbName == "" { dbName = bucketName } base.Log("Opening db /%s as bucket %q, pool %q, server <%s>", dbName, bucketName, pool, server) if err := db.ValidateDatabaseName(dbName); err != nil { return nil, err } var importDocs, autoImport bool switch config.ImportDocs { case nil, false: case true: importDocs = true case "continuous": importDocs = true autoImport = true default: return nil, fmt.Errorf("Unrecognized value for ImportDocs: %#v", config.ImportDocs) } // Connect to the bucket and add the database: spec := base.BucketSpec{ Server: server, PoolName: pool, BucketName: bucketName, } if config.Username != "" { spec.Auth = config } bucket, err := db.ConnectToBucket(spec) if err != nil { return nil, err } dbcontext, err := db.NewDatabaseContext(dbName, bucket, autoImport) if err != nil { return nil, err } syncFn := "" if config.Sync != nil { syncFn = *config.Sync } if err := sc.applySyncFunction(dbcontext, syncFn); err != nil { return nil, err } if importDocs { db, _ := db.GetDatabase(dbcontext, nil) if _, err := db.UpdateAllDocChannels(false, true); err != nil { return nil, err } } if config.RevsLimit != nil && *config.RevsLimit > 0 { dbcontext.RevsLimit = *config.RevsLimit } if dbcontext.ChannelMapper == nil { base.Log("Using default sync function 'channel(doc.channels)' for database %q", dbName) } // Create default users & roles: if err := sc.installPrincipals(dbcontext, config.Roles, "role"); err != nil { return nil, err } else if err := sc.installPrincipals(dbcontext, config.Users, "user"); err != nil { return nil, err } // Install bucket-shadower if any: if shadow := config.Shadow; shadow != nil { if err := sc.startShadowing(dbcontext, shadow); err != nil { base.Warn("Database %q: unable to connect to external bucket for shadowing: %v", dbName, err) } } // Register it so HTTP handlers can find it: if err := sc.registerDatabase(dbcontext); err != nil { dbcontext.Close() return nil, err } sc.setDatabaseConfig(config.name, config) return dbcontext, nil }