JoshuaMart/ScopesExtractor

View on GitHub
libs/platforms/bugcrowd/auth.rb

Summary

Maintainability
A
2 hrs
Test Coverage
# frozen_string_literal: true

module ScopesExtractor
  # Bugcrowd
  module Bugcrowd
    LOGIN_URL = 'https://identity.bugcrowd.com/login'

    def self.authenticate(config)
      url = "#{LOGIN_URL}?user_hint=researcher&returnTo=/dashboard"
      resp = HttpClient.get(url)
      return unless resp&.status == 307

      csrf = extract_csrf(resp)

      location = resp&.headers&.[]('location')
      resp = follow_redirects(HttpClient.get(location), 302)
      return unless resp&.status == 200

      challenge = extract_challenge(resp)
      return unless challenge && csrf

      redirect_to = login(config, challenge, csrf)
      return unless redirect_to

      resp = follow_redirects(HttpClient.get(redirect_to), 302, 303, 307)
      return unless resp

      location = resp&.headers&.[]('location')
      location == '/dashboard'
    end

    def self.login(config, challenge, csrf)
      options = {
        headers: { 'X-Csrf-Token' => csrf, 'Origin' => 'https://identity.bugcrowd.com' },
        body: prepare_body(config, challenge)
      }

      resp = HttpClient.post(LOGIN_URL, options)
      return unless resp&.status == 200

      body = Parser.json_parse(resp.body)
      body['redirect_to']
    end

    def self.prepare_body(config, challenge)
      "username=#{CGI.escape(config[:email])}&password=#{CGI.escape(config[:password])}&login_challenge=#{challenge}&user_type=RESEARCHER"
    end

    def self.extract_challenge(resp)
      match = resp.body&.match(/loginChallenge": "(?<challenge>[=\w-]+)/)
      return unless match

      match[:challenge]
    end

    def self.extract_csrf(resp)
      match = resp&.headers&.[]('set-cookie')&.match(%r{csrf-token=(?<csrf>[\w+/]+)})
      return unless match

      match[:csrf]
    end

    def self.follow_redirects(response, *expected_statuses)
      while expected_statuses.include?(response&.status)
        location = response&.headers&.[]('location')
        return unless location
        return response if location == '/dashboard'

        response = HttpClient.get(location)
      end
      response
    end
  end
end