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.
pull/2031/head
Johan Brandhorst-Satzkorn 4 years ago
parent 0e51f9cc92
commit a2bff4efdf

@ -42,9 +42,6 @@ message AuthToken {
// Output only. The time this Auth Token expires.
google.protobuf.Timestamp expiration_time = 110 [json_name="expiration_time"];
// Output only. The type of this auth token. Either "cookie" or "token".
string token_type = 120 [json_name="token_type"];
// Output only. The available actions on this resource for this user.
repeated string authorized_actions = 300 [json_name="authorized_actions"];
}

@ -10,6 +10,7 @@ import "google/api/visibility.proto";
import "google/protobuf/field_mask.proto";
import "google/protobuf/struct.proto";
import "controller/api/resources/authmethods/v1/auth_method.proto";
import "controller/api/resources/authtokens/v1/authtoken.proto";
import "controller/custom_options/v1/options.proto";
option (custom_options.v1.domain) = "auth";
@ -196,15 +197,6 @@ message OidcStartAttributes {
string cached_roundtrip_payload = 2;
}
// The layout of the struct for "attributes" field in AuthenticateRequest for a
// oidc type's token command. This message isn't directly referenced anywhere
// but is used here to define the expected field names and types.
message OidcTokenAttributes {
// The token id for the request. This is the token id that was returned from
// the oidc type's start command
string token_id = 1;
}
message AuthenticateRequest {
// The ID of the Auth Method in the system that should be used for authentication.
string auth_method_id = 1 [json_name = "auth_method_id"]; // @gotags: `class:"public"`
@ -214,8 +206,16 @@ message AuthenticateRequest {
// This can be "cookie" or "token". If not provided, "token" will be used. "cookie" activates a split-cookie method where the token is split partially between http-only and regular cookies in order
// to keep it safe from rogue JS in the browser.
string type = 6 [json_name = "type"]; // @gotags: `class:"public"`
// Attributes are passed to the Auth Method; the valid keys and values depend on the type of Auth Method as well as the command.
google.protobuf.Struct attributes = 4 [json_name = "attributes"];
oneof attrs {
// Attributes are passed to the Auth Method; the valid keys and values depend on the type of Auth Method as well as the command.
google.protobuf.Struct attributes = 4;
// Note: these fields have a custom mapping function for transforming to and from the generic attributes,
// they do not use the standard attribute transformation.
PasswordLoginAttributes password_login_attributes = 7 [(google.api.field_visibility).restriction = "INTERNAL"];
OidcStartAttributes oidc_start_attributes = 8 [(google.api.field_visibility).restriction = "INTERNAL"];
controller.api.resources.authmethods.v1.OidcAuthMethodAuthenticateCallbackRequest oidc_auth_method_authenticate_callback_request = 9 [(google.api.field_visibility).restriction = "INTERNAL"];
controller.api.resources.authmethods.v1.OidcAuthMethodAuthenticateTokenRequest oidc_auth_method_authenticate_token_request = 10 [(google.api.field_visibility).restriction = "INTERNAL"];
}
// The command to perform.
string command = 5 [json_name = "command"]; // @gotags: `class:"public"`
@ -229,8 +229,16 @@ message AuthenticateResponse {
reserved "item", "token_type";
// The type of the token returned. Either "cookie" or "token".
string type = 3; // @gotags: `class:"public"`
// Valid keys and values depend on the type of Auth Method as well as the command.
google.protobuf.Struct attributes = 4 [json_name = "attributes"];
oneof attrs {
// Valid keys and values depend on the type of Auth Method as well as the command.
google.protobuf.Struct attributes = 4 [json_name = "attributes"];
// Note: these fields have a custom mapping function for transforming to and from the generic attributes,
// they do not use the standard attribute transformation.
controller.api.resources.authmethods.v1.OidcAuthMethodAuthenticateStartResponse oidc_auth_method_authenticate_start_response = 6 [(google.api.field_visibility).restriction = "INTERNAL"];
controller.api.resources.authmethods.v1.OidcAuthMethodAuthenticateCallbackResponse oidc_auth_method_authenticate_callback_response = 7 [(google.api.field_visibility).restriction = "INTERNAL"];
controller.api.resources.authmethods.v1.OidcAuthMethodAuthenticateTokenResponse oidc_auth_method_authenticate_token_response = 8 [(google.api.field_visibility).restriction = "INTERNAL"];
controller.api.resources.authtokens.v1.AuthToken auth_token_response = 9 [(google.api.field_visibility).restriction = "INTERNAL"];
}
// The command that was performed.
string command = 5 [json_name = "command"]; // @gotags: `class:"public"`
}

@ -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"},

@ -84,11 +84,13 @@ func TestAuthenticate(t *testing.T) {
// TODO(johanbrandhorst): revert this when classification is done for auth methods
reqDetails := tests_api.GetEventDetails(t, got, "request")
tests_api.AssertRedactedValues(t, reqDetails)
tests_api.AssertRedactedValues(t, reqDetails["attributes"], "password", "login_name")
tests_api.AssertRedactedValues(t, reqDetails["Attrs"])
tests_api.AssertRedactedValues(t, reqDetails["Attrs"].(map[string]interface{})["PasswordLoginAttributes"], "password", "login_name")
respDetails := tests_api.GetEventDetails(t, got, "response")
tests_api.AssertRedactedValues(t, respDetails)
tests_api.AssertRedactedValues(t, respDetails["attributes"], "token", "approximate_last_used_time", "expiration_time", "token_type", "updated_time", "user_id", "account_id", "auth_method_id", "authorized_actions", "created_time", "id")
tests_api.AssertRedactedValues(t, respDetails["Attrs"])
tests_api.AssertRedactedValues(t, respDetails["Attrs"].(map[string]interface{})["AuthTokenResponse"], "token", "token_type", "user_id", "account_id", "auth_method_id", "authorized_actions", "id", "scope_id")
_ = os.WriteFile(eventConfig.AuditEvents.Name(), nil, 0o666) // clean out audit events from previous calls
tok, err = methods.Authenticate(tc.Context(), tc.Server().DevPasswordAuthMethodId, "login", map[string]interface{}{"login_name": "user", "password": "bad-pass"})
@ -98,5 +100,6 @@ func TestAuthenticate(t *testing.T) {
reqDetails = tests_api.GetEventDetails(t, got, "request")
tests_api.AssertRedactedValues(t, reqDetails)
tests_api.AssertRedactedValues(t, reqDetails["attributes"], "password", "login_name")
tests_api.AssertRedactedValues(t, reqDetails["Attrs"])
tests_api.AssertRedactedValues(t, reqDetails["Attrs"].(map[string]interface{})["PasswordLoginAttributes"], "password", "login_name")
}

@ -187,9 +187,16 @@ func TestList(t *testing.T) {
result, err = amClient.List(tc.Context(), global)
require.NoError(err)
require.Len(result.Items, 3)
assert.Empty(cmp.Diff(genOIDCAM, result.Items[0], cmpopts.IgnoreUnexported(authmethods.AuthMethod{})))
assert.Empty(cmp.Diff(genPWAM, result.Items[1], cmpopts.IgnoreUnexported(authmethods.AuthMethod{})))
assert.Empty(cmp.Diff(pwAM.Item, result.Items[2], cmpopts.IgnoreUnexported(authmethods.AuthMethod{})))
assert.Empty(
cmp.Diff(
result.Items,
[]*authmethods.AuthMethod{genOIDCAM, genPWAM, pwAM.Item},
cmpopts.IgnoreUnexported(authmethods.AuthMethod{}),
cmpopts.SortSlices(func(a, b *authmethods.AuthMethod) bool {
return a.Name < b.Name
}),
),
)
oidcAM, err := amClient.Create(tc.Context(), "oidc", global,
authmethods.WithName("foo"),
@ -206,10 +213,16 @@ func TestList(t *testing.T) {
result, err = amClient.List(tc.Context(), global)
require.NoError(err)
require.Len(result.Items, 4)
assert.Empty(cmp.Diff(genOIDCAM, result.Items[0], cmpopts.IgnoreUnexported(authmethods.AuthMethod{})))
assert.Empty(cmp.Diff(oidcAM.Item, result.Items[1], cmpopts.IgnoreUnexported(authmethods.AuthMethod{})))
assert.Empty(cmp.Diff(genPWAM, result.Items[2], cmpopts.IgnoreUnexported(authmethods.AuthMethod{})))
assert.Empty(cmp.Diff(pwAM.Item, result.Items[3], cmpopts.IgnoreUnexported(authmethods.AuthMethod{})))
assert.Empty(
cmp.Diff(
result.Items,
[]*authmethods.AuthMethod{genOIDCAM, genPWAM, pwAM.Item, oidcAM.Item},
cmpopts.IgnoreUnexported(authmethods.AuthMethod{}),
cmpopts.SortSlices(func(a, b *authmethods.AuthMethod) bool {
return a.Name < b.Name
}),
),
)
result, err = amClient.List(tc.Context(), global,
authmethods.WithFilter(`"/item/attributes/client_id"=="client-id"`))
@ -221,8 +234,16 @@ func TestList(t *testing.T) {
authmethods.WithFilter(`"/item/attributes/min_login_name_length"==3`))
require.NoError(err)
require.Len(result.Items, 2)
assert.Empty(cmp.Diff(genPWAM, result.Items[0], cmpopts.IgnoreUnexported(authmethods.AuthMethod{})))
assert.Empty(cmp.Diff(pwAM.Item, result.Items[1], cmpopts.IgnoreUnexported(authmethods.AuthMethod{})))
assert.Empty(
cmp.Diff(
result.Items,
[]*authmethods.AuthMethod{genPWAM, pwAM.Item},
cmpopts.IgnoreUnexported(authmethods.AuthMethod{}),
cmpopts.SortSlices(func(a, b *authmethods.AuthMethod) bool {
return a.Name < b.Name
}),
),
)
result, err = amClient.List(tc.Context(), global,
authmethods.WithFilter(`"/item/attributes/min_password_length"==10`))

@ -8,12 +8,15 @@ import (
"github.com/hashicorp/boundary/internal/gen/controller/api/services"
"github.com/hashicorp/boundary/sdk/pbs/controller/api"
"github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/authmethods"
"github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/authtokens"
"github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/scopes"
"github.com/hashicorp/boundary/sdk/wrapper"
"github.com/hashicorp/eventlogger"
"github.com/hashicorp/eventlogger/filters/encrypt"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
structpb "google.golang.org/protobuf/types/known/structpb"
"google.golang.org/protobuf/types/known/timestamppb"
)
// TestAuthenticate_Tags will test that the response filtering aligns with the
@ -38,26 +41,31 @@ func TestAuthenticate_Tags(t *testing.T) {
CreatedAt: now,
Payload: &services.AuthenticateResponse{
Command: "public-command",
Attributes: &structpb.Struct{
Fields: map[string]*structpb.Value{
"account_id": structpb.NewStringValue("public-account_id"),
"approximate_last_used_time": structpb.NewStringValue("public-approximate_last_used_time"),
"auth_method_id": structpb.NewStringValue("public-auth_method_id"),
"authorized_actions": structpb.NewStringValue("public-authorized_actions"),
"created_time": structpb.NewStringValue("public-created_time"),
"expiration_time": structpb.NewStringValue("public-expiration_time"),
"id": structpb.NewStringValue("public-id"),
"scope": structpb.NewStringValue("public-scope"),
"type": structpb.NewStringValue("public-type"),
"updated_time": structpb.NewStringValue("public-updated_time"),
"user_id": structpb.NewStringValue("public-user_id"),
"status": structpb.NewStringValue("public-status"),
"auth_url": structpb.NewStringValue("public-auth_url"),
"token_id": structpb.NewStringValue("public-token_id"),
"final_redirect_url": structpb.NewStringValue("public-final_redirect_url"),
"token": structpb.NewStringValue("secret-token"),
Attrs: &services.AuthenticateResponse_AuthTokenResponse{
AuthTokenResponse: &authtokens.AuthToken{
AccountId: "public-account_id",
ApproximateLastUsedTime: timestamppb.New(now),
AuthMethodId: "public-auth_method_id",
AuthorizedActions: []string{
"public-authorized_actions",
},
CreatedTime: timestamppb.New(now),
ExpirationTime: timestamppb.New(now),
Id: "public-id",
ScopeId: "public-scope_id",
Scope: &scopes.ScopeInfo{
Id: "public-scope_id",
Type: "public-scope_type",
Name: "public-scope_name",
Description: "public-scope_description",
ParentScopeId: "public-parent_scope_id",
},
UpdatedTime: timestamppb.New(now),
UserId: "public-user_id",
Token: "secret-type",
},
},
Type: "public-type",
},
},
wantEvent: &eventlogger.Event{
@ -66,26 +74,31 @@ func TestAuthenticate_Tags(t *testing.T) {
Payload: &services.AuthenticateResponse{
Command: "public-command",
// TODO(johanbrandhorst): update redaction once typed attributes are available
Attributes: &structpb.Struct{
Fields: map[string]*structpb.Value{
"account_id": structpb.NewStringValue(encrypt.RedactedData),
"approximate_last_used_time": structpb.NewStringValue(encrypt.RedactedData),
"auth_method_id": structpb.NewStringValue(encrypt.RedactedData),
"authorized_actions": structpb.NewStringValue(encrypt.RedactedData),
"created_time": structpb.NewStringValue(encrypt.RedactedData),
"expiration_time": structpb.NewStringValue(encrypt.RedactedData),
"id": structpb.NewStringValue(encrypt.RedactedData),
"scope": structpb.NewStringValue(encrypt.RedactedData),
"type": structpb.NewStringValue(encrypt.RedactedData),
"updated_time": structpb.NewStringValue(encrypt.RedactedData),
"user_id": structpb.NewStringValue(encrypt.RedactedData),
"status": structpb.NewStringValue(encrypt.RedactedData),
"auth_url": structpb.NewStringValue(encrypt.RedactedData),
"token_id": structpb.NewStringValue(encrypt.RedactedData),
"final_redirect_url": structpb.NewStringValue(encrypt.RedactedData),
"token": structpb.NewStringValue(encrypt.RedactedData),
Attrs: &services.AuthenticateResponse_AuthTokenResponse{
AuthTokenResponse: &authtokens.AuthToken{
AccountId: encrypt.RedactedData,
ApproximateLastUsedTime: timestamppb.New(now),
AuthMethodId: encrypt.RedactedData,
AuthorizedActions: []string{
encrypt.RedactedData,
},
CreatedTime: timestamppb.New(now),
ExpirationTime: timestamppb.New(now),
Id: encrypt.RedactedData,
ScopeId: encrypt.RedactedData,
Scope: &scopes.ScopeInfo{
Id: "public-scope_id",
Type: "public-scope_type",
Name: "public-scope_name",
Description: "public-scope_description",
ParentScopeId: "public-parent_scope_id",
},
UpdatedTime: timestamppb.New(now),
UserId: encrypt.RedactedData,
Token: encrypt.RedactedData,
},
},
Type: "public-type",
},
},
},
@ -98,14 +111,9 @@ func TestAuthenticate_Tags(t *testing.T) {
AuthMethodId: "public-auth-method-id",
TokenType: "public-token-type",
Command: "public-command",
Attributes: &structpb.Struct{
Fields: map[string]*structpb.Value{
"login_name": structpb.NewStringValue("public-login_name"),
"auth_url": structpb.NewStringValue("public-auth_url"),
"token_id": structpb.NewStringValue("public-token_id"),
"state": structpb.NewStringValue("public-state"),
"password": structpb.NewStringValue("secret-password"),
"code": structpb.NewStringValue("secret-code"),
Attrs: &services.AuthenticateRequest_OidcAuthMethodAuthenticateTokenRequest{
OidcAuthMethodAuthenticateTokenRequest: &authmethods.OidcAuthMethodAuthenticateTokenRequest{
TokenId: "public-token-id",
},
},
},
@ -118,14 +126,9 @@ func TestAuthenticate_Tags(t *testing.T) {
TokenType: "public-token-type",
Command: "public-command",
// TODO(johanbrandhorst): update redaction once typed attributes are available
Attributes: &structpb.Struct{
Fields: map[string]*structpb.Value{
"login_name": structpb.NewStringValue(encrypt.RedactedData),
"auth_url": structpb.NewStringValue(encrypt.RedactedData),
"token_id": structpb.NewStringValue(encrypt.RedactedData),
"state": structpb.NewStringValue(encrypt.RedactedData),
"password": structpb.NewStringValue(encrypt.RedactedData),
"code": structpb.NewStringValue(encrypt.RedactedData),
Attrs: &services.AuthenticateRequest_OidcAuthMethodAuthenticateTokenRequest{
OidcAuthMethodAuthenticateTokenRequest: &authmethods.OidcAuthMethodAuthenticateTokenRequest{
TokenId: encrypt.RedactedData,
},
},
},

@ -50,8 +50,6 @@ type AuthToken struct {
ApproximateLastUsedTime *timestamppb.Timestamp `protobuf:"bytes,100,opt,name=approximate_last_used_time,proto3" json:"approximate_last_used_time,omitempty"`
// Output only. The time this Auth Token expires.
ExpirationTime *timestamppb.Timestamp `protobuf:"bytes,110,opt,name=expiration_time,proto3" json:"expiration_time,omitempty"`
// Output only. The type of this auth token. Either "cookie" or "token".
TokenType string `protobuf:"bytes,120,opt,name=token_type,proto3" json:"token_type,omitempty"`
// Output only. The available actions on this resource for this user.
AuthorizedActions []string `protobuf:"bytes,300,rep,name=authorized_actions,proto3" json:"authorized_actions,omitempty"`
}
@ -165,13 +163,6 @@ func (x *AuthToken) GetExpirationTime() *timestamppb.Timestamp {
return nil
}
func (x *AuthToken) GetTokenType() string {
if x != nil {
return x.TokenType
}
return ""
}
func (x *AuthToken) GetAuthorizedActions() []string {
if x != nil {
return x.AuthorizedActions
@ -193,7 +184,7 @@ var file_controller_api_resources_authtokens_v1_authtoken_proto_rawDesc = []byte
0x6f, 0x1a, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2f, 0x61, 0x70,
0x69, 0x2f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x2f, 0x73, 0x63, 0x6f, 0x70,
0x65, 0x73, 0x2f, 0x76, 0x31, 0x2f, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74,
0x6f, 0x22, 0xe7, 0x04, 0x0a, 0x09, 0x41, 0x75, 0x74, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12,
0x6f, 0x22, 0xc7, 0x04, 0x0a, 0x09, 0x41, 0x75, 0x74, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12,
0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12,
0x1a, 0x0a, 0x08, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x14, 0x20, 0x01, 0x28,
0x09, 0x52, 0x08, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x5f, 0x69, 0x64, 0x12, 0x43, 0x0a, 0x05, 0x73,
@ -226,9 +217,7 @@ var file_controller_api_resources_authtokens_v1_authtoken_proto_rawDesc = []byte
0x6f, 0x6e, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x6e, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e,
0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e,
0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0f, 0x65, 0x78, 0x70, 0x69, 0x72,
0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x74, 0x6f,
0x6b, 0x65, 0x6e, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x78, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a,
0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x12, 0x2f, 0x0a, 0x12, 0x61, 0x75,
0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x12, 0x2f, 0x0a, 0x12, 0x61, 0x75,
0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x64, 0x5f, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73,
0x18, 0xac, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x12, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69,
0x7a, 0x65, 0x64, 0x5f, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x42, 0x56, 0x5a, 0x54, 0x67,

Loading…
Cancel
Save