* 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.
233 lines
5.9 KiB
Go
233 lines
5.9 KiB
Go
package auth
|
|
|
|
import (
|
|
"fmt"
|
|
"net/http"
|
|
"testing"
|
|
|
|
"github.com/hashicorp/watchtower/internal/types/action"
|
|
"github.com/hashicorp/watchtower/internal/types/resource"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestHandler_AuthDecoration(t *testing.T) {
|
|
cases := []struct {
|
|
name string
|
|
path string
|
|
method string
|
|
action action.Type
|
|
scope string
|
|
resource resource.Type
|
|
id string
|
|
pin string
|
|
wantErrContains string
|
|
}{
|
|
{
|
|
name: "global scope, read, with id",
|
|
path: "/v1/users/u_anon",
|
|
method: "GET",
|
|
action: action.Read,
|
|
scope: "global",
|
|
resource: resource.User,
|
|
id: "u_anon",
|
|
},
|
|
{
|
|
name: "global scope, update, with id",
|
|
path: "/v1/auth-methods/am_1234",
|
|
method: "PATCH",
|
|
action: action.Update,
|
|
scope: "global",
|
|
resource: resource.AuthMethod,
|
|
id: "am_1234",
|
|
},
|
|
{
|
|
name: "global scope, delete",
|
|
path: "/v1/orgs/o_1234",
|
|
method: "DELETE",
|
|
action: action.Delete,
|
|
scope: "global",
|
|
resource: resource.Org,
|
|
id: "o_1234",
|
|
},
|
|
{
|
|
name: "global scope, create",
|
|
path: "/v1/roles",
|
|
method: "POST",
|
|
action: action.Create,
|
|
scope: "global",
|
|
resource: resource.Role,
|
|
},
|
|
{
|
|
name: "global, invalid collection syntax",
|
|
path: "/v1/org",
|
|
wantErrContains: "invalid collection syntax",
|
|
},
|
|
{
|
|
name: "global, custom action",
|
|
path: "/v1/orgs/o_123/auth-methods/am_1234:authenticate",
|
|
method: "POST",
|
|
action: action.Authenticate,
|
|
scope: "o_123",
|
|
resource: resource.AuthMethod,
|
|
id: "am_1234",
|
|
},
|
|
{
|
|
name: "root, unknown action",
|
|
path: "/v1/:authentifake",
|
|
wantErrContains: "unknown action",
|
|
},
|
|
{
|
|
name: "root, unknown empty action",
|
|
path: "/v1/:",
|
|
wantErrContains: "unknown action",
|
|
},
|
|
{
|
|
name: "root, invalid method",
|
|
path: "/v1/:authenticate",
|
|
method: "FOOBAR",
|
|
wantErrContains: "unknown method",
|
|
},
|
|
{
|
|
name: "root, wrong number of colons",
|
|
path: "/v1/:auth:enticate",
|
|
wantErrContains: "unexpected number of colons",
|
|
},
|
|
{
|
|
name: "org scope, valid",
|
|
path: "/v1/orgs/o_abc123/auth-methods",
|
|
method: "POST",
|
|
action: action.Create,
|
|
scope: "o_abc123",
|
|
resource: resource.AuthMethod,
|
|
},
|
|
{
|
|
name: "project scope, valid",
|
|
path: "/v1/orgs/o_abc123/projects/p_1234/host-catalogs",
|
|
method: "POST",
|
|
action: action.Create,
|
|
scope: "p_1234",
|
|
resource: resource.HostCatalog,
|
|
},
|
|
{
|
|
name: "project scope, action on project",
|
|
path: "/v1/orgs/o_abc123/projects/p_1234/:set-principals",
|
|
method: "POST",
|
|
action: action.SetPrincipals,
|
|
scope: "p_1234",
|
|
resource: resource.Project,
|
|
id: "p_1234",
|
|
},
|
|
{
|
|
name: "org scope, action on project",
|
|
path: "/v1/orgs/o_abc123/projects/p_1234:set-principals",
|
|
method: "POST",
|
|
action: action.SetPrincipals,
|
|
scope: "o_abc123",
|
|
resource: resource.Project,
|
|
id: "p_1234",
|
|
},
|
|
{
|
|
name: "org scope, get on collection is list",
|
|
path: "/v1/orgs/o_abc123/projects",
|
|
action: action.List,
|
|
scope: "o_abc123",
|
|
resource: resource.Project,
|
|
},
|
|
{
|
|
name: "org scope, action on org",
|
|
path: "/v1/orgs/o_abc123/:deauthenticate",
|
|
action: action.Deauthenticate,
|
|
scope: "o_abc123",
|
|
resource: resource.Org,
|
|
id: "o_abc123",
|
|
},
|
|
{
|
|
name: "top level action, invalid",
|
|
path: "/v1/:read",
|
|
wantErrContains: "id and type both not found",
|
|
},
|
|
{
|
|
name: "top level, invalid",
|
|
path: "/v1/",
|
|
wantErrContains: "id and type both not found",
|
|
},
|
|
{
|
|
name: "non-api path",
|
|
path: "/",
|
|
wantErrContains: "id and type both not found",
|
|
},
|
|
{
|
|
name: "project scope, pinning collection",
|
|
path: "/v1/orgs/o_abc123/projects/p_1234/host-catalogs/hc_1234/host-sets",
|
|
action: action.List,
|
|
scope: "p_1234",
|
|
pin: "hc_1234",
|
|
resource: resource.HostSet,
|
|
},
|
|
{
|
|
name: "project scope, pinning collection, custom action",
|
|
path: "/v1/orgs/o_abc123/projects/p_1234/host-catalogs/hc_1234/host-sets:create",
|
|
action: action.Create,
|
|
scope: "p_1234",
|
|
pin: "hc_1234",
|
|
resource: resource.HostSet,
|
|
},
|
|
{
|
|
name: "project scope, pinning id",
|
|
path: "/v1/orgs/o_abc123/projects/p_1234/host-catalogs/hc_1234/host-sets/hs_abc",
|
|
action: action.Read,
|
|
id: "hs_abc",
|
|
scope: "p_1234",
|
|
pin: "hc_1234",
|
|
resource: resource.HostSet,
|
|
},
|
|
{
|
|
name: "project scope, pinning id, custom action",
|
|
path: "/v1/orgs/o_abc123/projects/p_1234/host-catalogs/hc_1234/host-sets/hs_abc:update",
|
|
action: action.Update,
|
|
id: "hs_abc",
|
|
scope: "p_1234",
|
|
pin: "hc_1234",
|
|
resource: resource.HostSet,
|
|
},
|
|
}
|
|
for _, tc := range cases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
require, assert := require.New(t), assert.New(t)
|
|
if tc.method == "" {
|
|
tc.method = "GET"
|
|
}
|
|
req, err := http.NewRequest(tc.method, fmt.Sprintf("http://127.0.0.1:9200:%s", tc.path), nil)
|
|
require.NoError(err)
|
|
|
|
v := &verifier{
|
|
requestInfo: RequestInfo{
|
|
Path: req.URL.Path,
|
|
Method: tc.method,
|
|
},
|
|
}
|
|
|
|
err = v.parseAuthParams()
|
|
if tc.wantErrContains != "" {
|
|
require.Error(err)
|
|
assert.Contains(err.Error(), tc.wantErrContains, err.Error())
|
|
return
|
|
}
|
|
require.NoError(err)
|
|
|
|
if tc.path == "/" {
|
|
return
|
|
}
|
|
|
|
require.NotNil(v.res)
|
|
require.NotEqual(action.Unknown, v.act)
|
|
assert.Equal(tc.scope, v.res.ScopeId, "scope")
|
|
assert.Equal(tc.action, v.act, "action")
|
|
assert.Equal(tc.resource, v.res.Type, "type")
|
|
assert.Equal(tc.id, v.res.Id, "id")
|
|
assert.Equal(tc.pin, v.res.Pin, "pin")
|
|
})
|
|
}
|
|
}
|