Ruby 和 IMAP - 使用 Oauth 2.0 访问 Office 365

Bra*_*doN 3 ruby outlook imap oauth-2.0

因此众所周知,MS 禁用了 IMAP 进行基本身份验证。

我试图弄清楚如何使用 ruby​​(而不是 ruby​​ on Rails)让 OAUTH 2.0 工作。我有 Azure APP 和所需的一切(我认为),但我找不到与 ruby​​ 和获取访问令牌相关的任何代码。

第一步已完成,下一步是获取访问令牌。 https://learn.microsoft.com/en-us/exchange/client-developer/legacy-protocols/how-to-authenticate-an-imap-pop-smtp-application-by-using-oauth

我需要阅读不同的 Outlook 邮箱。

有人可以解释一下如何做到这一点吗?

Bra*_*doN 5

我的解决方案!

我采取的步骤。

  1. 制作了一个 Azure 应用程序(“设备流”对我来说是最简单的方法)检查​​链接中的步骤。如果您想使用 IMAP,还需要更改 APP 中的一些设置。请在2:50 - 4:30 之间查看 YouTube 链接
  2. 从此链接获取邮递员请求(向下滚动一点)(单击此处
  3. 您可以从邮递员那里使用“ Device Flow”请求。
  4. 首先Device Authorization Request(您需要一个作用域和 client_id)我使用了https://outlook.office.com/IMAP.AccessAsUser.All作用域。
  5. 转到您从请求中返回的链接并输入所需的代码。
  6. 现在转到Device Access Token Request并使用上次请求中的“device_code”,并将其放在codebody 下。
  7. 你应该得到一个access_token

使用 ruby​​ 连接

require 'gmail_xoauth' # MUST HAVE! otherwise XOAUTH2 auth wont work
require 'net/imap'
    imap = Net::IMAP.new(HOST, PORT, true)
    access_token = "XXXXX"
    user_name = "email@outlook.com"
    p imap.authenticate('XOAUTH2',"#{user_name}", "#{access_token}")

    # example
    imap.list('','*').each do |folders|
      p folders
    end
Run Code Online (Sandbox Code Playgroud)

XOAUTH2 返回

#<struct Net::IMAP::TaggedResponse tag="RUBY0001", name="OK", data=#<struct Net::IMAP::ResponseText code=nil, text="AUTHENTICATE completed.">, raw_data="RUBY0001 OK AUTHENTICATE completed.\r\n
Run Code Online (Sandbox Code Playgroud)

只是为了指定

HOST = 'outlook.office365.com'
PORT = 993
Run Code Online (Sandbox Code Playgroud)

更新 25.01.2023

class Oauth2
  require 'selenium-webdriver'
  require 'webdrivers'
  require 'net/http'

  # Use: Oauth2.new.get_access_code
  # Grants access to Office 365 emails.

  def get_access_code
    p "### Access Request Started #{Time.now} ###"
    begin
      codes = device_auth_request
      authorize_device_code(codes[:user_code])
      access_code = device_access_token(codes[:device_code])
      access_code
    rescue => e
      p e
      p "Something went wrong with authorizing"
    end
  end

  def device_auth_request # Returns user_code and device_code
    url = URI('https://login.microsoftonline.com/organizations/oauth2/v2.0/devicecode')

    https = Net::HTTP.new(url.host, url.port)
    https.use_ssl = true

    request = Net::HTTP::Post.new(url)
    request.body = "client_id=YOUR_CLIENT_ID&scope=%09https%3A%2F%2Foutlook.office.com%2FIMAP.AccessAsUser.All"

    response = https.request(request)
    {
      user_code: JSON.parse(response.read_body)["user_code"],
      device_code: JSON.parse(response.read_body)["device_code"]
    }
  end

  def device_access_token(device_code)
    url = URI('https://login.microsoftonline.com/organizations/oauth2/v2.0/token')

    https = Net::HTTP.new(url.host, url.port)
    https.use_ssl = true

    request = Net::HTTP::Post.new(url)
    request.body = "grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Adevice_code&code=#{device_code}&client_id=YOUR_CLIENT_ID"

    response = https.request(request)
    JSON.parse(response.read_body)["access_token"]
  end

  def authorize_device_code(device_code)
    # SELENIUM SETUP
    driver = setup_selenium
    driver.get "https://microsoft.com/devicelogin"
    sleep(4)
    # ------------------------------------------

    # Give Access
    element = driver.find_element(:class, "form-control")
    element.send_keys(device_code)
    sleep(2)
    element = driver.find_element(:id, "idSIButton9")
    element.submit
    sleep(2)
    element = driver.find_element(:id, "i0116")
    element.send_keys("YOUR OUTLOOK ACCOUNT EMAIL")
    sleep(2)
    element = driver.find_element(:class, "button_primary")
    element.click
    sleep(2)
    element = driver.find_element(:id, "i0118")
    element.send_keys("YOUR OUTLOOK PASSWORD")
    element = driver.find_element(:class, "button_primary")
    element.click
    sleep(2)
    element = driver.find_element(:class, "button_primary")
    element.click
    sleep(2)
    # ------------------------------------------
    driver.quit
  end

  def setup_selenium
    require 'selenium-webdriver'

    # set up Selenium
    options = Selenium::WebDriver::Chrome::Options.new(
      prefs: {
        download: {
          prompt_for_download: false
        },
        plugins: {
          'always_open_pdf_externally' => true
        }
      }
    )
    options.add_argument('--headless')
    options.add_argument('--no-sandbox')
    # options.add_argument('-incognito')
    options.add_argument('disable-popup-blocking')
    Selenium::WebDriver.for :chrome, options: options
  end
end
Run Code Online (Sandbox Code Playgroud)