Use authenticate and Auth Method repo functions (#242)

This commit is contained in:
Todd Knight authored and GitHub committed 2020-08-07 10:18:40 -07:00
1 parent 908a299ea3
commit f84991c0f1
17 files changed
+324 -184

No files matched your search

+14 -12
View File
@@ -367,19 +367,21 @@ func (v verifier) performAuthCheck() (aclResults *perms.ACLResults, userId strin
userId = "u_anon"
// Validate the token and fetch the corresponding user ID
tokenRepo, err := v.authTokenRepoFn()
if err != nil {
retErr = fmt.Errorf("perform auth check: failed to get authtoken repo: %w", err)
return
}
if v.requestInfo.TokenFormat != AuthTokenTypeUnknown {
tokenRepo, err := v.authTokenRepoFn()
if err != nil {
retErr = fmt.Errorf("perform auth check: failed to get authtoken repo: %w", err)
return
}
at, err := tokenRepo.ValidateToken(v.ctx, v.requestInfo.PublicId, v.requestInfo.Token)
if err != nil {
retErr = fmt.Errorf("perform auth check: failed to validate token: %w", err)
return
}
if at != nil {
userId = at.GetIamUserId()
at, err := tokenRepo.ValidateToken(v.ctx, v.requestInfo.PublicId, v.requestInfo.Token)
if err != nil {
retErr = fmt.Errorf("perform auth check: failed to validate token: %w", err)
return
}
if at != nil {
userId = at.GetIamUserId()
}
}
// Fetch and parse grants for this user ID (which may include grants for
+8
View File
@@ -18,6 +18,7 @@ type options struct {
withDescription string
withLimit int
withConfig Configuration
withPublicId string
password string
withPassword bool
}
@@ -28,6 +29,13 @@ func getDefaultOptions() options {
}
}
// WithPublicId provides an optional public id
func WithPublicId(id string) Option {
return func(o *options) {
o.withPublicId = id
}
}
// WithDescription provides an optional description.
func WithDescription(desc string) Option {
return func(o *options) {
+6
View File
@@ -9,6 +9,12 @@ import (
func Test_GetOpts(t *testing.T) {
t.Parallel()
t.Run("WithPublicId", func(t *testing.T) {
opts := getOpts(WithPublicId("test id"))
testOpts := getDefaultOptions()
testOpts.withPublicId = "test id"
assert.Equal(t, opts, testOpts)
})
t.Run("WithName", func(t *testing.T) {
opts := getOpts(WithName("test"))
testOpts := getDefaultOptions()
@@ -16,8 +16,8 @@ import (
// contain a valid ScopeId. m must not contain a PublicId. The PublicId is
// generated and assigned by this method.
//
// WithConfiguration is the only valid option. All other options are
// ignored.
// WithConfiguration and WithPublicId are the only valid options. All other
// options are ignored.
//
// Both m.Name and m.Description are optional. If m.Name is set, it must be
// unique within m.ScopeId.
@@ -36,13 +36,21 @@ func (r *Repository) CreateAuthMethod(ctx context.Context, m *AuthMethod, opt ..
}
m = m.clone()
id, err := newAuthMethodId()
if err != nil {
return nil, fmt.Errorf("create: password auth method: %w", err)
}
m.PublicId = id
opts := getOpts(opt...)
if opts.withPublicId != "" {
if !strings.HasPrefix(opts.withPublicId, AuthMethodPrefix+"_") {
return nil, fmt.Errorf("create: password auth method: passed-in public ID %q has wrong prefix, should be %q: %w", opts.withPublicId, AuthMethodPrefix, db.ErrInvalidPublicId)
}
m.PublicId = opts.withPublicId
} else {
id, err := newAuthMethodId()
if err != nil {
return nil, fmt.Errorf("create: password auth method: %w", err)
}
m.PublicId = id
}
c, ok := opts.withConfig.(*Argon2Configuration)
if !ok {
return nil, fmt.Errorf("create: password auth method: unknown configuration: %w", ErrUnsupportedConfiguration)
@@ -51,6 +59,7 @@ func (r *Repository) CreateAuthMethod(ctx context.Context, m *AuthMethod, opt ..
return nil, fmt.Errorf("create: password auth method: %w", err)
}
var err error
c.PrivateId, err = newArgon2ConfigurationId()
if err != nil {
return nil, fmt.Errorf("create: password auth method: %w", err)
@@ -215,6 +215,42 @@ func TestRepository_CreateAuthMethod(t *testing.T) {
assert.Equal(in2.Description, got2.Description)
assert.Equal(got2.CreateTime, got2.UpdateTime)
})
t.Run("valid-with-publicid", func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
repo, err := NewRepository(rw, rw, wrapper)
require.NoError(err)
require.NotNil(repo)
org1, _ := iam.TestScopes(t, conn)
in := allocAuthMethod()
amId, err := newAuthMethodId()
require.NoError(err)
in.ScopeId = org1.GetPublicId()
got, err := repo.CreateAuthMethod(context.Background(), &in, WithPublicId(amId))
require.NoError(err)
require.NotNil(got)
assert.Equal(amId, got.GetPublicId())
assert.Equal(got.CreateTime, got.UpdateTime)
})
t.Run("invalid-with-badpublicid", func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
repo, err := NewRepository(rw, rw, wrapper)
require.NoError(err)
require.NotNil(repo)
org1, _ := iam.TestScopes(t, conn)
in := allocAuthMethod()
in.ScopeId = org1.GetPublicId()
got, err := repo.CreateAuthMethod(context.Background(), &in, WithPublicId("invalid_idwithabadprefix"))
assert.Error(err)
assert.Nil(got)
assert.True(errors.Is(err, db.ErrInvalidPublicId))
})
}
func TestRepository_LookupAuthMethod(t *testing.T) {
+54 -20
View File
@@ -21,13 +21,14 @@ import (
"github.com/hashicorp/vault/internalshared/configutil"
"github.com/hashicorp/vault/internalshared/gatedwriter"
"github.com/hashicorp/vault/internalshared/reloadutil"
"github.com/hashicorp/vault/sdk/helper/base62"
"github.com/hashicorp/vault/sdk/helper/logging"
"github.com/hashicorp/vault/sdk/helper/mlock"
"github.com/hashicorp/vault/sdk/logical"
"github.com/hashicorp/watchtower/globals"
"github.com/hashicorp/watchtower/internal/auth/password"
"github.com/hashicorp/watchtower/internal/db"
"github.com/hashicorp/watchtower/internal/iam"
"github.com/hashicorp/watchtower/internal/servers/controller/handlers/authenticate"
"github.com/hashicorp/watchtower/version"
"github.com/jinzhu/gorm"
"github.com/mitchellh/cli"
@@ -62,6 +63,8 @@ type Server struct {
DefaultOrgId string
DevAuthMethodId string
DevUsername string
DevPassword string
DevDatabaseUrl string
DevDatabaseCleanupFunc func() error
@@ -381,8 +384,13 @@ func (b *Server) RunShutdownFuncs() error {
func (b *Server) CreateDevDatabase(dialect string) error {
c, url, container, err := db.InitDbInDocker(dialect)
if err != nil {
// In case of an error, run the cleanup function. If we pass all errors, c should be set to a noop
// function before returning from this method
defer func() {
c()
}()
if err != nil {
return fmt.Errorf("unable to start dev database with dialect %s: %w", dialect, err)
}
@@ -398,7 +406,6 @@ func (b *Server) CreateDevDatabase(dialect string) error {
dbase, err := gorm.Open(dialect, url)
if err != nil {
c()
return fmt.Errorf("unable to create db object with dialect %s: %w", dialect, err)
}
b.Database = dbase
@@ -410,7 +417,6 @@ func (b *Server) CreateDevDatabase(dialect string) error {
rw := db.New(b.Database)
repo, err := iam.NewRepository(rw, rw, b.ControllerKMS)
if err != nil {
c()
return fmt.Errorf("unable to create repo for org id: %w", err)
}
@@ -424,7 +430,6 @@ func (b *Server) CreateDevDatabase(dialect string) error {
if b.DefaultOrgId != "" {
orgScope, err = repo.LookupScope(ctx, b.DefaultOrgId)
if err != nil {
c()
return fmt.Errorf("error looking up existing scope with org ID %q: %w", b.DefaultOrgId, err)
}
}
@@ -432,17 +437,14 @@ func (b *Server) CreateDevDatabase(dialect string) error {
if orgScope == nil {
orgScope, err = iam.NewOrg()
if err != nil {
c()
return fmt.Errorf("error creating new org scope: %w", err)
}
orgScope, err = repo.CreateScope(ctx, orgScope, iam.WithPublicId(b.DefaultOrgId))
if err != nil {
c()
return fmt.Errorf("error persisting new org scope: %w", err)
}
if b.DefaultOrgId != "" {
if orgScope.GetPublicId() != b.DefaultOrgId {
c()
return fmt.Errorf("expected org ID %q, got %q after persisting", b.DefaultOrgId, orgScope.GetPublicId())
}
} else {
@@ -480,27 +482,59 @@ func (b *Server) CreateDevDatabase(dialect string) error {
return fmt.Errorf("error adding principal to role for default dev grants: %w", err)
}
// TODO: Remove this when Auth Account repo is in place.
authenticate.Scope = orgScope.GetPublicId()
insert := `insert into auth_method
(public_id, scope_id)
values
($1, $2);`
pwRepo, err := password.NewRepository(rw, rw, b.ControllerKMS)
if err != nil {
return fmt.Errorf("error creating password repo: %w", err)
}
authMethod, err := password.NewAuthMethod(orgScope.GetPublicId())
if err != nil {
return fmt.Errorf("error creating new in memory auth method: %w", err)
}
amId := b.DevAuthMethodId
if amId == "" {
amId = "am_1234567890"
amId = "paum_1234567890"
}
authenticate.RWDb.Store(rw)
_, err = b.Database.DB().Exec(insert, amId, orgScope.GetPublicId())
authMethod, err = pwRepo.CreateAuthMethod(ctx, authMethod, password.WithPublicId(amId))
if err != nil {
c()
return err
return fmt.Errorf("error saving auth method to the db: %w", err)
}
b.InfoKeys = append(b.InfoKeys, "dev org id", "dev auth method id")
acctUserName := b.DevUsername
if acctUserName == "" {
acctUserName, err = base62.Random(10)
if err != nil {
return fmt.Errorf("unable to generate dev username: %w", err)
}
acctUserName = strings.ToLower(acctUserName)
}
pw := b.DevPassword
if pw == "" {
pw, err = base62.Random(20)
if err != nil {
return fmt.Errorf("unable to generate dev password: %w", err)
}
}
acct, err := password.NewAccount(amId, acctUserName)
if err != nil {
return fmt.Errorf("error creating new in memory auth account: %w", err)
}
acct, err = pwRepo.CreateAccount(ctx, acct, password.WithPassword(pw))
if err != nil {
return fmt.Errorf("error saving auth account to the db: %w", err)
}
b.InfoKeys = append(b.InfoKeys, "dev org id", "dev auth method id", "dev username", "dev password")
b.Info["dev org id"] = b.DefaultOrgId
b.Info["dev auth method id"] = amId
b.Info["dev username"] = acct.GetUserName()
b.Info["dev password"] = pw
// now that we have passed all the error cases, reset c to be a noop so the
// defer doesn't do anything.
c = func() error { return nil }
return nil
}
+31 -9
View File
@@ -12,6 +12,7 @@ import (
"github.com/hashicorp/vault/sdk/helper/mlock"
"github.com/hashicorp/vault/sdk/helper/strutil"
"github.com/hashicorp/watchtower/globals"
"github.com/hashicorp/watchtower/internal/auth/password"
"github.com/hashicorp/watchtower/internal/cmd/base"
"github.com/hashicorp/watchtower/internal/cmd/config"
"github.com/hashicorp/watchtower/internal/servers/controller"
@@ -44,7 +45,8 @@ type Command struct {
flagLogFormat string
flagCombineLogs bool
flagDev bool
flagDevAdminPassword string
flagDevUsername string
flagDevPassword string
flagDevControllerAPIListenAddr string
flagDevControllerClusterListenAddr string
flagDevOrgId string
@@ -129,13 +131,21 @@ func (c *Command) Flags() *base.FlagSets {
})
f.StringVar(&base.StringVar{
Name: "dev-admin-password",
Target: &c.flagDevAdminPassword,
EnvVar: "WATCHTWER_DEV_ADMIN_PASSWORD",
Name: "dev-password",
Target: &c.flagDevPassword,
EnvVar: "WATCHTWER_DEV_PASSWORD",
Usage: "Initial admin password. This only applies when running in \"dev\" " +
"mode.",
})
f.StringVar(&base.StringVar{
Name: "dev-username",
Target: &c.flagDevUsername,
EnvVar: "WATCHTWER_DEV_USERNAME",
Usage: "Initial admin username. This only applies when running in \"dev\" " +
"mode.",
})
f.StringVar(&base.StringVar{
Name: "dev-api-listen-address",
Target: &c.flagDevControllerAPIListenAddr,
@@ -322,11 +332,16 @@ func (c *Command) ParseFlagsAndConfig(args []string) int {
case len(c.flagConfig) == 0:
c.UI.Error("Must specify a config file using -config")
return 1
case c.flagDevAdminPassword != "":
case c.flagDevPassword != "":
c.UI.Warn(base.WrapAtLength(
"You cannot specify a custom admin password outside of \"dev\" mode. " +
"Your request has been ignored."))
c.flagDevAdminPassword = ""
c.flagDevPassword = ""
case c.flagDevUsername != "":
c.UI.Warn(base.WrapAtLength(
"You cannot specify a custom admin username outside of \"dev\" mode. " +
"Your request has been ignored."))
c.flagDevUsername = ""
}
c.Config, err = config.LoadFile(c.flagConfig, c.configKMS)
@@ -346,16 +361,23 @@ func (c *Command) ParseFlagsAndConfig(args []string) int {
c.Config.DefaultOrgId = c.flagDevOrgId
}
if c.flagDevAuthMethodId != "" {
if !strings.HasPrefix(c.flagDevAuthMethodId, "am_") {
c.UI.Error(fmt.Sprintf("Invalid dev auth method ID, must start with %q", "am_"))
prefix := password.AuthMethodPrefix + "_"
if !strings.HasPrefix(c.flagDevAuthMethodId, prefix) {
c.UI.Error(fmt.Sprintf("Invalid dev auth method ID, must start with %q", prefix))
return 1
}
if len(c.flagDevAuthMethodId) != 13 {
c.UI.Error(fmt.Sprintf("Invalid dev auth method ID, must be 10 base62 characters after %q", "am_"))
c.UI.Error(fmt.Sprintf("Invalid dev auth method ID, must be 10 base62 characters after %q", prefix))
return 1
}
c.DevAuthMethodId = c.flagDevAuthMethodId
}
if c.flagDevUsername != "" {
c.DevUsername = c.flagDevUsername
}
if c.flagDevPassword != "" {
c.DevPassword = c.flagDevPassword
}
c.Config.PassthroughDirectory = c.flagDevPassthroughDirectory
+24 -7
View File
@@ -6,6 +6,7 @@ import (
"strings"
"sync"
"github.com/hashicorp/watchtower/internal/auth/password"
"github.com/hashicorp/watchtower/internal/cmd/base"
controllercmd "github.com/hashicorp/watchtower/internal/cmd/commands/controller"
workercmd "github.com/hashicorp/watchtower/internal/cmd/commands/worker"
@@ -34,7 +35,8 @@ type Command struct {
flagLogFormat string
flagCombineLogs bool
flagDev bool
flagDevAdminPassword string
flagDevUsername string
flagDevPassword string
flagDevOrgId string
flagDevAuthMethodId string
flagDevControllerAPIListenAddr string
@@ -102,13 +104,21 @@ func (c *Command) Flags() *base.FlagSets {
})
f.StringVar(&base.StringVar{
Name: "dev-admin-password",
Target: &c.flagDevAdminPassword,
EnvVar: "WATCHTWER_DEV_ADMIN_PASSWORD",
Name: "dev-password",
Target: &c.flagDevPassword,
EnvVar: "WATCHTWER_DEV_PASSWORD",
Usage: "Initial admin password. This only applies when running in \"dev\" " +
"mode.",
})
f.StringVar(&base.StringVar{
Name: "dev-username",
Target: &c.flagDevUsername,
EnvVar: "WATCHTWER_DEV_USERNAME",
Usage: "Initial admin username. This only applies when running in \"dev\" " +
"mode.",
})
f.StringVar(&base.StringVar{
Name: "dev-api-listen-address",
Target: &c.flagDevControllerAPIListenAddr,
@@ -165,16 +175,23 @@ func (c *Command) Run(args []string) int {
devConfig.DefaultOrgId = c.flagDevOrgId
}
if c.flagDevAuthMethodId != "" {
if !strings.HasPrefix(c.flagDevAuthMethodId, "am_") {
c.UI.Error(fmt.Sprintf("Invalid dev auth method ID, must start with %q", "am_"))
prefix := password.AuthMethodPrefix + "_"
if !strings.HasPrefix(c.flagDevAuthMethodId, prefix) {
c.UI.Error(fmt.Sprintf("Invalid dev auth method ID, must start with %q", prefix))
return 1
}
if len(c.flagDevAuthMethodId) != 13 {
c.UI.Error(fmt.Sprintf("Invalid dev auth method ID, must be 10 base62 characters after %q", "am_"))
c.UI.Error(fmt.Sprintf("Invalid dev auth method ID, must be 10 base62 characters after %q", prefix))
return 1
}
c.DevAuthMethodId = c.flagDevAuthMethodId
}
if c.flagDevUsername != "" {
c.DevUsername = c.flagDevUsername
}
if c.flagDevPassword != "" {
c.DevPassword = c.flagDevPassword
}
devConfig.PassthroughDirectory = c.flagDevPassthroughDirectory
+1 -1
View File
@@ -1879,7 +1879,7 @@
},
"description": {
"type": "string",
"title": "Optional user-set descripton for identification purposes"
"title": "Optional user-set description for identification purposes"
},
"created_time": {
"type": "string",
@@ -44,7 +44,7 @@ type AuthMethod struct {
Scope *scopes.ScopeInfo `protobuf:"bytes,2,opt,name=scope,proto3" json:"scope,omitempty"`
// Optional name for identification purposes
Name *wrappers.StringValue `protobuf:"bytes,3,opt,name=name,proto3" json:"name,omitempty"`
// Optional user-set descripton for identification purposes
// Optional user-set description for identification purposes
Description *wrappers.StringValue `protobuf:"bytes,4,opt,name=description,proto3" json:"description,omitempty"`
// The time this resource was created
// Output only.
@@ -23,7 +23,7 @@ message AuthMethod {
// Optional name for identification purposes
google.protobuf.StringValue name = 3 [(custom_options.v1.generate_sdk_option) = true, (custom_options.v1.mask_mapping) = {this:"name" that: "name"}];
// Optional user-set descripton for identification purposes
// Optional user-set description for identification purposes
google.protobuf.StringValue description = 4 [(custom_options.v1.generate_sdk_option) = true, (custom_options.v1.mask_mapping) = {this:"description" that: "description"}];
// The time this resource was created
+1 -1
View File
@@ -89,7 +89,7 @@ func handleGrpcGateway(c *Controller) (http.Handler, error) {
if err := services.RegisterAccountServiceHandlerServer(ctx, mux, accts); err != nil {
return nil, fmt.Errorf("failed to register account service handler: %w", err)
}
auths, err := authenticate.NewService(c.IamRepoFn, c.AuthTokenRepoFn)
auths, err := authenticate.NewService(c.PasswordAuthRepoFn, c.IamRepoFn, c.AuthTokenRepoFn)
if err != nil {
return nil, fmt.Errorf("failed to create authentication handler service: %w", err)
}
+8 -3
View File
@@ -13,11 +13,16 @@ import (
)
func TestAuthenticationHandler(t *testing.T) {
c := NewTestController(t, &TestControllerOpts{DefaultOrgId: "o_1234567890"})
c := NewTestController(t, &TestControllerOpts{
DefaultOrgId: "o_1234567890",
DisableAuthorizationFailures: true,
DefaultUsername: "admin",
DefaultPassword: "password123",
})
defer c.Shutdown()
resp, err := http.Post(fmt.Sprintf("%s/v1/scopes/o_1234567890/auth-methods/am_1234567890:authenticate", c.ApiAddrs()[0]), "application/json",
strings.NewReader(`{"token_type": null, "credentials": {"name":"test", "password": "test"}}`))
resp, err := http.Post(fmt.Sprintf("%s/v1/scopes/o_1234567890/auth-methods/paum_1234567890:authenticate", c.ApiAddrs()[0]), "application/json",
strings.NewReader(`{"token_type": null, "credentials": {"name":"admin", "password": "password123"}}`))
require.NoError(t, err)
assert.Equal(t, http.StatusOK, resp.StatusCode, "Got response: %v", resp)
@@ -2,66 +2,66 @@ package authenticate
import (
"context"
"crypto/sha256"
"encoding/base32"
"fmt"
"regexp"
"strings"
"sync/atomic"
"github.com/hashicorp/watchtower/internal/auth"
"github.com/hashicorp/watchtower/internal/authtoken"
"github.com/hashicorp/watchtower/internal/db"
pba "github.com/hashicorp/watchtower/internal/gen/controller/api/resources/authtokens"
"github.com/hashicorp/watchtower/internal/gen/controller/api/resources/scopes"
pbs "github.com/hashicorp/watchtower/internal/gen/controller/api/services"
"github.com/hashicorp/watchtower/internal/iam"
iamStore "github.com/hashicorp/watchtower/internal/iam/store"
"github.com/hashicorp/watchtower/internal/servers/controller/common"
"github.com/hashicorp/watchtower/internal/servers/controller/handlers"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
const scopeIdFieldName = "scope_id"
const (
nameKey = "name"
pwKey = "password"
)
var (
reInvalidID = regexp.MustCompile("[^A-Za-z0-9]")
// TODO: Remove once auth methods are in
Scope string
RWDb = new(atomic.Value)
)
func init() {
RWDb.Store((*db.Db)(nil))
}
// Service handles request as described by the pbs.OrgServiceServer interface.
type Service struct {
pwRepo common.PasswordAuthRepoFactory
iamRepo common.IamRepoFactory
authTokenRepo common.AuthTokenRepoFactory
}
// NewService returns an org service which handles org related requests to watchtower.
func NewService(iamRepo common.IamRepoFactory, atRepo common.AuthTokenRepoFactory) (Service, error) {
func NewService(pwRepo common.PasswordAuthRepoFactory, iamRepo common.IamRepoFactory, atRepo common.AuthTokenRepoFactory) (Service, error) {
if iamRepo == nil {
return Service{}, fmt.Errorf("nil iam repository provided")
}
if atRepo == nil {
return Service{}, fmt.Errorf("nil auth token repository provided")
}
if pwRepo == nil {
return Service{}, fmt.Errorf("nil password repository provided")
}
return Service{iamRepo: iamRepo, authTokenRepo: atRepo}, nil
return Service{pwRepo: pwRepo, iamRepo: iamRepo, authTokenRepo: atRepo}, nil
}
var _ pbs.AuthenticationServiceServer = Service{}
// Authenticate implements the interface pbs.AuthenticationServiceServer.
func (s Service) Authenticate(ctx context.Context, req *pbs.AuthenticateRequest) (*pbs.AuthenticateResponse, error) {
authResults := auth.Verify(ctx)
if !authResults.Valid {
return nil, handlers.ForbiddenError()
}
if err := validateAuthenticateRequest(req); err != nil {
return nil, err
}
tok, err := s.authenticateWithRepo(ctx, req)
creds := req.GetCredentials().GetFields()
tok, err := s.authenticateWithRepo(ctx, req.GetAuthMethodId(), creds[nameKey].GetStringValue(), creds[pwKey].GetStringValue())
if err != nil {
return nil, err
}
@@ -73,7 +73,7 @@ func (s Service) Deauthenticate(ctx context.Context, req *pbs.DeauthenticateRequ
return nil, status.Error(codes.Unimplemented, "Requested method is unimplemented.")
}
func (s Service) authenticateWithRepo(ctx context.Context, req *pbs.AuthenticateRequest) (*pba.AuthToken, error) {
func (s Service) authenticateWithRepo(ctx context.Context, authMethodId, name, pw string) (*pba.AuthToken, error) {
iamRepo, err := s.iamRepo()
if err != nil {
return nil, err
@@ -82,29 +82,24 @@ func (s Service) authenticateWithRepo(ctx context.Context, req *pbs.Authenticate
if err != nil {
return nil, err
}
// Place holder for making a request to authenticate
creds := req.GetCredentials().GetFields()
pwName, password := creds["name"], creds["password"]
if pwName.GetStringValue() == "wrong" && password.GetStringValue() == "wrong" {
return nil, status.Error(codes.Unauthenticated, "Unable to authenticate.")
}
// Get back a password.Account with a CredentialId string and a public Id
// Create a fake auth account ID for the moment
acctIdBytes := sha256.Sum256([]byte(pwName.GetStringValue()))
acctId := fmt.Sprintf("aa_%s", base32.StdEncoding.EncodeToString(acctIdBytes[:])[0:10])
aAcct := &iam.AuthAccount{AuthAccount: &iamStore.AuthAccount{
PublicId: acctId,
ScopeId: Scope,
AuthMethodId: req.AuthMethodId,
}}
RWDb.Load().(*db.Db).Create(ctx, aAcct)
u, err := iamRepo.LookupUserWithLogin(ctx, acctId, iam.WithAutoVivify(true))
pwRepo, err := s.pwRepo()
if err != nil {
return nil, err
}
tok, err := atRepo.CreateAuthToken(ctx, u.GetPublicId(), acctId)
acct, err := pwRepo.Authenticate(ctx, authMethodId, name, pw)
if err != nil {
return nil, err
}
if acct == nil {
return nil, status.Error(codes.Unauthenticated, "Unable to authenticate.")
}
u, err := iamRepo.LookupUserWithLogin(ctx, acct.GetPublicId(), iam.WithAutoVivify(true))
if err != nil {
return nil, err
}
tok, err := atRepo.CreateAuthToken(ctx, u.GetPublicId(), acct.GetPublicId())
if err != nil {
return nil, err
}
@@ -156,6 +151,13 @@ func validateAuthenticateRequest(req *pbs.AuthenticateRequest) error {
if req.GetCredentials() == nil {
badFields["credentials"] = "This is a required field."
}
creds := req.GetCredentials().GetFields()
if _, ok := creds[nameKey]; !ok {
badFields["credentials.name"] = "This is a required field."
}
if _, ok := creds[pwKey]; !ok {
badFields["credentials.password"] = "This is a required field."
}
// TODO: Update this when we enable split cookie token types.
tType := strings.ToLower(strings.TrimSpace(req.GetTokenType()))
if tType != "" && tType != "token" {
@@ -2,59 +2,46 @@ package authenticate
import (
"context"
"crypto/sha256"
"encoding/base32"
"fmt"
"strings"
"testing"
structpb "github.com/golang/protobuf/ptypes/struct"
"github.com/hashicorp/watchtower/internal/auth"
"github.com/hashicorp/watchtower/internal/auth/password"
"github.com/hashicorp/watchtower/internal/authtoken"
"github.com/hashicorp/watchtower/internal/db"
pba "github.com/hashicorp/watchtower/internal/gen/controller/api/resources/authtokens"
pbs "github.com/hashicorp/watchtower/internal/gen/controller/api/services"
"github.com/hashicorp/watchtower/internal/iam"
iamStore "github.com/hashicorp/watchtower/internal/iam/store"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
func getAuthMethodAndAccountId(t *testing.T, org *iam.Scope, rw *db.Db) (string, string) {
// TODO: Remove this when Auth Account repo is in place.
insert := `insert into auth_method
(public_id, scope_id)
values
($1, $2);`
amId, err := db.NewPublicId("am")
require.NoError(t, err)
innerDb, err := rw.DB()
require.NoError(t, err)
_, err = innerDb.Exec(insert, amId, org.GetPublicId())
require.NoError(t, err)
aAcctId, err := db.NewPublicId("aa")
require.NoError(t, err)
aAcct := &iam.AuthAccount{AuthAccount: &iamStore.AuthAccount{
PublicId: aAcctId,
ScopeId: org.GetPublicId(),
AuthMethodId: amId,
}}
require.NoError(t, rw.Create(context.Background(), aAcct))
return aAcct.GetAuthMethodId(), aAcct.GetPublicId()
}
const (
testPassword = "thetestpassword"
testUsername = "default"
)
func TestAuthenticate(t *testing.T) {
conn, _ := db.TestSetup(t, "postgres")
rw := db.New(conn)
RWDb.Store(rw)
wrapper := db.TestWrapper(t)
o, _ := iam.TestScopes(t, conn)
authTokenRepoFn := func() (*authtoken.Repository, error) { return authtoken.NewRepository(rw, rw, wrapper) }
iamRepoFn := func() (*iam.Repository, error) { return iam.NewRepository(rw, rw, wrapper) }
amId, _ := getAuthMethodAndAccountId(t, o, rw)
passwordRepoFn := func() (*password.Repository, error) { return password.NewRepository(rw, rw, wrapper) }
am := password.TestAuthMethods(t, conn, o.GetPublicId(), 1)[0]
acct, err := password.NewAccount(am.GetPublicId(), testUsername)
require.NoError(t, err)
pwRepo, err := passwordRepoFn()
require.NoError(t, err)
acct, err = pwRepo.CreateAccount(context.Background(), acct, password.WithPassword(testPassword))
require.NoError(t, err)
cases := []struct {
name string
@@ -65,45 +52,45 @@ func TestAuthenticate(t *testing.T) {
{
name: "basic",
request: &pbs.AuthenticateRequest{
AuthMethodId: amId,
AuthMethodId: am.GetPublicId(),
TokenType: "token",
Credentials: func() *structpb.Struct {
creds := map[string]*structpb.Value{
"name": {Kind: &structpb.Value_StringValue{StringValue: "admin"}},
"password": {Kind: &structpb.Value_StringValue{StringValue: "hunter2"}},
"name": {Kind: &structpb.Value_StringValue{StringValue: testUsername}},
"password": {Kind: &structpb.Value_StringValue{StringValue: testPassword}},
}
return &structpb.Struct{Fields: creds}
}(),
},
want: &pbs.AuthenticateResponse{Item: &pba.AuthToken{
AuthMethodId: amId,
AuthMethodId: am.GetPublicId(),
}},
},
{
name: "no-token-type",
request: &pbs.AuthenticateRequest{
AuthMethodId: amId,
AuthMethodId: am.GetPublicId(),
Credentials: func() *structpb.Struct {
creds := map[string]*structpb.Value{
"name": {Kind: &structpb.Value_StringValue{StringValue: "admin"}},
"password": {Kind: &structpb.Value_StringValue{StringValue: "hunter2"}},
"name": {Kind: &structpb.Value_StringValue{StringValue: testUsername}},
"password": {Kind: &structpb.Value_StringValue{StringValue: testPassword}},
}
return &structpb.Struct{Fields: creds}
}(),
},
want: &pbs.AuthenticateResponse{Item: &pba.AuthToken{
AuthMethodId: amId,
AuthMethodId: am.GetPublicId(),
}},
},
{
name: "bad-token-type",
request: &pbs.AuthenticateRequest{
AuthMethodId: amId,
AuthMethodId: am.GetPublicId(),
TokenType: "email",
Credentials: func() *structpb.Struct {
creds := map[string]*structpb.Value{
"name": {Kind: &structpb.Value_StringValue{StringValue: "admin"}},
"password": {Kind: &structpb.Value_StringValue{StringValue: "hunter2"}},
"name": {Kind: &structpb.Value_StringValue{StringValue: testUsername}},
"password": {Kind: &structpb.Value_StringValue{StringValue: testPassword}},
}
return &structpb.Struct{Fields: creds}
}(),
@@ -115,8 +102,8 @@ func TestAuthenticate(t *testing.T) {
request: &pbs.AuthenticateRequest{
Credentials: func() *structpb.Struct {
creds := map[string]*structpb.Value{
"name": {Kind: &structpb.Value_StringValue{StringValue: "admin"}},
"password": {Kind: &structpb.Value_StringValue{StringValue: "hunter2"}},
"name": {Kind: &structpb.Value_StringValue{StringValue: testUsername}},
"password": {Kind: &structpb.Value_StringValue{StringValue: testPassword}},
}
return &structpb.Struct{Fields: creds}
}(),
@@ -124,14 +111,29 @@ func TestAuthenticate(t *testing.T) {
wantErr: status.Error(codes.InvalidArgument, "invalid argument"),
},
{
name: "wrong-username-password",
name: "wrong-password",
request: &pbs.AuthenticateRequest{
AuthMethodId: amId,
AuthMethodId: am.GetPublicId(),
TokenType: "token",
Credentials: func() *structpb.Struct {
creds := map[string]*structpb.Value{
"name": {Kind: &structpb.Value_StringValue{StringValue: testUsername}},
"password": {Kind: &structpb.Value_StringValue{StringValue: "wrong"}},
}
return &structpb.Struct{Fields: creds}
}(),
},
wantErr: status.Error(codes.Unauthenticated, "unauthenticated"),
},
{
name: "wrong-username",
request: &pbs.AuthenticateRequest{
AuthMethodId: am.GetPublicId(),
TokenType: "token",
Credentials: func() *structpb.Struct {
creds := map[string]*structpb.Value{
"name": {Kind: &structpb.Value_StringValue{StringValue: "wrong"}},
"password": {Kind: &structpb.Value_StringValue{StringValue: "wrong"}},
"password": {Kind: &structpb.Value_StringValue{StringValue: testPassword}},
}
return &structpb.Struct{Fields: creds}
}(),
@@ -143,22 +145,10 @@ func TestAuthenticate(t *testing.T) {
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
s, err := NewService(iamRepoFn, authTokenRepoFn)
s, err := NewService(passwordRepoFn, iamRepoFn, authTokenRepoFn)
require.NoError(err)
// TODO: remove when we stop faking things
name := tc.request.Credentials.Fields["name"].GetStringValue()
acctIdBytes := sha256.Sum256([]byte(name))
acctId := fmt.Sprintf("aa_%s", base32.StdEncoding.EncodeToString(acctIdBytes[:])[0:10])
aAcct := &iam.AuthAccount{AuthAccount: &iamStore.AuthAccount{
PublicId: acctId,
ScopeId: o.PublicId,
AuthMethodId: amId,
}}
RWDb.Load().(*db.Db).Create(context.Background(), aAcct)
resp, err := s.Authenticate(context.Background(), tc.request)
resp, err := s.Authenticate(auth.DisabledAuthTestContext(auth.WithScopeId(o.GetPublicId())), tc.request)
if tc.wantErr != nil {
assert.Error(err)
assert.Equal(status.Code(tc.wantErr), status.Code(err))
@@ -169,7 +159,7 @@ func TestAuthenticate(t *testing.T) {
assert.NotEmpty(aToken.GetId())
assert.NotEmpty(aToken.GetToken())
assert.True(strings.HasPrefix(aToken.GetToken(), aToken.GetId()))
assert.Equal(amId, aToken.GetAuthMethodId())
assert.Equal(am.GetPublicId(), aToken.GetAuthMethodId())
assert.Equal(aToken.GetCreatedTime(), aToken.GetUpdatedTime())
assert.Equal(aToken.GetCreatedTime(), aToken.GetApproximateLastUsedTime())
})
@@ -180,40 +170,36 @@ func TestAuthenticate_AuthAccountConnectedToIamUser(t *testing.T) {
assert, require := assert.New(t), require.New(t)
conn, _ := db.TestSetup(t, "postgres")
rw := db.New(conn)
RWDb.Store(rw)
wrapper := db.TestWrapper(t)
o, _ := iam.TestScopes(t, conn)
passwordRepoFn := func() (*password.Repository, error) { return password.NewRepository(rw, rw, wrapper) }
authTokenRepoFn := func() (*authtoken.Repository, error) { return authtoken.NewRepository(rw, rw, wrapper) }
iamRepoFn := func() (*iam.Repository, error) { return iam.NewRepository(rw, rw, wrapper) }
iamRepo, err := iamRepoFn()
am := password.TestAuthMethods(t, conn, o.GetPublicId(), 1)[0]
acct, err := password.NewAccount(am.GetPublicId(), testUsername)
require.NoError(err)
pwRepo, err := passwordRepoFn()
require.NoError(err)
acct, err = pwRepo.CreateAccount(context.Background(), acct, password.WithPassword(testPassword))
require.NoError(err)
// connected to an account.
amId, _ := getAuthMethodAndAccountId(t, o, rw)
name := "admin"
acctIdBytes := sha256.Sum256([]byte(name))
acctId := fmt.Sprintf("aa_%s", base32.StdEncoding.EncodeToString(acctIdBytes[:])[0:10])
aAcct := &iam.AuthAccount{AuthAccount: &iamStore.AuthAccount{
PublicId: acctId,
ScopeId: o.PublicId,
AuthMethodId: amId,
}}
RWDb.Load().(*db.Db).Create(context.Background(), aAcct)
iamUser, err := iamRepo.LookupUserWithLogin(context.Background(), acctId, iam.WithAutoVivify(true))
iamRepo, err := iamRepoFn()
require.NoError(err)
iamUser, err := iamRepo.LookupUserWithLogin(context.Background(), acct.GetPublicId(), iam.WithAutoVivify(true))
require.NoError(err)
s, err := NewService(iamRepoFn, authTokenRepoFn)
s, err := NewService(passwordRepoFn, iamRepoFn, authTokenRepoFn)
require.NoError(err)
resp, err := s.Authenticate(context.Background(), &pbs.AuthenticateRequest{
AuthMethodId: amId,
resp, err := s.Authenticate(auth.DisabledAuthTestContext(auth.WithScopeId(o.GetPublicId())), &pbs.AuthenticateRequest{
AuthMethodId: am.GetPublicId(),
Credentials: func() *structpb.Struct {
creds := map[string]*structpb.Value{
"name": {Kind: &structpb.Value_StringValue{StringValue: name}},
"password": {Kind: &structpb.Value_StringValue{StringValue: "hunter2"}},
"name": {Kind: &structpb.Value_StringValue{StringValue: testUsername}},
"password": {Kind: &structpb.Value_StringValue{StringValue: testPassword}},
}
return &structpb.Struct{Fields: creds}
}(),
@@ -222,7 +208,7 @@ func TestAuthenticate_AuthAccountConnectedToIamUser(t *testing.T) {
aToken := resp.GetItem()
assert.Equal(iamUser.GetPublicId(), aToken.GetUserId())
assert.Equal(amId, aToken.GetAuthMethodId())
assert.Equal(am.GetPublicId(), aToken.GetAuthMethodId())
assert.NotEmpty(aToken.GetId())
assert.NotEmpty(aToken.GetToken())
@@ -5,7 +5,7 @@ import (
structpb "github.com/golang/protobuf/ptypes/struct"
"github.com/google/go-cmp/cmp"
authpb "github.com/hashicorp/watchtower/internal/gen/controller/api/resources/auth"
authpb "github.com/hashicorp/watchtower/internal/gen/controller/api/resources/authmethods"
hostpb "github.com/hashicorp/watchtower/internal/gen/controller/api/resources/hosts"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
+13
View File
@@ -114,6 +114,12 @@ type TestControllerOpts struct {
// in the normal config.
DefaultOrgId string
// DefaultUsername is the username used when creating the default account.
DefaultUsername string
// DefaultPassword is the password used when creating the default account.
DefaultPassword string
// DisableDatabaseCreation can be set true to disable creating a dev
// database
DisableDatabaseCreation bool
@@ -163,6 +169,13 @@ func NewTestController(t *testing.T, opts *TestControllerOpts) *TestController {
tc.b.DefaultOrgId = opts.DefaultOrgId
}
if opts.DefaultUsername != "" {
tc.b.DevUsername = opts.DefaultUsername
}
if opts.DefaultPassword != "" {
tc.b.DevPassword = opts.DefaultPassword
}
// Start a logger
tc.b.Logger = hclog.New(&hclog.LoggerOptions{
Level: hclog.Trace,