diff --git a/README.md b/README.md index 9eb37ff..d4e8371 100644 --- a/README.md +++ b/README.md @@ -43,6 +43,7 @@ Add it to your mix dependencies: ```elixir defp deps do [ + {:goth, "~> 1.3"}, {:waffle_gcs, "~> 0.2"} ] end @@ -56,7 +57,8 @@ All configuration values are stored under the `:waffle` app key. E.g. config :waffle, storage: Waffle.Storage.Google.CloudStorage, bucket: "gcs-bucket", - storage_dir: "uploads/waffle" + storage_dir: "uploads/waffle", + token_fetcher: MyApp.WaffleTokenFetcher ``` **Note**: a valid bucket name is a required config. This can either be a @@ -66,32 +68,40 @@ module (e.g. `def bucket(), do: "my-bucket"`). Authentication is done through Goth which requires credentials (https://github.com/peburrows/goth#installation). -### Custom Token Generation ### +### Goth >= 1.3 ### -By default, the credentials provided to Goth will be used to generate tokens. -If you have multiple sets of credentials in Goth or otherwise need more control -over token generation, you can define your own module: +For newer versions of Goth, you **must** provide the token fetcher module, for example: ```elixir -defmodule MyCredentials do +defmodule MyApp.WaffleTokenFetcher do @behaviour Waffle.Storage.Google.Token.Fetcher - @impl Waffle.Storage.Google.Token.Fetcher - def get_token(scopes) when is_list(scopes), do: get_token(Enum.join(scopes, " ")) - @impl Waffle.Storage.Google.Token.Fetcher - def get_token(scope) when is_binary(scope) do - {:ok, token} = Goth.Token.for_scope({"my-user@my-gcs-account.com", scope}) - token.token + + @impl true + def get_token(_scope) when is_binary(_scope) do + Goth.fetch!(MyApp.Goth).token end end ``` -And configure it to use this new module instead of the default token generation: +And configure it to use your module: + +```elixir +config :waffle, + storage: Waffle.Storage.Google.CloudStorage, + bucket: "gcs-bucket-name", + token_fetcher: MyApp.WaffleTokenFetcher +``` + +### Goth < 1.3 ### + +You can use the Goth 1.1 token fetcher that reads the default credentials from +Goth. ```elixir config :waffle, storage: Waffle.Storage.Google.CloudStorage, bucket: "gcs-bucket-name", - token_fetcher: MyCredentials + token_fetcher: Waffle.Storage.Googke.Token.Fetcher.GothTokenFetcher ``` ## URL Signing diff --git a/config/config.exs b/config/config.exs index 731eebd..ffae427 100644 --- a/config/config.exs +++ b/config/config.exs @@ -2,6 +2,7 @@ import Config config :waffle, bucket: {:system, "WAFFLE_BUCKET"}, - storage: Waffle.Storage.Google.CloudStorage + storage: Waffle.Storage.Google.CloudStorage, + token_fetcher: Waffle.GothTokenFetcher config :goth, json: {:system, "GCP_CREDENTIALS"} diff --git a/lib/waffle/storage/google/cloud_storage.ex b/lib/waffle/storage/google/cloud_storage.ex index 0b5806a..564a020 100644 --- a/lib/waffle/storage/google/cloud_storage.ex +++ b/lib/waffle/storage/google/cloud_storage.ex @@ -78,10 +78,10 @@ defmodule Waffle.Storage.Google.CloudStorage do """ @spec conn(String.t()) :: Tesla.Env.client() def conn(scope \\ @full_control_scope) do - token_store = - Application.get_env(:waffle, :token_fetcher, Waffle.Storage.Google.Token.DefaultFetcher) + token_store = Application.fetch_env!(:waffle, :token_fetcher) - token_store.get_token(scope) + scope + |> token_store.get_token() |> Connection.new() end diff --git a/lib/waffle/storage/google/token/default_fetcher.ex b/lib/waffle/storage/google/token/default_fetcher.ex deleted file mode 100644 index 49da6e1..0000000 --- a/lib/waffle/storage/google/token/default_fetcher.ex +++ /dev/null @@ -1,9 +0,0 @@ -defmodule Waffle.Storage.Google.Token.DefaultFetcher do - @behaviour Waffle.Storage.Google.Token.Fetcher - - @impl Waffle.Storage.Google.Token.Fetcher - def get_token(scope) do - {:ok, token} = Goth.Token.for_scope(scope) - token.token - end -end diff --git a/lib/waffle/storage/google/token/goth_token_fetcher.ex b/lib/waffle/storage/google/token/goth_token_fetcher.ex new file mode 100644 index 0000000..f118992 --- /dev/null +++ b/lib/waffle/storage/google/token/goth_token_fetcher.ex @@ -0,0 +1,15 @@ +if Version.match?(to_string(Application.spec(:goth, :vsn)), "< 1.3.0") do + defmodule Waffle.Storage.Google.Token.GothTokenFetcher do + @moduledoc """ + A token fetcher that uses Goth 1.1 to fetch a token for a given scope. + This module would raise an runtime exception if Goth 1.3 or newer is available. + """ + @behaviour Waffle.Storage.Google.Token.Fetcher + + @impl true + def get_token(scope) when is_binary(scope) do + {:ok, token} = Goth.Token.for_scope(scope) + token.token + end + end +end diff --git a/mix.exs b/mix.exs index a07160e..e7a0e7c 100644 --- a/mix.exs +++ b/mix.exs @@ -40,6 +40,7 @@ defmodule Waffle.Storage.Google.CloudStorage.MixProject do {:waffle, "~> 1.1"}, {:goth, "~> 1.1"}, {:google_api_storage, "~> 0.34"}, + {:httpoison, "~> 2.2", only: :test}, {:ex_doc, ">= 0.0.0", only: :dev, runtime: false}, {:dialyxir, "~> 1.0", only: [:dev], runtime: false} ] diff --git a/mix.lock b/mix.lock index 52654b1..9a8dbc5 100644 --- a/mix.lock +++ b/mix.lock @@ -1,28 +1,35 @@ %{ + "castore": {:hex, :castore, "0.1.22", "4127549e411bedd012ca3a308dede574f43819fe9394254ca55ab4895abfa1a2", [:mix], [], "hexpm", "c17576df47eb5aa1ee40cc4134316a99f5cad3e215d5c77b8dd3cfef12a22cac"}, "certifi": {:hex, :certifi, "2.12.0", "2d1cca2ec95f59643862af91f001478c9863c2ac9cb6e2f89780bfd8de987329", [:rebar3], [], "hexpm", "ee68d85df22e554040cdb4be100f33873ac6051387baf6a8f6ce82272340ff1c"}, "dialyxir": {:hex, :dialyxir, "1.4.3", "edd0124f358f0b9e95bfe53a9fcf806d615d8f838e2202a9f430d59566b6b53b", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "bf2cfb75cd5c5006bec30141b131663299c661a864ec7fbbc72dfa557487a986"}, "earmark_parser": {:hex, :earmark_parser, "1.4.39", "424642f8335b05bb9eb611aa1564c148a8ee35c9c8a8bba6e129d51a3e3c6769", [:mix], [], "hexpm", "06553a88d1f1846da9ef066b87b57c6f605552cfbe40d20bd8d59cc6bde41944"}, "erlex": {:hex, :erlex, "0.2.6", "c7987d15e899c7a2f34f5420d2a2ea0d659682c06ac607572df55a43753aa12e", [:mix], [], "hexpm", "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75"}, "ex_doc": {:hex, :ex_doc, "0.31.1", "8a2355ac42b1cc7b2379da9e40243f2670143721dd50748bf6c3b1184dae2089", [:mix], [{:earmark_parser, "~> 1.4.39", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.1", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "3178c3a407c557d8343479e1ff117a96fd31bafe52a039079593fb0524ef61b0"}, + "finch": {:hex, :finch, "0.18.0", "944ac7d34d0bd2ac8998f79f7a811b21d87d911e77a786bc5810adb75632ada4", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: false]}, {:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.3", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 0.2.6 or ~> 1.0", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "69f5045b042e531e53edc2574f15e25e735b522c37e2ddb766e15b979e03aa65"}, "google_api_storage": {:hex, :google_api_storage, "0.34.0", "4811461a5065f8a5d59a7fe71efd9c491ba593d20743f0d6f4714153c1dd575f", [:mix], [{:google_gax, "~> 0.4", [hex: :google_gax, repo: "hexpm", optional: false]}], "hexpm", "b343f2f1688acd2e8a2302bed399a24d93626f8e8c809b358214bb0fb7d2669a"}, "google_gax": {:hex, :google_gax, "0.4.1", "310105070626013712c56f8007b6ff7b4ead02ecad1efe7888350c6eaba52783", [:mix], [{:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:poison, ">= 3.0.0 and < 5.0.0", [hex: :poison, repo: "hexpm", optional: false]}, {:tesla, "~> 1.2", [hex: :tesla, repo: "hexpm", optional: false]}], "hexpm", "aef7dce7e04840c0e611f962475e3223d27d50ebd5e7d8e9e963c5e9e3b1ca79"}, - "goth": {:hex, :goth, "1.2.0", "92d6d926065a72a7e0da8818cc3a133229b56edf378022c00d9886c4125ce769", [:mix], [{:httpoison, "~> 0.11 or ~> 1.0", [hex: :httpoison, repo: "hexpm", optional: false]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:joken, "~> 2.0", [hex: :joken, repo: "hexpm", optional: false]}], "hexpm", "4974932ab3b782c99a6fdeb0b968ddd61436ef14de5862bd6bb0227386c63b26"}, + "goth": {:hex, :goth, "1.4.3", "80e86225ae174844e6a77b61982fafadfc715277db465e0956348d8bdd56b231", [:mix], [{:finch, "~> 0.17", [hex: :finch, repo: "hexpm", optional: false]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:jose, "~> 1.11", [hex: :jose, repo: "hexpm", optional: false]}], "hexpm", "34e2012ed1af2fe2446083da60a988fd9829943d30e4447832646c3a6863a7e6"}, "hackney": {:hex, :hackney, "1.20.1", "8d97aec62ddddd757d128bfd1df6c5861093419f8f7a4223823537bad5d064e2", [:rebar3], [{:certifi, "~> 2.12.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "~> 6.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "~> 1.0.0", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~> 1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.4.1", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~> 1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "fe9094e5f1a2a2c0a7d10918fee36bfec0ec2a979994cff8cfe8058cd9af38e3"}, - "httpoison": {:hex, :httpoison, "1.7.0", "abba7d086233c2d8574726227b6c2c4f6e53c4deae7fe5f6de531162ce9929a0", [:mix], [{:hackney, "~> 1.16", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "975cc87c845a103d3d1ea1ccfd68a2700c211a434d8428b10c323dc95dc5b980"}, + "hpax": {:hex, :hpax, "0.1.2", "09a75600d9d8bbd064cdd741f21fc06fc1f4cf3d0fcc335e5aa19be1a7235c84", [:mix], [], "hexpm", "2c87843d5a23f5f16748ebe77969880e29809580efdaccd615cd3bed628a8c13"}, + "httpoison": {:hex, :httpoison, "2.2.1", "87b7ed6d95db0389f7df02779644171d7319d319178f6680438167d7b69b1f3d", [:mix], [{:hackney, "~> 1.17", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "51364e6d2f429d80e14fe4b5f8e39719cacd03eb3f9a9286e61e216feac2d2df"}, "idna": {:hex, :idna, "6.1.1", "8a63070e9f7d0c62eb9d9fcb360a7de382448200fbbd1b106cc96d3d8099df8d", [:rebar3], [{:unicode_util_compat, "~>0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "92376eb7894412ed19ac475e4a86f7b413c1b9fbb5bd16dccd57934157944cea"}, - "jason": {:hex, :jason, "1.4.0", "e855647bc964a44e2f67df589ccf49105ae039d4179db7f6271dfd3843dc27e6", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "79a3791085b2a0f743ca04cec0f7be26443738779d09302e01318f97bdb82121"}, + "jason": {:hex, :jason, "1.4.1", "af1504e35f629ddcdd6addb3513c3853991f694921b1b9368b0bd32beb9f1b63", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "fbb01ecdfd565b56261302f7e1fcc27c4fb8f32d56eab74db621fc154604a7a1"}, "joken": {:hex, :joken, "2.3.0", "62a979c46f2c81dcb8ddc9150453b60d3757d1ac393c72bb20fc50a7b0827dc6", [:mix], [{:jose, "~> 1.10", [hex: :jose, repo: "hexpm", optional: false]}], "hexpm", "57b263a79c0ec5d536ac02d569c01e6b4de91bd1cb825625fe90eab4feb7bc1e"}, - "jose": {:hex, :jose, "1.10.1", "16d8e460dae7203c6d1efa3f277e25b5af8b659febfc2f2eb4bacf87f128b80a", [:mix, :rebar3], [], "hexpm", "3c7ddc8a9394b92891db7c2771da94bf819834a1a4c92e30857b7d582e2f8257"}, + "jose": {:hex, :jose, "1.11.6", "613fda82552128aa6fb804682e3a616f4bc15565a048dabd05b1ebd5827ed965", [:mix, :rebar3], [], "hexpm", "6275cb75504f9c1e60eeacb771adfeee4905a9e182103aa59b53fed651ff9738"}, "makeup": {:hex, :makeup, "1.1.1", "fa0bc768698053b2b3869fa8a62616501ff9d11a562f3ce39580d60860c3a55e", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "5dc62fbdd0de44de194898b6710692490be74baa02d9d108bc29f007783b0b48"}, "makeup_elixir": {:hex, :makeup_elixir, "0.16.1", "cc9e3ca312f1cfeccc572b37a09980287e243648108384b97ff2b76e505c3555", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "e127a341ad1b209bd80f7bd1620a15693a9908ed780c3b763bccf7d200c767c6"}, "makeup_erlang": {:hex, :makeup_erlang, "0.1.4", "29563475afa9b8a2add1b7a9c8fb68d06ca7737648f28398e04461f008b69521", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "f4ed47ecda66de70dd817698a703f8816daa91272e7e45812469498614ae8b29"}, "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"}, "mime": {:hex, :mime, "1.6.0", "dabde576a497cef4bbdd60aceee8160e02a6c89250d6c0b29e56c0dfb00db3d2", [:mix], [], "hexpm", "31a1a8613f8321143dde1dafc36006a17d28d02bdfecb9e95a880fa7aabd19a7"}, "mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm", "f278585650aa581986264638ebf698f8bb19df297f66ad91b18910dfc6e19323"}, + "mint": {:hex, :mint, "1.5.2", "4805e059f96028948870d23d7783613b7e6b0e2fb4e98d720383852a760067fd", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "d77d9e9ce4eb35941907f1d3df38d8f750c357865353e21d335bdcdf6d892a02"}, + "nimble_options": {:hex, :nimble_options, "1.1.0", "3b31a57ede9cb1502071fade751ab0c7b8dbe75a9a4c2b5bbb0943a690b63172", [:mix], [], "hexpm", "8bbbb3941af3ca9acc7835f5655ea062111c9c27bcac53e004460dfd19008a99"}, "nimble_parsec": {:hex, :nimble_parsec, "1.4.0", "51f9b613ea62cfa97b25ccc2c1b4216e81df970acd8e16e8d1bdc58fef21370d", [:mix], [], "hexpm", "9c565862810fb383e9838c1dd2d7d2c437b3d13b267414ba6af33e50d2d1cf28"}, + "nimble_pool": {:hex, :nimble_pool, "1.0.0", "5eb82705d138f4dd4423f69ceb19ac667b3b492ae570c9f5c900bb3d2f50a847", [:mix], [], "hexpm", "80be3b882d2d351882256087078e1b1952a28bf98d0a287be87e4a24a710b67a"}, "parse_trans": {:hex, :parse_trans, "3.4.1", "6e6aa8167cb44cc8f39441d05193be6e6f4e7c2946cb2759f015f8c56b76e5ff", [:rebar3], [], "hexpm", "620a406ce75dada827b82e453c19cf06776be266f5a67cff34e1ef2cbb60e49a"}, "poison": {:hex, :poison, "4.0.1", "bcb755a16fac91cad79bfe9fc3585bb07b9331e50cfe3420a24bcc2d735709ae", [:mix], [], "hexpm", "ba8836feea4b394bb718a161fc59a288fe0109b5006d6bdf97b6badfcf6f0f25"}, "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.7", "354c321cf377240c7b8716899e182ce4890c5938111a1296add3ec74cf1715df", [:make, :mix, :rebar3], [], "hexpm", "fe4c190e8f37401d30167c8c405eda19469f34577987c76dde613e838bbc67f8"}, + "telemetry": {:hex, :telemetry, "1.2.1", "68fdfe8d8f05a8428483a97d7aab2f268aaff24b49e0f599faa091f1d4e7f61c", [:rebar3], [], "hexpm", "dad9ce9d8effc621708f99eac538ef1cbe05d6a874dd741de2e689c47feafed5"}, "tesla": {:hex, :tesla, "1.5.0", "7ee3616be87024a2b7231ae14474310c9b999c3abb1f4f8dbc70f86bd9678eef", [:mix], [{:castore, "~> 0.1", [hex: :castore, repo: "hexpm", optional: true]}, {:exjsx, ">= 3.0.0", [hex: :exjsx, repo: "hexpm", optional: true]}, {:finch, "~> 0.13", [hex: :finch, repo: "hexpm", optional: true]}, {:fuse, "~> 2.4", [hex: :fuse, repo: "hexpm", optional: true]}, {:gun, "~> 1.3", [hex: :gun, repo: "hexpm", optional: true]}, {:hackney, "~> 1.6", [hex: :hackney, repo: "hexpm", optional: true]}, {:ibrowse, "4.4.0", [hex: :ibrowse, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: true]}, {:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.0", [hex: :mint, repo: "hexpm", optional: true]}, {:msgpax, "~> 2.3", [hex: :msgpax, repo: "hexpm", optional: true]}, {:poison, ">= 1.0.0", [hex: :poison, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "1d0385e41fbd76af3961809088aef15dec4c2fdaab97b1c93c6484cb3695a122"}, "unicode_util_compat": {:hex, :unicode_util_compat, "0.7.0", "bc84380c9ab48177092f43ac89e4dfa2c6d62b40b8bd132b1059ecc7232f9a78", [:rebar3], [], "hexpm", "25eee6d67df61960cf6a794239566599b09e17e668d3700247bc498638152521"}, "waffle": {:hex, :waffle, "1.1.8", "9257275a10e7b58f968bf891d2433ecaeced801edc064b3c100dc510848b9760", [:mix], [{:ex_aws, "~> 2.1", [hex: :ex_aws, repo: "hexpm", optional: true]}, {:ex_aws_s3, "~> 2.1", [hex: :ex_aws_s3, repo: "hexpm", optional: true]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: false]}, {:sweet_xml, "~> 0.6", [hex: :sweet_xml, repo: "hexpm", optional: true]}], "hexpm", "30f1b00998ddb626d98f014bb7606173ff18610b1e89431ff84442dc1c94c6cd"}, diff --git a/test/support/goth_token_fetcher.ex b/test/support/goth_token_fetcher.ex new file mode 100644 index 0000000..288390c --- /dev/null +++ b/test/support/goth_token_fetcher.ex @@ -0,0 +1,11 @@ +defmodule Waffle.GothTokenFetcher do + @moduledoc """ + An example of fetching token for Goth >= 1.3.0 + """ + @behaviour Waffle.Storage.Google.Token.Fetcher + + @impl true + def get_token(_scope) do + Goth.fetch!(Waffle.Goth).token + end +end diff --git a/test/test_helper.exs b/test/test_helper.exs index 24689bd..dfae0e2 100644 --- a/test/test_helper.exs +++ b/test/test_helper.exs @@ -4,3 +4,12 @@ ExUnit.start() unless Version.compare(System.version(), "1.8.0") == :lt do ExUnit.after_suite(&Cleanup.execute/1) end + +credentials = + "GCP_CREDENTIALS" + |> System.fetch_env!() + |> Jason.decode!() + +source = {:service_account, credentials} + +Goth.start_link(name: Waffle.Goth, source: source)