Enable Split Cookies (#318)

This commit is contained in:
Todd Knight authored and GitHub committed 2020-09-01 09:23:41 -07:00
1 parent a4c20164f3
commit 0aba6db720
11 files changed
+174 -49

No files matched your search

+1
View File
@@ -77,6 +77,7 @@ func handleGrpcGateway(c *Controller, props HandlerProperties) (http.Handler, er
},
}),
runtime.WithErrorHandler(handlers.ErrorHandler(c.logger)),
runtime.WithForwardResponseOption(handlers.OutgoingIntercepter),
)
hcs, err := host_catalogs.NewService(c.StaticHostRepoFn)
if err != nil {
+51 -3
View File
@@ -1,6 +1,7 @@
package controller
import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
@@ -8,6 +9,7 @@ import (
"strings"
"testing"
"github.com/hashicorp/boundary/internal/servers/controller/handlers"
"github.com/hashicorp/boundary/internal/types/scope"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@@ -21,12 +23,23 @@ func TestAuthenticationHandler(t *testing.T) {
})
defer c.Shutdown()
resp, err := http.Post(fmt.Sprintf("%s/v1/scopes/%s/auth-methods/ampw_1234567890:authenticate", c.ApiAddrs()[0], scope.Global.String()), "application/json",
strings.NewReader(`{"token_type": null, "credentials": {"login_name":"admin", "password": "password123"}}`))
request := map[string]interface{}{
"credentials": map[string]interface{}{
"login_name": "admin",
"password": "password123",
},
}
// No token_type defined means "token" type
b, err := json.Marshal(request)
require.NoError(t, err)
resp, err := http.Post(fmt.Sprintf("%s/v1/scopes/%s/auth-methods/ampw_1234567890:authenticate", c.ApiAddrs()[0],
scope.Global.String()), "application/json", bytes.NewReader(b))
require.NoError(t, err)
assert.Equal(t, http.StatusOK, resp.StatusCode, "Got response: %v", resp)
b, err := ioutil.ReadAll(resp.Body)
b, err = ioutil.ReadAll(resp.Body)
require.NoError(t, err)
body := make(map[string]interface{})
require.NoError(t, json.Unmarshal(b, &body))
@@ -37,6 +50,41 @@ func TestAuthenticationHandler(t *testing.T) {
assert.NotEmpty(t, pubId)
assert.NotEmpty(t, tok)
assert.Truef(t, strings.HasPrefix(tok, pubId), "Token: %q, Id: %q", tok, pubId)
// Set the token type to cookie and make sure the body does not contain the token anymore.
request["token_type"] = "cookie"
b, err = json.Marshal(request)
resp, err = http.Post(fmt.Sprintf("%s/v1/scopes/%s/auth-methods/ampw_1234567890:authenticate", c.ApiAddrs()[0],
scope.Global.String()), "application/json", bytes.NewReader(b))
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, "auth_method_id")
require.Contains(t, body, "user_id")
require.NotContains(t, body, "token")
cookies := make(map[string]*http.Cookie)
for _, c := range resp.Cookies() {
cookies[c.Name] = c
}
require.Contains(t, cookies, handlers.HttpOnlyCookieName)
require.Contains(t, cookies, handlers.JsVisibleCookieName)
assert.NotEmpty(t, cookies[handlers.HttpOnlyCookieName].Value)
assert.NotEmpty(t, cookies[handlers.JsVisibleCookieName].Value)
assert.True(t, cookies[handlers.HttpOnlyCookieName].HttpOnly)
assert.False(t, cookies[handlers.JsVisibleCookieName].HttpOnly)
tok = cookies[handlers.JsVisibleCookieName].Value
pubId = body["id"].(string)
assert.NotEmpty(t, pubId)
assert.Truef(t, strings.HasPrefix(tok, pubId), "Token: %q, Id: %q", tok, pubId)
}
func TestHandleImplementedPaths(t *testing.T) {
@@ -70,7 +70,7 @@ func (s Service) Authenticate(ctx context.Context, req *pbs.AuthenticateRequest)
if err != nil {
return nil, err
}
return &pbs.AuthenticateResponse{Item: tok}, nil
return &pbs.AuthenticateResponse{Item: tok, TokenType: req.GetTokenType()}, nil
}
// Deauthenticate implements the interface pbs.AuthenticationServiceServer.
@@ -171,7 +171,7 @@ func validateAuthenticateRequest(req *pbs.AuthenticateRequest) error {
}
// TODO: Update this when we enable split cookie token types.
tType := strings.ToLower(strings.TrimSpace(req.GetTokenType()))
if tType != "" && tType != "token" {
if tType != "" && tType != "token" && tType != "cookie" {
badFields["token_type"] = "The only accepted type is 'token'."
}
if len(badFields) > 0 {
@@ -23,7 +23,7 @@ var (
func init() {
var err error
if maskManager, err = handlers.NewMaskManager(&pb.HostSet{}, &store.HostSet{}); err != nil {
if maskManager, err = handlers.NewMaskManager(&store.HostSet{}, &pb.HostSet{}); err != nil {
panic(err)
}
}
@@ -0,0 +1,40 @@
package handlers
import (
"context"
"net/http"
"strings"
pbs "github.com/hashicorp/boundary/internal/gen/controller/api/services"
"google.golang.org/protobuf/proto"
)
const (
HttpOnlyCookieName = "wt-http-token-cookie"
JsVisibleCookieName = "wt-js-token-cookie"
)
func OutgoingInterceptor(ctx context.Context, w http.ResponseWriter, m proto.Message) error {
m = m.ProtoReflect().Interface()
switch m := m.(type) {
case *pbs.AuthenticateResponse:
if strings.EqualFold(m.GetTokenType(), "cookie") {
tok := m.GetItem().GetToken()
m.GetItem().Token = ""
half := len(tok) / 2
jsTok := http.Cookie{
Name: JsVisibleCookieName,
Value: tok[:half],
}
httpTok := http.Cookie{
Name: HttpOnlyCookieName,
Value: tok[half:],
HttpOnly: true,
}
http.SetCookie(w, &jsTok)
http.SetCookie(w, &httpTok)
}
}
return nil
}
@@ -0,0 +1,24 @@
package handlers
import (
"context"
"net/http"
"net/http/httptest"
"testing"
pb "github.com/hashicorp/boundary/internal/gen/controller/api/resources/authtokens"
pbs "github.com/hashicorp/boundary/internal/gen/controller/api/services"
"github.com/stretchr/testify/assert"
)
func TestOutgoingSplitCookie(t *testing.T) {
rec := httptest.NewRecorder()
OutgoingIntercepter(context.Background(), rec, &pbs.AuthenticateResponse{
TokenType: "cookie",
Item: &pb.AuthToken{Token: "t_abc_1234567890"},
})
assert.ElementsMatch(t, rec.Result().Cookies(), []*http.Cookie{
{Name: HttpOnlyCookieName, Value: "34567890", HttpOnly: true, Raw: "wt-http-token-cookie=34567890; HttpOnly"},
{Name: JsVisibleCookieName, Value: "t_abc_12", Raw: "wt-js-token-cookie=t_abc_12"},
})
}