// Cleans up the Value property, and removes rows that aren't visible to the current user func filterViewResult(input walrus.ViewResult, user auth.User) (result walrus.ViewResult) { checkChannels := false var visibleChannels ch.TimedSet if user != nil { visibleChannels = user.InheritedChannels() checkChannels = !visibleChannels.Contains("*") } result.TotalRows = input.TotalRows result.Rows = make([]*walrus.ViewRow, 0, len(input.Rows)/2) for _, row := range input.Rows { value := row.Value.([]interface{}) // value[0] is the array of channels; value[1] is the actual value if !checkChannels || channelsIntersect(visibleChannels, value[0].([]interface{})) { // Add this row: stripSyncProperty(row) result.Rows = append(result.Rows, &walrus.ViewRow{ Key: row.Key, Value: value[1], ID: row.ID, Doc: row.Doc, }) } } return }
// Is any item of channels found in visibleChannels? func channelsIntersect(visibleChannels ch.TimedSet, channels []interface{}) bool { for _, channel := range channels { if visibleChannels.Contains(channel.(string)) || channel == "*" { return true } } return false }
// Cleans up the Value property, and removes rows that aren't visible to the current user func filterViewResult(input sgbucket.ViewResult, user auth.User, applyChannelFiltering bool) (result sgbucket.ViewResult) { hasStarChannel := false var visibleChannels ch.TimedSet if user != nil { visibleChannels = user.InheritedChannels() hasStarChannel = !visibleChannels.Contains("*") if !applyChannelFiltering { return // this is an error } } result.TotalRows = input.TotalRows result.Rows = make([]*sgbucket.ViewRow, 0, len(input.Rows)/2) for _, row := range input.Rows { if applyChannelFiltering { value, ok := row.Value.([]interface{}) if ok { // value[0] is the array of channels; value[1] is the actual value if !hasStarChannel || channelsIntersect(visibleChannels, value[0].([]interface{})) { // Add this row: stripSyncProperty(row) result.Rows = append(result.Rows, &sgbucket.ViewRow{ Key: row.Key, Value: value[1], ID: row.ID, Doc: row.Doc, }) } } } else { // Add the raw row: stripSyncProperty(row) result.Rows = append(result.Rows, &sgbucket.ViewRow{ Key: row.Key, Value: row.Value, ID: row.ID, Doc: row.Doc, }) } } result.TotalRows = len(result.Rows) return }
// HTTP handler for _all_docs func (h *handler) handleAllDocs() error { // http://wiki.apache.org/couchdb/HTTP_Bulk_Document_API includeDocs := h.getBoolQuery("include_docs") includeChannels := h.getBoolQuery("channels") includeAccess := h.getBoolQuery("access") && h.user == nil includeRevs := h.getBoolQuery("revs") includeSeqs := h.getBoolQuery("update_seq") // Get the doc IDs if this is a POST request: var explicitDocIDs []string if h.rq.Method == "POST" { input, err := h.readJSON() if err == nil { keys, ok := input["keys"].([]interface{}) explicitDocIDs = make([]string, len(keys)) for i := 0; i < len(keys); i++ { if explicitDocIDs[i], ok = keys[i].(string); !ok { break } } if !ok { err = base.HTTPErrorf(http.StatusBadRequest, "Bad/missing keys") } } } else if h.rq.Method == "GET" { keys := h.getQuery("keys") if len(keys) > 0 { var queryKeys []string err := json.Unmarshal([]byte(keys), &queryKeys) if err != nil { err = base.HTTPErrorf(http.StatusBadRequest, "Bad keys") } if len(queryKeys) > 0 { explicitDocIDs = queryKeys } } } // Get the set of channels the user has access to; nil if user is admin or has access to user "*" var availableChannels channels.TimedSet if h.user != nil { availableChannels = h.user.InheritedChannels() if availableChannels == nil { panic("no channels for user?") } if availableChannels.Contains(channels.UserStarChannel) { availableChannels = nil } } // Subroutines that filter a channel list down to the ones that the user has access to: filterChannels := func(channels []string) []string { if availableChannels == nil { return channels } dst := 0 for _, ch := range channels { if availableChannels.Contains(ch) { channels[dst] = ch dst++ } } if dst == 0 { return nil } return channels[0:dst] } filterChannelSet := func(channelMap channels.ChannelMap) []string { var result []string if availableChannels == nil { result = []string{} } for ch, rm := range channelMap { if availableChannels == nil || availableChannels.Contains(ch) { //Do not include channels doc removed from in this rev if rm == nil { result = append(result, ch) } } } return result } type allDocsRowValue struct { Rev string `json:"rev"` Channels []string `json:"channels,omitempty"` Access map[string]base.Set `json:"access,omitempty"` // for admins only } type allDocsRow struct { Key string `json:"key"` ID string `json:"id,omitempty"` Value *allDocsRowValue `json:"value,omitempty"` Doc db.Body `json:"doc,omitempty"` UpdateSeq uint64 `json:"update_seq,omitempty"` Error string `json:"error,omitempty"` Status int `json:"status,omitempty"` } // Subroutine that creates a response row for a document: totalRows := 0 createRow := func(doc db.IDAndRev, channels []string) *allDocsRow { row := &allDocsRow{Key: doc.DocID} value := allDocsRowValue{} // Filter channels to ones available to user, and bail out if inaccessible: if explicitDocIDs == nil { if channels = filterChannels(channels); channels == nil { return nil // silently skip this doc } } if explicitDocIDs != nil || includeDocs || includeAccess { // Fetch the document body and other metadata that lives with it: body, channelSet, access, roleAccess, _, _, err := h.db.GetRevAndChannels(doc.DocID, doc.RevID, includeRevs) if err != nil { row.Status, _ = base.ErrorAsHTTPStatus(err) return row } else if body["_removed"] != nil { row.Status = http.StatusForbidden return row } if explicitDocIDs != nil { if channels = filterChannelSet(channelSet); channels == nil { row.Status = http.StatusForbidden return row } doc.RevID = body["_rev"].(string) } if includeDocs { row.Doc = body } if includeAccess && (access != nil || roleAccess != nil) { value.Access = map[string]base.Set{} for userName, channels := range access { value.Access[userName] = channels.AsSet() } for roleName, channels := range roleAccess { value.Access["role:"+roleName] = channels.AsSet() } } } row.Value = &value row.ID = doc.DocID value.Rev = doc.RevID if includeSeqs { row.UpdateSeq = doc.Sequence } if includeChannels { row.Value.Channels = channels } return row } // Subroutine that writes a response entry for a document: writeDoc := func(doc db.IDAndRev, channels []string) bool { row := createRow(doc, channels) if row != nil { if row.Status >= 300 { row.Error = base.CouchHTTPErrorName(row.Status) } if totalRows > 0 { h.response.Write([]byte(",")) } totalRows++ h.addJSON(row) return true } return false } var options db.ForEachDocIDOptions options.Startkey = h.getQuery("startkey") options.Endkey = h.getQuery("endkey") options.Limit = h.getIntQuery("limit", 0) // Now it's time to actually write the response! lastSeq, _ := h.db.LastSequence() h.setHeader("Content-Type", "application/json") h.response.Write([]byte(`{"rows":[` + "\n")) if explicitDocIDs != nil { count := uint64(0) for _, docID := range explicitDocIDs { writeDoc(db.IDAndRev{DocID: docID, RevID: "", Sequence: 0}, nil) count++ if options.Limit > 0 && count == options.Limit { break } } } else { if err := h.db.ForEachDocID(writeDoc, options); err != nil { return err } } h.response.Write([]byte(fmt.Sprintf("],\n"+`"total_rows":%d,"update_seq":%d}`, totalRows, lastSeq))) return nil }