Working only through API (IAM tokens disabled), translate (with glossary), detect and list — working

This commit is contained in:
2019-06-29 14:48:42 +03:00
parent 3fd3f2c444
commit b3402f10d7
9 changed files with 185 additions and 59 deletions

View File

@@ -1,41 +1,2 @@
defmodule YandexTranslate do
@iam_token_url "https://iam.api.cloud.yandex.net/iam/v1/tokens"
def get_key(type, key \\ Application.get_env(:yandex_translate, :private_key))
def get_key(:public, key),
do: get_key(:private, key) |> JOSE.JWK.to_public()
def get_key(:private, key),
do: fetch_key(key)
defp fetch_key("-----BEGIN" <> _ = key_content), do: JOSE.JWK.from_pem(key_content)
defp fetch_key(key_file), do: JOSE.JWK.from_pem_file(key_file)
def get_iam_token(),
do: Application.get_all_env(:yandex_translate) |> Map.new() |> get_iam_token()
def get_iam_token(%{service_account_id: iss, private_key: private_key, authorized_key_id: kid}) do
now = DateTime.utc_now() |> DateTime.to_unix()
jwk = get_key(:private, private_key) |> JOSE.JWK.merge(%{"kid" => kid})
# JSON Web Signature (JWS)
jws = %{
"alg" => "RS256",
"typ" => "JWT",
"kid" => kid
}
# JSON Web Token (JWT)
jwt =
JOSE.JWT.from(%{
"iss" => iss,
"exp" => now + 60 * 60,
"iat" => now,
"aud" => @iam_token_url
})
_signed = JOSE.JWT.sign(jwk, jws, jwt) |> JOSE.JWS.compact() |> elem(1)
end
end

View File

@@ -0,0 +1,7 @@
defmodule YandexTranslate.Auth.ApiKey do
def get_auth_value() do
"Api-Key " <> get_api_key()
end
defp get_api_key(), do: Application.get_env(:yandex_translate, :api_key)
end

View File

@@ -0,0 +1,15 @@
defmodule YandexTranslate.Auth do
alias YandexTranslate.Auth.{ApiKey, IAMToken}
def get_config() do
Application.get_all_env(:yandex_translate) |> Map.new()
end
def authorization_value(config \\ get_config())
def authorization_value(%{private_key: _, authorized_key_id: _, service_account_id: _}),
do: IAMToken.get_auth_value()
def authorization_value(%{api_key: _}),
do: ApiKey.get_auth_value()
end

View File

@@ -0,0 +1,37 @@
defmodule YandexTranslate.Auth.IAMToken do
alias YandexTranslate.Auth.JWTTokenConfig
def get_auth_value() do
"Bearer " <> get_iam_token()
end
def get_iam_token() do
# Rewrite needed, signing process not worked. Fucking PS256 and hash length optoins on my opinion
# Pluged by old IAMToken
"CggVAgAAABoBMRKABDnO1uw1a7U-AIlRglhz8yQVQzXaKjXt1saXk5VDXHH41mlPpMpK3V9-GUgthtdposNYva9hj0DaaHE1YDZfBcZkiZU7T_45ilhFtouG5rARRP8E6GqR4-dMutrYo69XQ3Uj4-KPa0IkOa2HrCTJEePhDHGlz-iW_YnN0QXFIlKN7NMIrZQyYRJ1dvQExx29TARcy_OVWOO8234rU_Vi5BRBKXTfp7NFB589bMe6UhEoYTF-1PdOEtxDJ2DLMdqMiYw4BZnyhOwn81u8kAetHpC-R6kizR8P2omcMyZORk4zwMfgdWlDzAhrIGq4ev8aOpcQH2vH8xh8ZMTgqbRZ9rezOflEZ3bDdDVdcQWjMq4ZYABY1n278pzhucbiTZJl6uxxTXg9Ucbg7_9mpA8DOIBn72wx8dTmrJgXWTstSJ6aShpg_1VJE48gUSenO1AjXWnat9hcvhgrBGKdQFlmhqnrRTptnvhgdNctwvD_tjpjC9ZWa5pBPyE9pKdiZzYUkhSEKEpRKFAh7oGv4i0w5gCHDzQrQfrrMFxFUUPJCg8Hx0tUbLqMgF9sYXYS4vhJFWFtBl_x0dd3KMPy33Fg4oDxlbZwsolyrVI9QnT9Es2qXGp1Sw9TLMM3pAI8G8JOD7O8eTRCJalRrrFOgybbK8LjK7AKLBhI0MNtQs0A_QS-GnYKIGIxODQ5YTljMmQ4ZjRiNjQ5YTU1MzhhN2E1OTdkNDU3ELTgzugFGPSx0egFIj4KFGFqZTB1czQyMWdoNG9idmJwcW5iEg5leGNoYW5nZXJzNGRldioUYjFnbmw5N3VkNzltYXA3bmhnM2wwAjAFOAFQAVoA"
end
def get_jwt_token() do
signer = Joken.Signer.create("PS256", %{"pem" => get_rsa_key(:private)}, get_headers())
JWTTokenConfig.generate_and_sign(%{}, signer) |> elem(1)
end
defp get_headers(kid \\ Application.get_env(:yandex_translate, :authorized_key_id)),
do: %{"kid" => kid}
defp get_rsa_key(type, key \\ Application.get_env(:yandex_translate, :private_key))
defp get_rsa_key(:private, key), do: fetch_key(key)
defp get_rsa_key(:public, key) do
get_rsa_key(:private, key)
|> JOSE.JWK.from_pem()
|> JOSE.JWK.to_public()
|> JOSE.JWK.to_pem()
|> elem(1)
end
defp fetch_key("-----BEGIN" <> _ = key_content), do: key_content
defp fetch_key(key_file), do: File.read!(key_file) |> fetch_key()
end

View File

@@ -0,0 +1,15 @@
defmodule YandexTranslate.Auth.JWTTokenConfig do
@iam_token_url "https://iam.api.cloud.yandex.net/iam/v1/tokens"
use Joken.Config
@impl Joken.Config
def token_config() do
default_claims(
iss: Application.get_env(:yandex_translate, :service_account_id),
default_exp: 60 * 60,
skip: [:jti, :nbf],
aud: Application.get_env(:yandex_translate, :iam_token_url) || @iam_token_url
)
end
end

View File

@@ -0,0 +1,106 @@
defmodule YandexTranslate.Client do
@base_host "translate.api.cloud.yandex.net"
@base_path "/translate/v2/"
@api_methods %{
listLanguages: "languages",
detectLanguage: "detect",
translate: "translate"
}
@availaible_api_methods Map.keys(@api_methods)
@default_headers [{"content-type", "application/json"}]
alias YandexTranslate.Auth
defp generate_headers(headers \\ [])
defp generate_headers(header) when is_tuple(header),
do: generate_headers([header])
defp generate_headers(headers) when is_list(headers),
do: [{"authorization", Auth.authorization_value()} | @default_headers ++ headers]
defp generate_body(method, params) do
params_map = transform_body(method, params)
filter_params(method, params_map) |> Jason.encode!()
end
defp transform_body(:translate, params) do
params
|> transform_glossary()
|> transform_text()
end
# defp transform_body(:detectLanguage, params), do: params
defp transform_body(_, params), do: params
defp transform_glossary(%{glossary: glossary} = params) do
Map.put(params, :glossaryConfig, %{
glossaryData: %{
glossaryPairs: generate_glossary_pairs(glossary)
}
})
end
defp transform_glossary(result), do: result
defp generate_glossary_pairs(glossary) when is_list(glossary),
do: glossary |> Enum.map(fn {from, to} -> %{sourceText: from, translatedText: to} end)
defp transform_text(%{texts: text} = params) when is_binary(text),
do: %{params | texts: [text]}
defp transform_text(%{text: text} = params) when is_binary(text),
do: Map.put(params, :texts, [text])
defp filter_params(method, params), do: params |> Map.take(valid_argument_keys(method))
defp valid_argument_keys(:detectLanguage), do: [:text, :languageCodeHints]
defp valid_argument_keys(:translate),
do: [:texts, :sourceLanguageCode, :targetLanguageCode, :glossaryConfig]
defp valid_argument_keys(_), do: []
def call(method \\ :listLanguages, args \\ %{})
when method in @availaible_api_methods and is_map(args) do
body = generate_body(method, args)
headers = generate_headers()
method
|> fetch(headers, body)
|> parse()
end
defp parse(body), do: Jason.decode!(body)
defp fetch(method, headers, body) do
{:ok, conn} = Mint.HTTP.connect(:https, @base_host, 443)
{:ok, conn, request_ref} =
Mint.HTTP.request(
conn,
"POST",
@base_path <> @api_methods[method],
headers,
body
)
receive do
message ->
{:ok, conn, responses} = Mint.HTTP.stream(conn, message)
body =
responses
|> Enum.filter(fn
{:data, ^request_ref, _} -> true
_ -> false
end)
|> Enum.map(fn {_, _, data} -> data end)
Mint.HTTP.close(conn)
body
end
end
end

View File

@@ -1,15 +0,0 @@
# defmodule YandexTranslate.Token do
# @iam_token_url "https://iam.api.cloud.yandex.net/iam/v1/tokens"
# use Joken.Config
# @impl Joken.Config
# def token_config() do
# default_claims(
# iss: Application.get_env(:yandex_translate, :service_account_id),
# default_exp: 60 * 60,
# skip: [:jti, :nbf],
# aud: @iam_token_url
# )
# end
# end