diff --git a/.formatter.exs b/.formatter.exs new file mode 100644 index 0000000..d2cda26 --- /dev/null +++ b/.formatter.exs @@ -0,0 +1,4 @@ +# Used by "mix format" +[ + inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"] +] diff --git a/.gitignore b/.gitignore index 86e4c3f..37b182b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,9 +1,24 @@ -/_build -/cover -/deps -/doc +# The directory Mix will write compiled artifacts to. +/_build/ + +# If you run "mix test --cover", coverage assets end up here. +/cover/ + +# The directory Mix downloads your dependencies sources to. +/deps/ + +# Where third-party dependencies like ExDoc output generated docs. +/doc/ + +# Ignore .fetch files in case you like to edit your project deps locally. /.fetch + +# If the VM crashes, it generates a dump, let's ignore it too. erl_crash.dump + +# Also ignore archive artifacts (built via "mix archive.build"). *.ez -*.beam -/config/*.secret.exs + +# Ignore package tarball (built via "mix hex.build"). +rfc5849-*.tar + diff --git a/README.md b/README.md index cb5fa21..1cc29e5 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,21 @@ -# rfc5849 -OAuth/1 Elixir Implementation based on OTP +# Rfc5849 + +**TODO: Add description** + +## Installation + +If [available in Hex](https://hex.pm/docs/publish), the package can be installed +by adding `rfc5849` to your list of dependencies in `mix.exs`: + +```elixir +def deps do + [ + {:rfc5849, "~> 0.1.0"} + ] +end +``` + +Documentation can be generated with [ExDoc](https://github.com/elixir-lang/ex_doc) +and published on [HexDocs](https://hexdocs.pm). Once published, the docs can +be found at [https://hexdocs.pm/rfc5849](https://hexdocs.pm/rfc5849). + diff --git a/config/config.exs b/config/config.exs new file mode 100644 index 0000000..afeed98 --- /dev/null +++ b/config/config.exs @@ -0,0 +1,30 @@ +# This file is responsible for configuring your application +# and its dependencies with the aid of the Mix.Config module. +use Mix.Config + +# This configuration is loaded before any dependency and is restricted +# to this project. If another project depends on this project, this +# file won't be loaded nor affect the parent project. For this reason, +# if you want to provide default values for your application for +# third-party users, it should be done in your "mix.exs" file. + +# You can configure your application as: +# +# config :rfc5849, key: :value +# +# and access this configuration in your application as: +# +# Application.get_env(:rfc5849, :key) +# +# You can also configure a third-party app: +# +# config :logger, level: :info +# + +# It is also possible to import configuration files, relative to this +# directory. For example, you can emulate configuration per environment +# by uncommenting the line below and defining dev.exs, test.exs and such. +# Configuration from the imported file will override the ones defined +# here (which is why it is important to import them last). +# +# import_config "#{Mix.env()}.exs" diff --git a/lib/rfc5849.ex b/lib/rfc5849.ex new file mode 100644 index 0000000..576bb08 --- /dev/null +++ b/lib/rfc5849.ex @@ -0,0 +1,71 @@ +defmodule Rfc5849 do + + @moduledoc """ + Documentation for Rfc5849. + """ + + defstruct [ + realm: nil, + consumer_key: nil, + consumer_secret: nil, # must be protected + token: nil, + token_secret: nil, # must be protected + callback: nil, + verifier: nil, + signature: nil, + signature_method: :hmac_sha1, + timestamp: nil, + nonce: nil, + client: %{ + method: nil, + headers: nil, + params: nil + } + request: {} + ] + + use GenServer + + @doc """ + """ + def init(args) do + {:ok, args} + end + + @doc """ + """ + def terminate(reason, state) do + end + + @doc """ + """ + def handle_call(message, from, state) do + end + + @doc """ + """ + def handle_cast({:set, {:client, param}}, state) do + case param do + {:method, method} -> :ok + {:headers, headers} -> :ok + {:params, params} -> :ok + end + end + def handle_cast({:set, {:oauth, oauth}}, state) do + case oauth do + {:consumer_key, consumer_key} -> :ok + {:consumer_secret, consumer_secret} -> :ok + {:token, token} -> :ok + {:token_secret, token_secret} -> :ok + {:signature_method, signature_method} -> :ok + end + end + def handle_cast(message, state) do + end + + @doc """ + """ + def handle_info(message, state) do + end + +end diff --git a/lib/rfc5849.ex~ b/lib/rfc5849.ex~ new file mode 100644 index 0000000..49c6784 --- /dev/null +++ b/lib/rfc5849.ex~ @@ -0,0 +1,18 @@ +defmodule Rfc5849 do + @moduledoc """ + Documentation for Rfc5849. + """ + + @doc """ + Hello world. + + ## Examples + + iex> Rfc5849.hello() + :world + + """ + def hello do + :world + end +end diff --git a/lib/rfc5849_lib.ex b/lib/rfc5849_lib.ex new file mode 100644 index 0000000..7fee699 --- /dev/null +++ b/lib/rfc5849_lib.ex @@ -0,0 +1,145 @@ +defmodule Rfc5849.Lib do + + @moduledoc """ + > request = %Rfc5849{} + > Rfc5849.Lib.request(request) + {:ok, method, target, header, params} + """ + + def encode_percent(str) do + str |> URI.encode(&URI.char_unreserved?/1) + end + + @doc """ + Add a nonce (24bytes) in Rfc5849 structure + """ + @spec nonce(Rfc5849.t()) :: Rfc5849.t() + def nonce(struct) do + nonce(struct, 24) + end + + @doc """ + Add a nonce with a defined size in Rfc5849 structure + """ + @spec nonce(Rfc5849.t(), integer()) :: Rfc5849.t() + def nonce(struct, size) do + %Rfc5849{struct|nonce: gen_nonce(size)} + end + + @doc """ + Generate a nonce with `:crypto.string_rand_bytes` function and + encode it in base64. This code is based on OAuther. + """ + defp gen_nonce(size) do + :crypto.strong_rand_bytes(size) |> Base.encode64() + end + + @doc """ + add a timestamp to Rfc5849 structure. + """ + @spec timestamp(Rfc5849.t()) :: Rfc5849.t() + def timestamp(struct) do + %Rfc5849{struct|timestamp: gen_timestamp()} + end + + @doc """ + Generate a timestamp based on `:os.timestamp()` function. This code + is based on OAuther + """ + defp gen_timestamp() do + {megasec, sec, _microsec} = :os.timestamp() + megasec * 1_000_000 + sec + end + + @doc """ + Generate a signature based on information present in the Rfc5849 + structure + """ + @spec sign(Rfc5849.t()) :: Rfc5849.t() + def sign(%Rfc5849{signature_method: signature_method} = struct) do + end + + @doc """ + Generate a signature based on information in the Rfc5849 with a + different method. + """ + @spec sign(Rfc5849.t(), atom()) :: Rfc5849.t() + def sign(struct, method) do + :ok + end + + @doc """ + Generate a plaintext signature and return it + """ + @spec gen_signature_plain(Rfc5849.t()) :: string() + defp gen_signature_plain(struct) do + :ok + end + + @doc """ + Generate hmac-sha1 signature and return it. + """ + @spec gen_signature_sha1(Rfc5849.t()) :: string() + defp gen_signature_sha1(struct) do + :ok + end + + @doc """ + Generate rsa-sha1 signature and return it. + """ + @spec gen_signature_rsa(Rfc5849.t()) :: string() + defp gen_signature_rsa(struct) do + :ok + end + + + @doc """ + Change the signature_method and regenerate the signature + """ + @spec signature_method(Rfc5849.t(), string()) :: Rfc5849.t() + def signature_method(struct, method) do + %Rfc5849{struct|signature_method: method} |> sign + end + + @doc """ + Set the callback and encode it correctly. + """ + @spec callback(Rfc5849.t(), string()) :: Rfc5849.t() + def callback(struct, callback) do + %Rfc5849{struct|callback: callback} + end + + @doc """ + Set the verifier and encode it correctly. + """ + @spec verifier(Rfc5849.t(), string() | function()) :: Rfc5849.t() + def verifier(struct, verifier) do + %Rfc5849{struct|verifier: verifier} + end + + @doc """ + Set the consumer_secret, this value can be a function returning + {:ok, secret} or a string. + """ + @spec consumer_secret(Rfc5849.t(), string() | function()) :: Rfc5849.t() + def consumer_secret(struct, secret) do + end + + @doc """ + Set the token_secret, this value can be a function returning + {:ok, secret} or a string. + """ + @spec token_secret(Rfc5849.t(), string() | function()) :: Rfc5849.t() + def token_secret(struct, secret) do + end + + @doc """ + Generate a base string based on the content of the structure + """ + def gen_base_string(struct) do + method = struct.method + host = struct.host + path = struct.path + query = struct.query + end +end diff --git a/lib/rfc5849_lib.ex~ b/lib/rfc5849_lib.ex~ new file mode 100644 index 0000000..d261fb7 --- /dev/null +++ b/lib/rfc5849_lib.ex~ @@ -0,0 +1,2 @@ +defmodule Rfc5869.Lib do +end diff --git a/mix.exs b/mix.exs new file mode 100644 index 0000000..fb73591 --- /dev/null +++ b/mix.exs @@ -0,0 +1,28 @@ +defmodule Rfc5849.MixProject do + use Mix.Project + + def project do + [ + app: :rfc5849, + version: "0.1.0", + elixir: "~> 1.8", + start_permanent: Mix.env() == :prod, + deps: deps() + ] + end + + # Run "mix help compile.app" to learn about applications. + def application do + [ + extra_applications: [:logger] + ] + end + + # Run "mix help deps" to learn about dependencies. + defp deps do + [ + # {:dep_from_hexpm, "~> 0.3.0"}, + # {:dep_from_git, git: "https://github.com/elixir-lang/my_dep.git", tag: "0.1.0"} + ] + end +end diff --git a/test/rfc5849_lib_test.exs b/test/rfc5849_lib_test.exs new file mode 100644 index 0000000..a07864a --- /dev/null +++ b/test/rfc5849_lib_test.exs @@ -0,0 +1,113 @@ +defmodule Rfc5849.LibTest do + + test "URI" do + :ok + end + + test "do percent encoding" do + original = "https://photos.example.net/initiate" + result = "https%3A%2F%2Fphotos.example.net%2Finitiate" + assert(Rfc5849.Lib.encode_percent(original) == result) + end + + test "generate signature base string" do + struct = model_base_string() + result = ["POST&http%3A%2F%2Fexample.com%2Frequest&a2%3Dr%2520b%26a3%3D2%2520q", + "%26a3%3Da%26b5%3D%253D%25253D%26c%2540%3D%26c2%3D%26oauth_consumer_", + "key%3D9djdj82h48djs9d2%26oauth_nonce%3D7d8f3e4a%26oauth_signature_m", + "ethod%3DHMAC-SHA1%26oauth_timestamp%3D137131201%26oauth_token%3Dkkk", + "9d7dh3k39sjv7"] + assert(Rfc5849.Lib.base_string(struct) == result) + end + + test "set token secret" do + struct = %Rfc5849{} + Rfc5849.Lib.token_secret(struct) + end + + test "set consumer secret" do + struct = %Rfc5849{} + Rfc5849.Lib.consumer_secret(struct) + end + + test "generate nonce" do + struct = %Rfc5849{} + Rfc5849.Lib.nonce(struct) + end + + test "generate timestamp" do + struct = %Rfc5849{} + Rfc5849.Lib.timestamp(struct) + end + + test "generate plaintext signature" do + struct = %Rfc5849{} + Rfc5849.Lib.signature(struct, :plain) + end + + test "generate hmac-sha1 signature" do + struct = %Rfc5849{} + Rfc5849.Lib.signature(struct, :hmac_sha1) + end + + test "generate rsa-sha1 signature" do + struct = %Rfc5849{} + Rfc5849.Lib.signature(struct, :rsa_sha1) + end + + defp model1 do + %Rfc5849{ + consumer_key: "dpf43f3p2l4k3l03", + consumer_secret: "kd94hf93k423kf44", + signature_method: "HMAC-SHA1", + timestamp: "137131200", + nonce: "wIjqoS", + callback: "http%3A%2F%2Fprinter.example.com%2Fready", + signature: "74KNZJeDHnMBp0EMJ9ZHt%2FXKycU%3D", + realm: "Photos" + } + end + + defp model2 do + %Rfc5849{ + consumer_key: "dpf43f3p2l4k3l03", + consumer_secret: "kd94hf93k423kf44", + token: "hh5s93j4hdidpola", + signature_method: "HMAC-SHA1", + timestamp: "137131201", + nonce: "walatlh", + verifier: "hfdp7dh39dks9884", + callback: "http%3A%2F%2Fprinter.example.com%2Fready", + signature: "gKgrFCywp7rO0OXSjdot%2FIHF7IU%3D", + realm: "Photos" + } + end + + defp model3 do + %Rfc5849{ + consumer_key: "dpf43f3p2l4k3l03", + consumer_secret: "kd94hf93k423kf44", + token: "nnch734d00sl2jdk", + signature_method: "HMAC-SHA1", + timestamp: "137131202", + nonce: "chapoH", + verifier: "hfdp7dh39dks9884", + signature: "MdpQcU8iPSUjWoN%2FUDMsK2sui9I%3D", + realm: "Photos" + } + end + + def model_base_string do + %Rfc5849{ + consumer_key: "9djdj82h48djs9d2", + consumer_secret: "kd94hf93k423kf44", + token: "kkk9d7dh3k39sjv7", + signature_method: "HMAC-SHA1", + timestamp: "137131201", + nonce: "7d8f3e4a", + signature: "MdpQcU8iPSUjWoN%2FUDMsK2sui9I%3D", + realm: "bYT5CMsGcbgUdFHObYMEfcx6bsw%3D" + } + end + +end diff --git a/test/rfc5849_lib_test.exs~ b/test/rfc5849_lib_test.exs~ new file mode 100644 index 0000000..f7e38cf --- /dev/null +++ b/test/rfc5849_lib_test.exs~ @@ -0,0 +1,11 @@ +defmodule Rfc5849.LibTest do + + test "generate signature base string" do + :ok + end + + test "generate signature" do + :ok + end + +end diff --git a/test/rfc5849_test.exs b/test/rfc5849_test.exs new file mode 100644 index 0000000..400e39a --- /dev/null +++ b/test/rfc5849_test.exs @@ -0,0 +1,8 @@ +defmodule Rfc5849Test do + use ExUnit.Case + doctest Rfc5849 + + test "greets the world" do + assert Rfc5849.hello() == :world + end +end diff --git a/test/rfc5849_test.exs~ b/test/rfc5849_test.exs~ new file mode 100644 index 0000000..400e39a --- /dev/null +++ b/test/rfc5849_test.exs~ @@ -0,0 +1,8 @@ +defmodule Rfc5849Test do + use ExUnit.Case + doctest Rfc5849 + + test "greets the world" do + assert Rfc5849.hello() == :world + end +end diff --git a/test/test_helper.exs b/test/test_helper.exs new file mode 100644 index 0000000..869559e --- /dev/null +++ b/test/test_helper.exs @@ -0,0 +1 @@ +ExUnit.start()