Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add readonly api to gateway #1389

Closed
Closed
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions cmd/ipfs/daemon.go
Original file line number Diff line number Diff line change
Expand Up @@ -281,7 +281,7 @@ func serveHTTPApi(req cmds.Request) (error, <-chan error) {
var opts = []corehttp.ServeOption{
corehttp.CommandsOption(*req.Context()),
corehttp.WebUIOption,
apiGw.ServeOption(),
apiGw.ServeOption(nil),
corehttp.VersionOption(),
defaultMux("/debug/vars"),
defaultMux("/debug/pprof/"),
Expand Down Expand Up @@ -339,7 +339,7 @@ func serveHTTPGateway(req cmds.Request) (error, <-chan error) {
var opts = []corehttp.ServeOption{
corehttp.VersionOption(),
corehttp.IPNSHostnameOption(),
corehttp.GatewayOption(writable),
corehttp.GatewayOption(writable, req.Context()),
}

if len(cfg.Gateway.RootRedirect) > 0 {
Expand Down
42 changes: 41 additions & 1 deletion commands/http/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
context "github.com/ipfs/go-ipfs/Godeps/_workspace/src/golang.org/x/net/context"

cmds "github.com/ipfs/go-ipfs/commands"
commands "github.com/ipfs/go-ipfs/core/commands"
u "github.com/ipfs/go-ipfs/util"
)

Expand All @@ -21,6 +22,7 @@ var log = u.Logger("commands/http")
type internalHandler struct {
ctx cmds.Context
root *cmds.Command
readOnly bool
}

// The Handler struct is funny because we want to wrap our internal handler
Expand All @@ -47,14 +49,18 @@ var mimeTypes = map[string]string{
cmds.Text: "text/plain",
}

var readOnlyCmds = map[*cmds.Command]bool{
commands.RefsCmd: true,
}

func NewHandler(ctx cmds.Context, root *cmds.Command, allowedOrigin string) *Handler {
// allow whitelisted origins (so we can make API requests from the browser)
if len(allowedOrigin) > 0 {
log.Info("Allowing API requests from origin: " + allowedOrigin)
}

// Create a handler for the API.
internal := internalHandler{ctx, root}
internal := internalHandler{ctx, root, false}

// Create a CORS object for wrapping the internal handler.
c := cors.New(cors.Options{
Expand All @@ -71,6 +77,30 @@ func NewHandler(ctx cmds.Context, root *cmds.Command, allowedOrigin string) *Han
return &Handler{internal, c.Handler(internal)}
}

func NewReadOnlyHandler(ctx cmds.Context, root *cmds.Command, allowedOrigin string) *Handler {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

problem with making two handlers is drift in the implementations.

maybe instead of creating two different handlers, make the original handler always check a list of available commands, and then we can just instantiate the read-only and read-write handlers by passing two different command lists.

that way the

if i.readOnly == true {
  if _, ok := readOnlyCmds[req.Command()]; !ok { ... }
}

can turn into

if _, ok := availableCmds[req.Command()]; !ok { ... }

// allow whitelisted origins (so we can make API requests from the browser)
if len(allowedOrigin) > 0 {
log.Info("Allowing API requests from origin: " + allowedOrigin)
}

// Create a handler for the API.
internal := internalHandler{ctx, root, true}

// Create a CORS object for wrapping the internal handler.
c := cors.New(cors.Options{
AllowedMethods: []string{"GET"},

// use AllowOriginFunc instead of AllowedOrigins because we want to be
// restrictive by default.
AllowOriginFunc: func(origin string) bool {
return (allowedOrigin == "*") || (origin == allowedOrigin)
},
})

// Wrap the internal handler with CORS handling-middleware.
return &Handler{internal, c.Handler(internal)}
}

func (i internalHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
log.Debug("Incoming API request: ", r.URL)

Expand All @@ -89,6 +119,7 @@ func (i internalHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
}

req, err := Parse(r, i.root)

if err != nil {
if err == ErrNotFound {
w.WriteHeader(http.StatusNotFound)
Expand All @@ -99,6 +130,15 @@ func (i internalHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
return
}

if i.readOnly == true {
if _, ok := readOnlyCmds[req.Command()]; !ok {
// Or a 404?
w.WriteHeader(http.StatusForbidden)
w.Write([]byte("You may not execute this request on the read-only api."))
return
}
}

// get the node's context to pass into the commands.
node, err := i.ctx.GetNode()
if err != nil {
Expand Down
18 changes: 15 additions & 3 deletions core/corehttp/gateway.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,11 @@ import (
"fmt"
"net/http"
"sync"
"os"

commands "github.com/ipfs/go-ipfs/commands"
cmdsHttp "github.com/ipfs/go-ipfs/commands/http"
corecommands "github.com/ipfs/go-ipfs/core/commands"
core "github.com/ipfs/go-ipfs/core"
id "github.com/ipfs/go-ipfs/p2p/protocol/identify"
)
Expand All @@ -25,26 +29,34 @@ func NewGateway(conf GatewayConfig) *Gateway {
}
}

func (g *Gateway) ServeOption() ServeOption {
func (g *Gateway) ServeOption(cctx * commands.Context) ServeOption {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i dont think this is properly go fmt-ed ?

return func(n *core.IpfsNode, mux *http.ServeMux) (*http.ServeMux, error) {
gateway, err := newGatewayHandler(n, g.Config)
if err != nil {
return nil, err
}
mux.Handle("/ipfs/", gateway)
mux.Handle("/ipns/", gateway)

if cctx != nil {
origin := os.Getenv(originEnvKey)
cmdHandler := cmdsHttp.NewReadOnlyHandler(*cctx, corecommands.Root, origin)
mux.Handle(cmdsHttp.ApiPath+"/", cmdHandler)
}
return mux, nil
}
}

func GatewayOption(writable bool) ServeOption {

func GatewayOption(writable bool, cctx * commands.Context) ServeOption {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style fix: i think the commands.Context should come as first arg.

g := NewGateway(GatewayConfig{
Writable: writable,
BlockList: &BlockList{},
})
return g.ServeOption()
return g.ServeOption(cctx)
}


func VersionOption() ServeOption {
return func(n *core.IpfsNode, mux *http.ServeMux) (*http.ServeMux, error) {
mux.HandleFunc("/version", func(w http.ResponseWriter, r *http.Request) {
Expand Down