Skip to content

Commit

Permalink
Merge 0a0b093 into ada6a92
Browse files Browse the repository at this point in the history
  • Loading branch information
nikmolnar committed Mar 8, 2019
2 parents ada6a92 + 0a0b093 commit 7bce0a4
Show file tree
Hide file tree
Showing 3 changed files with 86 additions and 12 deletions.
8 changes: 4 additions & 4 deletions handlers/arcgis.go
Original file line number Diff line number Diff line change
Expand Up @@ -376,11 +376,11 @@ func (s *ServiceSet) ArcGISHandler(ef func(error)) http.Handler {

for id, db := range s.tilesets {
p := rootPath + id + "/MapServer"
m.Handle(p, wrapGetWithErrors(ef, s.arcgisService(id, db)))
m.Handle(p+"/layers", wrapGetWithErrors(ef, s.arcgisLayers(id, db)))
m.Handle(p+"/legend", wrapGetWithErrors(ef, s.arcgisLegend(id, db)))
m.Handle(p, wrapGetWithErrors(ef, hmacAuth(s.arcgisService(id, db), s.secretKey, id)))
m.Handle(p+"/layers", wrapGetWithErrors(ef, hmacAuth(s.arcgisLayers(id, db), s.secretKey, id)))
m.Handle(p+"/legend", wrapGetWithErrors(ef, hmacAuth(s.arcgisLegend(id, db), s.secretKey, id)))

m.Handle(p+"/tile/", wrapGetWithErrors(ef, s.arcgisTiles(db)))
m.Handle(p+"/tile/", wrapGetWithErrors(ef, hmacAuth(s.arcgisTiles(db), s.secretKey, id)))
}
return m
}
82 changes: 75 additions & 7 deletions handlers/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,12 @@ package handlers

import (
"bytes"
"crypto/hmac"
"crypto/sha1"
"crypto/subtle"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"html/template"
"io"
Expand All @@ -13,10 +18,13 @@ import (
"path/filepath"
"strconv"
"strings"
"time"

"github.com/consbio/mbtileserver/mbtiles"
)

const maxSignatureAge = time.Duration(15) * time.Minute

// scheme returns the underlying URL scheme of the original request.
func scheme(r *http.Request) string {
if r.TLS != nil {
Expand Down Expand Up @@ -57,6 +65,59 @@ func wrapGetWithErrors(ef func(error), hf handlerFunc) http.Handler {
})
}

func hmacAuth(hf handlerFunc, secretKey string, serviceId string) handlerFunc {
return func(w http.ResponseWriter, r *http.Request) (int, error) {
// If secret key isn't set, allow all requests
if secretKey == "" {
return hf(w, r)
}

var rawSignature, rawSignDate string

query := r.URL.Query()

if signature := query.Get("signature"); signature != "" {
rawSignature = signature
} else if signature := r.Header.Get("X-Signature"); signature != "" {
rawSignature = signature
} else {
return 400, errors.New("No signature provided")
}

if date := query.Get("date"); date != "" {
rawSignDate = date
} else if date := r.Header.Get("X-Signature-Date"); date != "" {
rawSignDate = date
} else {
return 400, errors.New("No signature date provided")
}

signDate, err := time.Parse(time.RFC3339Nano, rawSignDate)
if err != nil || time.Now().Sub(signDate) > maxSignatureAge {
return 400, errors.New("Signature date is not valid RFC3339")
}

signatureParts := strings.SplitN(rawSignature, ":", 2)
if len(signatureParts) != 2 {
return 400, errors.New("Signature does not contain salt")
}
salt, signature := signatureParts[0], signatureParts[1]

key := sha1.New()
key.Write([]byte(salt + secretKey))
hash := hmac.New(sha1.New, key.Sum(nil))
message := fmt.Sprintf("%s:%s", rawSignDate, serviceId)
hash.Write([]byte(message))
checkSignature := base64.RawURLEncoding.EncodeToString(hash.Sum(nil))

if subtle.ConstantTimeCompare([]byte(signature), []byte(checkSignature)) == 1 {
return hf(w, r)
}

return 400, errors.New("Invalid signature")
}
}

// ServiceInfo consists of two strings that contain the image type and a URL.
type ServiceInfo struct {
ImageType string `json:"imageType"`
Expand All @@ -70,6 +131,7 @@ type ServiceSet struct {
templates *template.Template
Domain string
Path string
secretKey string
}

// New returns a new ServiceSet. Use AddDBOnPath to add a mbtiles file.
Expand Down Expand Up @@ -101,7 +163,7 @@ func (s *ServiceSet) AddDBOnPath(filename string, urlPath string) error {
// NewFromBaseDir returns a ServiceSet that combines all .mbtiles files under
// the directory at baseDir. The DBs will all be served under their relative paths
// to baseDir.
func NewFromBaseDir(baseDir string) (*ServiceSet, error) {
func NewFromBaseDir(baseDir string, secretKey string) (*ServiceSet, error) {
var filenames []string
err := filepath.Walk(baseDir, func(p string, info os.FileInfo, err error) error {
if err != nil {
Expand All @@ -117,6 +179,7 @@ func NewFromBaseDir(baseDir string) (*ServiceSet, error) {
}

s := New()
s.secretKey = secretKey

for _, filename := range filenames {
subpath, err := filepath.Rel(baseDir, filename)
Expand Down Expand Up @@ -176,14 +239,19 @@ func (s *ServiceSet) listServices(w http.ResponseWriter, r *http.Request) (int,

func (s *ServiceSet) tileJSON(id string, db *mbtiles.DB, mapURL bool) handlerFunc {
return func(w http.ResponseWriter, r *http.Request) (int, error) {
query := ""
if (r.URL.RawQuery != "") {
query = "?" + r.URL.RawQuery
}

svcURL := fmt.Sprintf("%s%s", s.rootURL(r), r.URL.Path)
imgFormat := db.TileFormatString()
out := map[string]interface{}{
"tilejson": "2.1.0",
"id": id,
"scheme": "xyz",
"format": imgFormat,
"tiles": []string{fmt.Sprintf("%s/tiles/{z}/{x}/{y}.%s", svcURL, imgFormat)},
"tiles": []string{fmt.Sprintf("%s/tiles/{z}/{x}/{y}.%s%s", svcURL, imgFormat, query)},
}
if mapURL {
out["map"] = fmt.Sprintf("%s/map", svcURL)
Expand Down Expand Up @@ -212,7 +280,7 @@ func (s *ServiceSet) tileJSON(id string, db *mbtiles.DB, mapURL bool) handlerFun
}

if db.HasUTFGrid() {
out["grids"] = []string{fmt.Sprintf("%s/tiles/{z}/{x}/{y}.json", svcURL)}
out["grids"] = []string{fmt.Sprintf("%s/tiles/{z}/{x}/{y}.json%s", svcURL, query)}
}
bytes, err := json.Marshal(out)
if err != nil {
Expand Down Expand Up @@ -400,14 +468,14 @@ func (s *ServiceSet) tiles(db *mbtiles.DB) handlerFunc {
func (s *ServiceSet) Handler(ef func(error), publish bool) http.Handler {
m := http.NewServeMux()
if publish {
m.Handle("/services", wrapGetWithErrors(ef, s.listServices))
m.Handle("/services", wrapGetWithErrors(ef, hmacAuth(s.listServices, s.secretKey, "")))
}
for id, db := range s.tilesets {
p := "/services/" + id
m.Handle(p, wrapGetWithErrors(ef, s.tileJSON(id, db, publish)))
m.Handle(p+"/tiles/", wrapGetWithErrors(ef, s.tiles(db)))
m.Handle(p, wrapGetWithErrors(ef, hmacAuth(s.tileJSON(id, db, publish), s.secretKey, id)))
m.Handle(p+"/tiles/", wrapGetWithErrors(ef, hmacAuth(s.tiles(db), s.secretKey, id)))
if publish {
m.Handle(p+"/map", wrapGetWithErrors(ef, s.serviceHTML(id, db)))
m.Handle(p+"/map", wrapGetWithErrors(ef, hmacAuth(s.serviceHTML(id, db), s.secretKey, id)))
}
}
return m
Expand Down
8 changes: 7 additions & 1 deletion main.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ var (
privateKey string
pathPrefix string
domain string
secretKey string
sentryDSN string
verbose bool
autotls bool
Expand All @@ -56,10 +57,15 @@ func init() {
flags.StringVarP(&privateKey, "key", "k", "", "TLS private key")
flags.StringVar(&pathPrefix, "path", "", "URL root path of this server (if behind a proxy)")
flags.StringVar(&domain, "domain", "", "Domain name of this server")
flags.StringVarP(&secretKey, "secret-key", "s", "", "Shared secret key used for HMAC authentication")
flags.StringVar(&sentryDSN, "dsn", "", "Sentry DSN")
flags.BoolVarP(&verbose, "verbose", "v", false, "Verbose logging")
flags.BoolVarP(&autotls, "tls", "t", false, "Auto TLS via Let's Encrypt")
flags.BoolVarP(&redirect, "redirect", "r", false, "Redirect HTTP to HTTPS")

if secretKey == "" {
secretKey = os.Getenv("MBTILESERVER_SECRET_KEY")
}
}

func main() {
Expand Down Expand Up @@ -132,7 +138,7 @@ func serve() {
log.Infof("Found %v mbtiles files in %s", len(filenames), tilePath)
}

svcSet, err := handlers.NewFromBaseDir(tilePath)
svcSet, err := handlers.NewFromBaseDir(tilePath, secretKey)
if err != nil {
log.Errorf("Unable to create service set: %v", err)
}
Expand Down

0 comments on commit 7bce0a4

Please sign in to comment.