mail_xoauth2 1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 27a0ef9770c2172e574541dbb950bc4f7b074163a42903acfeadf2570c95609c
4
+ data.tar.gz: 499e917ef4edf4e6f4f3a3f8f51f5305f60c1d67bd6706ce94f8644ef331a2ff
5
+ SHA512:
6
+ metadata.gz: b0dcd0f7f2c8fbd565b680866e7a29e72e9c8acb3891be7a36e521913dd53ff392a9490d5c3ef94354324f127efe0e78c881ddc9294ecd65cadf56529c7c0ba0
7
+ data.tar.gz: 9a4f954d7350be1d603de16aa1cd813c2046d1516ffe99cb7b4ee13b5e4bece972a0fcd9b778777f166e39a44fc71a5049f755bd1acaecf1a201b6db66518d14
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2018 Mailbutler
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.markdown ADDED
@@ -0,0 +1,30 @@
1
+ # mail_xoauth2
2
+ Get access to IMAP and STMP via OAuth2, using the standard Ruby Net libraries.
3
+
4
+ This gem is based on and inspired by [gmail_xoauth](https://github.com/nfo/gmail_xoauth).
5
+
6
+ ## Usage
7
+
8
+ ### IMAP OAuth 2.0
9
+
10
+ ```ruby
11
+ require 'mail_xoauth2'
12
+ imap = Net::IMAP.new('imap.gmail.com', 993, usessl = true, certs = nil, verify = false)
13
+ imap.authenticate('XOAUTH2', 'myemail@gmail.com', my_oauth2_token)
14
+ messages_count = imap.status('INBOX', ['MESSAGES'])['MESSAGES']
15
+ puts "Seeing #{messages_count} messages in INBOX"
16
+ ```
17
+
18
+ ### SMTP OAuth 2.0
19
+
20
+ ```ruby
21
+ require 'mail_xoauth2'
22
+ smtp = Net::SMTP.new('smtp.gmail.com', 587)
23
+ smtp.enable_starttls_auto
24
+ smtp.start('gmail.com', 'myemail@gmail.com', my_oauth2_token, :xoauth2)
25
+ smtp.finish
26
+ ```
27
+
28
+ ## License
29
+
30
+ See LICENSE for details.
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'net/imap'
4
+
5
+ module MailXoauth2
6
+ class ImapXoauth2Authenticator
7
+ def process(_data)
8
+ build_oauth2_string(@user, @oauth2_token, true)
9
+ end
10
+
11
+ private
12
+
13
+ # +user+ is an email address: roger@gmail.com
14
+ # +oauth2_token+ is the OAuth2 token
15
+ def initialize(user, oauth2_token)
16
+ @user = user
17
+ @oauth2_token = oauth2_token
18
+ end
19
+
20
+ include Oauth2String
21
+ end
22
+ end
23
+
24
+ Net::IMAP.add_authenticator('XOAUTH2', MailXoauth2::ImapXoauth2Authenticator)
@@ -0,0 +1,17 @@
1
+ module MailXoauth2
2
+ module Oauth2String
3
+ private
4
+
5
+ #
6
+ # Builds the "oauth2 protocol authentication string". See https://developers.google.com/google-apps/gmail/xoauth2_protocol
7
+ #
8
+ # +user+ is an email address: roger@gmail.com
9
+ # +oauth2_token+ is the oauth2 token
10
+ def build_oauth2_string(user, oauth2_token, encode_base64 = false)
11
+ oauth2_string = format("user=%<user>s\1auth=Bearer %<token>s\1\1".encode('us-ascii'), user: user, token: oauth2_token)
12
+ oauth2_string = Base64.strict_encode64(oauth2_string) if encode_base64
13
+
14
+ oauth2_string
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,38 @@
1
+ require 'net/smtp'
2
+ require 'base64'
3
+
4
+ module MailXoauth2
5
+ module SmtpXoauth2Authenticator
6
+ def send_xoauth2(auth_token)
7
+ critical do
8
+ get_response("AUTH XOAUTH2 #{auth_token}")
9
+ end
10
+ end
11
+ private :send_xoauth2
12
+
13
+ def get_final_status
14
+ critical do
15
+ get_response('')
16
+ end
17
+ end
18
+ private :get_final_status
19
+
20
+ def auth_xoauth2(user, oauth2_token)
21
+ check_auth_args user, oauth2_token
22
+
23
+ auth_string = build_oauth2_string(user, oauth2_token, true)
24
+ res = send_xoauth2(auth_string)
25
+
26
+ # See note about SMTP protocol exchange in https://developers.google.com/gmail/xoauth2_protocol
27
+ res = get_final_status if res.continue?
28
+
29
+ check_auth_response res
30
+ res
31
+ end
32
+
33
+ include Oauth2String
34
+ end
35
+ end
36
+
37
+ # Not pretty, right ?
38
+ Net::SMTP.__send__('include', MailXoauth2::SmtpXoauth2Authenticator)
@@ -0,0 +1,3 @@
1
+ module MailXoauth2
2
+ VERSION = '1.0'.freeze
3
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'mail_xoauth2/oauth2_string'
4
+ require 'mail_xoauth2/imap_xoauth2_authenticator'
5
+ require 'mail_xoauth2/smtp_xoauth2_authenticator'
data/test/helper.rb ADDED
@@ -0,0 +1,46 @@
1
+ require 'yaml'
2
+
3
+ require 'rubygems'
4
+ require 'test/unit'
5
+ require 'mocha/test_unit'
6
+
7
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
8
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
9
+ require 'mail_xoauth2'
10
+
11
+ # Wanna debug ? Activate the IMAP debug mode, it will show the client/server conversation
12
+ # Net::IMAP.debug = true
13
+
14
+ # SMTP debugging can only be enabled on Net::SMTP instances
15
+ # Net::SMTP.class_eval do
16
+ # def initialize_with_debug(*args)
17
+ # initialize_without_debug(*args)
18
+ # @debug_output = STDERR
19
+ # end
20
+ # alias_method :initialize_without_debug, :initialize
21
+ # alias_method :initialize, :initialize_with_debug
22
+ # end
23
+
24
+ VALID_CREDENTIALS = begin
25
+ YAML.load_file(File.join(File.dirname(__FILE__), 'valid_credentials.yml'))
26
+ rescue Errno::ENOENT
27
+ STDERR.puts '
28
+ Warning: some tests are disabled because they require valid credentials.
29
+ To enable them, create a file "test/valid_credentials.yml".
30
+ It should also ontain a valid OAuth2 access token.
31
+ Of course, this file is .gitignored. Template:
32
+
33
+ ---
34
+ :email: someuser@gmail.com
35
+ :consumer_key: anonymous # "anonymous" is a valid value for testing
36
+ :consumer_secret: anonymous # "anonymous" is a valid value for testing
37
+ :token: 1/nE2xBCDOU0429bTeJySE11kRE95qzKQNlfTaaBcDeFg
38
+ :token_secret: 123Z/bMsi9fFhN6qHFWOabcd
39
+ :oauth2_token: ya29.AHES6ZTIpsLuSyMwnh-3C40WWcuiOe4N7he0a8xnkvDk_6Q_6yUg7E
40
+
41
+ '
42
+ false
43
+ end
44
+
45
+ class Test::Unit::TestCase
46
+ end
@@ -0,0 +1,30 @@
1
+ require 'helper'
2
+
3
+ class TestImapXoauth2Authenticator < Test::Unit::TestCase
4
+ def setup; end
5
+
6
+ def test_xoauth2_authenticator_is_enabled
7
+ authenticators = Net::IMAP.__send__('class_variable_get', '@@authenticators')
8
+ assert_not_nil authenticators['XOAUTH2']
9
+ assert_equal authenticators['XOAUTH2'], GmailXoauth::ImapXoauth2Authenticator
10
+ end
11
+
12
+ def test_authenticate_with_invalid_credentials
13
+ imap = Net::IMAP.new('imap.gmail.com', 993, usessl = true, certs = nil, verify = false)
14
+ assert_raise(Net::IMAP::NoResponseError) do
15
+ imap.authenticate('XOAUTH2', 'roger@moore.com', 'a')
16
+ end
17
+ end
18
+
19
+ def test_authenticate_with_valid_credentials
20
+ return unless VALID_CREDENTIALS
21
+
22
+ imap = Net::IMAP.new('imap.gmail.com', 993, usessl = true, certs = nil, verify = false)
23
+ imap.authenticate('XOAUTH2', VALID_CREDENTIALS[:email], VALID_CREDENTIALS[:oauth2_token])
24
+ mailboxes = imap.list('', '*')
25
+ assert_instance_of Array, mailboxes
26
+ assert_instance_of Net::IMAP::MailboxList, mailboxes.first
27
+ ensure
28
+ imap.disconnect if imap
29
+ end
30
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'helper'
4
+
5
+ class TestOauth2String < Test::Unit::TestCase
6
+ def setup; end
7
+
8
+ def test_build_oauth2_string
9
+ user = 'somebody@gmail.com'
10
+ oauth2_access_token = SecureRandom.urlsafe_base64
11
+
12
+ oauth2_string = SomeNetClass.new.__send__('build_oauth2_string', user, oauth2_access_token)
13
+
14
+ assert_equal(
15
+ "user=#{user}\1auth=Bearer #{oauth2_access_token}\1\1",
16
+ oauth2_string
17
+ )
18
+ end
19
+
20
+ def test_build_encoded_oauth2_string
21
+ user = 'somebody@gmail.com'
22
+ oauth2_access_token = SecureRandom.urlsafe_base64
23
+
24
+ oauth2_string = SomeNetClass.new.__send__('build_oauth2_string', user, oauth2_access_token, true)
25
+
26
+ assert_equal(
27
+ Base64.strict_encode64("user=#{user}\1auth=Bearer #{oauth2_access_token}\1\1"),
28
+ oauth2_string
29
+ )
30
+ end
31
+ end
32
+
33
+ class SomeNetClass
34
+ include MailXoauth2::Oauth2String
35
+ end
@@ -0,0 +1,56 @@
1
+ require 'helper'
2
+
3
+ class TestSmtpXoauth2Authenticator < Test::Unit::TestCase
4
+ def setup; end
5
+
6
+ def test_smtp_authenticator_is_enabled
7
+ assert Net::SMTP.new(nil).respond_to?(:auth_xoauth2), 'The Net::SMTP class should define the method :auth_xoauth2'
8
+ end
9
+
10
+ def test_authenticate_with_invalid_credentials
11
+ smtp = Net::SMTP.new('smtp.gmail.com', 587)
12
+ smtp.enable_starttls_auto
13
+ assert_raise(Net::SMTPAuthenticationError) do
14
+ smtp.start('gmail.com', 'roger@moore.com', 'a', :xoauth2)
15
+ end
16
+ end
17
+
18
+ def test_authenticate_with_valid_credentials
19
+ return unless VALID_CREDENTIALS
20
+
21
+ smtp = Net::SMTP.new('smtp.gmail.com', 587)
22
+ smtp.enable_starttls_auto
23
+
24
+ assert_nothing_raised do
25
+ smtp.start('gmail.com', VALID_CREDENTIALS[:email], VALID_CREDENTIALS[:oauth2_token], :xoauth2)
26
+ end
27
+ ensure
28
+ smtp.finish if smtp&.started?
29
+ end
30
+
31
+ # Test the handling of 3xx response to an "AUTH XOAUTH2" request... client
32
+ # should send a "\r\n" to get the actual error that occurred.
33
+ #
34
+ # See note about SMTP protocol exchange in https://developers.google.com/
35
+ # gmail/xoauth2_protocol
36
+ #
37
+ def test_authenticate_with_continuation
38
+ smtp = Net::SMTP.new('smtp.gmail.com', 587)
39
+
40
+ # Stub return from initial "AUTH XOAUTH2" request as well as the empty line sent to get final error
41
+ smtp.stubs(:send_xoauth2).returns(Net::SMTP::Response.parse('334 eyJzdGF0dXMiOiI0MDAiLCJzY2hlbWVzIjoiQmVhcmVyIiwic2NvcGUiOiJodHRwczovL21haWwuZ29vZ2xlLmNvbS8ifQ=='))
42
+ smtp.stubs(:get_final_status).returns(Net::SMTP::Response.parse('454 4.7.0 Too many login attempts, please try again later. j63sm3521185itj.19 - gsmtp'))
43
+
44
+ smtp.enable_starttls_auto
45
+
46
+ # Validate an error is still raised
47
+ ex = assert_raise(Net::SMTPAuthenticationError) do
48
+ smtp.start('gmail.com', 'roger@moore.com', 'a', :xoauth2)
49
+ end
50
+
51
+ # ...and that the 334 is not passed back to the caller.
52
+ assert_equal('454 4.7.0 Too many login attempts, please try again later. j63sm3521185itj.19 - gsmtp', ex.message)
53
+ ensure
54
+ smtp.finish if smtp&.started?
55
+ end
56
+ end
metadata ADDED
@@ -0,0 +1,84 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: mail_xoauth2
3
+ version: !ruby/object:Gem::Version
4
+ version: '1.0'
5
+ platform: ruby
6
+ authors:
7
+ - Fabian Jäger
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2018-06-07 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: mocha
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: shoulda
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ description: Get access to IMAP and STMP via OAuth2, using the standard Ruby Net libraries
42
+ email:
43
+ - fabian@mailbutler.io
44
+ executables: []
45
+ extensions: []
46
+ extra_rdoc_files: []
47
+ files:
48
+ - LICENSE
49
+ - README.markdown
50
+ - lib/mail_xoauth2.rb
51
+ - lib/mail_xoauth2/imap_xoauth2_authenticator.rb
52
+ - lib/mail_xoauth2/oauth2_string.rb
53
+ - lib/mail_xoauth2/smtp_xoauth2_authenticator.rb
54
+ - lib/mail_xoauth2/version.rb
55
+ - test/helper.rb
56
+ - test/test_imap_xoauth2_authenticator.rb
57
+ - test/test_oauth2_string.rb
58
+ - test/test_smtp_xoauth2_authenticator.rb
59
+ homepage: https://github.com/Mailbutler/mail_xoauth2
60
+ licenses:
61
+ - MIT
62
+ metadata: {}
63
+ post_install_message:
64
+ rdoc_options:
65
+ - "--charset=UTF-8"
66
+ require_paths:
67
+ - lib
68
+ required_ruby_version: !ruby/object:Gem::Requirement
69
+ requirements:
70
+ - - ">="
71
+ - !ruby/object:Gem::Version
72
+ version: '0'
73
+ required_rubygems_version: !ruby/object:Gem::Requirement
74
+ requirements:
75
+ - - ">="
76
+ - !ruby/object:Gem::Version
77
+ version: 1.3.6
78
+ requirements: []
79
+ rubyforge_project:
80
+ rubygems_version: 2.7.6
81
+ signing_key:
82
+ specification_version: 4
83
+ summary: Get access to IMAP and STMP via OAuth2, using the standard Ruby Net libraries
84
+ test_files: []