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 }
/* * From OAuth 2.0 spec * Handle OAuth 2.0 Authorize request * This might display a login page with a form to collect user credentials * which is part of an internal authentication flow */ func (h *handler) handleOidcTestProviderAuthorize() error { if !h.db.DatabaseContext.Options.UnsupportedOptions.OidcTestProvider.Enabled { return base.HTTPErrorf(http.StatusForbidden, "OIDC test provider is not enabled") } requestParams := h.rq.URL.RawQuery base.LogTo("OIDC", "handleOidcTestProviderAuthorize() raw authorize request raw query params = %v", requestParams) scope := h.rq.URL.Query().Get("scope") if scope == "" { return base.HTTPErrorf(http.StatusBadRequest, "missing scope parameter") } err := validateAuthRequestScope(scope) if err != nil { return err } p := &Page{Title: "Oidc Test Provider", Query: requestParams} t := template.New("Test Login") if t, err := t.Parse(login_html); err != nil { return base.HTTPErrorf(http.StatusInternalServerError, err.Error()) } else { t.Execute(h.response, p) } return nil }
func verifyGoogle(idToken string, allowedAppID []string) (*GoogleResponse, error) { destUrl := googleTokenInfoURL + idToken res, err := http.Get(destUrl) if err != nil { return nil, err } defer res.Body.Close() decoder := json.NewDecoder(res.Body) var response GoogleResponse err = decoder.Decode(&response) if err != nil { return nil, base.HTTPErrorf(http.StatusBadGateway, "Invalid response from Google token verifier") } if response.ErrorDescription != "" { return nil, base.HTTPErrorf(http.StatusUnauthorized, response.ErrorDescription) } if !isValidAud(response.Aud, allowedAppID) { return nil, base.HTTPErrorf(http.StatusUnauthorized, "Invalid application id, please add it in the config") } return &response, 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.db.Authenticator().RegisterNewUser(email, email) if err != nil { return err } } return h.makeSession(user) }
// 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 }
// 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 (db *Database) QueryDesignDoc(ddocName string, viewName string, options map[string]interface{}) (*sgbucket.ViewResult, error) { // Regular users have limitations on what they can query if db.user != nil { // * Regular users can only query when user views are enabled if !db.GetUserViewsEnabled() { return nil, base.HTTPErrorf(http.StatusForbidden, "forbidden") } // * Admins can query any design doc including the internal ones // * Regular users can query non-internal design docs if isInternalDDoc(ddocName) { return nil, base.HTTPErrorf(http.StatusForbidden, "forbidden") } } result, err := db.Bucket.View(ddocName, viewName, options) if err != nil { return nil, err } if isInternalDDoc(ddocName) { if options["include_docs"] == true { for _, row := range result.Rows { stripSyncProperty(row) } } } else { applyChannelFiltering := options["reduce"] != true && db.GetUserViewsEnabled() result = filterViewResult(result, db.user, applyChannelFiltering) } return &result, 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 { if len(h.rq.Header["Origin"]) > 0 { // CORS not allowed for login #115 return base.HTTPErrorf(http.StatusBadRequest, "No CORS") } 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.Logf("Persona: Verifying assertion %q for %q", params.Assertion, origin) verifiedInfo, err := VerifyPersona(params.Assertion, origin) if err != nil { base.Logf("Persona: Failed verify: %v", err) return err } base.Logf("Persona: Logged in %q!", verifiedInfo.Email) createUserIfNeeded := h.server.config.Persona.Register return h.makeSessionFromEmail(verifiedInfo.Email, createUserIfNeeded) }
// 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 }
/* * From OAuth 2.0 spec * Return public certificates for signing keys */ func (h *handler) handleOidcTestProviderCerts() error { if !h.db.DatabaseContext.Options.UnsupportedOptions.OidcTestProvider.Enabled { return base.HTTPErrorf(http.StatusForbidden, "OIDC test provider is not enabled") } base.LogTo("OIDC", "handleOidcTestProviderCerts() called") privateKey, err := privateKey() if err != nil { return base.HTTPErrorf(http.StatusInternalServerError, "Error getting private RSA Key") } oidcPrivateKey := key.PrivateKey{ KeyID: testProviderKeyIdentifier, PrivateKey: privateKey, } jwk := oidcPrivateKey.JWK() h.response.Write([]byte("{\r\n\"keys\":[\r\n")) if bytes, err := jwk.MarshalJSON(); err == nil { h.response.Write(bytes) } h.response.Write([]byte("\r\n]\r\n}")) return nil }
// HTTP handler for a POST to _bulk_docs func (h *handler) handleBulkDocs() error { body, err := h.readJSON() if err != nil { return err } newEdits, ok := body["new_edits"].(bool) if !ok { newEdits = true } docs, ok := body["docs"].([]interface{}) if !ok { err = base.HTTPErrorf(http.StatusBadRequest, "missing 'docs' property") return err } h.db.ReserveSequences(uint64(len(docs))) result := make([]db.Body, 0, len(docs)) for _, item := range docs { doc := item.(map[string]interface{}) docid, _ := doc["_id"].(string) var err error var revid string if newEdits { if docid != "" { revid, err = h.db.Put(docid, doc) } else { docid, revid, err = h.db.Post(doc) } } else { revisions := db.ParseRevisions(doc) if revisions == nil { err = base.HTTPErrorf(http.StatusBadRequest, "Bad _revisions") } else { revid = revisions[0] err = h.db.PutExistingRev(docid, doc, revisions) } } status := db.Body{} if docid != "" { status["id"] = docid } if err != nil { code, msg := base.ErrorAsHTTPStatus(err) status["status"] = code status["error"] = base.CouchHTTPErrorName(code) status["reason"] = msg base.Logf("\tBulkDocs: Doc %q --> %d %s (%v)", docid, code, msg, err) err = nil // wrote it to output already; not going to return it } else { status["rev"] = revid } result = append(result, status) } h.writeJSONStatus(http.StatusCreated, result) return nil }
// Stub handler for hadling create DB on the public API returns HTTP status 412 // if the db exists, and 403 if it doesn't. // fixes issue #562 func (h *handler) handleCreateTarget() error { dbname := h.PathVar("targetdb") if _, err := h.server.GetDatabase(dbname); err != nil { return base.HTTPErrorf(http.StatusForbidden, "Creating a DB over the public API is unsupported") } else { return base.HTTPErrorf(http.StatusPreconditionFailed, "Database already exists") } }
func parseClockSequenceID(str string, sequenceHasher *sequenceHasher) (s SequenceID, err error) { if str == "" { return SequenceID{ SeqType: ClockSequenceType, Clock: base.NewSequenceClockImpl(), }, nil } s.SeqType = ClockSequenceType components := strings.Split(str, ":") if len(components) == 1 { // Convert simple zero to empty clock, to handle clients sending zero to mean 'no previous since' if components[0] == "0" { s.Clock = base.NewSequenceClockImpl() } else { // Standard clock hash if s.Clock, err = sequenceHasher.GetClock(components[0]); err != nil { return SequenceID{}, err } } } else if len(components) == 2 { // TriggeredBy Clock Hash, and vb.seq sequence if s.TriggeredByClock, err = sequenceHasher.GetClock(components[0]); err != nil { return SequenceID{}, err } sequenceComponents := strings.Split(components[1], ".") if len(sequenceComponents) != 2 { base.Warn("Unexpected sequence format - ignoring and relying on triggered by") return } else { if vb64, err := strconv.ParseUint(sequenceComponents[0], 10, 16); err != nil { base.Warn("Unable to convert sequence %v to int.", sequenceComponents[0]) } else { s.vbNo = uint16(vb64) s.Seq, err = strconv.ParseUint(sequenceComponents[1], 10, 64) } } } else if len(components) == 3 { // Low hash, and vb.seq sequence. Use low hash as clock, ignore vb.seq if s.Clock, err = sequenceHasher.GetClock(components[0]); err != nil { return SequenceID{}, err } } else { err = base.HTTPErrorf(400, "Invalid sequence") } if err != nil { err = base.HTTPErrorf(400, "Invalid sequence") } return s, err }
// DELETE /_session logs out the current session func (h *handler) handleSessionDELETE() error { if len(h.rq.Header["Origin"]) > 0 { // CORS not allowed for login #115 return base.HTTPErrorf(http.StatusBadRequest, "No CORS") } cookie := h.db.Authenticator().DeleteSessionForCookie(h.rq) if cookie == nil { return base.HTTPErrorf(http.StatusNotFound, "no session") } http.SetCookie(h.response, cookie) 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 } status, start, end := h.handleRange(uint64(len(data))) if status > 299 { return base.HTTPErrorf(status, "") } else if status == http.StatusPartialContent { data = data[start:end] } h.setHeader("Content-Length", strconv.FormatUint(uint64(len(data)), 10)) h.setHeader("Etag", strconv.Quote(digest)) if contentType, ok := meta["content_type"].(string); ok { h.setHeader("Content-Type", contentType) } if encoding, ok := meta["encoding"].(string); ok { if h.getOptBoolQuery("content_encoding", true) { h.setHeader("Content-Encoding", encoding) } else { // Couchbase Lite wants to download the encoded form directly and store it that way, // but some HTTP client libraries like NSURLConnection will automatically decompress // the HTTP response if it has a Content-Encoding header. As a workaround, allow the // client to add ?content_encoding=false to the request URL to disable setting this // header. h.setHeader("X-Content-Encoding", encoding) h.setHeader("Content-Type", "application/gzip") } } if h.privs == adminPrivs { // #720 h.setHeader("Content-Disposition", fmt.Sprintf("attachment; filename=%q", attachmentName)) } h.response.WriteHeader(status) h.response.Write(data) return nil }
//Return the internal test RSA private key, this is decoded from a base64 encoded string //stored as a constant above func privateKey() (key *rsa.PrivateKey, err error) { decodedPrivateKey, err := base64.StdEncoding.DecodeString(base64EncodedPrivateKey) if err != nil { return nil, base.HTTPErrorf(http.StatusInternalServerError, "Error decoding private RSA Key") } key, err = x509.ParsePKCS1PrivateKey(decodedPrivateKey) if err != nil { return nil, base.HTTPErrorf(http.StatusInternalServerError, "Error parsing private RSA Key") } return }
// Adds an existing revision to a document along with its history (list of rev IDs.) // This is equivalent to the "new_edits":false mode of CouchDB. func (db *Database) PutExistingRev(docid string, body Body, docHistory []string) error { newRev := docHistory[0] generation, _ := ParseRevID(newRev) if generation < 0 { return base.HTTPErrorf(http.StatusBadRequest, "Invalid revision ID") } deleted, _ := body["_deleted"].(bool) expiry, err := body.extractExpiry() if err != nil { return base.HTTPErrorf(http.StatusBadRequest, "Invalid expiry: %v", err) } _, err = db.updateDoc(docid, false, expiry, func(doc *document) (Body, AttachmentData, error) { // (Be careful: this block can be invoked multiple times if there are races!) // Find the point where this doc's history branches from the current rev: currentRevIndex := len(docHistory) parent := "" for i, revid := range docHistory { if doc.History.contains(revid) { currentRevIndex = i parent = revid break } } if currentRevIndex == 0 { base.LogTo("CRUD+", "PutExistingRev(%q): No new revisions to add", docid) return nil, nil, couchbase.UpdateCancel // No new revisions to add } // Add all the new-to-me revisions to the rev tree: for i := currentRevIndex - 1; i >= 0; i-- { doc.History.addRevision(RevInfo{ ID: docHistory[i], Parent: parent, Deleted: (i == 0 && deleted)}) parent = docHistory[i] } // Process the attachments, replacing bodies with digests. parentRevID := doc.History[newRev].Parent newAttachments, err := db.storeAttachments(doc, body, generation, parentRevID, docHistory) if err != nil { return nil, nil, err } body["_rev"] = newRev return body, newAttachments, 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, revID 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 oldJsonBytes []byte if oldJsonBytes, err = db.getAncestorJSON(doc, revID); 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 if !doc.hasFlag(channels.Deleted) { // deleted docs can't grant access access = output.Access roles = output.Roles } err = output.Rejection if err != nil { base.Logf("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 }
// Lowest-level method that reads a document from the bucket. func (db *DatabaseContext) GetDoc(docid string) (*document, error) { key := realDocID(docid) if key == "" { return nil, base.HTTPErrorf(400, "Invalid doc ID") } dbExpvars.Add("document_gets", 1) 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 }
// 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") } for roleName, _ := range user.ExplicitRoles_ { if !IsValidPrincipalName(roleName) { return base.HTTPErrorf(http.StatusBadRequest, "Invalid role name %q", roleName) } } return 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 _dumpchannel func (h *handler) handleDumpChannel() error { channelName := h.PathVar("channel") since := h.getIntQuery("since", 0) base.LogTo("HTTP", "Dump channel %q", channelName) chanLog := h.db.GetChangeLog(channelName, since) 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[0].Sequence-1))) h.response.Write([]byte("\t<tr><th>Seq</th><th>Doc</th><th>Rev</th><th>Flags</th></tr>\n")) for _, entry := range chanLog { 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 }
// POST /_session creates a login session and sets its cookie func (h *handler) handleSessionPOST() error { // CORS not allowed for login #115 #762 originHeader := h.rq.Header["Origin"] if len(originHeader) > 0 { matched := "" if h.server.config.CORS != nil { matched = matchedOrigin(h.server.config.CORS.LoginOrigin, originHeader) } if matched == "" { return base.HTTPErrorf(http.StatusBadRequest, "No CORS") } } user, err := h.getUserFromSessionRequestBody() // If we fail to get a user from the body and we've got a non-GUEST authenticated user, create the session based on that user if user == nil && h.user != nil && h.user.Name() != "" { return h.makeSession(h.user) } else { if err != nil { return err } return h.makeSession(user) } }
// POST /_google creates a google-based login session and sets its cookie. func (h *handler) handleGooglePOST() error { // CORS not allowed for login #115 #762 originHeader := h.rq.Header["Origin"] if len(originHeader) > 0 { matched := matchedOrigin(h.server.config.CORS.LoginOrigin, originHeader) if matched == "" { return base.HTTPErrorf(http.StatusBadRequest, "No CORS") } } var params struct { IDToken string `json:"id_token"` } err := h.readJSONInto(¶ms) if err != nil { return err } //validate the google id token googleResponse, err := verifyGoogle(params.IDToken, h.server.config.Google.AppClientID) if err != nil { return err } createUserIfNeeded := h.server.config.Google.Register return h.makeSessionFromNameAndEmail(googleResponse.UserID, googleResponse.Email, createUserIfNeeded) }
// POST /_facebook creates a facebook-based login session and sets its cookie. func (h *handler) handleFacebookPOST() error { // CORS not allowed for login #115 #762 originHeader := h.rq.Header["Origin"] if len(originHeader) > 0 { matched := matchedOrigin(h.server.config.CORS.LoginOrigin, originHeader) if matched == "" { return base.HTTPErrorf(http.StatusBadRequest, "No CORS") } } var params struct { AccessToken string `json:"access_token"` } err := h.readJSONInto(¶ms) if err != nil { return err } facebookResponse, err := verifyFacebook(kFacebookOpenGraphURL, params.AccessToken) if err != nil { return err } createUserIfNeeded := h.server.config.Facebook.Register return h.makeSessionFromNameAndEmail(facebookResponse.Id, facebookResponse.Email, createUserIfNeeded) }
// POST /_session creates a login session and sets its cookie func (h *handler) handleSessionPOST() error { // CORS not allowed for login #115 #762 originHeader := h.rq.Header["Origin"] if len(originHeader) > 0 { matched := "" if h.server.config.CORS != nil { matched = matchedOrigin(h.server.config.CORS.LoginOrigin, originHeader) } if matched == "" { return base.HTTPErrorf(http.StatusBadRequest, "No CORS") } } var params struct { Name string `json:"name"` Password string `json:"password"` } err := h.readJSONInto(¶ms) if err != nil { return err } var user auth.User user, err = h.db.Authenticator().GetUser(params.Name) if err != nil { return err } if user != nil && !user.Authenticate(params.Password) { user = nil } return h.makeSession(user) }
// 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", strconv.Quote(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 }
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 (dc *DatabaseContext) TakeDbOffline(reason string) error { base.LogTo("CRUD", "Taking Database : %v, offline", dc.Name) dbState := atomic.LoadUint32(&dc.State) //If the DB is already trasitioning to: offline or is offline silently return if dbState == DBOffline || dbState == DBResyncing || dbState == DBStopping { return nil } if atomic.CompareAndSwapUint32(&dc.State, DBOnline, DBStopping) { //notify all active _changes feeds to close close(dc.ExitChanges) base.LogTo("CRUD", "Waiting for all active calls to complete on Database : %v", dc.Name) //Block until all current calls have returned, including _changes feeds dc.AccessLock.Lock() defer dc.AccessLock.Unlock() base.LogTo("CRUD", "Database : %v, is offline", dc.Name) //set DB state to Offline atomic.StoreUint32(&dc.State, DBOffline) if dc.EventMgr.HasHandlerForEvent(DBStateChange) { dc.EventMgr.RaiseDBStateChangeEvent(dc.Name, "offline", reason, *dc.Options.AdminInterface) } return nil } else { base.LogTo("CRUD", "Unable to take Database offline, database must be in Online state") return base.HTTPErrorf(http.StatusServiceUnavailable, "Unable to take Database offline, database must be in Online state") } }