httpapi.go 8.9 KB
Newer Older
1
// Copyright 2017 Vector Creations Ltd
T
tanggen 已提交
2
//
3 4 5
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
T
tanggen 已提交
6
//
7
//     http://www.apache.org/licenses/LICENSE-2.0
T
tanggen 已提交
8
//
9 10 11 12 13 14 15 16
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
//
// Modifications copyright (C) 2020 Finogeeks Co., Ltd
T
tanggen 已提交
17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268

package common

import (
	"net/http"
	"net/http/httputil"
	"net/url"
	"strconv"
	"strings"
	"time"

	"github.com/finogeeks/ligase/common/config"
	"github.com/finogeeks/ligase/common/filter"
	"github.com/finogeeks/ligase/common/jsonerror"
	"github.com/finogeeks/ligase/model/authtypes"
	"github.com/finogeeks/ligase/model/service"
	"github.com/finogeeks/ligase/skunkworks/gomatrixserverlib"
	util "github.com/finogeeks/ligase/skunkworks/gomatrixutil"
	log "github.com/finogeeks/ligase/skunkworks/log"
	hm "github.com/finogeeks/ligase/skunkworks/monitor/go-client/httpmonitor"
	"github.com/opentracing/opentracing-go"
	"github.com/opentracing/opentracing-go/ext"

	mon "github.com/finogeeks/ligase/skunkworks/monitor/go-client/monitor"
)

// MakeAuthAPI turns a util.JSONRequestHandler function into an http.Handler which checks the access token in the request.
func MakeAuthAPI(
	metricsName string, cache service.Cache, cfg config.Dendrite, devFilter *filter.SimpleFilter,
	histogram mon.LabeledHistogram, /* counter mon.LabeledCounter*/
	f func(*http.Request, *authtypes.Device) util.JSONResponse,
) http.Handler {
	h := func(req *http.Request) util.JSONResponse {
		start := time.Now()

		if config.DebugRequest {
			requestDump, err := httputil.DumpRequest(req, true)
			if err != nil {
				log.Errorf("MakeAuthAPI Debug request: %s %v", metricsName, err)
			}
			log.Infof("MakeAuthAPI Debug request: %s %s", metricsName, string(requestDump))
		}

		var resErr *util.JSONResponse
		token, err := extractAccessToken(req)
		if err != nil {
			log.Infof("missing token req:%s", req.RequestURI)
			resErr = &util.JSONResponse{
				Code: http.StatusUnauthorized,
				JSON: jsonerror.MissingToken(err.Error()),
			}
		}
		var device *authtypes.Device
		if resErr == nil {
			// device, resErr := auth.VerifyAccessToken(req, deviceDB)
			device, resErr = VerifyToken(token, req.RequestURI, cache, cfg, devFilter)
		}

		verifyTokenEnd := time.Now()
		log.Debugf("MakeAuthAPI before process use %v", verifyTokenEnd.Sub(start))

		if resErr != nil {
			return *resErr
		}

		res := f(req, device)

		duration := float64(time.Since(start)) / float64(time.Millisecond)
		code := strconv.Itoa(res.Code)
		if req.Method != "OPTION" {
			histogram.WithLabelValues(req.Method, metricsName, code).Observe(duration)
			// counter.WithLabelValues(req.Method, metricsName, code).Inc()
		}

		return res
	}
	return MakeExternalAPI(metricsName, h)
}

// MakeExternalAPI turns a util.JSONRequestHandler function into an http.Handler.
// This is used for APIs that are called from the internet.
func MakeExternalAPI(metricsName string, f func(*http.Request) util.JSONResponse) http.Handler {
	h := util.MakeJSONAPI(util.NewJSONRequestHandler(f))
	withSpan := func(w http.ResponseWriter, req *http.Request) {
		if config.DebugRequest {
			requestDump, err := httputil.DumpRequest(req, true)
			if err != nil {
				log.Errorf("MakeExternalAPI Debug request: %s %v", metricsName, err)
			}
			log.Infof("MakeExternalAPI Debug request: %s %s", metricsName, string(requestDump))
		}

		span := opentracing.StartSpan(metricsName)
		defer span.Finish()
		req = req.WithContext(opentracing.ContextWithSpan(req.Context(), span))
		h.ServeHTTP(w, req)
	}

	return http.HandlerFunc(withSpan)
}

// MakeInternalAPI turns a util.JSONRequestHandler function into an http.Handler.
// This is used for APIs that are internal to dendrite.
// If we are passed a tracing context in the request headers then we use that
// as the parent of any tracing spans we create.
func MakeInternalAPI(metricsName string, f func(*http.Request) util.JSONResponse) http.Handler {
	h := util.MakeJSONAPI(util.NewJSONRequestHandler(f))
	withSpan := func(w http.ResponseWriter, req *http.Request) {
		if config.DebugRequest {
			requestDump, err := httputil.DumpRequest(req, true)
			if err != nil {
				log.Errorf("MakeInternalAPI Debug request: %s %v", metricsName, err)
			}
			log.Infof("MakeInternalAPI Debug request: %s %s", metricsName, string(requestDump))
		}

		carrier := opentracing.HTTPHeadersCarrier(req.Header)
		tracer := opentracing.GlobalTracer()
		clientContext, err := tracer.Extract(opentracing.HTTPHeaders, carrier)
		var span opentracing.Span
		if err == nil {
			// Default to a span without RPC context.
			span = tracer.StartSpan(metricsName)
		} else {
			// Set the RPC context.
			span = tracer.StartSpan(metricsName, ext.RPCServerOption(clientContext))
		}
		defer span.Finish()
		req = req.WithContext(opentracing.ContextWithSpan(req.Context(), span))
		h.ServeHTTP(w, req)
	}

	return http.HandlerFunc(withSpan)
}

func MakeInternalAuthAPI(metricsName string, cache service.Cache, f func(*http.Request, *authtypes.Device) util.JSONResponse) http.Handler {
	h := func(req *http.Request) util.JSONResponse {
		now := time.Now()
		if config.DebugRequest {
			requestDump, err := httputil.DumpRequest(req, true)
			if err != nil {
				log.Errorf("MakeInternalAuthAPI Debug request: %s %v\n", metricsName, err)
			}
			log.Infof("MakeInternalAuthAPI Debug request: %s %s\n", metricsName, string(requestDump))
		}

		userId := req.URL.Query().Get("userId")
		if userId == "" {
			return util.JSONResponse{
				Code: http.StatusUnauthorized,
				JSON: jsonerror.MissingToken("missing userId"),
			}
		}

		var device authtypes.Device
		device.UserID = userId
		device.ID = userId
		device.DeviceType = "internal"
		device.IsHuman = true

		last := now
		now = time.Now()
		log.Debugf("MakeAuthAPI before process use %v\n", now.Sub(last))
		last = now

		return f(req, &device)
	}
	return MakeExternalAPI(metricsName, h)
}

// MakeFedAPI makes an http.Handler that checks matrix federation authentication.
func MakeFedAPI(
	metricsName string,
	serverName gomatrixserverlib.ServerName,
	keyRing gomatrixserverlib.KeyRing,
	histogram mon.LabeledHistogram, /*counter mon.LabeledCounter,*/
	f func(*http.Request, *gomatrixserverlib.FederationRequest) util.JSONResponse,
) http.Handler {
	h := func(req *http.Request) util.JSONResponse {
		start := time.Now()

		if config.DebugRequest {
			requestDump, err := httputil.DumpRequest(req, true)
			if err != nil {
				log.Errorf("MakeFedAPI Debug request: %s %v", metricsName, err)
			}
			log.Infof("MakeFedAPI Debug request: %s %s", metricsName, string(requestDump))
		}

		fedReq, _ /*errResp*/ := gomatrixserverlib.VerifyHTTPRequest(
			req, time.Now(), serverName, nil,
		)
		// if fedReq == nil {
		// 	return errResp
		// }
		res := f(req, fedReq)

		duration := float64(time.Since(start)) / float64(time.Millisecond)
		code := strconv.Itoa(res.Code)
		if req.Method != "OPTION" {
			histogram.WithLabelValues(req.Method, metricsName, code).Observe(duration)
			// counter.WithLabelValues(req.Method, metricsName, code).Inc()
		}

		return res
	}
	return MakeExternalAPI(metricsName, h)
}

// SetupHTTPAPI registers an HTTP API mux under /api and sets up a metrics
// listener.
func SetupHTTPAPI(servMux *http.ServeMux, apiMux http.Handler) {
	// This is deprecated.
	//servMux.Handle("/metrics", promhttp.Handler())
	servMux.Handle("/api/", hm.Wrap(StripPrefix("/api", apiMux)))
}

func StripPrefix(prefix string, h http.Handler) http.HandlerFunc {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if p := strings.TrimPrefix(r.URL.Path, prefix); len(p) < len(r.URL.Path) {
			r2 := new(http.Request)
			*r2 = *r
			r2.URL = new(url.URL)
			*r2.URL = *r.URL
			r2.URL.Path = p
			h.ServeHTTP(w, r2)
		} else {
			http.NotFound(w, r)
		}
	})
}

// WrapHandlerInCORS adds CORS headers to all responses, including all error
// responses.
// Handles OPTIONS requests directly.
func WrapHandlerInCORS(h http.Handler) http.HandlerFunc {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Access-Control-Allow-Origin", "*")
		w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
		w.Header().Set("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, Authorization")

		if r.Method == http.MethodOptions && r.Header.Get("Access-Control-Request-Method") != "" {
			// Its easiest just to always return a 200 OK for everything. Whether
			// this is technically correct or not is a question, but in the end this
			// is what a lot of other people do (including synapse) and the clients
			// are perfectly happy with it.
			w.WriteHeader(http.StatusOK)
		} else {
			h.ServeHTTP(w, r)
		}
	})
}