Skip to content

Add readonly api to gateway #1389

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

Closed
Closed
Show file tree
Hide file tree
Changes from all 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
6 changes: 3 additions & 3 deletions cmd/ipfswatch/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,13 +76,13 @@ func run(ipfsPath, watchPath string) error {
return err
}
defer node.Close()

cmd_ctx := cmdCtx(node, ipfsPath)
if *http {
addr := "/ip4/127.0.0.1/tcp/5001"
var opts = []corehttp.ServeOption{
corehttp.GatewayOption(true),
corehttp.GatewayOption(true, &cmd_ctx),
corehttp.WebUIOption,
corehttp.CommandsOption(cmdCtx(node, ipfsPath)),
corehttp.CommandsOption(cmd_ctx),
}
proc.Go(func(p process.Process) {
if err := corehttp.ListenAndServe(node, addr, opts...); err != nil {
Expand Down
5 changes: 3 additions & 2 deletions commands/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,9 @@ type Command struct {
// the Run Function.
//
// ie. If command Run returns &Block{}, then Command.Type == &Block{}
Type interface{}
Subcommands map[string]*Command
Type interface{}
Subcommands map[string]*Command
GatewayAccess bool
}

// ErrNotCallable signals a command that cannot be called.
Expand Down
13 changes: 11 additions & 2 deletions commands/http/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ var log = u.Logger("commands/http")
type internalHandler struct {
ctx cmds.Context
root *cmds.Command
availableCmds map[*cmds.Command]bool
}

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

func NewHandler(ctx cmds.Context, root *cmds.Command, allowedOrigin string) *Handler {
func NewHandler(ctx cmds.Context, root *cmds.Command, allowedOrigin string, commandList map[*cmds.Command]bool) *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, commandList}

// Create a CORS object for wrapping the internal handler.
c := cors.New(cors.Options{
Expand Down Expand Up @@ -89,6 +90,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 +101,13 @@ func (i internalHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
return
}

if access, ok := i.availableCmds[req.Command()]; !ok || access == false {
// Or a 404?
w.WriteHeader(http.StatusForbidden)
Copy link
Member

Choose a reason for hiding this comment

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

http.StatusForbidden is probably fine

w.Write([]byte("You may not execute this command on this server."))
return
}

// get the node's context to pass into the commands.
node, err := i.ctx.GetNode()
if err != nil {
Expand Down
43 changes: 40 additions & 3 deletions commands/http/handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"testing"

"github.com/ipfs/go-ipfs/commands"
corecommands "github.com/ipfs/go-ipfs/core/commands"
)

func assertHeaders(t *testing.T, resHeaders http.Header, reqHeaders map[string]string) {
Expand All @@ -21,7 +22,13 @@ func TestDisallowedOrigin(t *testing.T) {
req, _ := http.NewRequest("GET", "http://example.com/foo", nil)
req.Header.Add("Origin", "http://barbaz.com")

handler := NewHandler(commands.Context{}, nil, "")
commandList := map[*commands.Command]bool{}

for _, cmd := range corecommands.Root.Subcommands {
commandList[cmd] = true
}

handler := NewHandler(commands.Context{}, nil, "", commandList)
handler.ServeHTTP(res, req)

assertHeaders(t, res.Header(), map[string]string{
Expand All @@ -38,7 +45,13 @@ func TestWildcardOrigin(t *testing.T) {
req, _ := http.NewRequest("GET", "http://example.com/foo", nil)
req.Header.Add("Origin", "http://foobar.com")

handler := NewHandler(commands.Context{}, nil, "*")
commandList := map[*commands.Command]bool{}

for _, cmd := range corecommands.Root.Subcommands {
commandList[cmd] = true
}

handler := NewHandler(commands.Context{}, nil, "*", commandList)
handler.ServeHTTP(res, req)

assertHeaders(t, res.Header(), map[string]string{
Expand All @@ -57,7 +70,13 @@ func TestAllowedMethod(t *testing.T) {
req.Header.Add("Origin", "http://www.foobar.com")
req.Header.Add("Access-Control-Request-Method", "PUT")

handler := NewHandler(commands.Context{}, nil, "http://www.foobar.com")
commandList := map[*commands.Command]bool{}

for _, cmd := range corecommands.Root.Subcommands {
commandList[cmd] = true
}

handler := NewHandler(commands.Context{}, nil, "http://www.foobar.com", commandList)
handler.ServeHTTP(res, req)

assertHeaders(t, res.Header(), map[string]string{
Expand All @@ -69,3 +88,21 @@ func TestAllowedMethod(t *testing.T) {
"Access-Control-Expose-Headers": "",
})
}

func TestLimitingCommands(t *testing.T) {
res := httptest.NewRecorder()
req, _ := http.NewRequest("GET", "http://example.com/api/v0/cat", nil)

query := req.URL.Query()
query.Add("arg", "QmFooBar")
req.URL.RawQuery = query.Encode()

commandList := map[*commands.Command]bool{}

handler := NewHandler(commands.Context{}, corecommands.Root, "*", commandList)
Copy link
Contributor

Choose a reason for hiding this comment

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

@travisperson another option is to define corecommands.RootRO inside corecommands and use it here. This way, no need for extra commandList arguments elsewhere.

handler.ServeHTTP(res, req)

if(res.Code != http.StatusForbidden) {
t.Errorf("Invalid status code, expected `%d` received `%d`\nBody:\n %s", http.StatusForbidden, res.Code, res.Body)
}
}
2 changes: 2 additions & 0 deletions core/commands/block.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ multihash.
}

var blockStatCmd = &cmds.Command{
GatewayAccess: true,
Helptext: cmds.HelpText{
Tagline: "Print information of a raw IPFS block",
ShortDescription: `
Expand Down Expand Up @@ -80,6 +81,7 @@ on raw ipfs blocks. It outputs the following to stdout:
}

var blockGetCmd = &cmds.Command{
GatewayAccess: true,
Helptext: cmds.HelpText{
Tagline: "Get a raw IPFS block",
ShortDescription: `
Expand Down
1 change: 1 addition & 0 deletions core/commands/cat.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
const progressBarMinSize = 1024 * 1024 * 8 // show progress bar for outputs > 8MiB

var CatCmd = &cmds.Command{
GatewayAccess: true,
Helptext: cmds.HelpText{
Tagline: "Show IPFS object data",
ShortDescription: `
Expand Down
1 change: 1 addition & 0 deletions core/commands/ipns.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
)

var ipnsCmd = &cmds.Command{
GatewayAccess: true,
Helptext: cmds.HelpText{
Tagline: "Gets the value currently published at an IPNS name",
ShortDescription: `
Expand Down
1 change: 1 addition & 0 deletions core/commands/ls.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ type LsOutput struct {
}

var LsCmd = &cmds.Command{
GatewayAccess: true,
Helptext: cmds.HelpText{
Tagline: "List links from an object.",
ShortDescription: `
Expand Down
4 changes: 4 additions & 0 deletions core/commands/object.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ ipfs object patch <args> - Create new object from old ones
}

var objectDataCmd = &cmds.Command{
GatewayAccess: true,
Helptext: cmds.HelpText{
Tagline: "Outputs the raw bytes in an IPFS object",
ShortDescription: `
Expand Down Expand Up @@ -110,6 +111,7 @@ output is the raw data of the object.
}

var objectLinksCmd = &cmds.Command{
GatewayAccess: true,
Helptext: cmds.HelpText{
Tagline: "Outputs the links pointed to by the specified object",
ShortDescription: `
Expand Down Expand Up @@ -159,6 +161,7 @@ multihash.
}

var objectGetCmd = &cmds.Command{
GatewayAccess: true,
Helptext: cmds.HelpText{
Tagline: "Get and serialize the DAG node named by <key>",
ShortDescription: `
Expand Down Expand Up @@ -230,6 +233,7 @@ This command outputs data in the following encodings:
}

var objectStatCmd = &cmds.Command{
GatewayAccess: true,
Helptext: cmds.HelpText{
Tagline: "Get stats for the DAG node named by <key>",
ShortDescription: `
Expand Down
1 change: 1 addition & 0 deletions core/commands/refs.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ func KeyListTextMarshaler(res cmds.Response) (io.Reader, error) {
}

var RefsCmd = &cmds.Command{
GatewayAccess: true,
Helptext: cmds.HelpText{
Tagline: "Lists links (references) from an object",
ShortDescription: `
Expand Down
8 changes: 7 additions & 1 deletion core/corehttp/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,15 @@ const (
)

func CommandsOption(cctx commands.Context) ServeOption {
commandList := map[*commands.Command]bool{}

for _, cmd := range corecommands.Root.Subcommands {
commandList[cmd] = true
}

return func(n *core.IpfsNode, mux *http.ServeMux) (*http.ServeMux, error) {
origin := os.Getenv(originEnvKey)
cmdHandler := cmdsHttp.NewHandler(cctx, corecommands.Root, origin)
cmdHandler := cmdsHttp.NewHandler(cctx, corecommands.Root, origin, commandList)
mux.Handle(cmdsHttp.ApiPath+"/", cmdHandler)
return mux, nil
}
Expand Down
24 changes: 21 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,40 @@ 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 ?

var commandList = map[*commands.Command]bool{}

for _, cmd := range corecommands.Root.Subcommands {
commandList[cmd] = cmd.GatewayAccess
}

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.NewHandler(*cctx, corecommands.Root, origin, commandList)
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
4 changes: 3 additions & 1 deletion core/corehttp/gateway_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import (
repo "github.com/ipfs/go-ipfs/repo"
config "github.com/ipfs/go-ipfs/repo/config"
testutil "github.com/ipfs/go-ipfs/util/testutil"
commands "github.com/ipfs/go-ipfs/commands"
)

type mockNamesys map[string]path.Path
Expand Down Expand Up @@ -65,9 +66,10 @@ func TestGatewayGet(t *testing.T) {
}
ns["example.com"] = path.FromString("/ipfs/" + k)

cmd_ctx := commands.Context{}
h, err := makeHandler(n,
IPNSHostnameOption(),
GatewayOption(false),
GatewayOption(false, &cmd_ctx),
)
if err != nil {
t.Fatal(err)
Expand Down
5 changes: 3 additions & 2 deletions test/supernode_client/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,9 +106,10 @@ func run() error {
}
defer node.Close()

cmd_ctx := cmdCtx(node, repoPath)
opts := []corehttp.ServeOption{
corehttp.CommandsOption(cmdCtx(node, repoPath)),
corehttp.GatewayOption(false),
corehttp.CommandsOption(cmd_ctx),
corehttp.GatewayOption(false, &cmd_ctx),
}

if *cat {
Expand Down