Files
threshold/internal/servers/controller/handler_test.go
T
Jeff Mitchell 1a1d3058c9 Move verification to a shared package (#197)
* Move verification to a shared package

The verifier is constructed at HTTP request time and added to the
context. It can then be called from appropriate handlers with
standardized error handling after all the routing.

I intentionally did not add to the handlers yet as I am a good distance
into the scope updates and didn't want to cause even more merge
conflicts. So at the moment authz is again disabled, but once the scope
updates are done, this will be integrated back in.
2020-07-16 22:12:26 -04:00

230 lines
7.3 KiB
Go

package controller
import (
"context"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"net/http/httptest"
"strings"
"testing"
"github.com/grpc-ecosystem/grpc-gateway/runtime"
"github.com/hashicorp/watchtower/internal/gen/controller/api/services"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestGrpcGatewayRouting(t *testing.T) {
ctx := context.Background()
// The unimplemented result indicates the grpc routing is happening successfully otherwise it would return NotFound.
routed := http.StatusNotImplemented
unrouted := http.StatusNotFound
cases := []struct {
name string
setup func(mux *runtime.ServeMux)
url string
expectedResult int
}{
{
name: "project",
setup: func(mux *runtime.ServeMux) {
require.NoError(t, services.RegisterProjectServiceHandlerServer(ctx, mux, &services.UnimplementedProjectServiceServer{}))
},
url: "v1/orgs/someid/projects",
expectedResult: routed,
},
{
name: "users",
setup: func(mux *runtime.ServeMux) {
require.NoError(t, services.RegisterUserServiceHandlerServer(ctx, mux, &services.UnimplementedUserServiceServer{}))
},
url: "v1/orgs/someid/users",
expectedResult: routed,
},
{
name: "roles",
setup: func(mux *runtime.ServeMux) {
require.NoError(t, services.RegisterRoleServiceHandlerServer(ctx, mux, &services.UnimplementedRoleServiceServer{}))
},
url: "v1/orgs/someid/roles",
expectedResult: routed,
},
{
name: "project_scoped_roles",
setup: func(mux *runtime.ServeMux) {
require.NoError(t, services.RegisterRoleServiceHandlerServer(ctx, mux, &services.UnimplementedRoleServiceServer{}))
},
url: "v1/orgs/someid/projects/_someprojectid/roles",
expectedResult: routed,
},
{
name: "groups",
setup: func(mux *runtime.ServeMux) {
require.NoError(t, services.RegisterGroupServiceHandlerServer(ctx, mux, &services.UnimplementedGroupServiceServer{}))
},
url: "v1/orgs/someid/groups",
expectedResult: routed,
},
{
name: "project_scoped_groups",
setup: func(mux *runtime.ServeMux) {
require.NoError(t, services.RegisterGroupServiceHandlerServer(ctx, mux, &services.UnimplementedGroupServiceServer{}))
},
url: "v1/orgs/someid/projects/_someprojectid/groups",
expectedResult: routed,
},
{
name: "not routed",
setup: func(mux *runtime.ServeMux) {},
url: "v1/nothing",
expectedResult: unrouted,
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
mux := runtime.NewServeMux()
tc.setup(mux)
// List request
req := httptest.NewRequest("GET", fmt.Sprintf("http://localhost/%s", tc.url), nil)
resp := httptest.NewRecorder()
mux.ServeHTTP(resp, req)
assert.Equal(t, tc.expectedResult, resp.Result().StatusCode, "Got response %v", resp)
// Create request
req = httptest.NewRequest("POST", fmt.Sprintf("http://localhost/%s", tc.url), nil)
resp = httptest.NewRecorder()
mux.ServeHTTP(resp, req)
assert.Equal(t, tc.expectedResult, resp.Result().StatusCode, "Got response %v", resp)
// Get request
req = httptest.NewRequest("GET", fmt.Sprintf("http://localhost/%s/somemadeupid", tc.url), nil)
resp = httptest.NewRecorder()
mux.ServeHTTP(resp, req)
assert.Equal(t, tc.expectedResult, resp.Result().StatusCode, "Got response %v", resp)
// Update request
req = httptest.NewRequest("PATCH", fmt.Sprintf("http://localhost/%s/somemadeupid", tc.url), nil)
resp = httptest.NewRecorder()
mux.ServeHTTP(resp, req)
assert.Equal(t, tc.expectedResult, resp.Result().StatusCode, "Got response %v", resp)
// Delete request
req = httptest.NewRequest("DELETE", fmt.Sprintf("http://localhost/%s/somemadeupid", tc.url), nil)
resp = httptest.NewRecorder()
mux.ServeHTTP(resp, req)
assert.Equal(t, tc.expectedResult, resp.Result().StatusCode, "Got response %v", resp)
})
}
}
func TestAuthenticationHandler(t *testing.T) {
c := NewTestController(t, &TestControllerOpts{DefaultOrgId: "o_1234567890"})
defer c.Shutdown()
resp, err := http.Post(fmt.Sprintf("%s/v1/orgs/o_1234567890/auth-methods/am_1234567890:authenticate", c.ApiAddrs()[0]), "application/json",
strings.NewReader(`{"token_type": null, "credentials": {"name":"test", "password": "test"}}`))
require.NoError(t, err)
assert.Equal(t, http.StatusOK, resp.StatusCode, "Got response: %v", resp)
b, err := ioutil.ReadAll(resp.Body)
require.NoError(t, err)
body := make(map[string]interface{})
require.NoError(t, json.Unmarshal(b, &body))
require.Contains(t, body, "id")
require.Contains(t, body, "token")
pubId, tok := body["id"].(string), body["token"].(string)
assert.NotEmpty(t, pubId)
assert.NotEmpty(t, tok)
assert.Truef(t, strings.HasPrefix(tok, pubId), "Token: %q, Id: %q", tok, pubId)
}
func TestGrpcGatewayRouting_CustomActions(t *testing.T) {
ctx := context.Background()
// The unimplemented result indicates the grpc routing is happening successfully otherwise it would return NotFound.
routed := http.StatusNotImplemented
cases := []struct {
name string
setup func(mux *runtime.ServeMux)
post_urls []string
}{
{
name: "roles",
setup: func(mux *runtime.ServeMux) {
require.NoError(t, services.RegisterRoleServiceHandlerServer(ctx, mux, &services.UnimplementedRoleServiceServer{}))
},
post_urls: []string{
"v1/orgs/someid/roles/r_anotherid:add-principals",
"v1/orgs/someid/roles/r_anotherid:set-principals",
"v1/orgs/someid/roles/r_anotherid:remove-principals",
"v1/orgs/someid/projects/p_something/roles/r_anotherid:add-principals",
"v1/orgs/someid/projects/p_something/roles/r_anotherid:set-principals",
"v1/orgs/someid/projects/p_something/roles/r_anotherid:remove-principals",
"v1/orgs/someid/roles/r_anotherid:add-grants",
"v1/orgs/someid/roles/r_anotherid:set-grants",
"v1/orgs/someid/roles/r_anotherid:remove-grants",
"v1/orgs/someid/projects/p_something/roles/r_anotherid:add-grants",
"v1/orgs/someid/projects/p_something/roles/r_anotherid:set-grants",
"v1/orgs/someid/projects/p_something/roles/r_anotherid:remove-grants",
},
},
}
for _, tc := range cases {
for _, url := range tc.post_urls {
t.Run(tc.name+"_"+url, func(t *testing.T) {
mux := runtime.NewServeMux()
tc.setup(mux)
req := httptest.NewRequest("POST", fmt.Sprintf("http://localhost/%s", url), nil)
resp := httptest.NewRecorder()
mux.ServeHTTP(resp, req)
assert.Equal(t, routed, resp.Result().StatusCode, "Got response %v", resp)
})
}
}
}
func TestHandleGrpcGateway(t *testing.T) {
c := NewTestController(t, &TestControllerOpts{
DisableAuthorizationFailures: true,
})
defer c.Shutdown()
cases := []struct {
name string
path string
code int
}{
{
"Non existent path",
"v1/this-is-made-ups",
http.StatusNotFound,
},
{
"Unimplemented path",
"v1/orgs/1/projects/2/host-catalogs/3/host-sets/hs_4",
http.StatusMethodNotAllowed,
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
url := fmt.Sprintf("%s/%s", c.ApiAddrs()[0], tc.path)
resp, err := http.Get(url)
if err != nil {
t.Errorf("Got error: %v when non was expected.", err)
}
if got, want := resp.StatusCode, tc.code; got != want {
t.Errorf("GET on %q got code %d, wanted %d", tc.path, got, want)
}
})
}
}