Current section

57 Versions

Jump to

Compare versions

14 files changed
+187 additions
-75 deletions
  @@ -7,14 +7,13 @@
7 7 [![Hex.pm](http://img.shields.io/hexpm/dt/pigeon.svg)](https://hex.pm/packages/pigeon)
8 8
9 9 ## Installation
10 - *Requires Elixir 1.4/OTP 19.2 or later.*
11 10
12 11 Add pigeon and kadabra as `mix.exs` dependencies:
13 12
14 13 ```elixir
15 14 def deps do
16 15 [
17 - {:pigeon, "~> 1.4.0"},
16 + {:pigeon, "~> 1.5.0"},
18 17 {:kadabra, "~> 0.4.4"}
19 18 ]
20 19 end
  @@ -267,7 +266,7 @@ more complex.
267 266 )
268 267 end
269 268
270 - defp build_config(%{"type: "fcm"} = config) do
269 + defp build_config(%{type: "fcm"} = config) do
271 270 Pigeon.FCM.Config.new(
272 271 name: Atom.to_string(config.name),
273 272 key: config.key
  @@ -2,17 +2,18 @@
2 2 {<<"build_tools">>,[<<"mix">>]}.
3 3 {<<"description">>,
4 4 <<"HTTP2-compliant wrapper for sending iOS (APNS), Android (FCM),\nand Amazon Android (ADM) push notifications.">>}.
5 - {<<"elixir">>,<<"~> 1.4">>}.
5 + {<<"elixir">>,<<"~> 1.6">>}.
6 6 {<<"files">>,
7 - [<<"lib">>,<<"lib/pigeon">>,<<"lib/pigeon/configurable.ex">>,
8 - <<"lib/pigeon/encodable.ex">>,<<"lib/pigeon/connection">>,
7 + [<<"lib">>,<<"lib/pigeon">>,<<"lib/pigeon/config_error.ex">>,
8 + <<"lib/pigeon/configurable.ex">>,<<"lib/pigeon/encodable.ex">>,
9 + <<"lib/pigeon/connection">>,
9 10 <<"lib/pigeon/connection/notification_queue.ex">>,
10 11 <<"lib/pigeon/worker.ex">>,<<"lib/pigeon/apns">>,
11 12 <<"lib/pigeon/apns/notification.ex">>,<<"lib/pigeon/apns/config.ex">>,
12 13 <<"lib/pigeon/apns/error.ex">>,<<"lib/pigeon/apns/token.ex">>,
13 14 <<"lib/pigeon/apns/config_parser.ex">>,<<"lib/pigeon/apns/jwt_config.ex">>,
14 - <<"lib/pigeon/apns/shared.ex">>,<<"lib/pigeon/tasks.ex">>,
15 - <<"lib/pigeon/fcm.ex">>,<<"lib/pigeon/http2">>,
15 + <<"lib/pigeon/apns/shared.ex">>,<<"lib/pigeon/notification">>,
16 + <<"lib/pigeon/tasks.ex">>,<<"lib/pigeon/fcm.ex">>,<<"lib/pigeon/http2">>,
16 17 <<"lib/pigeon/http2/client.ex">>,<<"lib/pigeon/http2/stream.ex">>,
17 18 <<"lib/pigeon/http2/kadabra.ex">>,<<"lib/pigeon/adm">>,
18 19 <<"lib/pigeon/adm/notification.ex">>,<<"lib/pigeon/adm/worker.ex">>,
  @@ -20,8 +21,8 @@
20 21 <<"lib/pigeon/fcm">>,<<"lib/pigeon/fcm/notification.ex">>,
21 22 <<"lib/pigeon/fcm/result_parser.ex">>,<<"lib/pigeon/fcm/config.ex">>,
22 23 <<"lib/pigeon/adm.ex">>,<<"lib/pigeon/connection.ex">>,
23 - <<"lib/pigeon/apns.ex">>,<<"lib/pigeon/exceptions.ex">>,<<"lib/pigeon.ex">>,
24 - <<"mix.exs">>,<<"README.md">>,<<"LICENSE">>]}.
24 + <<"lib/pigeon/apns.ex">>,<<"lib/pigeon.ex">>,<<"mix.exs">>,<<"README.md">>,
25 + <<"LICENSE">>]}.
25 26 {<<"licenses">>,[<<"MIT">>]}.
26 27 {<<"links">>,[{<<"GitHub">>,<<"https://github.com/codedge-llc/pigeon">>}]}.
27 28 {<<"name">>,<<"pigeon">>}.
  @@ -30,7 +31,7 @@
30 31 {<<"name">>,<<"poison">>},
31 32 {<<"optional">>,false},
32 33 {<<"repository">>,<<"hexpm">>},
33 - {<<"requirement">>,<<"~> 2.0 or ~> 3.0">>}],
34 + {<<"requirement">>,<<"~> 2.0 or ~> 3.0 or ~> 4.0">>}],
34 35 [{<<"app">>,<<"httpoison">>},
35 36 {<<"name">>,<<"httpoison">>},
36 37 {<<"optional">>,false},
  @@ -40,7 +41,7 @@
40 41 {<<"name">>,<<"gen_stage">>},
41 42 {<<"optional">>,false},
42 43 {<<"repository">>,<<"hexpm">>},
43 - {<<"requirement">>,<<"~> 0.12">>}],
44 + {<<"requirement">>,<<"~> 0.12 or ~> 1.0">>}],
44 45 [{<<"app">>,<<"joken">>},
45 46 {<<"name">>,<<"joken">>},
46 47 {<<"optional">>,false},
  @@ -51,4 +52,4 @@
51 52 {<<"optional">>,true},
52 53 {<<"repository">>,<<"hexpm">>},
53 54 {<<"requirement">>,<<"~> 0.4.3">>}]]}.
54 - {<<"version">>,<<"1.4.0">>}.
55 + {<<"version">>,<<"1.5.0">>}.
  @@ -55,4 +55,25 @@ defmodule Pigeon.ADM.Config do
55 55 end
56 56
57 57 defp valid_item?(item), do: is_binary(item) and String.length(item) > 0
58 +
59 + @spec validate!(any) :: :ok
60 + def validate!(config) do
61 + if valid?(config) do
62 + :ok
63 + else
64 + raise Pigeon.ConfigError,
65 + reason: "attempted to start without valid client id and secret",
66 + config: redact(config)
67 + end
68 + end
69 +
70 + defp redact(config) do
71 + [:client_id, :client_secret]
72 + |> Enum.reduce(config, fn k, acc ->
73 + case Map.get(acc, k) do
74 + nil -> acc
75 + _ -> Map.put(acc, k, "[FILTERED]")
76 + end
77 + end)
78 + end
58 79 end
  @@ -25,6 +25,7 @@ defmodule Pigeon.ADM.Worker do
25 25 def init({:ok, config}), do: initialize_worker(config)
26 26
27 27 def initialize_worker(config) do
28 + Pigeon.ADM.Config.validate!(config)
28 29 {:ok,
29 30 %{
30 31 config: config,
  @@ -96,12 +96,14 @@ defmodule Pigeon.APNS.Config do
96 96 iex> Pigeon.APNS.Config.new(
97 97 ...> name: :test,
98 98 ...> mode: :prod,
99 - ...> cert: "test_cert.pem",
100 - ...> key: "test_key.pem",
99 + ...> cert: "test/support/test_cert.pem-mock",
100 + ...> key: "test/support/test_key.pem-mock",
101 101 ...> port: 2197,
102 102 ...> ping_period: 300_000
103 103 ...> )
104 104 %Pigeon.APNS.Config{uri: "api.push.apple.com", name: :test,
105 + certfile: Path.expand("test/support/test_cert.pem-mock"),
106 + keyfile: Path.expand("test/support/test_key.pem-mock"),
105 107 ping_period: 300000, port: 2197, reconnect: false}
106 108
107 109 iex> config = Pigeon.APNS.Config.new(:apns_default)
  @@ -114,17 +116,37 @@ defmodule Pigeon.APNS.Config do
114 116 %__MODULE__{
115 117 name: opts[:name],
116 118 reconnect: Keyword.get(opts, :reconnect, false),
117 - cert: ConfigParser.cert(opts[:cert]),
119 + cert: cert(opts[:cert]),
118 120 certfile: ConfigParser.file_path(opts[:cert]),
119 - key: ConfigParser.key(opts[:key]),
121 + key: key(opts[:key]),
120 122 keyfile: ConfigParser.file_path(opts[:key]),
121 123 uri: Keyword.get(opts, :uri, ConfigParser.uri_for_mode(opts[:mode])),
122 124 port: Keyword.get(opts, :port, 443),
123 125 ping_period: Keyword.get(opts, :ping_period, 600_000)
124 126 }
127 + |> ConfigParser.strip_errors(:cert, :certfile)
128 + |> ConfigParser.strip_errors(:key, :keyfile)
125 129 end
126 130
127 131 def new(name) when is_atom(name), do: ConfigParser.parse(name)
132 +
133 + defp cert(bin) when is_binary(bin) do
134 + case :public_key.pem_decode(bin) do
135 + [{:Certificate, cert, _}] -> cert
136 + _ -> {:error, {:invalid, bin}}
137 + end
138 + end
139 +
140 + defp cert(other), do: {:error, {:invalid, other}}
141 +
142 + defp key(bin) when is_binary(bin) do
143 + case :public_key.pem_decode(bin) do
144 + [{:RSAPrivateKey, key, _}] -> {:RSAPrivateKey, key}
145 + _ -> {:error, {:invalid, bin}}
146 + end
147 + end
148 +
149 + defp key(other), do: {:error, {:invalid, other}}
128 150 end
129 151
130 152 defimpl Pigeon.Configurable, for: Pigeon.APNS.Config do
  @@ -164,12 +186,32 @@ defimpl Pigeon.Configurable, for: Pigeon.APNS.Config do
164 186
165 187 defdelegate close(config), to: Shared
166 188
167 - def connect_socket_options(%{cert: nil, certfile: nil}) do
168 - {:error, :invalid_config}
189 + def validate!(config) do
190 + case config do
191 + %{cert: {:error, _}, certfile: {:error, _}} ->
192 + raise Pigeon.ConfigError,
193 + reason: "attempted to start without valid certificate",
194 + config: redact(config)
195 +
196 + %{key: {:error, _}, keyfile: {:error, _}} ->
197 + raise Pigeon.ConfigError,
198 + reason: "attempted to start without valid key",
199 + config: redact(config)
200 +
201 + _ ->
202 + :ok
203 + end
169 204 end
170 205
171 - def connect_socket_options(%{key: nil, keyfile: nil}) do
172 - {:error, :invalid_config}
206 + defp redact(config) do
207 + [:cert, :key]
208 + |> Enum.reduce(config, fn key, acc ->
209 + case Map.get(acc, key) do
210 + bin when is_binary(bin) -> Map.put(acc, key, "[FILTERED]")
211 + {:RSAPrivateKey, _bin} -> Map.put(acc, key, "[FILTERED]")
212 + _ -> acc
213 + end
214 + end)
173 215 end
174 216
175 217 def connect_socket_options(config) do
Loading more files…