Skip to content

Commit

Permalink
Allow updates(first_name, last_name, disable, password) to built-in u…
Browse files Browse the repository at this point in the history
…sers

Signed-off-by: Yuva Shankar <[email protected]>
  • Loading branch information
yuva29 committed Dec 14, 2016
1 parent b661634 commit ee7a023
Show file tree
Hide file tree
Showing 6 changed files with 262 additions and 29 deletions.
46 changes: 28 additions & 18 deletions db/local.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,30 @@ import (
// This file contains all local user management APIs.
// NOTE: Built-in users(admin, ops) cannot be changed/updated. it needs to be consumed in the way its defined in code.

// deleteLocalUser helper for `DeleteLocalUser`
// param:
// username: to be deleted from the data store
// return values:
// error: as returned by the consecutive calls or any relevant custom errors
func deleteLocalUser(username string) error {
stateDrv, err := state.GetStateDriver()
if err != nil {
return err
}

// handles `ErrKeyNotFound`
_, err = getLocalUser(username, stateDrv)
if err != nil {
return err
}

if err := stateDrv.Clear(GetPath(RootLocalUsers, username)); err != nil {
return fmt.Errorf("Failed to clear %q from store: %#v", username, err)
}

return nil
}

// getLocalUser helper function that retrieves a user entry from `/ccn_proxy/local_users` using username
// params:
// username:string; name of the user to be fetched from store
Expand Down Expand Up @@ -90,9 +114,10 @@ func GetLocalUser(username string) (*types.LocalUser, error) {
// username: string; of the user that requires update
// user: local user object to be updated in the data store
// return values:
// error: as returned by AddLocalUser, DeleteLocalUser
// error: as returned by AddLocalUser, deleteLocalUser
func UpdateLocalUser(username string, user *types.LocalUser) error {
err := DeleteLocalUser(username)
err := deleteLocalUser(username)

switch err {
case nil:
// if the password is getting updated; clear the existing hash
Expand Down Expand Up @@ -122,22 +147,7 @@ func DeleteLocalUser(username string) error {
return ccnerrors.ErrIllegalOperation
}

stateDrv, err := state.GetStateDriver()
if err != nil {
return err
}

// handles `ErrKeyNotFound`
_, err = getLocalUser(username, stateDrv)
if err != nil {
return err
}

if err := stateDrv.Clear(GetPath(RootLocalUsers, username)); err != nil {
return fmt.Errorf("Failed to clear %q from store: %#v", username, err)
}

return nil
return deleteLocalUser(username)
}

// AddLocalUser adds a new user entry to /ccn_proxy/local_users/.
Expand Down
50 changes: 50 additions & 0 deletions db/local_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -246,3 +246,53 @@ func (s *dbSuite) TestGetLocalUsers(c *C) {
c.Assert(usernames, DeepEquals, allUsers)

}

func (s *dbSuite) TestUpdateBuiltInUsers(c *C) {
s.addBuiltInUsers(c)

// update all the details except `password`
for _, username := range builtInUsers {
user, err := GetLocalUser(username)
c.Assert(err, IsNil)

uUser := &types.LocalUser{
Username: user.Username,
Disable: true,
FirstName: "BuiltIn",
LastName: "User",
PasswordHash: user.PasswordHash,
}

err = UpdateLocalUser(username, uUser)
c.Assert(err, IsNil)
uUser.PasswordHash = user.PasswordHash

obtainedUser, err := GetLocalUser(username)
c.Assert(err, IsNil)
c.Assert(obtainedUser, DeepEquals, uUser)
}

// update password and check hash
for _, username := range builtInUsers {
user, err := GetLocalUser(username)
c.Assert(err, IsNil)

uUser := &types.LocalUser{
Username: user.Username,
Password: user.Username + "_U",
}

err = UpdateLocalUser(username, uUser)
c.Assert(err, IsNil)

obtainedUser, err := GetLocalUser(username)
c.Assert(err, IsNil)
c.Assert(string(obtainedUser.PasswordHash) != string(user.PasswordHash), Equals, true)

// all other attributes got default value
c.Assert(obtainedUser.LastName, Equals, "")
c.Assert(obtainedUser.FirstName, Equals, "")
c.Assert(obtainedUser.Disable, Equals, false)
}

}
1 change: 0 additions & 1 deletion proxy/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -242,7 +242,6 @@ func deleteLocalUser(token *auth.Token, w http.ResponseWriter, req *http.Request
// updateLocalUser updates the existing user with the given details.
// it can return various HTTP status codes:
// 204 (NoContent; update was successful)
// 400 (BadRequest; invalid role/cannot update built-in user)
// 404 (NotFound; user not found)
// 500 (internal server error)
func updateLocalUser(token *auth.Token, w http.ResponseWriter, req *http.Request) {
Expand Down
1 change: 1 addition & 0 deletions systemtests/basic_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ func (s *systemtestSuite) TestRequestProxying(c *C) {
token := adminToken(c)

resp, body := proxyGet(c, token, endpoint)
c.Assert(resp.StatusCode, Equals, 200)
c.Assert(data, Equals, string(body))

// check that the Content-Type header was set properly.
Expand Down
53 changes: 43 additions & 10 deletions systemtests/init_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ func login(username, password string) (string, *http.Response, error) {
return "", nil, err
}

resp, data, err := insecurePostJSONBody("", proxy.LoginPath, loginBytes)
resp, data, err := insecureJSONBody("", proxy.LoginPath, "POST", loginBytes)
if err != nil {
return "", resp, err
}
Expand Down Expand Up @@ -200,7 +200,6 @@ func proxyGet(c *C, token, path string) (*http.Response, []byte) {

resp, err := insecureClient().Do(req)
c.Assert(err, IsNil)
c.Assert(resp.StatusCode, Equals, 200)

defer resp.Body.Close()

Expand All @@ -210,26 +209,60 @@ func proxyGet(c *C, token, path string) (*http.Response, []byte) {
return resp, data
}

// proxyDelete is a convenience function which sends an insecure HTTPS DELETE
// request to the proxy.
func proxyDelete(c *C, token, path string) (*http.Response, []byte) {
url := "https://" + proxyHost + path

log.Info("GET to ", url)

req, err := http.NewRequest("DELETE", url, nil)
c.Assert(err, IsNil)

if len(token) > 0 {
log.Println("Setting X-Auth-token to:", token)
req.Header.Set("X-Auth-Token", token)
}

resp, err := insecureClient().Do(req)
c.Assert(err, IsNil)

defer resp.Body.Close()

data, err := ioutil.ReadAll(resp.Body)
c.Assert(err, IsNil)

return resp, data
}

// proxyPatch is a convenience function which sends an insecure HTTPS PATCH
// request with the specified body to the proxy.
func proxyPatch(c *C, token, path string, body []byte) (*http.Response, []byte) {
resp, body, err := insecureJSONBody(token, path, "PATCH", body)
c.Assert(err, IsNil)

return resp, body
}

// proxyPost is a convenience function which sends an insecure HTTPS POST
// request with the specified body to the proxy.
func proxyPost(c *C, token, path string, body []byte) (*http.Response, []byte) {
resp, body, err := insecurePostJSONBody(token, path, body)
resp, body, err := insecureJSONBody(token, path, "POST", body)
c.Assert(err, IsNil)
c.Assert(resp.StatusCode/100, Equals, 2) // 2xx is okay

return resp, body
}

// insecurePostBody sends an insecure HTTPS POST request with the specified
// insecureJSONBody sends an insecure HTTPS POST request with the specified
// JSON payload as the body.
func insecurePostJSONBody(token, path string, body []byte) (*http.Response, []byte, error) {
func insecureJSONBody(token, path, requestType string, body []byte) (*http.Response, []byte, error) {
url := "https://" + proxyHost + path

log.Info("POST to ", url)
log.Info(requestType, " to ", url)

req, err := http.NewRequest("POST", url, bytes.NewBuffer(body))
req, err := http.NewRequest(requestType, url, bytes.NewBuffer(body))
if err != nil {
log.Debugf("POST request creation failed: %s", err)
log.Debugf("%v request creation failed: %s", requestType, err)
return nil, nil, err
}

Expand All @@ -242,7 +275,7 @@ func insecurePostJSONBody(token, path string, body []byte) (*http.Response, []by

resp, err := insecureClient().Do(req)
if err != nil {
log.Debugf("POST request failed: %s", err)
log.Debugf("%v request failed: %s", requestType, err)
return nil, nil, err
}

Expand Down
140 changes: 140 additions & 0 deletions systemtests/local_user_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
package systemtests

import (
"github.com/contiv/ccn_proxy/common/types"
"github.com/contiv/ccn_proxy/proxy"
. "gopkg.in/check.v1"
)

// TestLocalUserEndpoints tests ccn_proxy's local user endpoints
func (s *systemtestSuite) TestLocalUserEndpoints(c *C) {

runTest(func(p *proxy.Server, ms *MockServer) {
token := adminToken(c)
endpoint := "/api/v1/ccn_proxy/local_users"

resp, body := proxyGet(c, token, endpoint)
c.Assert(resp.StatusCode, Equals, 200)
c.Assert(len(body), Not(Equals), 0)

// add new local_user to the system
data := `{"username":"testuser","password":"testpwd", "disable":false}`
respBody := `{"username":"testuser","first_name":"","last_name":"","disable":false}`
s.addLocalUser(c, data, respBody, token)

// get `testuser`
endpoint = "/api/v1/ccn_proxy/local_users/testuser"
resp, body = proxyGet(c, token, endpoint)
c.Assert(resp.StatusCode, Equals, 200)
c.Assert(string(body), DeepEquals, respBody)

// try login using `testuser`
testuserToken := loginAs(c, "testuser", "testpwd")
c.Assert(len(testuserToken), Not(Equals), 0)

// delete `testuser`
resp, body = proxyDelete(c, token, endpoint)
c.Assert(resp.StatusCode, Equals, 204)
c.Assert(len(body), Equals, 0)

// get `testuser`
resp, body = proxyGet(c, token, endpoint)
c.Assert(resp.StatusCode, Equals, 404)
c.Assert(len(body), Equals, 0)

})
}

// TestLocalUserUpdateEndpoint tests ccn_proxy's local user update endpoint
func (s *systemtestSuite) TestLocalUserUpdateEndpoint(c *C) {

runTest(func(p *proxy.Server, ms *MockServer) {
token := adminToken(c)

// add new local_user to the system
data := `{"username":"testuser","password":"testpwd", "disable":false}`
respBody := `{"username":"testuser","first_name":"","last_name":"","disable":false}`
s.addLocalUser(c, data, respBody, token)

// try login using `testuser`
testuserToken := loginAs(c, "testuser", "testpwd")
c.Assert(len(testuserToken), Not(Equals), 0)

// update `testuser` details
data = `{"first_name":"Temp", "last_name": "User"}`
respBody = `{"username":"testuser","first_name":"Temp","last_name":"User","disable":false}`
s.updateLocalUser(c, "testuser", data, respBody, token)

// try login again using `testuser`
testuserToken = loginAs(c, "testuser", "testpwd")
c.Assert(len(testuserToken), Not(Equals), 0)

// update `testuser` password
data = `{"password":"test"}`
s.updateLocalUser(c, "testuser", data, respBody, token)

// try login again using old password
testuserToken, resp, err := login("testuser", "testpwd")
c.Assert(err, IsNil)
c.Assert(resp.StatusCode, Equals, 401)
c.Assert(len(testuserToken), Equals, 0)

// try login again using new password
testuserToken = loginAs(c, "testuser", "test")
c.Assert(len(testuserToken), Not(Equals), 0)
})
}

// TestBuiltInUserUpdate tests built-in user update functionality
func (s *systemtestSuite) TestBuiltInUserUpdate(c *C) {
runTest(func(p *proxy.Server, ms *MockServer) {
token := adminToken(c)

for _, username := range []string{types.Admin.String(), types.Ops.String()} {
// update user details
data := `{"first_name":"Built-in", "last_name": "User", "disable":false}`
respBody := `{"username":"` + username + `","first_name":"Built-in","last_name":"User","disable":false}`
s.updateLocalUser(c, username, data, respBody, token)

// login
testuserToken := loginAs(c, username, username)
c.Assert(len(testuserToken), Not(Equals), 0)

// update password
data = `{"password":"test"}`
s.updateLocalUser(c, username, data, respBody, token)

// try login again using old password
testuserToken, resp, err := login(username, username)
c.Assert(err, IsNil)
c.Assert(resp.StatusCode, Equals, 401)
c.Assert(len(testuserToken), Equals, 0)

// try login again using new password
testuserToken = loginAs(c, username, "test")
c.Assert(len(testuserToken), Not(Equals), 0)

// revert password so that it wont block other tests
data = `{"password":"` + username + `"}`
s.updateLocalUser(c, username, data, respBody, token)
}
})
}

// addLocalUser helper function for the tests
func (s *systemtestSuite) addLocalUser(c *C, data, expectedRespBody, token string) {
endpoint := "/api/v1/ccn_proxy/local_users"

resp, body := proxyPost(c, token, endpoint, []byte(data))
c.Assert(resp.StatusCode, Equals, 201)
c.Assert(string(body), DeepEquals, expectedRespBody)
}

// updateLocalUser helper function for the tests
func (s *systemtestSuite) updateLocalUser(c *C, username, data, expectedRespBody, token string) {
endpoint := "/api/v1/ccn_proxy/local_users/" + username

resp, body := proxyPatch(c, token, endpoint, []byte(data))
c.Assert(resp.StatusCode, Equals, 200)
c.Assert(string(body), DeepEquals, expectedRespBody)
}

0 comments on commit ee7a023

Please sign in to comment.