1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
|
defmodule AzureEx.TokenHosting do
@moduledoc false
use GenServer
require Logger
defmodule Token do
@moduledoc """
Azure API token.
"""
defstruct [:access_token, :expires_in, :created_at]
@type t :: %__MODULE__{
access_token: binary,
expires_in: integer,
created_at: NaiveDateTime.t()
}
def new(%{access_token: access_token, expires_in: expires_in}) do
created_at = NaiveDateTime.utc_now()
%__MODULE__{access_token: access_token, expires_in: expires_in, created_at: created_at}
end
end
defmodule Params do
@moduledoc """
Parameters for applying token.
"""
defstruct [:tenant, :client_id, :client_secret]
@type t :: %__MODULE__{
tenant: binary,
client_id: binary,
client_secret: binary
}
end
def start_link(opts \\ [], start_opts \\ []) do
tenant = Keyword.get(opts, :tenant)
client_id = Keyword.get(opts, :client_id)
client_secret = Keyword.get(opts, :client_secret)
params = %Params{tenant: tenant, client_id: client_id, client_secret: client_secret}
{:ok, token} = apply_token(params)
GenServer.start_link(__MODULE__, %{params: params, token: token}, start_opts)
end
@scope "https://management.azure.com//.default"
@grant_type "client_credentials"
@headers [{"Content-Type", "application/x-www-form-urlencoded"}]
@spec apply_token(Params.t()) :: {:ok, Token.t()} | {:error, any}
defp apply_token(%{tenant: tt, client_id: ci, client_secret: cs} = params) do
endpoint = "https://login.microsoftonline.com/#{tt}/oauth2/v2.0/token"
form = [
client_id: ci,
scope: @scope,
client_secret: cs,
grant_type: @grant_type
]
case HTTPoison.post(endpoint, {:form, form}, @headers) do
{:ok, %HTTPoison.Response{body: body}} ->
token = body |> Jason.decode!(keys: :atoms) |> Token.new()
{:ok, token}
{:error, %HTTPoison.Error{reason: :timeout}} ->
Logger.warn("the token request timed out, retrying...")
apply_token(params)
e ->
e
end
end
@impl true
def init(state) do
schedule_token_refresh()
{:ok, state}
end
def get_process_token() do
GenServer.call(Process.get(__MODULE__), :get)
end
def get_token(name) do
GenServer.call(__MODULE__, :get)
end
@impl true
def handle_call(:get, _from, %{token: %{access_token: at}} = state) do
{:reply, at, state}
end
@impl true
def handle_info(:refresh, %{params: params} = state) do
{:ok, token} = apply_token(params)
schedule_token_refresh()
{:noreply, %{state | token: token}}
end
@interval 1000 * 3500
defp schedule_token_refresh do
Process.send_after(__MODULE__, :refresh, @interval)
end
end
|