diff --git a/builtin/credential/github/backend_test.go b/builtin/credential/github/backend_test.go index cfcd5f30ec..b2e4d859b5 100644 --- a/builtin/credential/github/backend_test.go +++ b/builtin/credential/github/backend_test.go @@ -49,6 +49,7 @@ func testAccMap(t *testing.T) logicaltest.TestStep { }, } } + func testAccLogin(t *testing.T) logicaltest.TestStep { return logicaltest.TestStep{ Operation: logical.WriteOperation, diff --git a/builtin/credential/userpass/backend.go b/builtin/credential/userpass/backend.go index 36ee4324a8..e17634472f 100644 --- a/builtin/credential/userpass/backend.go +++ b/builtin/credential/userpass/backend.go @@ -20,11 +20,12 @@ func Backend() *framework.Backend { }, Unauthenticated: []string{ - "login", + "login/*", }, }, Paths: append([]*framework.Path{ + pathLogin(&b), pathUsers(&b), }), } diff --git a/builtin/credential/userpass/backend_test.go b/builtin/credential/userpass/backend_test.go index 02664c557b..f8d6c4792f 100644 --- a/builtin/credential/userpass/backend_test.go +++ b/builtin/credential/userpass/backend_test.go @@ -9,6 +9,18 @@ import ( "github.com/mitchellh/mapstructure" ) +func TestBackend_basic(t *testing.T) { + b := Backend() + + logicaltest.Test(t, logicaltest.TestCase{ + Backend: b, + Steps: []logicaltest.TestStep{ + testAccStepUser(t, "web", "password", "foo"), + testAccStepLogin(t, "web", "password"), + }, + }) +} + func TestBackend_userCrud(t *testing.T) { b := Backend() @@ -23,6 +35,19 @@ func TestBackend_userCrud(t *testing.T) { }) } +func testAccStepLogin(t *testing.T, user string, pass string) logicaltest.TestStep { + return logicaltest.TestStep{ + Operation: logical.WriteOperation, + Path: "login/" + user, + Data: map[string]interface{}{ + "password": pass, + }, + Unauthenticated: true, + + Check: logicaltest.TestCheckAuth([]string{"foo"}), + } +} + func testAccStepUser( t *testing.T, name string, password string, policies string) logicaltest.TestStep { return logicaltest.TestStep{ diff --git a/builtin/credential/userpass/path_login.go b/builtin/credential/userpass/path_login.go new file mode 100644 index 0000000000..6439062b4d --- /dev/null +++ b/builtin/credential/userpass/path_login.go @@ -0,0 +1,53 @@ +package userpass + +import ( + "strings" + + "github.com/hashicorp/vault/logical" + "github.com/hashicorp/vault/logical/framework" +) + +func pathLogin(b *backend) *framework.Path { + return &framework.Path{ + Pattern: `login/(?P\w+)`, + Fields: map[string]*framework.FieldSchema{ + "name": &framework.FieldSchema{ + Type: framework.TypeString, + Description: "Username of the user.", + }, + + "password": &framework.FieldSchema{ + Type: framework.TypeString, + Description: "Password for this user.", + }, + }, + + Callbacks: map[logical.Operation]framework.OperationFunc{ + logical.WriteOperation: b.pathLogin, + }, + } +} + +func (b *backend) pathLogin( + req *logical.Request, d *framework.FieldData) (*logical.Response, error) { + username := strings.ToLower(d.Get("name").(string)) + + // Get the user and validate auth + user, err := b.User(req.Storage, username) + if err != nil { + return nil, err + } + if user == nil || user.Password != d.Get("password").(string) { + return logical.ErrorResponse("unknown username or password"), nil + } + + return &logical.Response{ + Auth: &logical.Auth{ + Policies: user.Policies, + Metadata: map[string]string{ + "username": username, + }, + DisplayName: username, + }, + }, nil +} diff --git a/builtin/credential/userpass/path_users.go b/builtin/credential/userpass/path_users.go index 6558a2a5c0..04657bd3cf 100644 --- a/builtin/credential/userpass/path_users.go +++ b/builtin/credential/userpass/path_users.go @@ -39,7 +39,7 @@ func pathUsers(b *backend) *framework.Path { } func (b *backend) User(s logical.Storage, n string) (*UserEntry, error) { - entry, err := s.Get("user/" + n) + entry, err := s.Get("user/" + strings.ToLower(n)) if err != nil { return nil, err } @@ -57,7 +57,7 @@ func (b *backend) User(s logical.Storage, n string) (*UserEntry, error) { func (b *backend) pathUserDelete( req *logical.Request, d *framework.FieldData) (*logical.Response, error) { - err := req.Storage.Delete("user/" + d.Get("name").(string)) + err := req.Storage.Delete("user/" + strings.ToLower(d.Get("name").(string))) if err != nil { return nil, err } @@ -67,7 +67,7 @@ func (b *backend) pathUserDelete( func (b *backend) pathUserRead( req *logical.Request, d *framework.FieldData) (*logical.Response, error) { - user, err := b.User(req.Storage, d.Get("name").(string)) + user, err := b.User(req.Storage, strings.ToLower(d.Get("name").(string))) if err != nil { return nil, err } @@ -84,7 +84,7 @@ func (b *backend) pathUserRead( func (b *backend) pathUserWrite( req *logical.Request, d *framework.FieldData) (*logical.Response, error) { - name := d.Get("name").(string) + name := strings.ToLower(d.Get("name").(string)) policies := strings.Split(d.Get("policies").(string), ",") for i, p := range policies { policies[i] = strings.TrimSpace(p) @@ -117,4 +117,9 @@ Manage users allowed to authenticate. const pathUserHelpDesc = ` This endpoint allows you to create, read, update, and delete users that are allowed to authenticate. + +Deleting a user will not revoke auth for prior authenticated users +with that name. To do this, do a revoke on "login/" for +the username you want revoked. If you don't need to revoke login immediately, +then the next renew will cause the lease to expire. `