strongself / fabricio

Fetch Fabric.io statistics like never before
MIT License
156 stars 25 forks source link

`invalid_resource_owner` on login #66

Open LeffelMania opened 5 years ago

LeffelMania commented 5 years ago

Hello, I've had a script running nightly that pulls the crash-free rate for my apps. Two nights ago that script started failing with the following error trace:

/Library/Ruby/Gems/2.3.0/gems/fabricio-1.3.0/lib/fabricio/authorization/authorization_client.rb:47:in `perform_authorization': Incorrect authorization response: {"error"=>"invalid_resource_owner", "error_description"=>"The provided resource owner credentials are not valid, or resource owner cannot be found", "status"=>"unauthorized"} (StandardError)
    from /Library/Ruby/Gems/2.3.0/gems/fabricio-1.3.0/lib/fabricio/authorization/authorization_client.rb:22:in `auth'
    from /Library/Ruby/Gems/2.3.0/gems/fabricio-1.3.0/lib/fabricio/client/client.rb:82:in `obtain_session'
    from /Library/Ruby/Gems/2.3.0/gems/fabricio-1.3.0/lib/fabricio/client/client.rb:55:in `initialize'
    from ./bin/mobile-crashfree:15:in `new'
    from ./bin/mobile-crashfree:15:in `<main>'

My login code:

# credentials are stored in CircleCI env vars
fabric_user = ENV.fetch('FABRIC_USER')
fabric_password = ENV.fetch('FABRIC_PASSWORD')

fabric = Fabricio::Client.new do |config|
    config.username = fabric_user
    config.password = fabric_password
end

I've confirmed my credentials are still valid in Fabric, and I've tried running the script with other user credentials with the same result. Also, for what it's worth, using intentionally incorrect credentials yields the same result as the valid credentials.

I imagine this could be related to the migration to Firebase, but that's purely conjecture.

vkill commented 5 years ago

The new logic demo.

require "http"
require "nokogiri"
require "json"

class FabricioApiServcie

  attr_reader :email, :password
  attr_reader :httprb, :ssl_context

  attr_reader :csrf_token
  attr_reader :cookies
  attr_reader :developer_token

  attr_reader :current_organization
  attr_reader :current_account

  UA = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.80 Safari/537.36"

  def initialize(email, password)
    @email = email
    @password = password

    logger = Logger.new STDOUT
    logger.level = Logger::DEBUG
    logger.formatter = Logger::Formatter.new
    @httprb = HTTP.timeout(10).use(logging: {logger: logger})

    @ssl_context = OpenSSL::SSL::SSLContext.new
    @ssl_context.ssl_version = :TLSv1_2
    @ssl_context.verify_mode = OpenSSL::SSL::VERIFY_NONE

    @csrf_token = fetch_csrf_token
  end

  def fetch_growth_analytics_events_purchase(app_id, organization_id=nil)
    if current_account.nil? or current_organization.nil?
      sign_in
    end

    organization_id ||= current_organization.fetch("id")
    url = "https://fabric.io/api/v2/organizations/#{organization_id}/apps/#{app_id}/growth_analytics/events_purchase.json"

    now = Time.now.to_i
    start_time = now - (60 * 60 * 24)
    end_time = now

    params = {
      start: start_time,
      end: end_time,
      display_currency: "USD",
      transformation: "5seasonal"
    }

    res = httprb.headers({
      "X-CRASHLYTICS-DEVELOPER-TOKEN" => developer_token,
      "X-CSRF-Token" => @csrf_token,
      "X-Requested-With" => "XMLHttpRequest",
      "User-Agent" => UA,
      "Accept" => "application/json"
    }).cookies(cookies).get(url, params: params, ssl_context: ssl_context)

    raise "" if res.code != 200

    @cookies = res.cookies if !res.cookies.count.zero?

    body = JSON.parse res.body

    body
  end

  def fetch_growth_analytics_daily_new(app_id, organization_id=nil)
    if current_account.nil? or current_organization.nil?
      sign_in
    end

    organization_id ||= current_organization.fetch("id")
    url = "https://fabric.io/api/v2/organizations/#{organization_id}/apps/#{app_id}/growth_analytics/daily_new.json"

    now = Time.now.to_i
    start_time = now
    end_time = now

    params = {
      start: start_time,
      end: end_time,
      transformation: "seasonal"
    }

    res = httprb.headers({
      "X-CRASHLYTICS-DEVELOPER-TOKEN" => developer_token,
      "X-CSRF-Token" => @csrf_token,
      "X-Requested-With" => "XMLHttpRequest",
      "User-Agent" => UA,
      "Accept" => "application/json"
    }).cookies(cookies).get(url, params: params, ssl_context: ssl_context)

    raise "" if res.code != 200

    @cookies = res.cookies if !res.cookies.count.zero?

    body = JSON.parse res.body

    body
  end

  def fetch_graphql_sidebar_route
    if current_account.nil? or current_organization.nil?
      sign_in
    end

    organization_id ||= current_organization.fetch("id")
    url = "https://api-dash.fabric.io/graphql"

    params = {
      relayDebugName: "Sidebar_route"
    }

    frontend_access_token = current_account.fetch("frontend_access_token")
    json = {
      query: "query Sidebar_route {currentAccount {id,...F2}} fragment F0 on Project {name,identifier,platform,iconUrl,organization {alias,name,id},id} fragment F1 on Account {_projects4cqQId:projects(first:100) {pageInfo {hasNextPage,hasPreviousPage},edges {node {externalId,name,identifier,platform,organization {name,alias,id},id,...F0},cursor}},id} fragment F2 on Account {id,...F1}",
      variables: {}
    }

    res = httprb.headers({
      "Authorization" => "Bearer #{frontend_access_token}",
      "X-Relay-Debug-Name" => "Sidebar_route",
      "User-Agent" => UA,
      "Accept" => "application/json",
      "Content-Type" => "application/json"
    }).post(url, params: params, json: json, ssl_context: ssl_context)

    raise "" if res.code != 200

    body = JSON.parse res.body
  end

  def sign_in
    # Should new Cookie[_fabric_session] and X-CSRF-Token

    developer_token || fetch_client_boot_config_data

    url = "https://fabric.io/api/v2/session"

    res = httprb.headers({
      "X-CRASHLYTICS-DEVELOPER-TOKEN" => developer_token,
      "X-CSRF-Token" => csrf_token,
      "X-Requested-With" => "XMLHttpRequest",
      "User-Agent" => UA,
      "Accept" => "application/json"
    }).cookies(cookies).post(url, form: {email: email, password: password}, ssl_context: ssl_context)

    raise "" if res.code != 201

    body = JSON.parse res.body

    @cookies = res.cookies if !res.cookies.count.zero?

    fetch_client_boot_config_data
  end

  private

  def fetch_csrf_token
    url = "https://fabric.io/login"

    res = httprb.headers({
      "User-Agent" => UA
    }).get(url, ssl_context: ssl_context)

    raise "" if res.code != 200

    raise "" if res.cookies.count.zero?
    @cookies = res.cookies

    doc = Nokogiri::HTML res.body.to_s
    doc.search("meta[@name=csrf-token]").first.attribute("content").value
  end

  def fetch_client_boot_config_data
    url = "https://fabric.io/api/v2/client_boot/config_data"

    res = httprb.headers({
      "X-CSRF-Token" => csrf_token,
      "X-Requested-With" => "XMLHttpRequest",
      "User-Agent" => UA,
      "Accept" => "application/json"
    }).cookies(cookies).get(url, ssl_context: ssl_context)

    raise "" if res.code != 200

    @cookies = res.cookies if !res.cookies.count.zero?

    body = JSON.parse res.body

    @developer_token = body.fetch("developer_token")
    @current_account = body.fetch("current_account", nil)
    @current_organization = body.fetch("current_organization", nil)
  end

end
LeffelMania commented 5 years ago

OK so what's next step here? Getting that into a PR to update the login logic here?

Emil113 commented 5 years ago

I'm having the same issue but I'm using cURL commands and Ruby doesn't help me. Is it still possible to use the API with cURL or this change now shuts that down?

If it's still possible, what changes do I have to make to https://github.com/strongself/fabricio/blob/develop/docs/api_reference.md#post---oauthtoken

peiyun007 commented 5 years ago

I'm having the same issue.Can someone post a PR to fix this issue? please.

pabclsn commented 5 years ago

Same here 🙃

TinkZhang commented 5 years ago

me, too😭

amugana commented 5 years ago

here too.

pulyavin commented 5 years ago

Same here 😭

emilaleborn commented 5 years ago

Afaik this impacts ALL users of this project.

andreshj87 commented 5 years ago

Same here ☹️

vgoyalpunchh commented 5 years ago

Issue still exists; any resolution?

nishantt12 commented 5 years ago

This issue is still there, getting Incorrect authorization.