package api import ( "net" "net/http" "strings" "time" ) // requestIdentity belongs to one authorized request, including its confirmation binding. type requestIdentity struct { session session sessionID string } // authorize adapts bearer, local-development and cookie authentication to one // request identity. CSRF validation remains after authentication and scope checks. func (s *Server) authorize(w http.ResponseWriter, r *http.Request, path string) (http.ResponseWriter, requestIdentity, bool) { var sess session cookie := &http.Cookie{Value: "development"} bearer := r.Header.Get("Authorization") != "" if bearer { value := strings.TrimPrefix(r.Header.Get("Authorization"), "Bearer ") credential, err := s.store.VerifyToken(value) if err != nil { fail(w, 401, "invalid or expired API token") return w, requestIdentity{}, false } if !tokenPermits(credential, path, r.Method) { fail(w, 403, "token scope does not permit this operation") return w, requestIdentity{}, false } sess = session{User: credential.Owner, Revision: credential.Revision} cookie.Value = "token:" + credential.ID w = tokenResponseWriter{w} } else if !s.authEnabled { ip := net.ParseIP(source(r)) host := r.Host if h, _, e := net.SplitHostPort(host); e == nil { host = h } hostIP := net.ParseIP(host) if ip == nil || !ip.IsLoopback() || (host != "localhost" && (hostIP == nil || !hostIP.IsLoopback())) { fail(w, 403, "development mode is local only") return w, requestIdentity{}, false } sess = session{User: "开发者", CSRF: s.devCSRF} } else { var err error cookie, err = r.Cookie("linkport_session") if err != nil { fail(w, 401, "login required") return w, requestIdentity{}, false } s.mu.Lock() var ok bool sess, ok = s.sessions[cookie.Value] if time.Now().After(sess.Expires) { delete(s.sessions, cookie.Value) ok = false } s.mu.Unlock() rev, err := s.store.PrimaryRevision(sess.User) if !ok || err != nil || rev != sess.Revision { fail(w, 401, "session expired") return w, requestIdentity{}, false } } if !bearer && r.Method != "GET" && r.Header.Get("X-CSRF-Token") != sess.CSRF { fail(w, 403, "CSRF token required") return w, requestIdentity{}, false } return w, requestIdentity{session: sess, sessionID: cookie.Value}, true }