ripple-api/common/method_data.go
Morgan Bazalgette 8ebe5f6a02
Require client to specify explicitly in websockets whether restricted users should be seen
This is only allowed to those having the user privilege AdminPrivilegeManageUsers, having being identified by the API AND having sent a message of type set_restricted_visibility stating specifically in the data that they want to get info also about restricted users.
This also includes some more information in the new_scores, such as the username and userid of the user who submitted the score.
2017-07-25 14:49:14 +02:00

162 lines
4.3 KiB
Go

package common
import (
"encoding/json"
"fmt"
"strconv"
"strings"
"github.com/DataDog/datadog-go/statsd"
"github.com/getsentry/raven-go"
"github.com/jmoiron/sqlx"
"github.com/valyala/fasthttp"
"gopkg.in/redis.v5"
)
// RavenClient is the raven client to which report errors happening.
// If nil, errors will just be fmt.Println'd
var RavenClient *raven.Client
// MethodData is a struct containing the data passed over to an API method.
type MethodData struct {
User Token
DB *sqlx.DB
Doggo *statsd.Client
R *redis.Client
Ctx *fasthttp.RequestCtx
}
// ClientIP implements a best effort algorithm to return the real client IP, it parses
// X-Real-IP and X-Forwarded-For in order to work properly with reverse-proxies such us: nginx or haproxy.
func (md MethodData) ClientIP() string {
clientIP := strings.TrimSpace(string(md.Ctx.Request.Header.Peek("X-Real-Ip")))
if len(clientIP) > 0 {
return clientIP
}
clientIP = string(md.Ctx.Request.Header.Peek("X-Forwarded-For"))
if index := strings.IndexByte(clientIP, ','); index >= 0 {
clientIP = clientIP[0:index]
}
clientIP = strings.TrimSpace(clientIP)
if len(clientIP) > 0 {
return clientIP
}
return md.Ctx.RemoteIP().String()
}
// Err logs an error. If RavenClient is set, it will use the client to report
// the error to sentry, otherwise it will just write the error to stdout.
func (md MethodData) Err(err error) {
user := &raven.User{
ID: strconv.Itoa(md.User.UserID),
Username: md.User.Value,
IP: md.Ctx.RemoteAddr().String(),
}
// Generate tags for error
tags := map[string]string{
"endpoint": string(md.Ctx.RequestURI()),
"token": md.User.Value,
}
_err(err, tags, user, md.Ctx)
}
// Err for peppy API calls
func Err(c *fasthttp.RequestCtx, err error) {
// Generate tags for error
tags := map[string]string{
"endpoint": string(c.RequestURI()),
}
_err(err, tags, nil, c)
}
// WSErr is the error function for errors happening in the websockets.
func WSErr(err error) {
_err(err, map[string]string{
"endpoint": "/api/v1/ws",
}, nil, nil)
}
func _err(err error, tags map[string]string, user *raven.User, c *fasthttp.RequestCtx) {
if RavenClient == nil {
fmt.Println("ERROR!!!!")
fmt.Println(err)
return
}
// Create stacktrace
st := raven.NewStacktrace(0, 3, []string{"zxq.co/ripple", "git.zxq.co/ripple"})
ifaces := []raven.Interface{st, generateRavenHTTP(c)}
if user != nil {
ifaces = append(ifaces, user)
}
RavenClient.CaptureError(
err,
tags,
ifaces...,
)
}
func generateRavenHTTP(ctx *fasthttp.RequestCtx) *raven.Http {
if ctx == nil {
return nil
}
// build uri
uri := ctx.URI()
// safe to use b2s because a new string gets allocated eventually for
// concatenation
sURI := b2s(uri.Scheme()) + "://" + b2s(uri.Host()) + b2s(uri.Path())
// build header map
// using ctx.Request.Header.Len would mean calling .VisitAll two times
// which can be quite expensive since it means iterating over all the
// headers, so we give a rough estimate of the number of headers we expect
// to have
m := make(map[string]string, 16)
ctx.Request.Header.VisitAll(func(k, v []byte) {
// not using b2s because we mustn't keep references to the underlying
// k and v
m[string(k)] = string(v)
})
return &raven.Http{
URL: sURI,
// Not using b2s because raven sending is concurrent and may happen
// AFTER the request, meaning that values could potentially be replaced
// by new ones.
Method: string(ctx.Method()),
Query: string(uri.QueryString()),
Cookies: string(ctx.Request.Header.Peek("Cookie")),
Headers: m,
}
}
// ID retrieves the Token's owner user ID.
func (md MethodData) ID() int {
return md.User.UserID
}
// Query is shorthand for md.C.Query.
func (md MethodData) Query(q string) string {
return b2s(md.Ctx.QueryArgs().Peek(q))
}
// HasQuery returns true if the parameter is encountered in the querystring.
// It returns true even if the parameter is "" (the case of ?param&etc=etc)
func (md MethodData) HasQuery(q string) bool {
return md.Ctx.QueryArgs().Has(q)
}
// Unmarshal unmarshals a request's JSON body into an interface.
func (md MethodData) Unmarshal(into interface{}) error {
return json.Unmarshal(md.Ctx.PostBody(), into)
}
// IsBearer tells whether the current token is a Bearer (oauth) token.
func (md MethodData) IsBearer() bool {
return md.User.ID == -1
}