Skip to content

Commit

Permalink
Merge 662d8ef into c352e37
Browse files Browse the repository at this point in the history
  • Loading branch information
mattgarvin1 committed Oct 19, 2020
2 parents c352e37 + 662d8ef commit 343867e
Show file tree
Hide file tree
Showing 3 changed files with 187 additions and 1 deletion.
77 changes: 77 additions & 0 deletions arborist/role.go
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,83 @@ func (role *Role) createInDb(db *sqlx.DB) *ErrorResponse {
return nil
}

func (role *Role) overwriteInDb(db *sqlx.DB) *ErrorResponse {
errResponse := role.validate()
if errResponse != nil {
return errResponse
}

tx, err := db.Beginx()
if err != nil {
msg := fmt.Sprintf("couldn't open database transaction: %s", err.Error())
return newErrorResponse(msg, 500, &err)
}

var roleID int
stmt := `
INSERT INTO role(name, description)
VALUES ($1, $2)
ON CONFLICT(name) DO UPDATE
SET description = $2
RETURNING id
`
row := tx.QueryRowx(stmt, role.Name, role.Description)
err = row.Scan(&roleID)
if err != nil {
_ = tx.Rollback()
msg := fmt.Sprintf("couldn't overwrite role: %s", err.Error())
return newErrorResponse(msg, 500, &err)
}

// upsert permissions
// permissions are unique per combination of role_id + name
stmt = `
INSERT INTO permission(role_id, name, service, method, constraints, description)
VALUES ($1, $2, $3, $4, $5, $6)
ON CONFLICT(role_id, name) DO UPDATE
SET
service = $3,
method = $4,
constraints = $5,
description = $6
`
for _, permission := range role.Permissions {
constraints, err := json.Marshal(permission.Constraints)
if err != nil {
_ = tx.Rollback()
msg := fmt.Sprintf(
"couldn't write constraints for permission %s: %s",
permission.Name,
err.Error(),
)
return newErrorResponse(msg, 500, &err)
}
_, err = tx.Exec(
stmt,
roleID,
permission.Name,
permission.Action.Service,
permission.Action.Method,
constraints,
permission.Description,
)
if err != nil {
_ = tx.Rollback()
msg := fmt.Sprintf("couldn't overwrite permissions: %s", err.Error())
return newErrorResponse(msg, 500, &err)
}
}

err = tx.Commit()
if err != nil {
_ = tx.Rollback()
msg := fmt.Sprintf("couldn't commit database transaction: %s", err.Error())
return newErrorResponse(msg, 500, &err)
}

return nil
}

func (role *Role) deleteInDb(db *sqlx.DB) *ErrorResponse {
stmt := "DELETE FROM role WHERE name = $1"
_, err := db.Exec(stmt, role.Name)
Expand Down
63 changes: 63 additions & 0 deletions arborist/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ func (server *Server) MakeRouter(out io.Writer) http.Handler {
router.Handle("/role", http.HandlerFunc(server.handleRoleList)).Methods("GET")
router.Handle("/role", http.HandlerFunc(server.parseJSON(server.handleRoleCreate))).Methods("POST")
router.Handle("/role/{roleID}", http.HandlerFunc(server.handleRoleRead)).Methods("GET")
router.Handle("/role/{roleID}", http.HandlerFunc(server.parseJSON(server.handleRoleOverwrite))).Methods("PUT")
router.Handle("/role/{roleID}", http.HandlerFunc(server.handleRoleDelete)).Methods("DELETE")

router.Handle("/user", http.HandlerFunc(server.handleUserList)).Methods("GET")
Expand Down Expand Up @@ -937,6 +938,68 @@ func (server *Server) handleRoleRead(w http.ResponseWriter, r *http.Request) {
_ = jsonResponseFrom(role, http.StatusOK).write(w, r)
}

func (server *Server) handleRoleOverwrite(w http.ResponseWriter, r *http.Request, body []byte) {
role := &Role{}
err := json.Unmarshal(body, role)
if err != nil {
msg := fmt.Sprintf("could not parse role from JSON: %s", err.Error())
server.logger.Info("tried to overwrite role but input was invalid: %s", msg)
response := newErrorResponse(msg, 400, nil)
_ = response.write(w, r)
return
}

name := mux.Vars(r)["roleID"]
if name != role.Name {
msg := fmt.Sprintf("roleID '%s' from URL did not match roleID '%s' from JSON", name, role.Name)
server.logger.Info("tried to overwrite role but input was invalid: %s", msg)
response := newErrorResponse(msg, 400, nil)
_ = response.write(w, r)
return
}

roleFromQuery, err := roleWithName(server.db, name)
if err != nil {
msg := fmt.Sprintf("role query failed: %s", err.Error())
errResponse := newErrorResponse(msg, 500, nil)
errResponse.log.write(server.logger)
_ = errResponse.write(w, r)
return
}

var errResponse *ErrorResponse
if roleFromQuery == nil {
errResponse = role.createInDb(server.db)
if errResponse != nil {
errResponse.log.write(server.logger)
_ = errResponse.write(w, r)
return
}
server.logger.Info("created role %s", role.Name)
created := struct {
Created *Role `json:"created"`
}{
Created: role,
}
_ = jsonResponseFrom(created, 201).write(w, r)
return
}

errResponse = role.overwriteInDb(server.db)
if errResponse != nil {
errResponse.log.write(server.logger)
_ = errResponse.write(w, r)
return
}
server.logger.Info("updated role %s", role.Name)
updated := struct {
Updated *Role `json:"updated"`
}{
Updated: role,
}
_ = jsonResponseFrom(updated, 200).write(w, r)
}

func (server *Server) handleRoleDelete(w http.ResponseWriter, r *http.Request) {
name := mux.Vars(r)["roleID"]
role := &Role{Name: name}
Expand Down
48 changes: 47 additions & 1 deletion arborist/server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1121,6 +1121,29 @@ func TestServer(t *testing.T) {
httpError(t, w, "couldn't read response from role creation")
}

t.Run("OverwriteCreate", func(t *testing.T) {
w := httptest.NewRecorder()
body := []byte(`{
"id": "thisNewRole",
"permissions": [
{"id": "thisNewID", "action": {"service": "test-overwrite", "method": "bar"}}
]
}`)
req := newRequest("PUT", "/role/thisNewRole", bytes.NewBuffer(body))
handler.ServeHTTP(w, req)
if w.Code != http.StatusCreated {
httpError(t, w, "couldn't create role")
}
// make one-off struct to read the response into
result := struct {
_ interface{} `json:"created"`
}{}
err = json.Unmarshal(w.Body.Bytes(), &result)
if err != nil {
httpError(t, w, "couldn't read response from role creation")
}
})

t.Run("AlreadyExists", func(t *testing.T) {
w := httptest.NewRecorder()
body := []byte(`{
Expand Down Expand Up @@ -1165,6 +1188,29 @@ func TestServer(t *testing.T) {
assert.Equal(t, "foo", result.Name, msg)
})

t.Run("Overwrite", func(t *testing.T) {
w := httptest.NewRecorder()
body := []byte(`{
"id": "foo",
"permissions": [
{"id": "foo", "action": {"service": "*", "method": "bar"}}
]
}`)
req := newRequest("PUT", "/role/foo", bytes.NewBuffer(body))
handler.ServeHTTP(w, req)
if w.Code != http.StatusOK {
httpError(t, w, "couldn't update role")
}
// make one-off struct to read the response into
result := struct {
_ interface{} `json:"updated"`
}{}
err = json.Unmarshal(w.Body.Bytes(), &result)
if err != nil {
httpError(t, w, "couldn't read response from role overwrite")
}
})

t.Run("List", func(t *testing.T) {
w := httptest.NewRecorder()
req := newRequest("GET", "/role", nil)
Expand All @@ -1180,7 +1226,7 @@ func TestServer(t *testing.T) {
httpError(t, w, "couldn't read response from roles list")
}
msg := fmt.Sprintf("got response body: %s", w.Body.String())
assert.Equal(t, 1, len(result.Roles), msg)
assert.Equal(t, 2, len(result.Roles), msg)
})

t.Run("Delete", func(t *testing.T) {
Expand Down

0 comments on commit 343867e

Please sign in to comment.