Skip to content

#issue-144/user create security #153

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

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
118 changes: 118 additions & 0 deletions api/web/controllers/customer_controller.ex
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,15 @@ defmodule Perseids.CustomerController do
use Perseids.Web, :controller
import Perseids.Gettext

defstruct(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@gorzki IMO lepiej będzie zrobić model bez kolekcji w bazie (analogicznie jak modele PORO w Rails), a dla tych co mają bazę i będziemy je chcieli walidować po prostu dodamy defstruct. Generalnie spoko pomysł z wykorzystanie structa 👍 Po prostu nie róbmy go dla całego kontrolera.

customer: %{
email: :required,
firstname: :required,
lastname: :required
},
password: :required
)

def info(conn, _params) do
case conn.assigns[:store_view] |> conn.assigns[:store_view] |> Magento.customer_info(conn.assigns[:magento_token]) do
{:ok, response} ->
Expand All @@ -19,6 +28,10 @@ defmodule Perseids.CustomerController do
end

def create(conn, params) do
params
|> get_keys
|> raise_error?
|> fields_exists?(conn)
case conn.assigns[:store_view] |> Magento.create_account(params) do
{:ok, response} ->
response = Perseids.CustomerHelper.default_lang(response)
Expand Down Expand Up @@ -61,4 +74,109 @@ defmodule Perseids.CustomerController do
defp maybe_put_key(true, params, key, value), do: Map.put(params, key, value)
defp maybe_put_key(false, params, _key, _value), do: params

defp check_is_valid?(struct, errors \\ []) do
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@gorzki Całą logikę walidacji trzeba umieścić w oddzielnym module, który będzie działał na zasadzie przyjmowania structa i paramsów i zwracał co trzeba. Dzięki temu będziemy go mogli używać gdzie zechcemy w jasny sposób.

struct |> Map.to_list |> check_key(errors)
end

defp check_key([head | tail], errors) do
[key | l ] = head |> Tuple.to_list
value = l |> List.first

if is_map(value) do
errors = errors ++ check_is_valid?(value, errors)
else
errors = errors ++ check(value, key)
end
check_key(tail, errors)
end
defp check_key([], errors), do: errors

defp check(:required, name), do: ["Field #{name} is required"]
defp check(_, _name), do: []

defp get_keys(params, struct \\ Map.from_struct(Perseids.CustomerController), errors \\ []) when is_map(params) do
{ struct, errors } = params |> Map.to_list |> get_key(struct, errors)
struct_errors = struct |> check_is_valid?
errors ++ struct_errors
end

defp get_sub_keys(params, struct, errors) when is_map(params) do
params |> Map.to_list |> get_key(struct, errors)
end

defp get_key([head | tail], struct, errors) do
[h | l ] = head |> Tuple.to_list
key = h |> String.to_atom
value = l |> List.first

case Map.has_key?(struct, key) do
true ->
current_struct = Map.fetch!(struct, key)
errors = errors ++ do_validate?(current_struct, value, key)
if is_map(value) do
{ prev_struct, errors } = get_sub_keys(value, Map.fetch!(struct, key), errors)
end
struct = check_struct(prev_struct, struct, key)
false ->
errors = errors ++ ["Undefined params key -> #{key}"]
if is_map(value) do
{ _, errors } = get_sub_keys(value, %{}, errors)
end
end
get_key(tail, struct, errors)
end
defp get_key([], struct, errors), do: { struct, errors }

defp check_struct(nil, struct, key), do: Map.drop(struct, [key])
defp check_struct(prev_struct, struct, key) do
prev_keys = prev_struct |> Map.keys
error_struct = Map.fetch!(struct, key) |> Enum.reduce(%{}, &(maybe_put_key(&1, &2, prev_keys)))
replace(struct, key, error_struct)
end

defp do_validate?(:required, value, key) when is_binary(value), do: apply(__MODULE__, do_function(key), [value, key])
defp do_validate?(:optional, value, key) when is_binary(value), do: apply(__MODULE__, do_function(key), [value, key])
defp do_validate?(:default, _value, _key), do: []
defp do_validate?(_struct, _value, _key), do: []

defp do_function(key, string \\ "validate") do
string <> "_" <> Atom.to_string(key)
|> String.replace("-", "_")
|> String.to_atom
end


def validate_email(value, name), do: validation(value, name)
def validate_firstname(value, name), do: validation(value, name)
def validate_lastname(value, name), do: validation(value, name)
def validate_password(value, name), do: validation(value, name)

defp validation(value, name) do
value
|> String.length
|> response(name)
end

defp response(0, name), do: [ "#{name} is too short" ]
defp response(_other, _name), do: []

defp raise_error?([]), do: false
defp raise_error?(errors), do: errors

defp fields_exists?(false, _conn), do: nil
defp fields_exists?(errors, conn), do: conn |> put_status(400) |> json(%{ errors: errors })

defp replace(map, key, value) do # added function from Map lib (Map.replace!) beacuse is not available in this elixir version.
case map do
%{^key => _value} ->
Map.put(map, key, value)

%{} ->
map

other ->
:erlang.error({:badmap, other})
end
end

end