// Fetch a configuration for a database from the ConfigServer func (sc *ServerContext) getDbConfigFromServer(dbName string) (*DbConfig, error) { if sc.config.ConfigServer == nil { return nil, base.HTTPErrorf(http.StatusNotFound, "not_found") } urlStr := *sc.config.ConfigServer if !strings.HasSuffix(urlStr, "/") { urlStr += "/" } urlStr += url.QueryEscape(dbName) res, err := sc.HTTPClient.Get(urlStr) if err != nil { return nil, base.HTTPErrorf(http.StatusBadGateway, "Error contacting config server: %v", err) } else if res.StatusCode >= 300 { return nil, base.HTTPErrorf(res.StatusCode, res.Status) } var config DbConfig j := json.NewDecoder(res.Body) if err = j.Decode(&config); err != nil { return nil, base.HTTPErrorf(http.StatusBadGateway, "Bad response from config server: %v", err) } if err = config.setup(dbName); err != nil { return nil, err } return &config, nil }
// Verifies a Persona/BrowserID assertion received from a client, returning either the verification // response (which includes the verified email address) or an error. // The 'audience' parameter must be the same as the 'origin' parameter the client used when // requesting the assertion, i.e. the root URL of this website. func VerifyPersona(assertion string, audience string) (*PersonaResponse, error) { // See <https://developer.mozilla.org/en-US/docs/Persona/Remote_Verification_API> res, err := http.PostForm("https://verifier.login.persona.org/verify", url.Values{"assertion": {assertion}, "audience": {audience}}) if err != nil { return nil, err } defer res.Body.Close() if res.StatusCode >= 300 { return nil, base.HTTPErrorf(http.StatusBadGateway, "Persona verification server status %d", res.StatusCode) } responseBody, err := ioutil.ReadAll(res.Body) if err != nil { return nil, base.HTTPErrorf(http.StatusBadGateway, "Invalid response from Persona verifier") } var response PersonaResponse if err = json.Unmarshal(responseBody, &response); err != nil { return nil, base.HTTPErrorf(http.StatusBadGateway, "Invalid response from Persona verifier") } if response.Status != "okay" { return nil, base.HTTPErrorf(http.StatusUnauthorized, response.Reason) } return &response, nil }
// Parses a JSON MIME body, unmarshaling it into "into". func ReadJSONFromMIME(headers http.Header, input io.Reader, into interface{}) error { contentType := headers.Get("Content-Type") if contentType != "" && !strings.HasPrefix(contentType, "application/json") { return base.HTTPErrorf(http.StatusUnsupportedMediaType, "Invalid content type %s", contentType) } switch headers.Get("Content-Encoding") { case "gzip": var err error if input, err = gzip.NewReader(input); err != nil { return err } case "": break default: return base.HTTPErrorf(http.StatusUnsupportedMediaType, "Unsupported Content-Encoding; use gzip") } decoder := json.NewDecoder(input) if err := decoder.Decode(into); err != nil { base.Warn("Couldn't parse JSON in HTTP request: %v", err) return base.HTTPErrorf(http.StatusBadRequest, "Bad JSON") } return nil }
func verifyFacebook(fbUrl, accessToken string) (*FacebookResponse, error) { params := url.Values{"fields": []string{"id,name,email"}, "access_token": []string{accessToken}} destUrl := fbUrl + "/me?" + params.Encode() res, err := http.Get(destUrl) if err != nil { return nil, err } defer res.Body.Close() if res.StatusCode >= 300 { return nil, base.HTTPErrorf(http.StatusUnauthorized, "Facebook verification server status %d", res.StatusCode) } decoder := json.NewDecoder(res.Body) var response FacebookResponse err = decoder.Decode(&response) if err != nil { return nil, base.HTTPErrorf(http.StatusBadGateway, "Invalid response from Facebook verifier") } return &response, nil }
// Handles PUT and POST for a user or a role. func (h *handler) updatePrincipal(name string, isUser bool) error { h.assertAdminOnly() // Unmarshal the request body into a PrincipalConfig struct: body, _ := ioutil.ReadAll(h.rq.Body) var newInfo PrincipalConfig var err error if err = json.Unmarshal(body, &newInfo); err != nil { return err } if h.rq.Method == "POST" { // On POST, take the name from the "name" property in the request body: if newInfo.Name == nil { return base.HTTPErrorf(http.StatusBadRequest, "Missing name property") } } else { // ON PUT, verify the name matches, if given: if newInfo.Name == nil { newInfo.Name = &name } else if *newInfo.Name != name { return base.HTTPErrorf(http.StatusBadRequest, "Name mismatch (can't change name)") } } replaced, err := updatePrincipal(h.db.DatabaseContext, newInfo, isUser, h.rq.Method != "POST") if err != nil { return err } status := http.StatusOK if !replaced { status = http.StatusCreated } h.response.WriteHeader(status) return nil }
func (h *handler) makeSessionFromEmail(email string, createUserIfNeeded bool) error { // Email is verified. Look up the user and make a login session for her: user, err := h.db.Authenticator().GetUserByEmail(email) if err != nil { return err } if user == nil { // The email address is authentic but we have no user account for it. if !createUserIfNeeded { return base.HTTPErrorf(http.StatusUnauthorized, "No such user") } if len(email) < 1 { return base.HTTPErrorf(http.StatusBadRequest, "Cannot register new user: email is missing") } // Create a User with the given email address as username and a random password. user, err = h.registerNewUser(email) if err != nil { return err } } return h.makeSession(user) }
// HTTP handler for a POST to _bulk_get // Request looks like POST /db/_bulk_get?revs=___&attachments=___ // where the boolean ?revs parameter adds a revision history to each doc // and the boolean ?attachments parameter includes attachment bodies. // The body of the request is JSON and looks like: // { // "docs": [ // {"id": "docid", "rev": "revid", "atts_since": [12,...]}, ... // ] // } func (h *handler) handleBulkGet() error { includeRevs := h.getBoolQuery("revs") includeAttachments := h.getBoolQuery("attachments") body, err := h.readJSON() if err != nil { return err } err = h.writeMultipart(func(writer *multipart.Writer) error { for _, item := range body["docs"].([]interface{}) { doc := item.(map[string]interface{}) docid, _ := doc["id"].(string) revid := "" revok := true if doc["rev"] != nil { revid, revok = doc["rev"].(string) } if docid == "" || !revok { return base.HTTPErrorf(http.StatusBadRequest, "Invalid doc/rev ID") } var attsSince []string = nil if includeAttachments { if doc["atts_since"] != nil { raw, ok := doc["atts_since"].([]interface{}) if ok { attsSince = make([]string, len(raw)) for i := 0; i < len(raw); i++ { attsSince[i], ok = raw[i].(string) if !ok { break } } } if !ok { return base.HTTPErrorf(http.StatusBadRequest, "Invalid atts_since") } } else { attsSince = []string{} } } body, err := h.db.GetRev(docid, revid, includeRevs, attsSince) if err != nil { status, reason := base.ErrorAsHTTPStatus(err) errStr := base.CouchHTTPErrorName(status) body = db.Body{"id": docid, "error": errStr, "reason": reason, "status": status} if revid != "" { body["rev"] = revid } } h.db.WriteRevisionAsPart(body, err != nil, writer) } return nil }) return err }
// Calls the JS sync function to assign the doc to channels, grant users // access to channels, and reject invalid documents. func (db *Database) getChannelsAndAccess(doc *document, body Body, parentRevID string) (result base.Set, access channels.AccessMap, roles channels.AccessMap, err error) { base.LogTo("CRUD+", "Invoking sync on doc %q rev %s", doc.ID, body["_rev"]) // Get the parent revision, to pass to the sync function: var oldJson string if parentRevID != "" { var oldJsonBytes []byte oldJsonBytes, err = db.getRevisionJSON(doc, parentRevID) if err != nil { if base.IsDocNotFoundError(err) { err = nil } return } oldJson = string(oldJsonBytes) } if db.ChannelMapper != nil { // Call the ChannelMapper: var output *channels.ChannelMapperOutput output, err = db.ChannelMapper.MapToChannelsAndAccess(body, oldJson, makeUserCtx(db.user)) if err == nil { result = output.Channels access = output.Access roles = output.Roles err = output.Rejection if err != nil { base.Log("Sync fn rejected: new=%+v old=%s --> %s", body, oldJson, err) } else if !validateAccessMap(access) || !validateRoleAccessMap(roles) { err = base.HTTPErrorf(500, "Error in JS sync function") } } else { base.Warn("Sync fn exception: %+v; doc = %s", err, body) err = base.HTTPErrorf(500, "Exception in JS sync function") } } else { // No ChannelMapper so by default use the "channels" property: value, _ := body["channels"].([]interface{}) if value != nil { array := make([]string, 0, len(value)) for _, channel := range value { channelStr, ok := channel.(string) if ok && len(channelStr) > 0 { array = append(array, channelStr) } } result, err = channels.SetFromArray(array, channels.KeepStar) } } return }
// Given a CouchDB document body about to be stored in the database, goes through the _attachments // dict, finds attachments with inline bodies, copies the bodies into the Couchbase db, and replaces // the bodies with the 'digest' attributes which are the keys to retrieving them. func (db *Database) storeAttachments(doc *document, body Body, generation int, parentRev string) error { var parentAttachments map[string]interface{} atts := BodyAttachments(body) if atts == nil && body["_attachments"] != nil { return base.HTTPErrorf(400, "Invalid _attachments") } for name, value := range atts { meta, ok := value.(map[string]interface{}) if !ok { return base.HTTPErrorf(400, "Invalid _attachments") } data, exists := meta["data"] if exists { // Attachment contains data, so store it in the db: attachment, err := decodeAttachment(data) if err != nil { return err } key, err := db.setAttachment(attachment) if err != nil { return err } delete(meta, "data") meta["stub"] = true meta["digest"] = string(key) meta["revpos"] = generation if meta["encoding"] == nil { meta["length"] = len(attachment) delete(meta, "encoded_length") } else { meta["encoded_length"] = len(attachment) } } else { // No data given; look it up from the parent revision. if parentAttachments == nil { parent, err := db.getAvailableRev(doc, parentRev) if err != nil { base.Warn("storeAttachments: no such parent rev %q to find %v", parentRev, meta) return err } parentAttachments, exists = parent["_attachments"].(map[string]interface{}) if !exists { return base.HTTPErrorf(400, "Unknown attachment %s", name) } } parentAttachment := parentAttachments[name] if parentAttachment == nil { return base.HTTPErrorf(400, "Unknown attachment %s", name) } atts[name] = parentAttachment } } return nil }
// 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 (db *Database) GetDoc(docid string) (*document, error) { key := db.realDocID(docid) if key == "" { return nil, base.HTTPErrorf(400, "Invalid doc ID") } doc := newDocument(docid) err := db.Bucket.Get(key, doc) if err != nil { return nil, err } else if !doc.hasValidSyncData() { return nil, base.HTTPErrorf(404, "Not imported") } return doc, nil }
func (h *handler) writeMultipart(subtype string, callback func(*multipart.Writer) error) error { if !h.requestAccepts("multipart/") { return base.HTTPErrorf(http.StatusNotAcceptable, "Response is multipart") } // Get the output stream. Due to a CouchDB bug, if we're sending to it we need to buffer the // output in memory so we can trim the final bytes. var output io.Writer var buffer bytes.Buffer if h.userAgentIs("CouchDB") { output = &buffer } else { output = h.response } writer := multipart.NewWriter(output) h.setHeader("Content-Type", fmt.Sprintf("multipart/%s; boundary=%q", subtype, writer.Boundary())) err := callback(writer) writer.Close() if err == nil && output == &buffer { // Trim trailing newline; CouchDB is allergic to it: _, err = h.response.Write(bytes.TrimRight(buffer.Bytes(), "\r\n")) } return err }
// Reads & parses the request body, handling either JSON or multipart. func (h *handler) readDocument() (db.Body, error) { contentType, attrs, _ := mime.ParseMediaType(h.rq.Header.Get("Content-Type")) switch contentType { case "", "application/json": return h.readJSON() case "multipart/related": if DebugMultipart { raw, err := h.readBody() if err != nil { return nil, err } reader := multipart.NewReader(bytes.NewReader(raw), attrs["boundary"]) body, err := db.ReadMultipartDocument(reader) if err != nil { ioutil.WriteFile("GatewayPUT.mime", raw, 0600) base.Warn("Error reading MIME data: copied to file GatewayPUT.mime") } return body, err } else { reader := multipart.NewReader(h.requestBody, attrs["boundary"]) return db.ReadMultipartDocument(reader) } default: return nil, base.HTTPErrorf(http.StatusUnsupportedMediaType, "Invalid content type %s", contentType) } }
// HTTP handler for a PUT of a document func (h *handler) handlePutDoc() error { docid := h.PathVar("docid") body, err := h.readDocument() if err != nil { return err } var newRev string if h.getQuery("new_edits") != "false" { // Regular PUT: if oldRev := h.getQuery("rev"); oldRev != "" { body["_rev"] = oldRev } else if ifMatch := h.rq.Header.Get("If-Match"); ifMatch != "" { body["_rev"] = ifMatch } newRev, err = h.db.Put(docid, body) if err != nil { return err } h.setHeader("Etag", newRev) } else { // Replicator-style PUT with new_edits=false: revisions := db.ParseRevisions(body) if revisions == nil { return base.HTTPErrorf(http.StatusBadRequest, "Bad _revisions") } err = h.db.PutExistingRev(docid, body, revisions) if err != nil { return err } newRev = body["_rev"].(string) } h.writeJSONStatus(http.StatusCreated, db.Body{"ok": true, "id": docid, "rev": newRev}) return nil }
// "Delete" a database (it doesn't actually do anything to the underlying bucket) func (h *handler) handleDeleteDB() error { h.assertAdminOnly() if !h.server.RemoveDatabase(h.db.Name) { return base.HTTPErrorf(http.StatusNotFound, "missing") } return nil }
// HTTP handler for _dumpchannel func (h *handler) handleDumpChannel() error { channelName := h.PathVar("channel") since := h.getIntQuery("since", 0) base.LogTo("HTTP", "Dump channel %q", channelName) chanLog, err := h.db.GetChangeLog(channelName, since) if err != nil { return err } else if chanLog == nil { return base.HTTPErrorf(http.StatusNotFound, "no such channel") } title := fmt.Sprintf("/%s: ā%sā Channel", html.EscapeString(h.db.Name), html.EscapeString(channelName)) h.setHeader("Content-Type", `text/html; charset="UTF-8"`) h.response.Write([]byte(fmt.Sprintf( `<!DOCTYPE html><html><head><title>%s</title></head><body> <h1>%s</h1><code> <p>Since = %d</p> <table border=1> `, title, title, chanLog.Since))) h.response.Write([]byte("\t<tr><th>Seq</th><th>Doc</th><th>Rev</th><th>Flags</th></tr>\n")) for _, entry := range chanLog.Entries { h.response.Write([]byte(fmt.Sprintf("\t<tr><td>%d</td><td>%s</td><td>%s</td><td>%08b</td>", entry.Sequence, html.EscapeString(entry.DocID), html.EscapeString(entry.RevID), entry.Flags))) h.response.Write([]byte("</tr>\n")) } h.response.Write([]byte("</table>\n</code></html></body>")) return nil }
// HTTP handler for a GET of a specific doc attachment func (h *handler) handleGetAttachment() error { docid := h.PathVar("docid") attachmentName := h.PathVar("attach") revid := h.getQuery("rev") body, err := h.db.GetRev(docid, revid, false, nil) if err != nil { return err } if body == nil { return kNotFoundError } meta, ok := db.BodyAttachments(body)[attachmentName].(map[string]interface{}) if !ok { return base.HTTPErrorf(http.StatusNotFound, "missing attachment %s", attachmentName) } digest := meta["digest"].(string) data, err := h.db.GetAttachment(db.AttachmentKey(digest)) if err != nil { return err } h.setHeader("Etag", digest) if contentType, ok := meta["content_type"].(string); ok { h.setHeader("Content-Type", contentType) } if encoding, ok := meta["encoding"].(string); ok { h.setHeader("Content-Encoding", encoding) } h.response.Write(data) return nil }
// POST /_persona creates a browserID-based login session and sets its cookie. // It's API-compatible with the CouchDB plugin: <https://github.com/iriscouch/browserid_couchdb/> func (h *handler) handlePersonaPOST() error { var params struct { Assertion string `json:"assertion"` } err := h.readJSONInto(¶ms) if err != nil { return err } origin := h.server.config.Persona.Origin if origin == "" { base.Warn("Can't accept Persona logins: Server URL not configured") return base.HTTPErrorf(http.StatusInternalServerError, "Server url not configured") } // OK, now verify it: base.Log("Persona: Verifying assertion %q for %q", params.Assertion, origin) verifiedInfo, err := VerifyPersona(params.Assertion, origin) if err != nil { base.Log("Persona: Failed verify: %v", err) return err } base.Log("Persona: Logged in %q!", verifiedInfo.Email) createUserIfNeeded := h.server.config.Persona.Register return h.makeSessionFromEmail(verifiedInfo.Email, createUserIfNeeded) }
// Parses a JSON request body, unmarshaling it into "into". func ReadJSONFromMIME(headers http.Header, input io.Reader, into interface{}) error { contentType := headers.Get("Content-Type") if contentType != "" && !strings.HasPrefix(contentType, "application/json") { return base.HTTPErrorf(http.StatusUnsupportedMediaType, "Invalid content type %s", contentType) } body, err := ioutil.ReadAll(input) if err != nil { return base.HTTPErrorf(http.StatusBadRequest, "") } err = json.Unmarshal(body, into) if err != nil { base.Warn("Couldn't parse JSON:\n%s", body) return base.HTTPErrorf(http.StatusBadRequest, "Bad JSON") } return nil }
func (user *userImpl) SetEmail(email string) error { if email != "" && !IsValidEmail(email) { return base.HTTPErrorf(http.StatusBadRequest, "Invalid email address") } user.Email_ = email return nil }
func ValidateDatabaseName(dbName string) error { // http://wiki.apache.org/couchdb/HTTP_database_API#Naming_and_Addressing if match, _ := regexp.MatchString(`^[a-z][-a-z0-9_$()+/]*$`, dbName); !match { return base.HTTPErrorf(http.StatusBadRequest, "Illegal database name: %s", dbName) } return nil }
// DELETE /_session logs out the current session func (h *handler) handleSessionDELETE() error { cookie := h.db.Authenticator().DeleteSessionForCookie(h.rq) if cookie == nil { return base.HTTPErrorf(http.StatusNotFound, "no session") } http.SetCookie(h.response, cookie) return nil }
// Returns the body of the asked-for revision or the most recent available ancestor. // Does NOT fill in _id, _rev, etc. func (db *Database) getAvailableRev(doc *document, revid string) (Body, error) { for ; revid != ""; revid = doc.History[revid].Parent { if body, _ := db.getRevision(doc, revid); body != nil { return body, nil } } return nil, base.HTTPErrorf(404, "missing") }
// Updates or creates a principal from a PrincipalConfig structure. func updatePrincipal(dbc *db.DatabaseContext, newInfo PrincipalConfig, isUser bool, allowReplace bool) (replaced bool, err error) { // Get the existing principal, or if this is a POST make sure there isn't one: var princ auth.Principal var user auth.User authenticator := dbc.Authenticator() if isUser { user, err = authenticator.GetUser(internalUserName(*newInfo.Name)) princ = user } else { princ, err = authenticator.GetRole(*newInfo.Name) } if err != nil { return } replaced = (princ != nil) if !replaced { // If user/role didn't exist already, instantiate a new one: if isUser { user, err = authenticator.NewUser(internalUserName(*newInfo.Name), "", nil) princ = user } else { princ, err = authenticator.NewRole(*newInfo.Name, nil) } if err != nil { return } } else if !allowReplace { err = base.HTTPErrorf(http.StatusConflict, "Already exists") return } // Now update the Principal object from the properties in the request, first the channels: updatedChannels := princ.ExplicitChannels() if updatedChannels == nil { updatedChannels = ch.TimedSet{} } lastSeq, err := dbc.LastSequence() if err != nil { return } updatedChannels.UpdateAtSequence(newInfo.ExplicitChannels, lastSeq+1) princ.SetExplicitChannels(updatedChannels) // Then the roles: if isUser { user.SetEmail(newInfo.Email) if newInfo.Password != nil { user.SetPassword(*newInfo.Password) } user.SetDisabled(newInfo.Disabled) user.SetExplicitRoleNames(newInfo.ExplicitRoleNames) } // And finally save the Principal: err = authenticator.Save(princ) return }
func decodeAttachment(att interface{}) ([]byte, error) { switch att := att.(type) { case string: return base64.StdEncoding.DecodeString(att) case []byte: return att, nil } return nil, base.HTTPErrorf(400, "invalid attachment data") }
// Checks whether this userImpl object contains valid data; if not, returns an error. func (user *userImpl) validate() error { if err := (&user.roleImpl).validate(); err != nil { return err } else if user.Email_ != "" && !IsValidEmail(user.Email_) { return base.HTTPErrorf(http.StatusBadRequest, "Invalid email address") } else if user.OldPasswordHash_ != nil { return base.HTTPErrorf(http.StatusBadRequest, "Obsolete password hash present") } else if (user.Name_ == "") != (user.PasswordHash_ == nil) { // Real user must have a password; anon user must not have a password return base.HTTPErrorf(http.StatusBadRequest, "Invalid password") } for _, roleName := range user.ExplicitRoleNames_ { if !IsValidPrincipalName(roleName) { return base.HTTPErrorf(http.StatusBadRequest, "Invalid role name %q", roleName) } } return nil }
// Gets a revision of a document as raw JSON. // If it's obsolete it will be loaded from the database if possible. // Does not add _id or _rev properties. func (db *Database) getRevisionJSON(doc *document, revid string) ([]byte, error) { if body := doc.getRevisionJSON(revid); body != nil { return body, nil } else if !doc.History.contains(revid) { return nil, base.HTTPErrorf(404, "missing") } else { return db.getOldRevisionJSON(doc.ID, revid) } }
// Top-level handler call. It's passed a pointer to the specific method to run. func (h *handler) invoke(method handlerMethod) error { restExpvars.Add("requests_total", 1) restExpvars.Add("requests_active", 1) defer restExpvars.Add("requests_active", -1) var err error if h.server.config.CompressResponses == nil || *h.server.config.CompressResponses { if encoded := NewEncodedResponseWriter(h.response, h.rq); encoded != nil { h.response = encoded defer encoded.Close() } } switch h.rq.Header.Get("Content-Encoding") { case "": h.requestBody = h.rq.Body case "gzip": if h.requestBody, err = gzip.NewReader(h.rq.Body); err != nil { return err } h.rq.Header.Del("Content-Encoding") // to prevent double decoding later on default: return base.HTTPErrorf(http.StatusUnsupportedMediaType, "Unsupported Content-Encoding; use gzip") } h.setHeader("Server", VersionString) // If there is a "db" path variable, look up the database context: var dbContext *db.DatabaseContext if dbname := h.PathVar("db"); dbname != "" { if dbContext, err = h.server.GetDatabase(dbname); err != nil { h.logRequestLine() return err } } // Authenticate, if not on admin port: if h.privs != adminPrivs { if err = h.checkAuth(dbContext); err != nil { h.logRequestLine() return err } } h.logRequestLine() // Now set the request's Database (i.e. context + user) if dbContext != nil { h.db, err = db.GetDatabase(dbContext, h.user) if err != nil { return err } } return method(h) // Call the actual handler code }
func (db *Database) QueryDesignDoc(ddocName string, viewName string, options map[string]interface{}, result interface{}) error { // Query has slightly different access control than checkDDocAccess(): // * Admins can query any design doc including the internal ones // * Regular users can query non-internal design docs //NOTE: Restricting query access to admins until we implement proper result-set filtering. if db.user != nil /*&& isInternalDDoc(ddocName)*/ { return base.HTTPErrorf(http.StatusForbidden, "forbidden") } return db.Bucket.ViewCustom(ddocName, viewName, options, result) }
// Helper function to open a Couchbase connection and return a specific bucket. func ConnectToBucket(spec base.BucketSpec) (bucket base.Bucket, err error) { bucket, err = base.GetBucket(spec) if err != nil { err = base.HTTPErrorf(http.StatusBadGateway, "Unable to connect to server: %s", err) } else { err = installViews(bucket) } return }