feat(authmethods): Add typed attributes to authenticate request and response

The AuthenticateRequest and AuthenticateResponse types have a
complex mapping rule system, which requires a pair of custom
callback functions.
This commit is contained in:
Johan Brandhorst-Satzkorn committed 2022-04-20 13:32:31 -04:00
1 parent 0e51f9cc92
commit a2bff4efdf
14 files changed
+1346 -678

No files matched your search

@@ -32,9 +32,16 @@ import (
"github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/scopes"
"github.com/hashicorp/go-secure-stdlib/strutil"
"google.golang.org/grpc/codes"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/types/known/structpb"
"google.golang.org/protobuf/types/known/wrapperspb"
)
func init() {
subtypes.RegisterRequestTransformationFunc(&pbs.AuthenticateRequest{}, transformAuthenticateRequestAttributes)
subtypes.RegisterResponseTransformationFunc(&pbs.AuthenticateResponse{}, transformAuthenticateResponseAttributes)
}
const (
// general auth method field names
commandField = "command"
@@ -792,7 +799,6 @@ func toAuthMethodProto(ctx context.Context, in auth.AuthMethod, opt ...handlers.
OidcAuthMethodsAttributes: attrs,
}
}
fmt.Println("attrs", out.Attrs)
return &out, nil
}
@@ -1194,20 +1200,117 @@ func (s Service) convertToAuthenticateResponse(ctx context.Context, req *pbs.Aut
ScopeId: authResults.Scope.Id,
Type: resource.AuthToken,
}
tok.AuthorizedActions = authResults.FetchActionSetForId(ctx, tok.Id, authtokens.IdActions, requestauth.WithResource(res)).Strings()
retAttrs, err := handlers.ProtoToStruct(tok)
if err != nil {
return nil, err
}
tokenType := req.GetType()
if tokenType == "" {
// Fall back to deprecated field if type is not set
tokenType = req.GetTokenType()
}
tok.AuthorizedActions = authResults.FetchActionSetForId(ctx, tok.Id, authtokens.IdActions, requestauth.WithResource(res)).Strings()
return &pbs.AuthenticateResponse{
Command: req.GetCommand(),
Attributes: retAttrs,
Type: tokenType,
Command: req.GetCommand(),
Attrs: &pbs.AuthenticateResponse_AuthTokenResponse{
AuthTokenResponse: tok,
},
Type: tokenType,
}, nil
}
func transformAuthenticateRequestAttributes(msg proto.Message) error {
const op = "authmethod.transformAuthenticateRequestAttributes"
authRequest, ok := msg.(*pbs.AuthenticateRequest)
if !ok {
return fmt.Errorf("%s: message is not an AuthenticateRequest", op)
}
attrs := authRequest.GetAttributes()
if attrs == nil {
return nil
}
switch subtypes.SubtypeFromId(domain, authRequest.GetAuthMethodId()) {
case password.Subtype:
newAttrs := &pbs.PasswordLoginAttributes{}
if err := handlers.StructToProto(attrs, newAttrs); err != nil {
return err
}
authRequest.Attrs = &pbs.AuthenticateRequest_PasswordLoginAttributes{
PasswordLoginAttributes: newAttrs,
}
case oidc.Subtype:
switch authRequest.GetCommand() {
case startCommand:
newAttrs := &pbs.OidcStartAttributes{}
if err := handlers.StructToProto(attrs, newAttrs); err != nil {
return err
}
authRequest.Attrs = &pbs.AuthenticateRequest_OidcStartAttributes{
OidcStartAttributes: newAttrs,
}
case callbackCommand:
newAttrs := &pb.OidcAuthMethodAuthenticateCallbackRequest{}
if err := handlers.StructToProto(attrs, newAttrs, handlers.WithDiscardUnknownFields(true)); err != nil {
return err
}
authRequest.Attrs = &pbs.AuthenticateRequest_OidcAuthMethodAuthenticateCallbackRequest{
OidcAuthMethodAuthenticateCallbackRequest: newAttrs,
}
case tokenCommand:
newAttrs := &pb.OidcAuthMethodAuthenticateTokenRequest{}
if err := handlers.StructToProto(attrs, newAttrs); err != nil {
return err
}
authRequest.Attrs = &pbs.AuthenticateRequest_OidcAuthMethodAuthenticateTokenRequest{
OidcAuthMethodAuthenticateTokenRequest: newAttrs,
}
default:
return fmt.Errorf("%s: unknown command %q", op, authRequest.GetCommand())
}
default:
return fmt.Errorf("%s: unknown auth method subtype in ID %q", op, authRequest.GetAuthMethodId())
}
return nil
}
func transformAuthenticateResponseAttributes(msg proto.Message) error {
const op = "authmethod.transformAuthenticateResponseAttributes"
authResponse, ok := msg.(*pbs.AuthenticateResponse)
if !ok {
return fmt.Errorf("%s: message is not an AuthenticateResponse", op)
}
attrs := authResponse.GetAttrs()
if attrs == nil {
return nil
}
var newAttrs *structpb.Struct
var err error
switch attrs := attrs.(type) {
case *pbs.AuthenticateResponse_Attributes:
// No transformation necessary
newAttrs = attrs.Attributes
case *pbs.AuthenticateResponse_AuthTokenResponse:
newAttrs, err = handlers.ProtoToStruct(attrs.AuthTokenResponse)
if err != nil {
return err
}
case *pbs.AuthenticateResponse_OidcAuthMethodAuthenticateStartResponse:
newAttrs, err = handlers.ProtoToStruct(attrs.OidcAuthMethodAuthenticateStartResponse)
if err != nil {
return err
}
case *pbs.AuthenticateResponse_OidcAuthMethodAuthenticateCallbackResponse:
newAttrs, err = handlers.ProtoToStruct(attrs.OidcAuthMethodAuthenticateCallbackResponse)
if err != nil {
return err
}
case *pbs.AuthenticateResponse_OidcAuthMethodAuthenticateTokenResponse:
newAttrs, err = handlers.ProtoToStruct(attrs.OidcAuthMethodAuthenticateTokenResponse)
if err != nil {
return err
}
default:
return fmt.Errorf("%s: unknown attributes type %T", op, attrs)
}
authResponse.Attrs = &pbs.AuthenticateResponse_Attributes{
Attributes: newAttrs,
}
return nil
}
@@ -0,0 +1,396 @@
package authmethods
import (
"testing"
"github.com/google/go-cmp/cmp"
pbs "github.com/hashicorp/boundary/internal/gen/controller/api/services"
pb "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/authmethods"
pba "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/authtokens"
"github.com/stretchr/testify/require"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/testing/protocmp"
"google.golang.org/protobuf/types/known/structpb"
)
func TestTransformAuthenticateRequestAttributes(t *testing.T) {
t.Parallel()
cases := []struct {
name string
input *pbs.AuthenticateRequest
expected *pbs.AuthenticateRequest
}{
{
name: "empty-request",
input: &pbs.AuthenticateRequest{},
expected: &pbs.AuthenticateRequest{},
},
{
name: "password-attributes",
input: &pbs.AuthenticateRequest{
AuthMethodId: "apw_test",
Attrs: &pbs.AuthenticateRequest_Attributes{
Attributes: &structpb.Struct{
Fields: map[string]*structpb.Value{
"login_name": structpb.NewStringValue("login-name"),
"password": structpb.NewStringValue("password"),
},
},
},
},
expected: &pbs.AuthenticateRequest{
AuthMethodId: "apw_test",
Attrs: &pbs.AuthenticateRequest_PasswordLoginAttributes{
PasswordLoginAttributes: &pbs.PasswordLoginAttributes{
LoginName: "login-name",
Password: "password",
},
},
},
},
{
name: "oidc-start-attributes",
input: &pbs.AuthenticateRequest{
AuthMethodId: "amoidc_test",
Command: "start",
Attrs: &pbs.AuthenticateRequest_Attributes{
Attributes: &structpb.Struct{
Fields: map[string]*structpb.Value{
"roundtrip_payload": structpb.NewStructValue(&structpb.Struct{
Fields: map[string]*structpb.Value{
"field1": structpb.NewBoolValue(true),
"field2": structpb.NewStringValue("value2"),
},
}),
"cached_roundtrip_payload": structpb.NewStringValue("cached-roundtrip-payload"),
},
},
},
},
expected: &pbs.AuthenticateRequest{
AuthMethodId: "amoidc_test",
Command: "start",
Attrs: &pbs.AuthenticateRequest_OidcStartAttributes{
OidcStartAttributes: &pbs.OidcStartAttributes{
RoundtripPayload: &structpb.Struct{
Fields: map[string]*structpb.Value{
"field1": structpb.NewBoolValue(true),
"field2": structpb.NewStringValue("value2"),
},
},
CachedRoundtripPayload: "cached-roundtrip-payload",
},
},
},
},
{
name: "oidc-callback-attributes",
input: &pbs.AuthenticateRequest{
AuthMethodId: "amoidc_test",
Command: "callback",
Attrs: &pbs.AuthenticateRequest_Attributes{
Attributes: &structpb.Struct{
Fields: map[string]*structpb.Value{
"code": structpb.NewStringValue("code"),
"state": structpb.NewStringValue("state"),
"error": structpb.NewStringValue("error"),
"error_description": structpb.NewStringValue("error-description"),
"error_uri": structpb.NewStringValue("error-uri"),
},
},
},
},
expected: &pbs.AuthenticateRequest{
AuthMethodId: "amoidc_test",
Command: "callback",
Attrs: &pbs.AuthenticateRequest_OidcAuthMethodAuthenticateCallbackRequest{
OidcAuthMethodAuthenticateCallbackRequest: &pb.OidcAuthMethodAuthenticateCallbackRequest{
Code: "code",
State: "state",
Error: "error",
ErrorDescription: "error-description",
ErrorUri: "error-uri",
},
},
},
},
{
name: "oidc-callback-attributes-with-extra-fields",
input: &pbs.AuthenticateRequest{
AuthMethodId: "amoidc_test",
Command: "callback",
Attrs: &pbs.AuthenticateRequest_Attributes{
Attributes: &structpb.Struct{
Fields: map[string]*structpb.Value{
"code": structpb.NewStringValue("code"),
"state": structpb.NewStringValue("state"),
"non-callback-field": structpb.NewBoolValue(true),
},
},
},
},
expected: &pbs.AuthenticateRequest{
AuthMethodId: "amoidc_test",
Command: "callback",
Attrs: &pbs.AuthenticateRequest_OidcAuthMethodAuthenticateCallbackRequest{
OidcAuthMethodAuthenticateCallbackRequest: &pb.OidcAuthMethodAuthenticateCallbackRequest{
Code: "code",
State: "state",
},
},
},
},
{
name: "oidc-token-attributes",
input: &pbs.AuthenticateRequest{
AuthMethodId: "amoidc_test",
Command: "token",
Attrs: &pbs.AuthenticateRequest_Attributes{
Attributes: &structpb.Struct{
Fields: map[string]*structpb.Value{
"token_id": structpb.NewStringValue("token-id"),
},
},
},
},
expected: &pbs.AuthenticateRequest{
AuthMethodId: "amoidc_test",
Command: "token",
Attrs: &pbs.AuthenticateRequest_OidcAuthMethodAuthenticateTokenRequest{
OidcAuthMethodAuthenticateTokenRequest: &pb.OidcAuthMethodAuthenticateTokenRequest{
TokenId: "token-id",
},
},
},
},
}
for _, c := range cases {
c := c
t.Run(c.name, func(t *testing.T) {
t.Parallel()
in := proto.Clone(c.input)
require.NoError(t, transformAuthenticateRequestAttributes(in))
require.Empty(t, cmp.Diff(c.expected, in, protocmp.Transform()))
})
}
}
func TestTransformAuthenticateRequestAttributesErrors(t *testing.T) {
t.Parallel()
t.Run("not-an-authenticate-request", func(t *testing.T) {
t.Parallel()
require.Error(t, transformAuthenticateRequestAttributes(&pb.AuthMethod{}))
})
t.Run("invalid-auth-method-id", func(t *testing.T) {
t.Parallel()
require.Error(t, transformAuthenticateRequestAttributes(&pbs.AuthenticateRequest{
AuthMethodId: "invalid",
Attrs: &pbs.AuthenticateRequest_Attributes{
Attributes: &structpb.Struct{},
},
}))
})
t.Run("invalid-oidc-command", func(t *testing.T) {
t.Parallel()
require.Error(t, transformAuthenticateRequestAttributes(&pbs.AuthenticateRequest{
AuthMethodId: "amoidc_test",
Command: "invalid",
Attrs: &pbs.AuthenticateRequest_Attributes{
Attributes: &structpb.Struct{},
},
}))
})
t.Run("invalid-password-attributes", func(t *testing.T) {
t.Parallel()
require.Error(t, transformAuthenticateRequestAttributes(&pbs.AuthenticateRequest{
AuthMethodId: "apw_test",
Attrs: &pbs.AuthenticateRequest_Attributes{
Attributes: &structpb.Struct{
Fields: map[string]*structpb.Value{
"field1": structpb.NewBoolValue(true),
"field2": structpb.NewStringValue("value2"),
},
},
},
}))
})
t.Run("invalid-oidc-start-attributes", func(t *testing.T) {
t.Parallel()
require.Error(t, transformAuthenticateRequestAttributes(&pbs.AuthenticateRequest{
AuthMethodId: "amoidc_test",
Command: "start",
Attrs: &pbs.AuthenticateRequest_Attributes{
Attributes: &structpb.Struct{
Fields: map[string]*structpb.Value{
"field1": structpb.NewBoolValue(true),
"field2": structpb.NewStringValue("value2"),
},
},
},
}))
})
t.Run("invalid-oidc-token-attributes", func(t *testing.T) {
t.Parallel()
require.Error(t, transformAuthenticateRequestAttributes(&pbs.AuthenticateRequest{
AuthMethodId: "amoidc_test",
Command: "token",
Attrs: &pbs.AuthenticateRequest_Attributes{
Attributes: &structpb.Struct{
Fields: map[string]*structpb.Value{
"field1": structpb.NewBoolValue(true),
"field2": structpb.NewStringValue("value2"),
},
},
},
}))
})
}
func TestTransformAuthenticateResponseAttributes(t *testing.T) {
t.Parallel()
cases := []struct {
name string
input *pbs.AuthenticateResponse
expected *pbs.AuthenticateResponse
}{
{
name: "empty-attributes",
input: &pbs.AuthenticateResponse{
Command: "testcommand",
},
expected: &pbs.AuthenticateResponse{
Command: "testcommand",
},
},
{
name: "unstructured-attributes",
input: &pbs.AuthenticateResponse{
Command: "testcommand",
Attrs: &pbs.AuthenticateResponse_Attributes{
Attributes: &structpb.Struct{
Fields: map[string]*structpb.Value{
"field1": structpb.NewBoolValue(true),
"field2": structpb.NewStringValue("value2"),
},
},
},
},
expected: &pbs.AuthenticateResponse{
Command: "testcommand",
Attrs: &pbs.AuthenticateResponse_Attributes{
Attributes: &structpb.Struct{
Fields: map[string]*structpb.Value{
"field1": structpb.NewBoolValue(true),
"field2": structpb.NewStringValue("value2"),
},
},
},
},
},
{
name: "authtoken-attributes",
input: &pbs.AuthenticateResponse{
Command: "testcommand",
Attrs: &pbs.AuthenticateResponse_AuthTokenResponse{
AuthTokenResponse: &pba.AuthToken{
Id: "token-id",
},
},
},
expected: &pbs.AuthenticateResponse{
Command: "testcommand",
Attrs: &pbs.AuthenticateResponse_Attributes{
Attributes: &structpb.Struct{
Fields: map[string]*structpb.Value{
"id": structpb.NewStringValue("token-id"),
},
},
},
},
},
{
name: "oidc-start-attributes",
input: &pbs.AuthenticateResponse{
Command: "testcommand",
Attrs: &pbs.AuthenticateResponse_OidcAuthMethodAuthenticateStartResponse{
OidcAuthMethodAuthenticateStartResponse: &pb.OidcAuthMethodAuthenticateStartResponse{
AuthUrl: "auth-url",
TokenId: "token-id",
},
},
},
expected: &pbs.AuthenticateResponse{
Command: "testcommand",
Attrs: &pbs.AuthenticateResponse_Attributes{
Attributes: &structpb.Struct{
Fields: map[string]*structpb.Value{
"auth_url": structpb.NewStringValue("auth-url"),
"token_id": structpb.NewStringValue("token-id"),
},
},
},
},
},
{
name: "oidc-callback-attributes",
input: &pbs.AuthenticateResponse{
Command: "testcommand",
Attrs: &pbs.AuthenticateResponse_OidcAuthMethodAuthenticateCallbackResponse{
OidcAuthMethodAuthenticateCallbackResponse: &pb.OidcAuthMethodAuthenticateCallbackResponse{
FinalRedirectUrl: "final-redirect-url",
},
},
},
expected: &pbs.AuthenticateResponse{
Command: "testcommand",
Attrs: &pbs.AuthenticateResponse_Attributes{
Attributes: &structpb.Struct{
Fields: map[string]*structpb.Value{
"final_redirect_url": structpb.NewStringValue("final-redirect-url"),
},
},
},
},
},
{
name: "oidc-token-attributes",
input: &pbs.AuthenticateResponse{
Command: "testcommand",
Attrs: &pbs.AuthenticateResponse_OidcAuthMethodAuthenticateTokenResponse{
OidcAuthMethodAuthenticateTokenResponse: &pb.OidcAuthMethodAuthenticateTokenResponse{
Status: "status",
},
},
},
expected: &pbs.AuthenticateResponse{
Command: "testcommand",
Attrs: &pbs.AuthenticateResponse_Attributes{
Attributes: &structpb.Struct{
Fields: map[string]*structpb.Value{
"status": structpb.NewStringValue("status"),
},
},
},
},
},
}
for _, c := range cases {
c := c
t.Run(c.name, func(t *testing.T) {
t.Parallel()
in := proto.Clone(c.input)
require.NoError(t, transformAuthenticateResponseAttributes(in))
require.Empty(t, cmp.Diff(c.expected, in, protocmp.Transform()))
})
}
}
func TestTransformAuthenticateResponseAttributesErrors(t *testing.T) {
t.Parallel()
t.Run("not-an-authenticate-response", func(t *testing.T) {
t.Parallel()
require.Error(t, transformAuthenticateResponseAttributes(&pb.AuthMethod{}))
})
}
@@ -17,7 +17,6 @@ import (
"github.com/hashicorp/boundary/internal/types/action"
pb "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/authmethods"
"google.golang.org/grpc/codes"
"google.golang.org/protobuf/types/known/structpb"
)
const (
@@ -159,7 +158,7 @@ func (s Service) authenticateOidc(ctx context.Context, req *pbs.AuthenticateRequ
return s.authenticateOidcToken(ctx, req, authResults)
}
return &pbs.AuthenticateResponse{Command: req.GetCommand(), Attributes: nil}, nil
return &pbs.AuthenticateResponse{Command: req.GetCommand()}, nil
}
func (s Service) authenticateOidcStart(ctx context.Context, req *pbs.AuthenticateRequest) (*pbs.AuthenticateResponse, error) {
@@ -169,10 +168,7 @@ func (s Service) authenticateOidcStart(ctx context.Context, req *pbs.Authenticat
}
var opts []oidc.Option
attrs := new(pbs.OidcStartAttributes)
if err := handlers.StructToProto(req.GetAttributes(), attrs); err != nil {
return nil, errors.New(ctx, errors.InvalidParameter, op, "Error parsing request attributes.", errors.WithWrap(err))
}
attrs := req.GetOidcStartAttributes()
if attrs.GetCachedRoundtripPayload() != "" {
opts = append(opts, oidc.WithRoundtripPayload(attrs.GetCachedRoundtripPayload()))
}
@@ -185,15 +181,15 @@ func (s Service) authenticateOidcStart(ctx context.Context, req *pbs.Authenticat
return nil, errors.New(ctx, errors.Internal, op, "Error generating parameters for starting the OIDC flow. See the controller's log for more information.")
}
respAttrs := &pb.OidcAuthMethodAuthenticateStartResponse{
AuthUrl: authUrl.String(),
TokenId: tokenId,
}
resp := &pbs.AuthenticateResponse{Command: req.GetCommand()}
if resp.Attributes, err = handlers.ProtoToStruct(respAttrs); err != nil {
return nil, errors.New(ctx, errors.Internal, op, "Error marshaling parameters.", errors.WithWrap(err))
}
return resp, nil
return &pbs.AuthenticateResponse{
Command: req.GetCommand(),
Attrs: &pbs.AuthenticateResponse_OidcAuthMethodAuthenticateStartResponse{
OidcAuthMethodAuthenticateStartResponse: &pb.OidcAuthMethodAuthenticateStartResponse{
AuthUrl: authUrl.String(),
TokenId: tokenId,
},
},
}, nil
}
// authenticateOidcCallback behaves differently than other service methods.
@@ -236,20 +232,17 @@ func (s Service) authenticateOidcCallback(ctx context.Context, req *pbs.Authenti
}
u.Add("error", string(out))
errRedirect := fmt.Sprintf("%s?%s", errRedirectBase, u.Encode())
respAttrs, err := handlers.ProtoToStruct(&pb.OidcAuthMethodAuthenticateCallbackResponse{
FinalRedirectUrl: errRedirect,
})
if err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg("failed creating error redirect response"))
}
return &pbs.AuthenticateResponse{Command: callbackCommand, Attributes: respAttrs}, nil
return &pbs.AuthenticateResponse{
Command: callbackCommand,
Attrs: &pbs.AuthenticateResponse_OidcAuthMethodAuthenticateCallbackResponse{
OidcAuthMethodAuthenticateCallbackResponse: &pb.OidcAuthMethodAuthenticateCallbackResponse{
FinalRedirectUrl: errRedirect,
},
},
}, nil
}
attrs := new(pb.OidcAuthMethodAuthenticateCallbackRequest)
// Note that this conversion has already happened in the validate call so we don't expect errors here.
if err := handlers.StructToProto(req.GetAttributes(), attrs, handlers.WithDiscardUnknownFields(true)); err != nil {
return errResponse(err)
}
attrs := req.GetOidcAuthMethodAuthenticateCallbackRequest()
var finalRedirectUrl string
if attrs.GetError() != "" {
@@ -268,14 +261,14 @@ func (s Service) authenticateOidcCallback(ctx context.Context, req *pbs.Authenti
return errResponse(errors.New(ctx, errors.InvalidParameter, op, "Callback validation failed.", errors.WithWrap(err)))
}
respAttrs, err := handlers.ProtoToStruct(&pb.OidcAuthMethodAuthenticateCallbackResponse{
FinalRedirectUrl: finalRedirectUrl,
})
if err != nil {
return errResponse(errors.New(ctx, errors.Internal, op, "Error marshaling parameters after successful callback", errors.WithWrap(err)))
}
return &pbs.AuthenticateResponse{Command: req.GetCommand(), Attributes: respAttrs}, nil
return &pbs.AuthenticateResponse{
Command: req.GetCommand(),
Attrs: &pbs.AuthenticateResponse_OidcAuthMethodAuthenticateCallbackResponse{
OidcAuthMethodAuthenticateCallbackResponse: &pb.OidcAuthMethodAuthenticateCallbackResponse{
FinalRedirectUrl: finalRedirectUrl,
},
},
}, nil
}
func (s Service) authenticateOidcToken(ctx context.Context, req *pbs.AuthenticateRequest, authResults *auth.VerifyResults) (*pbs.AuthenticateResponse, error) {
@@ -286,14 +279,11 @@ func (s Service) authenticateOidcToken(ctx context.Context, req *pbs.Authenticat
if authResults == nil {
return nil, errors.New(ctx, errors.InvalidParameter, op, "Nil auth results.")
}
if req.GetAttributes() == nil {
if req.GetOidcAuthMethodAuthenticateTokenRequest() == nil {
return nil, errors.New(ctx, errors.InvalidParameter, op, "Nil request attributes.")
}
attrs := new(pb.OidcAuthMethodAuthenticateTokenRequest)
if err := handlers.StructToProto(req.GetAttributes(), attrs); err != nil {
return nil, errors.New(ctx, errors.InvalidParameter, op, "Error parsing request attributes.", errors.WithWrap(err))
}
attrs := req.GetOidcAuthMethodAuthenticateTokenRequest()
if attrs.TokenId == "" {
return nil, errors.New(ctx, errors.InvalidParameter, op, "Empty token ID in request attributes.")
}
@@ -313,15 +303,16 @@ func (s Service) authenticateOidcToken(ctx context.Context, req *pbs.Authenticat
}
}
if token == nil {
attrs, err := structpb.NewStruct(map[string]interface{}{
statusField: "unknown",
})
if err != nil {
return nil, errors.New(ctx, errors.Internal, op, "Error generating response attributes.", errors.WithWrap(err))
}
return &pbs.AuthenticateResponse{
Command: req.Command,
Attributes: attrs,
Command: req.Command,
Attrs: &pbs.AuthenticateResponse_OidcAuthMethodAuthenticateTokenResponse{
OidcAuthMethodAuthenticateTokenResponse: &pb.OidcAuthMethodAuthenticateTokenResponse{
Status: "unknown",
},
},
}, nil
}
@@ -340,12 +331,8 @@ func validateAuthenticateOidcRequest(req *pbs.AuthenticateRequest) error {
switch req.GetCommand() {
case startCommand:
if req.GetAttributes() != nil {
attrs := new(pbs.OidcStartAttributes)
if err := handlers.StructToProto(req.GetAttributes(), attrs); err != nil {
badFields[attributesField] = "Could not be parsed, or contains invalid fields."
break
}
if req.GetOidcStartAttributes() != nil {
attrs := req.GetOidcStartAttributes()
// Ensure we pay no attention to cache information provided by the client
attrs.CachedRoundtripPayload = ""
@@ -362,23 +349,10 @@ func validateAuthenticateOidcRequest(req *pbs.AuthenticateRequest) error {
} else {
// Cache for later
attrs.CachedRoundtripPayload = string(m)
req.Attributes, err = handlers.ProtoToStruct(attrs)
if err != nil {
return fmt.Errorf("unable to convert map back to proto")
}
}
}
case callbackCommand:
if req.GetAttributes() == nil {
badFields[attributesField] = "No callback attributes provided."
break
}
attrs := new(pb.OidcAuthMethodAuthenticateCallbackRequest)
if err := handlers.StructToProto(req.GetAttributes(), attrs, handlers.WithDiscardUnknownFields(true)); err != nil {
badFields[attributesField] = "Unable to parse callback request attributes."
break
}
attrs := req.GetOidcAuthMethodAuthenticateCallbackRequest()
if attrs.GetCode() == "" && attrs.GetError() == "" {
badFields[codeField] = "Code field not supplied in callback request."
@@ -1404,12 +1404,13 @@ func TestChangeState_OIDC(t *testing.T) {
},
{
name: "Make Complete Private",
req: &pbs.ChangeStateRequest{Id: oidcam.GetPublicId(), Version: oidcam.GetVersion(), Attrs: &pbs.ChangeStateRequest_OidcChangeStateAttributes{
OidcChangeStateAttributes: &pbs.OidcChangeStateAttributes{
State: "active-private",
req: &pbs.ChangeStateRequest{
Id: oidcam.GetPublicId(), Version: oidcam.GetVersion(), Attrs: &pbs.ChangeStateRequest_OidcChangeStateAttributes{
OidcChangeStateAttributes: &pbs.OidcChangeStateAttributes{
State: "active-private",
},
},
},
},
res: &pbs.ChangeStateResponse{Item: func() *pb.AuthMethod {
am := proto.Clone(wantTemplate).(*pb.AuthMethod)
am.GetOidcAuthMethodsAttributes().State = "active-private"
@@ -1419,12 +1420,13 @@ func TestChangeState_OIDC(t *testing.T) {
},
{
name: "Make Complete Public",
req: &pbs.ChangeStateRequest{Id: oidcam.GetPublicId(), Version: 2, Attrs: &pbs.ChangeStateRequest_OidcChangeStateAttributes{
OidcChangeStateAttributes: &pbs.OidcChangeStateAttributes{
State: "active-public",
req: &pbs.ChangeStateRequest{
Id: oidcam.GetPublicId(), Version: 2, Attrs: &pbs.ChangeStateRequest_OidcChangeStateAttributes{
OidcChangeStateAttributes: &pbs.OidcChangeStateAttributes{
State: "active-public",
},
},
},
},
res: &pbs.ChangeStateResponse{Item: func() *pb.AuthMethod {
am := proto.Clone(wantTemplate).(*pb.AuthMethod)
am.GetOidcAuthMethodsAttributes().State = "active-public"
@@ -1434,12 +1436,13 @@ func TestChangeState_OIDC(t *testing.T) {
},
{
name: "Make Complete Inactive",
req: &pbs.ChangeStateRequest{Id: oidcam.GetPublicId(), Version: 3, Attrs: &pbs.ChangeStateRequest_OidcChangeStateAttributes{
OidcChangeStateAttributes: &pbs.OidcChangeStateAttributes{
State: "inactive",
req: &pbs.ChangeStateRequest{
Id: oidcam.GetPublicId(), Version: 3, Attrs: &pbs.ChangeStateRequest_OidcChangeStateAttributes{
OidcChangeStateAttributes: &pbs.OidcChangeStateAttributes{
State: "inactive",
},
},
},
},
res: &pbs.ChangeStateResponse{Item: func() *pb.AuthMethod {
am := proto.Clone(wantTemplate).(*pb.AuthMethod)
am.GetOidcAuthMethodsAttributes().State = "inactive"
@@ -1529,16 +1532,18 @@ func TestAuthenticate_OIDC_Start(t *testing.T) {
request: &pbs.AuthenticateRequest{
Command: "start",
AuthMethodId: s.authMethod.GetPublicId(),
Attributes: func() *structpb.Struct {
ret, err := structpb.NewStruct(map[string]interface{}{
"roundtrip_payload": map[string]interface{}{
"foo": "bar",
"baz": true,
},
})
require.NoError(t, err)
return ret
}(),
Attrs: &pbs.AuthenticateRequest_OidcStartAttributes{
OidcStartAttributes: &pbs.OidcStartAttributes{
RoundtripPayload: func() *structpb.Struct {
ret, err := structpb.NewStruct(map[string]interface{}{
"foo": "bar",
"baz": true,
})
require.NoError(t, err)
return ret
}(),
},
},
},
},
}
@@ -1556,13 +1561,10 @@ func TestAuthenticate_OIDC_Start(t *testing.T) {
require.Equal(got.GetCommand(), "start")
// We can't really compare directly as a lot of the values contain
// random data, so just verify existence
require.NotNil(got.GetAttributes())
m := got.GetAttributes().AsMap()
require.NotNil(m)
require.Contains(m, "auth_url")
require.NotEmpty(m["auth_url"])
require.Contains(m, "token_id")
require.NotEmpty(m["token_id"])
got.GetAuthTokenResponse()
require.NotNil(got.GetOidcAuthMethodAuthenticateStartResponse())
require.NotEmpty(got.GetOidcAuthMethodAuthenticateStartResponse().GetAuthUrl())
require.NotEmpty(got.GetOidcAuthMethodAuthenticateStartResponse().GetTokenId())
})
}
}
@@ -1623,18 +1625,16 @@ func TestAuthenticate_OIDC_Token(t *testing.T) {
request: &pbs.AuthenticateRequest{
Command: "token",
AuthMethodId: s.authMethod.GetPublicId(),
Attributes: func() *structpb.Struct {
ret, err := structpb.NewStruct(map[string]interface{}{
"token_id": func() string {
Attrs: &pbs.AuthenticateRequest_OidcAuthMethodAuthenticateTokenRequest{
OidcAuthMethodAuthenticateTokenRequest: &pb.OidcAuthMethodAuthenticateTokenRequest{
TokenId: func() string {
tokenPublicId, err := authtoken.NewAuthTokenId()
require.NoError(t, err)
oidc.TestPendingToken(t, testAtRepo, testUser, testAcct, tokenPublicId)
return oidc.TestTokenRequestId(t, s.authMethod, s.kmsCache, 200*time.Second, tokenPublicId)
}(),
})
require.NoError(t, err)
return ret
}(),
},
},
},
},
{
@@ -1642,18 +1642,16 @@ func TestAuthenticate_OIDC_Token(t *testing.T) {
request: &pbs.AuthenticateRequest{
Command: "token",
AuthMethodId: s.authMethod.GetPublicId(),
Attributes: func() *structpb.Struct {
ret, err := structpb.NewStruct(map[string]interface{}{
"token_id": func() string {
Attrs: &pbs.AuthenticateRequest_OidcAuthMethodAuthenticateTokenRequest{
OidcAuthMethodAuthenticateTokenRequest: &pb.OidcAuthMethodAuthenticateTokenRequest{
TokenId: func() string {
tokenPublicId, err := authtoken.NewAuthTokenId()
require.NoError(t, err)
oidc.TestPendingToken(t, testAtRepo, testUser, testAcct, tokenPublicId)
return oidc.TestTokenRequestId(t, s.authMethod, s.kmsCache, -20*time.Second, tokenPublicId)
}(),
})
require.NoError(t, err)
return ret
}(),
},
},
},
wantErrMatch: errors.T(errors.AuthAttemptExpired),
},
@@ -1662,13 +1660,11 @@ func TestAuthenticate_OIDC_Token(t *testing.T) {
request: &pbs.AuthenticateRequest{
Command: "token",
AuthMethodId: s.authMethod.GetPublicId(),
Attributes: func() *structpb.Struct {
ret, err := structpb.NewStruct(map[string]interface{}{
"token_id": "bad-token-id",
})
require.NoError(t, err)
return ret
}(),
Attrs: &pbs.AuthenticateRequest_OidcAuthMethodAuthenticateTokenRequest{
OidcAuthMethodAuthenticateTokenRequest: &pb.OidcAuthMethodAuthenticateTokenRequest{
TokenId: "bad-token-id",
},
},
},
wantErrMatch: errors.T(errors.Unknown),
},
@@ -1689,11 +1685,8 @@ func TestAuthenticate_OIDC_Token(t *testing.T) {
}
require.NoError(err)
require.Equal(got.GetCommand(), "token")
require.NotNil(got.GetAttributes())
m := got.GetAttributes().AsMap()
require.NotNil(m)
require.Contains(m, "token")
require.NotEmpty(m["token"])
require.NotNil(got.GetAuthTokenResponse())
require.NotEmpty(got.GetAuthTokenResponse().GetToken())
})
}
}
@@ -1710,27 +1703,23 @@ func TestAuthenticate_OIDC_Callback_ErrorRedirect(t *testing.T) {
req: &pbs.AuthenticateRequest{
AuthMethodId: s.authMethod.GetPublicId(),
Command: "callback",
Attributes: func() *structpb.Struct {
st, err := handlers.ProtoToStruct(&pb.OidcAuthMethodAuthenticateCallbackRequest{
Attrs: &pbs.AuthenticateRequest_OidcAuthMethodAuthenticateCallbackRequest{
OidcAuthMethodAuthenticateCallbackRequest: &pb.OidcAuthMethodAuthenticateCallbackRequest{
State: "anything",
})
require.NoError(t, err)
return st
}(),
},
},
},
},
{
name: "No Auth Method Id",
req: &pbs.AuthenticateRequest{
Command: "callback",
Attributes: func() *structpb.Struct {
st, err := handlers.ProtoToStruct(&pb.OidcAuthMethodAuthenticateCallbackRequest{
Attrs: &pbs.AuthenticateRequest_OidcAuthMethodAuthenticateCallbackRequest{
OidcAuthMethodAuthenticateCallbackRequest: &pb.OidcAuthMethodAuthenticateCallbackRequest{
Code: "anythingworks",
State: "anything",
})
require.NoError(t, err)
return st
}(),
},
},
},
},
{
@@ -1738,13 +1727,11 @@ func TestAuthenticate_OIDC_Callback_ErrorRedirect(t *testing.T) {
req: &pbs.AuthenticateRequest{
AuthMethodId: s.authMethod.GetPublicId(),
Command: "callback",
Attributes: func() *structpb.Struct {
st, err := handlers.ProtoToStruct(&pb.OidcAuthMethodAuthenticateCallbackRequest{
Attrs: &pbs.AuthenticateRequest_OidcAuthMethodAuthenticateCallbackRequest{
OidcAuthMethodAuthenticateCallbackRequest: &pb.OidcAuthMethodAuthenticateCallbackRequest{
Code: "anythingworks",
})
require.NoError(t, err)
return st
}(),
},
},
},
},
}
@@ -1784,16 +1771,13 @@ func TestAuthenticate_OIDC_Callback_ErrorRedirect(t *testing.T) {
&pbs.AuthenticateRequest{
AuthMethodId: s.authMethod.GetPublicId(),
Command: "callback",
Attributes: func() *structpb.Struct {
st, err := handlers.ProtoToStruct(tc.attrs)
require.NoError(t, err)
return st
}(),
Attrs: &pbs.AuthenticateRequest_OidcAuthMethodAuthenticateCallbackRequest{
OidcAuthMethodAuthenticateCallbackRequest: tc.attrs,
},
})
require.NoError(t, err)
respAttrs := &pb.OidcAuthMethodAuthenticateCallbackResponse{}
require.NoError(t, handlers.StructToProto(got.GetAttributes(), respAttrs))
respAttrs := got.GetOidcAuthMethodAuthenticateCallbackResponse()
assert.Contains(t, respAttrs.FinalRedirectUrl, fmt.Sprintf("%s/authentication-error?", s.authMethod.GetApiUrl()))
u, err := url.Parse(respAttrs.GetFinalRedirectUrl())
require.NoError(t, err)
@@ -88,8 +88,8 @@ func (s Service) updatePwInRepo(ctx context.Context, scopeId, id string, mask []
}
func (s Service) authenticatePassword(ctx context.Context, req *pbs.AuthenticateRequest, authResults *auth.VerifyResults) (*pbs.AuthenticateResponse, error) {
reqAttrs := req.GetAttributes().GetFields()
tok, err := s.authenticateWithPwRepo(ctx, authResults.Scope.GetId(), req.GetAuthMethodId(), reqAttrs[loginNameField].GetStringValue(), reqAttrs[passwordField].GetStringValue())
reqAttrs := req.GetPasswordLoginAttributes()
tok, err := s.authenticateWithPwRepo(ctx, authResults.Scope.GetId(), req.GetAuthMethodId(), reqAttrs.LoginName, reqAttrs.Password)
if err != nil {
return nil, err
}
@@ -136,17 +136,11 @@ func (s Service) authenticateWithPwRepo(ctx context.Context, scopeId, authMethod
func validateAuthenticatePasswordRequest(req *pbs.AuthenticateRequest) error {
badFields := make(map[string]string)
if req.GetAttributes() == nil || req.GetAttributes().GetFields() == nil {
badFields["attributes"] = "This is a required field."
// Return early because we need non-nil values in the rest of the check.
return handlers.InvalidArgumentErrorf("Invalid fields provided in request.", badFields)
}
attrs := req.GetAttributes().GetFields()
if _, ok := attrs[loginNameField]; !ok {
attrs := req.GetPasswordLoginAttributes()
if attrs.LoginName == "" {
badFields["attributes.login_name"] = "This is a required field."
}
if _, ok := attrs[passwordField]; !ok {
if attrs.Password == "" {
badFields["attributes.password"] = "This is a required field."
}
if req.GetCommand() == "" {
@@ -19,14 +19,12 @@ import (
"github.com/hashicorp/boundary/internal/servers/controller/handlers/authmethods"
"github.com/hashicorp/boundary/internal/types/scope"
pb "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/authmethods"
authtokenpb "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/authtokens"
scopepb "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/scopes"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/genproto/protobuf/field_mask"
"google.golang.org/grpc/codes"
"google.golang.org/protobuf/testing/protocmp"
"google.golang.org/protobuf/types/known/structpb"
"google.golang.org/protobuf/types/known/timestamppb"
"google.golang.org/protobuf/types/known/wrapperspb"
)
@@ -510,13 +508,12 @@ func TestAuthenticate_Password(t *testing.T) {
request: &pbs.AuthenticateRequest{
AuthMethodId: am.GetPublicId(),
TokenType: "token",
Attributes: func() *structpb.Struct {
creds := map[string]*structpb.Value{
"login_name": {Kind: &structpb.Value_StringValue{StringValue: testLoginName}},
"password": {Kind: &structpb.Value_StringValue{StringValue: testPassword}},
}
return &structpb.Struct{Fields: creds}
}(),
Attrs: &pbs.AuthenticateRequest_PasswordLoginAttributes{
PasswordLoginAttributes: &pbs.PasswordLoginAttributes{
LoginName: testLoginName,
Password: testPassword,
},
},
},
wantType: "token",
},
@@ -525,13 +522,12 @@ func TestAuthenticate_Password(t *testing.T) {
request: &pbs.AuthenticateRequest{
AuthMethodId: am.GetPublicId(),
TokenType: "cookie",
Attributes: func() *structpb.Struct {
creds := map[string]*structpb.Value{
"login_name": {Kind: &structpb.Value_StringValue{StringValue: testLoginName}},
"password": {Kind: &structpb.Value_StringValue{StringValue: testPassword}},
}
return &structpb.Struct{Fields: creds}
}(),
Attrs: &pbs.AuthenticateRequest_PasswordLoginAttributes{
PasswordLoginAttributes: &pbs.PasswordLoginAttributes{
LoginName: testLoginName,
Password: testPassword,
},
},
},
wantType: "cookie",
},
@@ -539,13 +535,12 @@ func TestAuthenticate_Password(t *testing.T) {
name: "no-token-type",
request: &pbs.AuthenticateRequest{
AuthMethodId: am.GetPublicId(),
Attributes: func() *structpb.Struct {
creds := map[string]*structpb.Value{
"login_name": {Kind: &structpb.Value_StringValue{StringValue: testLoginName}},
"password": {Kind: &structpb.Value_StringValue{StringValue: testPassword}},
}
return &structpb.Struct{Fields: creds}
}(),
Attrs: &pbs.AuthenticateRequest_PasswordLoginAttributes{
PasswordLoginAttributes: &pbs.PasswordLoginAttributes{
LoginName: testLoginName,
Password: testPassword,
},
},
},
},
{
@@ -553,26 +548,24 @@ func TestAuthenticate_Password(t *testing.T) {
request: &pbs.AuthenticateRequest{
AuthMethodId: am.GetPublicId(),
TokenType: "email",
Attributes: func() *structpb.Struct {
creds := map[string]*structpb.Value{
"login_name": {Kind: &structpb.Value_StringValue{StringValue: testLoginName}},
"password": {Kind: &structpb.Value_StringValue{StringValue: testPassword}},
}
return &structpb.Struct{Fields: creds}
}(),
Attrs: &pbs.AuthenticateRequest_PasswordLoginAttributes{
PasswordLoginAttributes: &pbs.PasswordLoginAttributes{
LoginName: testLoginName,
Password: testPassword,
},
},
},
wantErr: handlers.ApiErrorWithCode(codes.InvalidArgument),
},
{
name: "no-authmethod",
request: &pbs.AuthenticateRequest{
Attributes: func() *structpb.Struct {
creds := map[string]*structpb.Value{
"login_name": {Kind: &structpb.Value_StringValue{StringValue: testLoginName}},
"password": {Kind: &structpb.Value_StringValue{StringValue: testPassword}},
}
return &structpb.Struct{Fields: creds}
}(),
Attrs: &pbs.AuthenticateRequest_PasswordLoginAttributes{
PasswordLoginAttributes: &pbs.PasswordLoginAttributes{
LoginName: testLoginName,
Password: testPassword,
},
},
},
wantErr: handlers.ApiErrorWithCode(codes.InvalidArgument),
},
@@ -581,13 +574,12 @@ func TestAuthenticate_Password(t *testing.T) {
request: &pbs.AuthenticateRequest{
AuthMethodId: am.GetPublicId(),
TokenType: "token",
Attributes: func() *structpb.Struct {
creds := map[string]*structpb.Value{
"login_name": {Kind: &structpb.Value_StringValue{StringValue: testLoginName}},
"password": {Kind: &structpb.Value_StringValue{StringValue: "wrong"}},
}
return &structpb.Struct{Fields: creds}
}(),
Attrs: &pbs.AuthenticateRequest_PasswordLoginAttributes{
PasswordLoginAttributes: &pbs.PasswordLoginAttributes{
LoginName: testLoginName,
Password: "wrong",
},
},
},
wantErr: handlers.ApiErrorWithCode(codes.Unauthenticated),
},
@@ -596,13 +588,12 @@ func TestAuthenticate_Password(t *testing.T) {
request: &pbs.AuthenticateRequest{
AuthMethodId: am.GetPublicId(),
TokenType: "token",
Attributes: func() *structpb.Struct {
creds := map[string]*structpb.Value{
"login_name": {Kind: &structpb.Value_StringValue{StringValue: "wrong"}},
"password": {Kind: &structpb.Value_StringValue{StringValue: testPassword}},
}
return &structpb.Struct{Fields: creds}
}(),
Attrs: &pbs.AuthenticateRequest_PasswordLoginAttributes{
PasswordLoginAttributes: &pbs.PasswordLoginAttributes{
LoginName: "wrong",
Password: testPassword,
},
},
},
wantErr: handlers.ApiErrorWithCode(codes.Unauthenticated),
},
@@ -621,8 +612,8 @@ func TestAuthenticate_Password(t *testing.T) {
return
}
require.NoError(err)
aToken := &authtokenpb.AuthToken{}
require.NoError(handlers.StructToProto(resp.GetAttributes(), aToken, handlers.WithDiscardUnknownFields(true)))
aToken := resp.GetAuthTokenResponse()
assert.NotEmpty(aToken.GetId())
assert.NotEmpty(aToken.GetToken())
assert.True(strings.HasPrefix(aToken.GetToken(), aToken.GetId()))
@@ -678,18 +669,16 @@ func TestAuthenticate_AuthAccountConnectedToIamUser_Password(t *testing.T) {
require.NoError(err)
resp, err := s.Authenticate(auth.DisabledAuthTestContext(iamRepoFn, o.GetPublicId()), &pbs.AuthenticateRequest{
AuthMethodId: am.GetPublicId(),
Attributes: func() *structpb.Struct {
creds := map[string]*structpb.Value{
"login_name": {Kind: &structpb.Value_StringValue{StringValue: testLoginName}},
"password": {Kind: &structpb.Value_StringValue{StringValue: testPassword}},
}
return &structpb.Struct{Fields: creds}
}(),
Attrs: &pbs.AuthenticateRequest_PasswordLoginAttributes{
PasswordLoginAttributes: &pbs.PasswordLoginAttributes{
LoginName: testLoginName,
Password: testPassword,
},
},
})
require.NoError(err)
aToken := &authtokenpb.AuthToken{}
require.NoError(handlers.StructToProto(resp.GetAttributes(), aToken, handlers.WithDiscardUnknownFields(true)))
aToken := resp.GetAuthTokenResponse()
assert.Equal(iamUser.GetPublicId(), aToken.GetUserId())
assert.Equal(am.GetPublicId(), aToken.GetAuthMethodId())
assert.Equal(acct.GetPublicId(), aToken.GetAccountId())
@@ -10,7 +10,7 @@ import (
"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
pbs "github.com/hashicorp/boundary/internal/gen/controller/api/services"
pb "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/authtokens"
pba "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/authtokens"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/grpc/metadata"
@@ -19,9 +19,9 @@ import (
func TestOutgoingSplitCookie(t *testing.T) {
rec := httptest.NewRecorder()
attrs, err := ProtoToStruct(&pb.AuthToken{Token: "t_abc_1234567890"})
attrs, err := ProtoToStruct(&pba.AuthToken{Token: "t_abc_1234567890"})
require.NoError(t, err)
require.NoError(t, OutgoingResponseFilter(context.Background(), rec, &pbs.AuthenticateResponse{Attributes: attrs, Type: "cookie"}))
require.NoError(t, OutgoingResponseFilter(context.Background(), rec, &pbs.AuthenticateResponse{Attrs: &pbs.AuthenticateResponse_Attributes{Attributes: attrs}, Type: "cookie"}))
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"},