func TestSyncFnOnPush(t *testing.T) { db := setupTestDB(t) defer tearDownTestDB(t, db) db.ChannelMapper = channels.NewChannelMapper(`function(doc, oldDoc) { log("doc _id = "+doc._id+", _rev = "+doc._rev); if (oldDoc) log("oldDoc _id = "+oldDoc._id+", _rev = "+oldDoc._rev); channel(doc.channels); }`) // Create first revision: body := Body{"key1": "value1", "key2": 1234, "channels": []string{"public"}} rev1id, err := db.Put("doc1", body) assertNoError(t, err, "Couldn't create document") // Add several revisions at once to a doc, as on a push: log.Printf("Check PutExistingRev...") body["_rev"] = "4-four" body["key1"] = "fourth value" body["key2"] = int64(4444) body["channels"] = "clibup" history := []string{"4-four", "3-three", "2-488724414d0ed6b398d6d2aeb228d797", rev1id} err = db.PutExistingRev("doc1", body, history) assertNoError(t, err, "PutExistingRev failed") // Check that the doc has the correct channel (test for issue #300) doc, err := db.GetDoc("doc1") assert.DeepEquals(t, doc.Channels, channels.ChannelMap{ "clibup": nil, // i.e. it is currently in this channel (no removal) "public": &channels.ChannelRemoval{Seq: 2, RevID: "4-four"}, }) assert.DeepEquals(t, doc.History["4-four"].Channels, base.SetOf("clibup")) }
func TestAccessFunctionValidation(t *testing.T) { db := setupTestDB(t) defer tearDownTestDB(t, db) var err error db.ChannelMapper = channels.NewChannelMapper(`function(doc){access(doc.users,doc.userChannels);}`) body := Body{"users": []string{"username"}, "userChannels": []string{"BBC1"}} _, err = db.Put("doc1", body) assertNoError(t, err, "") body = Body{"users": []string{"role:rolename"}, "userChannels": []string{"BBC1"}} _, err = db.Put("doc2", body) assertNoError(t, err, "") body = Body{"users": []string{"bad username"}, "userChannels": []string{"BBC1"}} _, err = db.Put("doc3", body) assertHTTPError(t, err, 500) body = Body{"users": []string{"role:bad rolename"}, "userChannels": []string{"BBC1"}} _, err = db.Put("doc4", body) assertHTTPError(t, err, 500) body = Body{"users": []string{"roll:over"}, "userChannels": []string{"BBC1"}} _, err = db.Put("doc5", body) assertHTTPError(t, err, 500) body = Body{"users": []string{"username"}, "userChannels": []string{"bad name"}} _, err = db.Put("doc6", body) assertHTTPError(t, err, 500) }
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) }
// Sets the database object's channelMapper and validator based on the JS code in _design/channels func (db *Database) ReadDesignDocument() error { body, err := db.Get("_design/channels") if err != nil { if status, _ := base.ErrorAsHTTPStatus(err); status == http.StatusNotFound { err = nil // missing design document is not an error } return err } src, ok := body["channelmap"].(string) if ok { log.Printf("Channel mapper = %s", src) db.ChannelMapper, err = channels.NewChannelMapper(src) if err != nil { log.Printf("WARNING: Error loading channel mapper: %s", err) return err } } src, ok = body["validate_doc_update"].(string) if ok { log.Printf("Validator = %s", src) db.Validator, err = NewValidator(src) if err != nil { log.Printf("WARNING: Error loading validator: %s", err) return err } } return nil }
// Initialize REST handlers. Call this once on launch. func InitREST(bucket *couchbase.Bucket, dbName string, serverURL string) *context { if dbName == "" { dbName = bucket.Name } dbcontext := &db.DatabaseContext{Name: dbName, Bucket: bucket} newdb, _ := db.GetDatabase(dbcontext, nil) newdb.ReadDesignDocument() if dbcontext.ChannelMapper == nil { log.Printf("Channel mapper undefined; using default") // Always have a channel mapper object even if it does nothing: dbcontext.ChannelMapper, _ = channels.NewChannelMapper("") } if dbcontext.Validator == nil { log.Printf("Validator undefined; no validation") } c := &context{ dbcontext: dbcontext, auth: auth.NewAuthenticator(bucket), serverURL: serverURL, } http.Handle("/", createHandler(c)) return c }
func callREST(method, resource string, body string) *httptest.ResponseRecorder { input := bytes.NewBufferString(body) request, _ := http.NewRequest(method, "http://localhost"+resource, input) response := httptest.NewRecorder() mapper, _ := channels.NewChannelMapper(`function(doc) {sync(doc.channels);}`) response.Code = 200 // doesn't seem to be initialized by default; filed Go bug #4188 dbcontext := &db.DatabaseContext{"db", gTestBucket, mapper, nil} context := &context{dbcontext, nil, ""} handler := createHandler(context) handler.ServeHTTP(response, request) return response }
// Sets the database context's channelMapper based on the JS code from config func (context *DatabaseContext) ApplySyncFun(syncFun string) error { var err error if context.ChannelMapper != nil { _, err = context.ChannelMapper.SetFunction(syncFun) } else { context.ChannelMapper, err = channels.NewChannelMapper(syncFun) } if err != nil { base.Warn("Error setting sync function: %s", err) return err } return nil }
// Sets the database context's sync function based on the JS code from config. // If the function is different from the prior one, all documents are run through it again to // update their channel assignments and the access privileges they assign to users and roles. // If importExistingDocs is true, documents in the bucket that are not known to Sync Gateway will // be imported (have _sync data added) and run through the sync function. func (context *DatabaseContext) ApplySyncFun(syncFun string, importExistingDocs bool) error { var err error if syncFun == "" { context.ChannelMapper = nil } else if context.ChannelMapper != nil { _, err = context.ChannelMapper.SetFunction(syncFun) } else { context.ChannelMapper = channels.NewChannelMapper(syncFun) } if err != nil { base.Warn("Error setting sync function: %s", err) return err } // Check whether the sync function is different from the previous one: var syncData struct { Sync string } err = context.Bucket.Get(kSyncDataKey, &syncData) syncDataMissing := base.IsDocNotFoundError(err) if err != nil && !syncDataMissing { return err } else if syncFun == syncData.Sync { // Sync function hasn't changed. But if importing, scan imported docs anyway: if importExistingDocs { db := &Database{context, nil} return db.UpdateAllDocChannels(false, importExistingDocs) } return nil } else { if !syncDataMissing { // It's changed, so re-run it on all docs: db := &Database{context, nil} if err = db.UpdateAllDocChannels(true, importExistingDocs); err != nil { return err } } // Finally save the new function source: syncData.Sync = syncFun return context.Bucket.Set(kSyncDataKey, 0, syncData) } }
// Sets the database context's sync function based on the JS code from config. // Returns a boolean indicating whether the function is different from the saved one. // If multiple gateway instances try to update the function at the same time (to the same new // value) only one of them will get a changed=true result. func (context *DatabaseContext) UpdateSyncFun(syncFun string) (changed bool, err error) { if syncFun == "" { context.ChannelMapper = nil } else if context.ChannelMapper != nil { _, err = context.ChannelMapper.SetFunction(syncFun) } else { context.ChannelMapper = channels.NewChannelMapper(syncFun) } if err != nil { base.Warn("Error setting sync function: %s", err) return } var syncData struct { // format of the sync-fn document Sync string } err = context.Bucket.Update(kSyncDataKey, 0, func(currentValue []byte) ([]byte, error) { // The first time opening a new db, currentValue will be nil. Don't treat this as a change. if currentValue != nil { parseErr := json.Unmarshal(currentValue, &syncData) if parseErr != nil || syncData.Sync != syncFun { changed = true } } if changed || currentValue == nil { syncData.Sync = syncFun return json.Marshal(syncData) } else { return nil, couchbase.UpdateCancel // value unchanged, no need to save } }) if err == couchbase.UpdateCancel { err = nil } return }