Skip to content

Commit

Permalink
gateway: make API commands configurable
Browse files Browse the repository at this point in the history
License: MIT
Signed-off-by: Lars Gierth <[email protected]>
  • Loading branch information
Lars Gierth committed Oct 5, 2018
1 parent ea3adae commit 0545005
Show file tree
Hide file tree
Showing 7 changed files with 123 additions and 87 deletions.
2 changes: 1 addition & 1 deletion cmd/ipfs/daemon.go
Original file line number Diff line number Diff line change
Expand Up @@ -567,7 +567,7 @@ func serveHTTPGateway(req *cmds.Request, cctx *oldcmds.Context) (<-chan error, e
corehttp.GatewayOption(writable, "/ipfs", "/ipns"),
corehttp.VersionOption(),
corehttp.CheckVersionOption(),
corehttp.CommandsROOption(*cctx),
corehttp.GatewayCommandsOption(*cctx, cfg.Gateway.APICommands),
}

if len(cfg.Gateway.RootRedirect) > 0 {
Expand Down
28 changes: 7 additions & 21 deletions core/commands/commands_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,31 +17,17 @@ func collectPaths(prefix string, cmd *cmds.Command, out map[string]struct{}) {

func TestROCommands(t *testing.T) {
list := []string{
"/block",
"/block/get",
"/block/stat",
"/cat",
"/commands",
"/dag",
"/dag/get",
"/dag/resolve",
"/dns",
"/get",
"/ls",
"/name",
"/name/resolve",
"/object",
"/object/data",
"/object/get",
"/object/links",
"/object/stat",
"/refs",
"/resolve",
"/version",
}

root, err := RootSubset([]string{"ls", "cat"})
if err != nil {
t.Errorf("RootSubset error: %s", err)
}

cmdSet := make(map[string]struct{})
collectPaths("", RootRO, cmdSet)
collectPaths("", root, cmdSet)

for _, path := range list {
if _, ok := cmdSet[path]; !ok {
Expand All @@ -58,7 +44,7 @@ func TestROCommands(t *testing.T) {
for _, path := range list {
path = path[1:] // remove leading slash
split := strings.Split(path, "/")
sub, err := RootRO.Get(split)
sub, err := root.Get(split)
if err != nil {
t.Errorf("error getting subcommand %q: %v", path, err)
} else if sub == nil {
Expand Down
91 changes: 37 additions & 54 deletions core/commands/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package commands

import (
"errors"
"fmt"
"io"
"strings"

Expand Down Expand Up @@ -147,64 +148,46 @@ var rootSubcommands = map[string]*cmds.Command{
"cid": CidCmd,
}

// RootRO is the readonly version of Root
var RootRO = &cmds.Command{}

var CommandsDaemonROCmd = CommandsCmd(RootRO)

var RefsROCmd = &oldcmds.Command{}

var rootROSubcommands = map[string]*cmds.Command{
"commands": CommandsDaemonROCmd,
"cat": CatCmd,
"block": &cmds.Command{
Subcommands: map[string]*cmds.Command{
"stat": blockStatCmd,
"get": blockGetCmd,
},
},
"get": GetCmd,
"dns": lgc.NewCommand(DNSCmd),
"ls": lgc.NewCommand(LsCmd),
"name": &cmds.Command{
Subcommands: map[string]*cmds.Command{
"resolve": name.IpnsCmd,
},
},
"object": lgc.NewCommand(&oldcmds.Command{
Subcommands: map[string]*oldcmds.Command{
"data": ocmd.ObjectDataCmd,
"links": ocmd.ObjectLinksCmd,
"get": ocmd.ObjectGetCmd,
"stat": ocmd.ObjectStatCmd,
},
}),
"dag": lgc.NewCommand(&oldcmds.Command{
Subcommands: map[string]*oldcmds.Command{
"get": dag.DagGetCmd,
"resolve": dag.DagResolveCmd,
},
}),
"resolve": ResolveCmd,
"version": lgc.NewCommand(VersionCmd),
}

func init() {
Root.ProcessHelp()
*RootRO = *Root

// sanitize readonly refs command
*RefsROCmd = *RefsCmd
RefsROCmd.Subcommands = map[string]*oldcmds.Command{}

// this was in the big map definition above before,
// but if we leave it there lgc.NewCommand will be executed
// before the value is updated (:/sanitize readonly refs command/)
rootROSubcommands["refs"] = lgc.NewCommand(RefsROCmd)

Root.Subcommands = rootSubcommands
}

RootRO.Subcommands = rootROSubcommands
func RootSubset(allowed []string) (*cmds.Command, error) {
subset := new(cmds.Command)
*subset = *Root
subset.Subcommands = map[string]*cmds.Command{}

commands := false
for _, path := range allowed {
if path == "commands" {
commands = true
continue
}

pathelems := strings.Split(path, "/")
in := Root
out := subset
for _, elem := range pathelems {
nextIn, ok := in.Subcommands[elem]
if !ok {
return nil, fmt.Errorf("unknown command: %s", path)
}

nextOut := new(cmds.Command)
*nextOut = *nextIn
nextOut.Subcommands = map[string]*cmds.Command{}

out.Subcommands[elem] = nextOut
out = nextOut
in = nextIn
}
}

if commands {
subset.Subcommands["commands"] = CommandsCmd(subset)
}
return subset, nil
}

type MessageOutput struct {
Expand Down
1 change: 0 additions & 1 deletion core/commands/root_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,4 @@ func TestCommandTree(t *testing.T) {
}
}
printErrors(Root.DebugValidate())
printErrors(RootRO.DebugValidate())
}
33 changes: 31 additions & 2 deletions core/corehttp/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,26 @@ cli arguments:
// APIPath is the path at which the API is mounted.
const APIPath = "/api/v0"

var GatewayAPICommands = []string{
"commands",
"cat",
"get",
"ls",
"resolve",
"name/resolve",
"dag/get",
"dag/resolve",
"object/data",
"object/links",
"object/get",
"object/stat",
"refs",
"block/stat",
"block/get",
"dns",
"version",
}

var defaultLocalhostOrigins = []string{
"http://127.0.0.1:<port>",
"https://127.0.0.1:<port>",
Expand Down Expand Up @@ -143,8 +163,17 @@ func CommandsOption(cctx oldcmds.Context) ServeOption {

// CommandsROOption constructs a ServerOption for hooking the read-only commands
// into the HTTP server.
func CommandsROOption(cctx oldcmds.Context) ServeOption {
return commandsOption(cctx, corecommands.RootRO)
func GatewayCommandsOption(cctx oldcmds.Context, allowed []string) ServeOption {
if len(allowed) == 0 {
allowed = append(allowed, GatewayAPICommands...)
}
root, err := corecommands.RootSubset(allowed)
if err != nil {
return func(n *core.IpfsNode, l net.Listener, mux *http.ServeMux) (*http.ServeMux, error) {
return nil, err
}
}
return commandsOption(cctx, root)
}

// CheckVersionOption returns a ServeOption that checks whether the client ipfs version matches. Does nothing when the user agent string does not contain `/go-ipfs/`
Expand Down
22 changes: 22 additions & 0 deletions core/corehttp/gateway_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (

version "github.com/ipfs/go-ipfs"
core "github.com/ipfs/go-ipfs/core"
commands "github.com/ipfs/go-ipfs/core/commands"
coreunix "github.com/ipfs/go-ipfs/core/coreunix"
namesys "github.com/ipfs/go-ipfs/namesys"
nsopts "github.com/ipfs/go-ipfs/namesys/opts"
Expand Down Expand Up @@ -597,3 +598,24 @@ func TestVersion(t *testing.T) {
t.Fatalf("response doesn't contain protocol version:\n%s", s)
}
}

func TestCommands(t *testing.T) {
printErrors := func(errs map[string][]error) {
if errs == nil {
return
}
t.Error("In Root command tree:")
for cmd, err := range errs {
t.Errorf(" In X command %s:", cmd)
for _, e := range err {
t.Errorf(" %s", e)
}
}
}

gwroot, err := commands.RootSubset(GatewayAPICommands)
if err != nil {
t.Errorf("RootSubset error: %s", err)
}
printErrors(gwroot.DebugValidate())
}
33 changes: 25 additions & 8 deletions test/sharness/t0113-gateway-api.sh
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,30 @@ test_description="Test API on Gateway"
. lib/test-lib.sh

test_init_ipfs

test_curl_gateway_api() {
curl -sfo "$1" "http://127.0.0.1:$port/api/v0/$2"
}

test_expect_success 'allow API command' '
ipfs config Gateway.APICommands --json '"'"'["config"]'"'"'
'

test_launch_ipfs_daemon
port=$GWAY_PORT

test_expect_success 'verify allowed API command' '
echo '"'"'{"Key":"Gateway.APICommands","Value":["config"]}'"'"' >> commands_expected &&
test_curl_gateway_api commands_actual "config?arg=Gateway.APICommands" &&
test_cmp commands_expected commands_actual
'

test_expect_success 'revert to default API commands' '
ipfs config Gateway.APICommands --json '"'"'[]'"'"'
'

test_kill_ipfs_daemon
test_launch_ipfs_daemon
port=$GWAY_PORT

for cmd in add \
Expand Down Expand Up @@ -53,21 +75,16 @@ test_expect_success "GET IPFS directory path succeeds" '
HASH2=$(tail -n 1 actual)
'

test_curl_gateway_api() {
curl -sfo actual "http://127.0.0.1:$port/api/v0/$1"
}

test_expect_success "get IPFS directory file through readonly API succeeds" '
test_curl_gateway_api "cat?arg=$HASH2/test"
test_curl_gateway_api dir_actual "cat?arg=$HASH2/test"
'

test_expect_success "get IPFS directory file through readonly API output looks good" '
test_cmp dir/test actual
test_cmp dir/test dir_actual
'

test_expect_success "refs IPFS directory file through readonly API succeeds" '
test_curl_gateway_api "refs?arg=$HASH2/test"
test_curl_gateway_api file_actual "refs?arg=$HASH2/test"
'

test_kill_ipfs_daemon
test_done

0 comments on commit 0545005

Please sign in to comment.