Coder Social home page Coder Social logo

peburrows / goth Goto Github PK

View Code? Open in Web Editor NEW
275.0 7.0 104.0 296 KB

Elixir package for Oauth authentication via Google Cloud APIs

Home Page: http://hexdocs.pm/goth

License: MIT License

Elixir 100.00%
gcp google authentication google-cloud-platform elixir

goth's People

Contributors

aledsz avatar amuino avatar brosquinha avatar dazuma avatar gonzooo avatar ilyutov avatar jayjun avatar kianmeng avatar lytedev avatar martosaur avatar mcrumm avatar michaelst avatar mrdotb avatar nitinstp23 avatar p42ul avatar pacoguzman avatar paveltyk avatar pavledjo avatar peburrows avatar rajrajhans avatar rjacobskind avatar sadraskol avatar sam701 avatar savtrip avatar sgerrand avatar stratus3d avatar tazjin avatar thefirstavenger avatar wojtekmach avatar yuyabee avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

goth's Issues

JWT ArgumentError (JOSE)

Unsure what is missing here.
Looking for advice.

Deps

      {:goth, "~> 1.3-rc"},
      {:hackney, "~> 1.17"},
      {:google_api_compute, "~> 0.34.0"}

application.ex

  def start(_type, _args) do
    credentials =
      "GOOGLE_APPLICATION_CREDENTIALS_JSON"
      |> System.fetch_env!()
      |> Jason.decode!()

    source = {:service_account, credentials, []}
...
      {Goth, name: Toolbox.Goth, source: source}

Error Trace

** (Mix) Could not start application toolbox: Toolbox.Application.start(:normal, []) returned an error: shutdown: failed to start child: Goth.Server
    ** (EXIT) an exception was raised:
        ** (ArgumentError) argument error: [[], {:jose_jws, {:jose_jws_alg_rsa_pkcs1_v1_5, :RS256}, :undefined, %{"typ" => "JWT"}}, %{"aud" => "https://oauth2.googleapis.com/token,", "exp" => 1616384563, "iat" => 1616380963, "iss" => "[email protected],", "scope" => "https://www.googleapis.com/auth/cloud-platform"}]
            (jose 1.11.1) src/jwt/jose_jwt.erl:184: :jose_jwt.sign/3
            (goth 1.3.0-rc.0) lib/goth/token.ex:200: Goth.Token.jwt/2
            (goth 1.3.0-rc.0) lib/goth/token.ex:149: Goth.Token.request/1
            (goth 1.3.0-rc.0) lib/goth/token.ex:109: Goth.Token.fetch/1
            (goth 1.3.0-rc.0) lib/goth/server.ex:49: Goth.Server.init/1
      ```

Suggestion for scope parameter

The Goth's source parameters has a field scope. That is a string but actually it can contain multiple scopes separated by a single space. The code below works:

    source = {
      :service_account, credentials,
      scope: "https://www.googleapis.com/auth/gmail.readonly https://www.googleapis.com/auth/gmail.compose",
      sub: "[email protected]"
    }
    children = [{Goth, name: :my_goth, source: source}]
    {:ok, _} = Supervisor.start_link(children, strategy: :one_for_one)

It would be nice if the scope becomes scopes and expects a list of strings. Because

  • not everybody is familiar with the OAuth2 spec,
  • so we could avoid annoying typos in the string, e.g. double spaces, tabs, etc.

Bearer token construction

Hey! When requesting for the access token of a google service account JSON file I have in my project I get a strange bearer token construction. With this behaviour the token still works, but it's strange to just give this output.

def get_authorization_token(scope \\ default_scopes()) do
    case Token.for_scope(scope) do
      {:ok, %Goth.Token{token: token, type: type}} ->
        {:ok, {"authorization", "#{type} #{token}"}}

      _ ->
        {:error, :authorization_code}
    end
end

When calling the function, I get the following:

iex(1)> Google.Auth.get_authorization_token
{:ok, {"authorization", "Bearer ya29.c.Kq{token}bE...{an absurd repetition of dots πŸ˜‚}..."}}

Configurable timeout calling google oauth

Today I was having strange issues with a container running on GKE, when trying to upload an image using https://github.com/martide/arc_gcs every request was throwing this error:

  ** (MatchError) no match of right hand side value: {:error, %HTTPoison.Error{id: nil, reason: :connect_timeout}}
      (goth) lib/goth/client.ex:53: Goth.Client.get_access_token/2
      (goth) lib/goth/token.ex:94: Goth.Token.retrieve_and_store!/1
      lib/arc/storage/gcs.ex:79: Arc.Storage.GCS.get_token/0
      lib/arc/storage/gcs.ex:113: Arc.Storage.GCS.default_headers/0
      lib/arc/storage/gcs.ex:61: Arc.Storage.GCS.do_put/4
      (elixir) lib/task/supervised.ex:85: Task.Supervised.do_apply/2
      (elixir) lib/task/supervised.ex:36: Task.Supervised.reply/5
      (stdlib) proc_lib.erl:247: :proc_lib.init_p_do_apply/3

is the timeout for httpoison on goth configurable? Also, it seems pretty strange that I got timeouts since it was a request within google datacenter, after restarting the container everything worked correctly

HTTPoison error when calling `for_scope/1`

When calling Goth.Token.for_scope/1, I see

** (MatchError) no match of right hand side value: 
 {:error, %HTTPoison.Error{id: nil, reason: {:options, {:sslv3, 
     {:versions, [:"tlsv1.2", :"tlsv1.1", :tlsv1, :sslv3]}}}}}

Is this a misconfiguration on my system (Elixir 1.10.4, Linux Mint 20 x86_64)?

Do we really need Client.check_metadata_scope?

Goth.Client.check_metadata_scope checks to see if the requested scope is declared with the service account token provided by the metadata service, and errors out if not. But I don't think a straight string comparison does the right thing, because some scopes imply others.

For example, I'm trying to run an app in an App Engine VM. By default, GAE instance service accounts don't provide the https://www.googleapis.com/auth/datastore scope explicitly, but do instead provide https://www.googleapis.com/auth/cloud-platform which is kind of a catch-all that includes datastore. Since Diplomat requests https://www.googleapis.com/auth/datastore specifically, it's failing the check_metadata_scope test, even though the scope is actually valid.

I suspect we should just omit the check. If the metadata-provided token has insufficient scopes, the downstream services will check for us.

sub Support

I noticed that there are two PRs (#7, #12) on this. Before I try to work on the 3rd one, is this something this lib wants to support?

example does not work.

I tested the example shown in the readme, but even so it keeps giving error, is there any more complete documentation?

image

mix.exs

image

Error:
image

Fix required versions - poison is unresolvable

goth depends on:

  • poison 2.1 or 3.0
  • json_web_token

json_web_token depends on:

  • poison 3.1

This makes the dependency graph unresolvable. The workaround is to set "override: true" for the poison version.

Jason cannot decode json file

I have an issue regarding decoding my json file.

config :goth, json: "config/gcs.json" |> Path.expand() |> File.read!()

Screen Shot 2019-11-25 at 8 03 23 PM

is it possible to use Jason.encode_to_iodata() for the the keys to be decoded properly?

Config from ENV var in README does not behave as described

Referring to this section:
https://github.com/peburrows/goth/blob/master/README.md#L26-L29

In the README it says to pass in the path from system ENV, but looking function Goth.Config.load_and_init/1, it is triggering Goth.Config.from_json/1 function.

https://github.com/peburrows/goth/blob/master/lib/goth/config.ex#L121-L127

It will produce not valid json format from Jason.

I think it is missing a step. If :json is a path, it should load the json file before decoding with Jason. Or, it should be limited to just json string when using this config option?

Failed to retrieve project data from GCE internal metadata service

mix.exs deps looks like this:
{:goth, "~> 1.2", config: :goth, json: "file.json" |> File.read!},

I also ran export GOOGLE_APP_CREDENTIALS=... already. What's going on?

** (Mix) Could not start application goth: Goth.start(:normal, []) returned an error: shutdown: failed to start child: Goth.Config
    ** (EXIT) an exception was raised:
        ** (RuntimeError)  Failed to retrieve project data from GCE internal metadata service.
                   Either you haven't configured your GCP credentials, you aren't running on GCE, or both.
                   Please see README.md for instructions on configuring your credentials.
            (goth 1.2.0) lib/goth/config.ex:182: Goth.Config.determine_project_id/2
            (goth 1.2.0) lib/goth/config.ex:73: anonymous fn/2 in Goth.Config.load_and_init/1
            (elixir 1.10.2) lib/enum.ex:1400: anonymous fn/3 in Enum.map/2
            (stdlib 3.12.1) maps.erl:232: :maps.fold_1/3
            (elixir 1.10.2) lib/enum.ex:2127: Enum.map/2
            (goth 1.2.0) lib/goth/config.ex:71: Goth.Config.load_and_init/1
            (stdlib 3.12.1) gen_server.erl:374: :gen_server.init_it/2
            (stdlib 3.12.1) gen_server.erl:342: :gen_server.init_it/6
            (stdlib 3.12.1) proc_lib.erl:249: :proc_lib.init_p_do_apply/3

1.3-rc cannot work as expected

I switch to 1.3-rc and it didn't work as 1.2

setup

-      {:goth, "~> 1.2.0"},
+      {:goth, "~> 1.3-rc"},
+      {:hackney, "~> 1.17"},
  def start(_type, _args) do
    credentials =
      "GOOGLE_APPLICATION_CREDENTIALS" |> System.fetch_env!() |> File.read!() |> Jason.decode!()
    scopes = ["https://www.googleapis.com/auth/datastore"]
    source =
      {:service_account, credentials, scopes: scopes}

    children = [
      {Goth, name: OurApp.Goth, source: source},
    ]
  end

usage

-    {:ok, token} = Goth.Token.for_scope("https://www.googleapis.com/auth/datastore")
+    {:ok, token} = Goth.fetch(QonverApi.Goth)

Look for ADC credentials at ~/.config/gcloud/application_default_credentials.json

When you run the command gcloud auth application-default-credentials login the credentials are deposited at ~/.config/gcloud/application_default_credentials.json.

This behavior doesn't seem to be documented in the Application Default Credentials documentation. However, many of the google supported libraries seem to identify credentials at this location (such as the python api).

It would be great if this library would also support his credentials location as part of the ADC lookup procedure.

Google returning json that is not parseable

I am getting results from the token request that is not parseable by Goth.

dev.exs:
config :goth,
json: "./xxxxxxxxxxx.json" |> File.read!

Code:
{:ok, token} = Token.for_scope("[email protected]")

returns:
** (ArithmeticError) bad argument in arithmetic expression
:erlang.+(1558592257, nil)
lib/goth/token.ex:117: Goth.Token.from_response_json/3

Problem seems to be in token.ex where it is trying to parse the attributes of the returned JSON. When I inspect the JSON returned it looks like a JWT token.
%{
"id_token" => "eyJhbGciOiJSUzI1NiIsImtpZCI6IjJjM2ZhYzE2YjczZmM4NDhkNDI2ZDVhMjI1YWM4MmJjMWMwMmFlZmQiLCJ0eXAiOiJKV1QifQ.eyJpc3MiOiJodHRwczovL2FjY291bnRzLmdvb2dsZS5jb20iLCJhdWQiOiJzbmFwcHktc3RhY2tlci01NThAYXBwc3BvdC5nc2VydmljZWFjY291bnQuY29tIiwiYXpwIjoic25hcHB5LXN0YWNrZXItNTU4QGFwcHNwb3QuZ3NlcnZpY2VhY2NvdW50LmNvbSIsInN1YiI6IjEwNTA4OTc0ODgyNzQxNjE1NjU5NSIsImVtYWlsIjoic25hcHB5LXN0YWNrZXItNTU4QGFwcHNwb3QuZ3NlcnZpY2VhY2NvdW50LmNvbSIsImVtYWlsX3ZlcmlmaWVkIjp0cnVlLCJpYXQiOjE1NTg1OTIyNTcsImV4cCI6MTU1ODU5NTg1N30.Wbzpj8X5g11pfTSeFOFqqkq8HmDzO3ojVL2GXYkP4oeDACg5GlFXCxw9mJPzLs0tQgkPXPKgunJKaAlTDzSXUUa4mbz9eCKz_uFm5S90lbwfYN0zGd0wkawjXYk6pMsgAU4K9Mg4R6p4u-PdVYxBzUBKQLpirN3MmLGvlLnss6-e12njHaVjN-PVJE9RiGsq66UdPlJTz4phQH6jARD9kgvSMV6ufrlscS93_09A3JMJqKjUi1LtlduRcWEftmsze1fQ4LfAiKbJTh7IBF6h4kJQCFUkG9-yI6YO9CpFLTKAInODB-RtApSeWU9F71ibqNpax_r4UhO_oEOeG4SAqg"
}

When I decode that assuming it is a JWT token, I get:
%{
"aud" => "[email protected]",
"azp" => "[email protected]",
"email" => "[email protected]",
"email_verified" => true,
"exp" => 1558596508,
"iat" => 1558592908,
"iss" => "https://accounts.google.com",
"sub" => "105089748827416156595"
}

None of those attributes are the ones expected by goth (access_token, token_type, expires_in).

Am I doing something wrong?

Using Goth on GAE with no extra configuration - Config.get(:client_email) errors

I am trying to use Goth + Waffle + waffle_gcs on Google App Engine using the default service account, and without providing the credentials in any way through custom environmental variable or JSON file. I am using Goth 1.2, i.e. the version before redesign at the moment.

Things seem to work just fine, including uploading attachments to cloud storage, but waffle / waffle_gcs seem to fail on generating signed URL to display / download uploaded files.

I have pin pointed the error to this line over here:

https://github.com/tyler-eon/waffle_gcs/blob/master/lib/waffle/storage/google/url_v2.ex#L82

And indeed, when I try to manually execute Goth.Config.get(:client_email) I am getting :error instead of OK tuple.

I am not entirely sure where the problem is, and if that's a Goth or waffle_gcs issue so please excuse me if this is a wrong place to ask. Do you have any ideas what's wrong with my set up and if I do have to do a custom set up and give up using default service accoutnt?

Using several service accounts

Is there a way to use different service account (possibly related to completely different projects)?

In the context of my current project, I need to call the Google APIs as a dozen different users in order to retrieve data from all my projects. Does the feature already exist?

Thanks.

Unknown CA error in Goth

Hello.
I'm using goth with elixir-google-api.

Goth works in my dev environment, but It does not work in my remote docker container.

** (MatchError) no match of right hand side value: {:error, %HTTPoison.Error{id: nil, reason: {:tls_alert, {:unknown_ca, 'TLS client: In state wait_cert_cr at ssl_handshake.erl:1895 generated CLIENT ALERT: Fatal - Unknown CA\n'}}}}

It occurs when I run this function.

{:ok, token} = Goth.Token.for_scope("https://www.googleapis.com/auth/cloud-platform")

Is there any hints to resolve it?

Invalid type specification for Token.for_scope/1

Problem

The type specification for the method Token.for_scope/1 specifies that it can return an :error atom but it never does

Solution

In which cases can Token.for_scope/1 return an error? If it can actually can return an error then it should be handled, if not, the type specification should be removed

Proposed additional ways to infer project ID from the environment

Currently we infer project ID only from the Compute Engine metadata server (if available).

While I don't think there's a documented recipe for how to infer the project, the way that we have for application default credentials, there are some common practices being used (still somewhat inconsistently) by the auth libraries for other languages:

  • Honor the GOOGLE_CLOUD_PROJECT (and the legacy GCLOUD_PROJECT) environment variables, which are set in the App Engine Flexible Environment, and also often set by applications. (example from nodejs)
  • Honor DEVSHELL_PROJECT_ID environment variable, which is set by Google Cloud Shell. (example from ruby)
  • Try to grab it from the gcloud sdk if it is present.

I can provide a pull request if you agree with these.

Error when call for_scope

I'm using {:ok, goth} = Goth.Token.for_scope("https://www.googleapis.com/auth/devstorage.full_control")
But i seen a bug. Please help me

** (exit) an exception was raised:
** (UndefinedFunctionError) function :crypto.mpint/1 is undefined or private
(crypto) :crypto.mpint(19687873431742193920055970243130294467124206658682087298983794038798216955530975915146333586732314900700035611611045789143545904247799428447896803420118324471667046274052296120024954651561469080093219328190691149886440049200669636272584661976356321702914680136369124759272732574776698998044554776915282967732669956832401458331945711479325049514685797410840542208523089896590986235120222095570675226125560522026179882816977851505433864383972624362893774956738669017687990725035030542706539434148824251987312659383648295704127681204473207295376320651645980971185202320992998884262991006767805018245431366563316696488933)

Can't fetch token due to handshake error

elixir -v
Erlang/OTP 22 [erts-10.7] [source] [64-bit] [smp:12:12] [ds:12:12:10] [async-threads:1] [hipe] [dtrace]

Elixir 1.10.2 (compiled with Erlang/OTP 22)
iex(1)> Goth.Token.for_scope("https://www.googleapis.com/auth/pubsub")
[info] TLS :client: In state :certify at ssl_handshake.erl:377 generated CLIENT ALERT: Fatal - Internal Error
 - {:unexpected_error, :undef}
{:error,
 %HTTPoison.Error{
   id: nil,
   reason: {:tls_alert,
    {:internal_error,
     'TLS client: In state certify at ssl_handshake.erl:377 generated CLIENT ALERT: Fatal - Internal Error\n {unexpected_error,undef}'}}
 }}

Is it possible generate oauth token without using service account?

Hey, For working with development env goth works smooth as butter, but while deploying the app in kubernetes I already have Kubernetes Service Account binded to Google Service Account however is there a way to directly pass the Kubernetes Service Account identity to goth instead of JSON file?

As in production from security perspective using JSON file is not a good practise.

Add a way to clear or bypass the TokenStore

It would help a lot with testing if we could have a setting to ignore the TokenStore, forcing it to always get a new one.

Or, if there was a TokenStore.clear method we could at least create a test helper to clear the cache before specific tests, making them predictable.

I'm using bypass to simulate several conditions (token created, error creating token etc) and there comes a point where the token I created for a given scope is already cached from previous tests, affecting their predictability.

Add default source with ADC support

As mentioned in #77, Goth should support ADC. Additionally Goth should Just Workℒ️ out of the box without needing to provide an explicit source in the case that default credentials exist. I propose we mirror the google-auth Python library that I suspect most GCP users have experience with. They have the order of defaults to use documented here. I propose we mirror this order where Goth supports the auth methods (it's unclear the GAE method is supported).

Joken in v1.0.0 is not working in cloud

I am trying to use GCP system credentials in Google Cloud and am receiving an error ** (UndefinedFunctionError) function Joken.Signer.create/2 is undefined (module Joken.Signer is not available)

Change format of environment variable?

Hi there,

Is there any way to make it possible to accept a Base 64 encoded version of the JSON auth credentials? The huge JSON string is a bit unwieldy.

Library Structure

Goth is structured so it reads configuration at start and then attempts to verify credentials. This makes it very difficult for it to work in environments where configuration is loaded dynamically. Take a look at this article for best practices to avoid this issue and other cascading ones: https://michal.muskala.eu/2017/07/30/configuring-elixir-libraries.html

I need to be able to disable all Goth functionality for local development and use it only in production. There's really no clean way to do this in Goth currently

Tag v1.2.0

I noticed that although version 1.2.0 has been released, it hasn't been tagged yet. While GitHub provides a nice visual diff for the changes which have been merged since v1.1.0, this will change as soon as new commits get added.

Would you please tag the commit which was built and pushed to Hex.

Invalid typespec for Goth.Token.for_scope/1

Goth.Token.for_scope/1 has a typespec{:ok, t}, but I believe should be {:ok, t} | {:error, any()} as it will return HTTPoison errors.

Not the biggest of deals, but it makes downstream dialyzer a bit noisy.

Thanks for your work on this library. Its been super useful!

"Too many failed attempts" after dev computer wakes from long sleep

This is most likely an issue on my end, but the solution is not obvious to me.

I previously used Goth 1.2 without any issues. After upgrading to 1.3.0-rc.3 and configuring it exactly as the readme says, I have the following problem.

When developing, if my laptop suspends for longer than the token expiration window, when resuming, Goth will attempt to get a token from Google using the already expired cached refresh token. Google replies with a 400, which causes Goth to raise and fail. However, when the supervisor restarts it, Goth does not ask for a new token using the service account credentials, but instead uses the stale refresh token again. This repeats every second until I restart the app.

At least that's how it appears to me.

Here's what I see in my logs. Formatted for legibility:

[error] GenServer {Goth.Registry, Readyroom.Goth} terminating
** (RuntimeError) too many failed attempts to refresh, last error:
  %RuntimeError{
    message: "unexpected status 400 from Google\n\n{
        \"error\":\"invalid_grant\",
        \"error_description\":\"Invalid JWT: Token must be a short-lived token (60 minutes) and in a reasonable timeframe. Check your iat and exp values in the JWT claim.\"}\n"
    }
    (goth 1.3.0-rc.3) lib/goth/server.ex:80: Goth.Server.handle_info/2
    (stdlib 3.16.1) gen_server.erl:695: :gen_server.try_dispatch/4
    (stdlib 3.16.1) gen_server.erl:771: :gen_server.handle_msg/6
    (stdlib 3.16.1) proc_lib.erl:226: :proc_lib.init_p_do_apply/3

Last message: :refresh

State: %Goth.Server{
  http_client: {Goth.HTTPClient.Hackney, %Goth.HTTPClient.Hackney{default_opts: []}},
  name: Readyroom.Goth,
  refresh_before: 300,
  retries: 1,
  retry_after: 1000,
  source: {
    :service_account, %{
      "auth_provider_x509_cert_url" => "https://www.googleapis.com/oauth2/v1/certs",
      "auth_uri" => "https://accounts.google.com/o/oauth2/auth",
      "client_email" => "<elided>.iam.gserviceaccount.com",
      "client_id" => "<elided>",
      "client_x509_cert_url" => "https://www.googleapis.com/robot/v1/metadata/x509/<elided>.iam.gserviceaccount.com",
      "private_key" => "-----BEGIN PRIVATE KEY-----\n<elided>\n-----END PRIVATE KEY-----\n",
      "private_key_id" => "<elided>",
      "project_id" => "<elided>",
      "token_uri" => "https://oauth2.googleapis.com/token",
      "type" => "service_account"
    },
    [scopes: ["https://www.googleapis.com/auth/cloud-platform"]]
  }
}

Any assistance you can give would be appreciated.

Is this project dead?

Hello is this project dead? I'm trying to get a token for Cloud Storage but it doesn't seem to work:

{:ok, token} = Token.for_scope("https://www.googleapis.com/storage/v1/b")
** (MatchError) no match of right hand side value: :error
    (goth) lib/goth/client.ex:35: Goth.Client.jwt/2
    (goth) lib/goth/client.ex:10: Goth.Client.get_access_token/1
    (goth) lib/goth/token.ex:66: Goth.Token.retrieve_and_store!/1

Access token for Cloud Function

Is there a way to get an access token for a Google Cloud Function invocation via Goth? This is the code example, but I would prefer to rely on Goth instead:

cloud_function_url = "https://<REGION>-<PROJECT_ID>.cloudfunctions.net/<FUNCTION_NAME>"

{:ok, client_email} = Goth.Config.get(:client_email)
{:ok, private_key} = Goth.Config.get(:private_key)
signer = Joken.Signer.create("RS256", %{"pem" => private_key})

claims = %{
  target_audience: cloud_function_url,
  aud: "https://www.googleapis.com/oauth2/v4/token",
  iss: client_email,
  sub: client_email,
  iat: :os.system_time(:seconds),
  exp: :os.system_time(:seconds) + 10
}

{:ok, jwt} = Joken.Signer.sign(claims, signer)

google_api_url = "https://www.googleapis.com/oauth2/v4/token"

body =
  {:form,
   [
     grant_type: "urn:ietf:params:oauth:grant-type:jwt-bearer",
     assertion: jwt
   ]}

headers = [{"Content-Type", "application/x-www-form-urlencoded"}]
result = HTTPoison.post(google_api_url, body, headers)

{:ok, %{status_code: 200, body: "" <> _ = body}} = result
{:ok, %{"id_token" => google_token}} = Jason.decode(body)

Thank you!

just an error

{:ok, token} = Goth.Token.for_scope({
...(1)>     "[email protected]",
...(1)>     "https://www.googleapis.com/auth/cloud-platform.read-only"})
** (MatchError) no match of right hand side value: :error
    lib/goth/client.ex:44: Goth.Client.get_access_token/2 
    lib/goth/token.ex:150: Goth.Token.retrieve_and_store!/2

Im using the JSON file from GCP.

Skipping auth with PUBSUB_EMULATOR_HOST

Could authentication/credential logic be skipped when $PUBSUB_EMULATOR_HOST is defined, so that just the project_id key is included in the Goth config module? Other google cloud clients also use that environment variable: https://cloud.google.com/pubsub/docs/emulator#accessing_environment_variables

Alternatively, the Kane pubsub library could be updated to use a mock Goth module in that case.

I was running an elixir app in docker against the emulator locally, and was running into an nxdomain error when goth tried to fetch metadata before creating a topic. It took a while for me to figure out that goth uses the default location of application credentials on my machine to authenticate when :json is absent (https://github.com/peburrows/goth/blob/master/lib/goth/config.ex#L142), which was why the library worked locally but not on the docker container.

If emulator support won't be added here, it may be helpful to handle that error with a more informative message ("Credentials missing" etc.)

How to do impersonalisation?

I'd like to call Gmail API with a service account key. Goth 1.2.0 has Goth.Token.for_scope/2, where I can put the user ID in the second argument. But how can I achieve the same with Goth 1.3.0?

Here is my code

  def start_goth do
    info("Starting goth")
    credentials = Application.fetch_env!(:my_app, :gmail_token_file)
      |> Path.expand()
      |> File.read!()
      |> Jason.decode!()
    source = {:service_account, credentials, scope: "https://www.googleapis.com/auth/gmail.readonly"}
    children = [{Goth, name: :my_goth, source: source}]
    {:ok, _} = Supervisor.start_link(children, strategy: :one_for_one)

    {:ok, token} = Goth.fetch(:my_goth)
    conn = GoogleApi.Gmail.V1.Connection.new(token.token)
    warn("token: #{inspect(token)}")

    GoogleApi.Gmail.V1.Api.Users.gmail_users_messages_list(conn, "[email protected]", [maxResults: 10])
  end

When passing hackney without default_opts as http_client to Goth.Token.fetch it breaks

Goth version: 1.3-rc

When passing hackney without default_opts as http_client to Goth.Token.fetch it breaks:

This does not work:

iex(3)> Goth.Token.fetch(%{source: {:metadata, []}, http_client: {Goth.HTTPClient.Hackney, []}})
** (ArgumentError) you attempted to apply :default_opts on []. If you are using apply/3, make sure the module is an atom. If you are using the dot syntax, such as map.field or module.function(), make sure the left side of the dot is an atom or a map
    :erlang.apply([], :default_opts, [])
    (goth 1.3.0-rc.3) lib/goth/http_client/hackney.ex:40: Goth.HTTPClient.Hackney.request/6
    (goth 1.3.0-rc.3) lib/goth/token.ex:175: Goth.Token.request/1

This does work:

Goth.Token.fetch(%{source: {:metadata, []}, http_client: {Goth.HTTPClient.Hackney, %{default_opts: []}}}) 

Goth redesign

Hi, I'd like to propose a Goth redesign that replaces the global, application env-based, configuration with a more direct approach. This will in general give more control to the users (e.g. when exactly Goth starts, how it's configured, etc) and in particular it will solve one of the long standing issues of supporting multiple credentials. The new design is also more scalable as it replaces a single GenServer with process-less lookups. Finally, I'd like to also make the http client swappable and reduce the number of dependencies.

Here's the plan I have in mind:

  • (#83) Deprecate existing API
  • (#83, #91, 330fcc5) Introduce new API: Goth.start_link/1, Goth.fetch/1, Goth.Token.fetch/1
  • (#85, #91) Support fetching token from the metadata service in the new API
  • (#88) Make http client configurable
  • (#92) Use JOSE directly instead of going through Joken
  • (#93) Remove httpoison and make hackney dependency optional
  • Release v1.3.0-rc.0
  • Release v1.3.0

I decided to keep the existing code as is (with @moduledoc false, @doc false, etc) to not break existing clients.

I'll send the PR for the first two items shortly.

Disabled flag not working as expected

I want to set up a test environment where I can run tests without credentials for goth to start. In my test.exs I have:

config :goth,
  disabled: true

When I run mix test with this, I get an error:

** (Mix) Could not start application goth: Goth.start(:normal, []) returned an error: shutdown: failed to start child: Goth.Config
    ** (EXIT) an exception was raised:
        ** (RuntimeError)  Failed to retrieve project data from GCE internal metadata service.
                   Either you haven't configured your GCP credentials, you aren't running on GCE, or both.
                   Please see README.md for instructions on configuring your credentials.
            (goth) lib/goth/config.ex:139: Goth.Config.determine_project_id/2
            (goth) lib/goth/config.ex:60: Goth.Config.init/1
            (stdlib) gen_server.erl:374: :gen_server.init_it/2
            (stdlib) gen_server.erl:342: :gen_server.init_it/6
            (stdlib) proc_lib.erl:249: :proc_lib.init_p_do_apply/3

When I substitute in the valid credentials for the prod environment, the tests are able to run as expected. Is my config for disabling goth incorrect?

HTTPoison.Error when trying to retrieve token

This only appears to have cropped up recently but there is now an error when retrieving the token from Goth.

Goth.Client.get_access_token("https://www.googleapis.com/auth/pubsub")
=NOTICE REPORT==== 21-May-2020::17:29:31.899288 ===
TLS client: In state certify at tls_connection.erl:1164 generated CLIENT ALERT: Fatal - Handshake Failure - malformed_handshake_data

{:error,
%HTTPoison.Error{
  id: nil,
  reason: {:tls_alert,
   {:handshake_failure,
    'received CLIENT ALERT: Fatal - Handshake Failure - malformed_handshake_data'}}
}}

I am using erlang 22.0 along with Elixir 1.9.4 and 1.10.3.

There is a server that is using elrang 23 as a base and that also has the same issue.

TokenStore - Define init/1 required by behaviour GenServer

Getting this warning message on compilation -

warning: function init/1 required by behaviour GenServer is not implemented (in module Goth.TokenStore).

We will inject a default implementation for now:

    def init(args) do
      {:ok, args}
    end

But you want to define your own implementation that converts the arguments given to GenServer.start_link/3 to the server state

Configure when goth starts.

Hello, thanks for goth it's great. I have a question.

I made a lib that has goth as a dependancy. Let's call it gcp_secret_provider. I want to use gcp_secret_provider in another app, App A.

I have configured goth in gcp_secret_provider, and App A shouldn't know about Goth at all. But currently if I include gcp_secret_provider in App A, app A will crash on start up in :dev env or :test env because goth is incorrectly configured.

In gcp_secret_provider I have this in config/config.exs

config(:goth, json: ~S({
    "project_id": "PROJECT_ID",
    "private_key": "MOCK_KEY",
    "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/my_lovely_app.iam.gserviceaccount.com"
  }))

So I would expect when I start App A that the above config would be given to goth and it would all be fine. Do you know why it's not?

Is it something to do with when Goth's genserver(s) get started?

Thanks for any help.

Exception when HTTP POST fails

On these lines you pattern match for (only) {:ok, response}:

{:ok, response} = HTTPoison.post(url, body, headers)

{:ok, response} = HTTPoison.post(url, body, headers)

I see quite a few exceptions like this after switching networks and/or waking my Mac:

** (MatchError) no match of right hand side value: {:error, %HTTPoison.Error{id: nil, reason: :nxdomain}}
    (goth) lib/goth/client.ex:66: Goth.Client.get_access_token/3
    (goth) lib/goth/token.ex:100: Goth.Token.retrieve_and_store!/2
    ...

Is it a deliberate choice that these kinds of errors should cause exceptions, or have you just not gotten around to handling more error cases gracefully? If it’s the latter, would you be open to a PR?

:nxdomain when calling Goth.Token.for_scope/1

I have been trying to auth with Google with Goth.Token.for_scope/1, and would get this error:

** (MatchError) no match of right hand side value: {:error, %HTTPoison.Error{id: nil, reason: :nxdomain}}
    (goth) lib/goth/client.ex:64: Goth.Client.get_access_token/3
    (goth) lib/goth/token.ex:151: Goth.Token.retrieve_and_store!/2

Looking in to the code base, I found it is trying to call http://metadata.google.internal, hence the :nxdomain error. I feel I missed something during the setup, but can't figure it out. How should I understand this error?

Thanks

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    πŸ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. πŸ“ŠπŸ“ˆπŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❀️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.