Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What this the best way to ignore unwanted fields in a JSON payload from a PUT/PATCH using Golang?

I have a situation where people consuming our API will need to do a partial update in my resource. I understand that the HTTP clearly specifies that this is a PATCH operation, even though people on our side are used to send a PUT request for this and that's how the legacy code is built.

For exemplification, imagine the simple following struct:

type Person struct {
    Name string
    Age int
    Address string
}

On a POST request, I will provide a payload with all three values (Name, Age, Address) and validate them accordingly on my Golang backend. Simple.

On a PUT/PATCH request though, we know that, for instance, a name never changes. But say I would like to change the age, then I would simply send a JSON payload containing the new age:

PUT /person/1 {age:30}

Now to my real question: What is the best practice to prevent name from being used/updated intentionally or unintentionally modified in case a consumer of our API send a JSON payload containing the name field?

Example:

PUT /person/1 {name:"New Name", age:35} 

Possible solutions I thought of, but I don't actually like them, are:

  1. On my validator method, I would either forcibly remove the unwanted field name OR respond with an error message saying that name is not allowed.

  2. Create a DTO object/struct that would be pretty much an extension of my Person struct and then unmarshall my JSON payload into it, for instance

    type PersonPut struct { Age int Address string }

In my opinion this would add needless extra code and logic to abstract the problem, however I don't see any other elegant solution.

I honestly don't like those two approaches and I would like to know if you guys faced the same problem and how you solved it.

Thanks!

like image 243
Matheus Felipe Avatar asked Jan 10 '15 00:01

Matheus Felipe


3 Answers

The first solution your brought is a good one. Some well known frameworks use to implement similar logic.

As an example, latests Rails versions come with a built in solution to prevent users to add extra data in the request, causing the server to update wrong fields in database. It is a kind of whitelist implemented by ActionController::Parameters class.

Let's suppose we have a controller class as bellow. For purpose of this explanation, it contains two update actions. But you won't see it in real code.

class PeopleController < ActionController::Base

  # 1st version - Unsafe, it will rise an exception. Don't do it
  def update
    person = current_account.people.find(params[:id])
    person.update!(params[:person])
    redirect_to person
  end

  # 2nd version - Updates only permitted parameters
  def update
    person = current_account.people.find(params[:id])
    person.update!(person_params) # call to person_params method
    redirect_to person
  end


  private

  def person_params
    params.require(:person).permit(:name, :age)
  end

end

Since the second version allows only permitted values, it'll block the user to change the payload and send a JSON containing a new password value:

{ name: "acme", age: 25, password: 'account-hacked' }

For more details, see Rails docs: Action Controller Overview and ActionController::Parameters

like image 148
jlucasps Avatar answered Nov 15 '22 03:11

jlucasps


If the name cannot be written it is not valid to provide it for any update request. I would reject the request if the name was present. If I wanted to be more lenient, I might consider only rejecting the request if name is different from the current name.

I would not silently ignore a name which was different from the current name.

like image 27
Lawrence Dol Avatar answered Nov 15 '22 04:11

Lawrence Dol


This can be solved by decoding the JSON body into a map[string]json.RawMessage first. The json.RawMessage type is useful for delaying the actual decoding. Afterwards, a whitelist can be applied on the map[string]json.RawMessage map, ignoring unwanted properties and only decoding the json.RawMessages of the properties we want to keep.

The process of decoding the whitelisted JSON body into a struct can be automated using the reflect package; an example implementation can be found here.

like image 43
rkusa Avatar answered Nov 15 '22 03:11

rkusa