From 8efe8db5b745c183f4cb2f995b6c8f494f41f3d1 Mon Sep 17 00:00:00 2001 From: Renato Massaro Date: Wed, 11 Oct 2017 12:48:42 -0300 Subject: [PATCH] Add Helix.Software macro --- lib/software/model/file.ex | 19 +- lib/software/model/file_module.ex | 4 +- lib/software/model/software.ex | 57 ++++ lib/software/model/software_module.ex | 42 --- lib/software/model/software_type.ex | 99 ------- lib/software/software.ex | 264 ++++++++++++++++++ ...171011150235_change_software_type_name.exs | 9 + priv/repo/software/seeds.exs | 26 +- test/support/software/helper.ex | 12 +- test/support/software/setup.ex | 2 +- 10 files changed, 363 insertions(+), 171 deletions(-) create mode 100644 lib/software/model/software.ex delete mode 100644 lib/software/model/software_module.ex delete mode 100644 lib/software/model/software_type.ex create mode 100644 lib/software/software.ex create mode 100644 priv/repo/software/migrations/20171011150235_change_software_type_name.exs diff --git a/lib/software/model/file.ex b/lib/software/model/file.ex index b246a261..419cb156 100644 --- a/lib/software/model/file.ex +++ b/lib/software/model/file.ex @@ -9,10 +9,10 @@ defmodule Helix.Software.Model.File do alias Ecto.Changeset alias HELL.Constant alias Helix.Software.Model.FileModule - alias Helix.Software.Model.SoftwareType + alias Helix.Software.Model.Software alias Helix.Software.Model.Storage - @type t :: t_of_type(SoftwareType.type) + @type t :: t_of_type(Software.type) @type t_of_type(type) :: %__MODULE__{ file_id: id, @@ -20,7 +20,7 @@ defmodule Helix.Software.Model.File do path: path, full_path: full_path, file_size: size, - type: SoftwareType.t, + type: Software.Type.t, software_type: type, storage_id: Storage.id, storage: term, @@ -30,11 +30,12 @@ defmodule Helix.Software.Model.File do crypto_version: crypto_version } + @type extension :: String.t @type path :: String.t @type full_path :: path @type name :: String.t @type size :: pos_integer - @type type :: SoftwareType.type + @type type :: Software.type @type crypto_version :: nil | pos_integer @type modules :: FileModule.t @@ -44,7 +45,7 @@ defmodule Helix.Software.Model.File do name: name, path: path, file_size: size, - software_type: SoftwareType.type, + software_type: Software.type, storage_id: Storage.idtb } @@ -62,7 +63,7 @@ defmodule Helix.Software.Model.File do @required_fields ~w/name path file_size software_type storage_id/a - @software_types Map.keys(SoftwareType.possible_types()) + @software_types Software.Type.all() schema "files" do field :file_id, ID, @@ -78,9 +79,9 @@ defmodule Helix.Software.Model.File do field :full_path, :string - belongs_to :type, SoftwareType, + belongs_to :type, Software.Type, foreign_key: :software_type, - references: :software_type, + references: :type, define_field: false belongs_to :storage, Storage, foreign_key: :storage_id, @@ -178,7 +179,7 @@ defmodule Helix.Software.Model.File do path = get_field(changeset, :path) name = get_field(changeset, :name) software_type = get_field(changeset, :software_type) - extension = SoftwareType.possible_types()[software_type].extension + extension = Software.Type.get(software_type).extension full_path = path <> "/" <> name <> "." <> extension diff --git a/lib/software/model/file_module.ex b/lib/software/model/file_module.ex index e8111f1f..a69f86b2 100644 --- a/lib/software/model/file_module.ex +++ b/lib/software/model/file_module.ex @@ -19,7 +19,7 @@ defmodule Helix.Software.Model.FileModule do alias HELL.Constant alias Helix.Software.Model.File alias Helix.Software.Model.FileModule.Data, as: FileModuleData - alias Helix.Software.Model.SoftwareModule + alias Helix.Software.Model.Software @type t :: %{ name => FileModuleData.t @@ -87,7 +87,7 @@ defmodule Helix.Software.Model.FileModule do def generic_validations(changeset) do changeset |> validate_number(:version, greater_than: 0) - |> validate_inclusion(:name, SoftwareModule.possible_modules()) + |> validate_inclusion(:name, Software.Module.all()) end @spec format(schema) :: diff --git a/lib/software/model/software.ex b/lib/software/model/software.ex new file mode 100644 index 00000000..c25f43e8 --- /dev/null +++ b/lib/software/model/software.ex @@ -0,0 +1,57 @@ +defmodule Helix.Software.Model.Software do + + use Helix.Software + + software \ + type: :cracker, + extension: "crc", + modules: [:bruteforce, :overflow] + + software \ + type: :firewall, + extension: "fwl", + modules: [:fwl_active, :fwl_passive] + + software \ + type: :text, + extension: "txt" + + software \ + type: :exploit, + extension: "exp", + modules: [:ftp, :ssh] + + software \ + type: :hasher, + extension: "hash", + modules: [:password] + + software \ + type: :log_forger, + extension: "logf", + modules: [:log_create, :log_edit] + + software \ + type: :log_recover, + extension: "logr", + modules: [:log_recover] + + software \ + type: :encryptor, + extension: "enc", + modules: [:enc_file, :enc_log, :enc_connection, :enc_process] + + software \ + type: :decryptor, + extension: "dec", + modules: [:dec_file, :dec_log, :dec_connection, :dec_process] + + software \ + type: :anymap, + extension: "map", + modules: [:map_geo, :map_net] + + software \ + type: :crypto_key, + extension: "key" +end diff --git a/lib/software/model/software_module.ex b/lib/software/model/software_module.ex deleted file mode 100644 index 4a7860d0..00000000 --- a/lib/software/model/software_module.ex +++ /dev/null @@ -1,42 +0,0 @@ -defmodule Helix.Software.Model.SoftwareModule do - - use Ecto.Schema - - alias HELL.Constant - alias Helix.Software.Model.SoftwareType - - @type t :: %__MODULE__{ - module: Constant.t, - software_type: String.t - } - - @software_modules Enum.flat_map( - SoftwareType.possible_types(), - fn {_, %{modules: m}} -> m end) - - @primary_key false - schema "software_modules" do - field :module, Constant, - primary_key: true - - # FK to SoftwareType - field :software_type, Constant - end - - @doc false - def possible_modules, - do: @software_modules - - defmodule Query do - import Ecto.Query - - alias Ecto.Queryable - alias HELL.Constant - alias Helix.Software.Model.SoftwareModule - - @spec by_software_type(Queryable.t, Constant.t) :: - Queryable.t - def by_software_type(query \\ SoftwareModule, software_type), - do: where(query, [m], m.software_type == ^software_type) - end -end diff --git a/lib/software/model/software_type.ex b/lib/software/model/software_type.ex deleted file mode 100644 index f63d4843..00000000 --- a/lib/software/model/software_type.ex +++ /dev/null @@ -1,99 +0,0 @@ -defmodule Helix.Software.Model.SoftwareType do - - use Ecto.Schema - - alias HELL.Constant - - @type t :: %__MODULE__{ - software_type: type, - extension: String.t - } - - @type type :: - :cracker - | :exploit - | :firewall - | :hasher - | :log_forger - | :log_recover - | :encryptor - | :decryptor - | :anymap - | :crypto_key - - # TODO: Add module types once file_module refactor is done - - # TODO: Software macro: - # software \ - # type: :cracker, - # extension: ".crc", - # modules: [:bruteforce, :overflow] - - @primary_key false - schema "software_types" do - field :software_type, Constant, - primary_key: true - - field :extension, :string - end - - @doc false - def possible_types do - %{ - text: %{ - extension: "txt", - modules: [] - }, - cracker: %{ - extension: "crc", - modules: [:bruteforce, :overflow] - }, - exploit: %{ - extension: "exp", - modules: [:ftp, :ssh] - }, - firewall: %{ - extension: "fwl", - modules: [:fwl_active, :fwl_passive] - }, - hasher: %{ - extension: "hash", - modules: [:password] - }, - log_forger: %{ - extension: "logf", - modules: [:log_create, :log_edit] - }, - log_recover: %{ - extension: "logr", - modules: [:log_recover] - }, - encryptor: %{ - extension: "enc", - modules: [ - :encrypt_file, - :encrypt_log, - :encrypt_connection, - :encrypt_process - ] - }, - decryptor: %{ - extension: "dec", - modules: [ - :decrypt_file, - :decrypt_log, - :decrypt_connection, - :decrypt_process - ] - }, - anymap: %{ - extension: "map", - modules: [:map_geo, :map_net] - }, - crypto_key: %{ - extension: "key", - modules: [] - } - } - end -end diff --git a/lib/software/software.ex b/lib/software/software.ex new file mode 100644 index 00000000..472f1a3b --- /dev/null +++ b/lib/software/software.ex @@ -0,0 +1,264 @@ +# credo:disable-for-this-file Credo.Check.Refactor.LongQuoteBlocks +defmodule Helix.Software do + @moduledoc """ + This module is a big big helper to defining new softwares. It removes most of + the boilerplate and gives us leverage on enhancing the Software behavior. + """ + + @doc """ + By `using` this module, we are able to accumulate module attributes, which + will be used as a temporary storage for `__before_compile__` macro. + """ + defmacro __using__(_) do + quote do + + import unquote(__MODULE__) + + Module.register_attribute( + __MODULE__, + :all_software, + accumulate: true, + persist: :false + ) + Module.register_attribute( + __MODULE__, + :all_modules, + accumulate: true, + persist: :false + ) + + @before_compile unquote(__MODULE__) + + unquote(__MODULE__) + end + end + + @doc """ + `__before_compile__` is responsible for actually building the Software-related + modules (Software.Module and Software.Type), as well as all helper functions + generated through the accumulated attributes (`@all_software` and + `@all_modules`). + """ + defmacro __before_compile__(_env) do + quote unquote: false do + + all_software = @all_software + all_modules = @all_modules |> List.flatten() + + # Ensure there are no repeated entries for software types or modules + ensure_unique_software(all_software) + ensure_unique_modules(all_modules) + + alias HELL.Constant + alias Helix.Software.Model.File + alias Helix.Software.Model.FileModule + + @type t :: %{ + type: type, + extension: File.extension, + modules: [module_name] + } + + # TODO: Once I get a better grasp of macros, generate the types below + # extensively, from `all_software` and `all_modules` + @type type :: Constant.t + @type modules :: Constant.t + + @type extension :: File.extension + @type module_name :: FileModule.name + + @spec all :: + [t] + @doc """ + Returns a list of all valid software. + """ + def all, + do: @all_software + + defmodule Type do + @moduledoc """ + Software.Type holds information about software types, as well as the + underlying schema for the "software_types" table. + """ + + use Ecto.Schema + + import Ecto.Changeset + + alias Ecto.Changeset + alias HELL.Constant + alias Helix.Software.Model.Software + + @type t :: %__MODULE__{ + type: Software.type, + extension: Software.extension + } + + @type creation_params :: Software.t + + @type changeset :: %Changeset{data: %__MODULE__{}} + + @all_software all_software + + @primary_key false + schema "software_types" do + field :type, Constant, + primary_key: true + field :extension, :string + end + + @spec create_changeset(creation_params) :: + changeset + def create_changeset(params = %{}) do + %__MODULE__{} + |> cast(params, [:type, :extension]) + |> validate_inclusion(:type, all()) + |> validate_inclusion(:extension, all_extensions()) + |> validate_required([:type, :extension]) + end + + # Defines all possible software types that can be `get/1`ed + Enum.each(all_software, fn software -> + + @doc false + def get(unquote(software.type)) do + %{ + type: unquote(software.type), + extension: unquote(software.extension), + modules: unquote(software.modules) + } + end + + end) + + @spec all :: + [Software.type] + @doc """ + Returns a list of all possible software. + """ + def all do + @all_software + |> Enum.map(&(&1.type)) + end + + @spec all_extensions :: + [Software.extension] + @doc """ + Returns a list of all valid extensions. + """ + def all_extensions do + @all_software + |> Enum.map(&(&1.extension)) + |> Enum.uniq() + end + end + + defmodule Module do + @moduledoc """ + Software.Module contains information about Software Modules, as well as + the underlying schema for the "software_modules" table. + """ + + use Ecto.Schema + + import Ecto.Changeset + + alias Ecto.Changeset + alias HELL.Constant + alias Helix.Software.Model.Software + + @type t :: %__MODULE__{ + module: Software.module_name, + software_type: Software.type + } + + @type creation_params :: %{ + module: Software.module_name, + software_type: Software.type + } + + @type changeset :: %Changeset{data: %__MODULE__{}} + + @primary_key false + schema "software_modules" do + field :module, Constant, + primary_key: true + + field :software_type, Constant + end + + @spec create_changeset(creation_params) :: + changeset + def create_changeset(params = %{}) do + %__MODULE__{} + |> cast(params, [:software_type, :module]) + |> validate_required([:software_type, :module]) + |> validate_inclusion(:module, all()) + end + + @spec exists?(Software.module_name) :: + boolean + @doc """ + Verifies whether the given module exists + """ + def exists?(module), + do: module in unquote(all_modules) + + @spec all :: + [Software.module_name] + @doc """ + Returns a list of all valid modules + """ + def all, + do: unquote(all_modules) + end + end + + end + + @doc """ + Ensures there are no repeated entries for software types + """ + def ensure_unique_software(all_software) do + uniq = Enum.uniq_by(all_software, &(&1.type)) + + all_software == uniq || raise "\n\nrepeated software type definition\n" + end + + @doc """ + Ensures there are no repeated entries for software modules + """ + def ensure_unique_modules(all_modules) do + uniq = Enum.uniq(all_modules) + + all_modules == uniq || raise "\n\nrepeated software modules definition\n" + end + + @doc """ + Macro to register a new software. + + The registered software information is accumulated and will be used to build + all software types/modules at the `__before_compile__` macro. + """ + defmacro software(type: type, extension: extension, modules: modules) do + quote do + + software = %{ + type: unquote(type), + extension: unquote(extension), + modules: unquote(modules) + } + + # Accumulate the software + Module.put_attribute(__MODULE__, :all_software, software) + Module.put_attribute(__MODULE__, :all_modules, unquote(modules)) + + end + end + + defmacro software(type: type, extension: extension) do + quote do + software(type: unquote(type), extension: unquote(extension), modules: []) + end + end +end diff --git a/priv/repo/software/migrations/20171011150235_change_software_type_name.exs b/priv/repo/software/migrations/20171011150235_change_software_type_name.exs new file mode 100644 index 00000000..b5f2e5ab --- /dev/null +++ b/priv/repo/software/migrations/20171011150235_change_software_type_name.exs @@ -0,0 +1,9 @@ +defmodule Helix.Software.Repo.Migrations.ChangeSoftwareTypeName do + use Ecto.Migration + + def change do + + rename table(:software_types), :software_type, to: :type + + end +end diff --git a/priv/repo/software/seeds.exs b/priv/repo/software/seeds.exs index 27261d0a..822a6db5 100644 --- a/priv/repo/software/seeds.exs +++ b/priv/repo/software/seeds.exs @@ -1,22 +1,26 @@ alias Helix.Software.Repo -alias Helix.Software.Model.SoftwareType -alias Helix.Software.Model.SoftwareModule +alias Helix.Software.Model.Software Repo.transaction fn -> - Enum.each(SoftwareType.possible_types(), fn {type, %{extension: extension}} -> - software_type = %SoftwareType{software_type: type, extension: extension} + all_software = Software.all() - Repo.insert!(software_type, on_conflict: :nothing) - end) + # Stores all software information (type and module) + Enum.each(all_software, fn software -> + # Insert software type entry + software + |> Software.Type.create_changeset() + |> Repo.insert!(on_conflict: :nothing) - Enum.each(SoftwareType.possible_types(), fn {type, %{modules: modules}} -> - Enum.each(modules, fn module -> - software_module = %SoftwareModule{ - software_type: type, + # Insert entry for all modules that belong to this software + Enum.each(software.modules, fn module -> + params = %{ + software_type: software.type, module: module } - Repo.insert!(software_module, on_conflict: :nothing) + params + |> Software.Module.create_changeset() + |> Repo.insert!(on_conflict: :nothing) end) end) end diff --git a/test/support/software/helper.ex b/test/support/software/helper.ex index 3f2c8b35..0f04e514 100644 --- a/test/support/software/helper.ex +++ b/test/support/software/helper.ex @@ -3,7 +3,7 @@ defmodule Helix.Test.Software.Helper do alias Helix.Cache.Query.Cache, as: CacheQuery alias Helix.Server.Model.Server alias Helix.Software.Model.FileModule - alias Helix.Software.Model.SoftwareType + alias Helix.Software.Model.Software alias Helix.Software.Model.Storage alias Helix.Software.Query.Storage, as: StorageQuery @@ -42,7 +42,7 @@ defmodule Helix.Test.Software.Helper do (:cracker, %{bruteforce: 20, overflow: 10}) """ def generate_module(type, version_map) do - modules = SoftwareType.possible_types()[type].modules + modules = Software.Type.get(type).modules Enum.reduce(modules, %{}, fn (module, acc) -> version = Map.fetch!(version_map, module) @@ -57,7 +57,7 @@ defmodule Helix.Test.Software.Helper do Returns modules with random versions for the given file. """ def get_modules(type) do - modules = SoftwareType.possible_types()[type].modules + modules = Software.Type.get(type).modules Enum.reduce(modules, %{}, fn (module, acc) -> module @@ -85,10 +85,8 @@ defmodule Helix.Test.Software.Helper do |> Enum.join("/") end - def random_file_type do - {software_type, _} = Enum.random(SoftwareType.possible_types()) - software_type - end + def random_file_type, + do: Enum.random(Software.Type.all()) def random_version, do: Random.number(min: 10, max: 50) diff --git a/test/support/software/setup.ex b/test/support/software/setup.ex index 4337832e..6b987239 100644 --- a/test/support/software/setup.ex +++ b/test/support/software/setup.ex @@ -64,7 +64,7 @@ defmodule Helix.Test.Software.Setup do @doc """ - name: Set file name - size: Set file size - - type: Set file type. SoftwareType.t + - type: Set file type. Software.type - path: Set file path - modules: Set file module. If set, `type` must also be set. - server_id: Server that file belongs to. Will use the first storage it finds.