Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Userpass: Update the password and policies associated to user #1216

Merged
merged 15 commits into from Mar 16, 2016
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions builtin/credential/userpass/backend.go
Expand Up @@ -29,6 +29,8 @@ func Backend() *framework.Backend {

Paths: append([]*framework.Path{
pathUsers(&b),
pathUserPolicies(&b),
pathUserPassword(&b),
},
mfa.MFAPaths(b.Backend, pathLogin(&b))...,
),
Expand Down
110 changes: 107 additions & 3 deletions builtin/credential/userpass/backend_test.go
Expand Up @@ -81,7 +81,7 @@ func TestBackend_basic(t *testing.T) {
Backend: b,
Steps: []logicaltest.TestStep{
testAccStepUser(t, "web", "password", "foo"),
testAccStepLogin(t, "web", "password"),
testAccStepLogin(t, "web", "password", []string{"default", "foo"}),
},
})
}
Expand Down Expand Up @@ -109,6 +109,98 @@ func TestBackend_userCrud(t *testing.T) {
})
}

func TestBackend_userCreateOperation(t *testing.T) {
b, err := Factory(&logical.BackendConfig{
Logger: nil,
System: &logical.StaticSystemView{
DefaultLeaseTTLVal: testSysTTL,
MaxLeaseTTLVal: testSysMaxTTL,
},
})
if err != nil {
t.Fatalf("Unable to create backend: %s", err)
}

logicaltest.Test(t, logicaltest.TestCase{
Backend: b,
Steps: []logicaltest.TestStep{
testUserCreateOperation(t, "web", "password", "foo"),
testAccStepLogin(t, "web", "password", []string{"default", "foo"}),
},
})
}

func TestBackend_passwordUpdate(t *testing.T) {
b, err := Factory(&logical.BackendConfig{
Logger: nil,
System: &logical.StaticSystemView{
DefaultLeaseTTLVal: testSysTTL,
MaxLeaseTTLVal: testSysMaxTTL,
},
})
if err != nil {
t.Fatalf("Unable to create backend: %s", err)
}

logicaltest.Test(t, logicaltest.TestCase{
Backend: b,
Steps: []logicaltest.TestStep{
testAccStepUser(t, "web", "password", "foo"),
testAccStepReadUser(t, "web", "foo"),
testAccStepLogin(t, "web", "password", []string{"default", "foo"}),
testUpdatePassword(t, "web", "newpassword"),
testAccStepLogin(t, "web", "newpassword", []string{"default", "foo"}),
},
})

}

func TestBackend_policiesUpdate(t *testing.T) {
b, err := Factory(&logical.BackendConfig{
Logger: nil,
System: &logical.StaticSystemView{
DefaultLeaseTTLVal: testSysTTL,
MaxLeaseTTLVal: testSysMaxTTL,
},
})
if err != nil {
t.Fatalf("Unable to create backend: %s", err)
}

logicaltest.Test(t, logicaltest.TestCase{
Backend: b,
Steps: []logicaltest.TestStep{
testAccStepUser(t, "web", "password", "foo"),
testAccStepReadUser(t, "web", "foo"),
testAccStepLogin(t, "web", "password", []string{"default", "foo"}),
testUpdatePolicies(t, "web", "foo,bar"),
testAccStepReadUser(t, "web", "foo,bar"),
testAccStepLogin(t, "web", "password", []string{"bar", "default", "foo"}),
},
})

}

func testUpdatePassword(t *testing.T, user, password string) logicaltest.TestStep {
return logicaltest.TestStep{
Operation: logical.UpdateOperation,
Path: "users/" + user + "/password",
Data: map[string]interface{}{
"password": password,
},
}
}

func testUpdatePolicies(t *testing.T, user, policies string) logicaltest.TestStep {
return logicaltest.TestStep{
Operation: logical.UpdateOperation,
Path: "users/" + user + "/policies",
Data: map[string]interface{}{
"policies": policies,
},
}
}

func testUsersWrite(t *testing.T, user string, data map[string]interface{}, expectError bool) logicaltest.TestStep {
return logicaltest.TestStep{
Operation: logical.UpdateOperation,
Expand Down Expand Up @@ -139,7 +231,7 @@ func testLoginWrite(t *testing.T, user string, data map[string]interface{}, expe
}
}

func testAccStepLogin(t *testing.T, user string, pass string) logicaltest.TestStep {
func testAccStepLogin(t *testing.T, user string, pass string, policies []string) logicaltest.TestStep {
return logicaltest.TestStep{
Operation: logical.UpdateOperation,
Path: "login/" + user,
Expand All @@ -148,7 +240,19 @@ func testAccStepLogin(t *testing.T, user string, pass string) logicaltest.TestSt
},
Unauthenticated: true,

Check: logicaltest.TestCheckAuth([]string{"foo"}),
Check: logicaltest.TestCheckAuth(policies),
}
}

func testUserCreateOperation(
t *testing.T, name string, password string, policies string) logicaltest.TestStep {
return logicaltest.TestStep{
Operation: logical.CreateOperation,
Path: "users/" + name,
Data: map[string]interface{}{
"password": password,
"policies": policies,
},
}
}

Expand Down
18 changes: 13 additions & 5 deletions builtin/credential/userpass/path_login.go
Expand Up @@ -2,6 +2,7 @@ package userpass

import (
"crypto/subtle"
"fmt"
"strings"

"github.com/hashicorp/vault/logical"
Expand All @@ -11,9 +12,9 @@ import (

func pathLogin(b *backend) *framework.Path {
return &framework.Path{
Pattern: "login/" + framework.GenericNameRegex("name"),
Pattern: "login/" + framework.GenericNameRegex("username"),
Fields: map[string]*framework.FieldSchema{
"name": &framework.FieldSchema{
"username": &framework.FieldSchema{
Type: framework.TypeString,
Description: "Username of the user.",
},
Expand All @@ -35,11 +36,18 @@ func pathLogin(b *backend) *framework.Path {

func (b *backend) pathLogin(
req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
username := strings.ToLower(d.Get("name").(string))
username := strings.ToLower(d.Get("username").(string))
if username == "" {
return nil, fmt.Errorf("missing username")
}

password := d.Get("password").(string)
if password == "" {
return nil, fmt.Errorf("missing password")
}

// Get the user and validate auth
user, err := b.User(req.Storage, username)
user, err := b.user(req.Storage, username)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -78,7 +86,7 @@ func (b *backend) pathLogin(
func (b *backend) pathLoginRenew(
req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
// Get the user
user, err := b.User(req.Storage, req.Auth.Metadata["username"])
user, err := b.user(req.Storage, req.Auth.Metadata["username"])
if err != nil {
return nil, err
}
Expand Down
49 changes: 49 additions & 0 deletions builtin/credential/userpass/path_user_password.go
@@ -0,0 +1,49 @@
package userpass

import (
"fmt"

"github.com/hashicorp/vault/logical"
"github.com/hashicorp/vault/logical/framework"
)

func pathUserPassword(b *backend) *framework.Path {
return &framework.Path{
Pattern: "users/" + framework.GenericNameRegex("username") + "/password$",
Fields: map[string]*framework.FieldSchema{
"username": &framework.FieldSchema{
Type: framework.TypeString,
Description: "Username for this user.",
},

"password": &framework.FieldSchema{
Type: framework.TypeString,
Description: "Password for this user.",
},
},

Callbacks: map[logical.Operation]framework.OperationFunc{
logical.UpdateOperation: b.pathUserPasswordUpdate,
},

HelpSynopsis: pathUserPasswordHelpSyn,
HelpDescription: pathUserPasswordHelpDesc,
}
}

func (b *backend) pathUserPasswordUpdate(
req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
password := d.Get("password").(string)
if password == "" {
return nil, fmt.Errorf("missing password")
}
return b.userCreateUpdate(req, d)
}

const pathUserPasswordHelpSyn = `
Reset user's password.
`

const pathUserPasswordHelpDesc = `
This endpoint allows resetting the user's password.
`
42 changes: 42 additions & 0 deletions builtin/credential/userpass/path_user_policies.go
@@ -0,0 +1,42 @@
package userpass

import (
"github.com/hashicorp/vault/logical"
"github.com/hashicorp/vault/logical/framework"
)

func pathUserPolicies(b *backend) *framework.Path {
return &framework.Path{
Pattern: "users/" + framework.GenericNameRegex("username") + "/policies$",
Fields: map[string]*framework.FieldSchema{
"username": &framework.FieldSchema{
Type: framework.TypeString,
Description: "Username for this user.",
},
"policies": &framework.FieldSchema{
Type: framework.TypeString,
Description: "Comma-separated list of policies",
},
},

Callbacks: map[logical.Operation]framework.OperationFunc{
logical.UpdateOperation: b.pathUserPoliciesUpdate,
},

HelpSynopsis: pathUserPoliciesHelpSyn,
HelpDescription: pathUserPoliciesHelpDesc,
}
}

func (b *backend) pathUserPoliciesUpdate(
req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
return b.userCreateUpdate(req, d)
}

const pathUserPoliciesHelpSyn = `
Update the policies associated with the username.
`

const pathUserPoliciesHelpDesc = `
This endpoint allows updating the policies associated with the username.
`