osa 0.1.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.rubocop.yml +1 -0
- data/Gemfile +2 -1
- data/Gemfile.lock +48 -23
- data/README.md +23 -12
- data/Rakefile +7 -6
- data/bin/console +3 -4
- data/bin/osa +28 -0
- data/exe/osa +6 -4
- data/lib/osa.rb +2 -1
- data/lib/osa/clients/http_client.rb +6 -1
- data/lib/osa/clients/ms_graph_client.rb +70 -22
- data/lib/osa/migrations/00004_create_reports.rb +16 -0
- data/lib/osa/migrations/00005_create_dns_blacklists.rb +35 -0
- data/lib/osa/scripts/dashboard_server.rb +68 -0
- data/lib/osa/scripts/scan_junk_folder.rb +42 -3
- data/lib/osa/services/auth_service.rb +1 -1
- data/lib/osa/services/setup_service.rb +1 -2
- data/lib/osa/util/constants.rb +1 -1
- data/lib/osa/util/context.rb +1 -1
- data/lib/osa/util/db.rb +13 -2
- data/lib/osa/version.rb +2 -1
- data/lib/osa/views/index.erb +296 -0
- data/lib/osa/views/layout.erb +46 -0
- data/lib/osa/views/spammer.erb +209 -0
- data/osa.gemspec +6 -2
- metadata +52 -4
- data/lib/osa/scripts/scan_report_folder.rb +0 -33
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 1008becc098b639309f218c9f763e57d4d01f201b28fd105c478cd46534de913
|
|
4
|
+
data.tar.gz: 2c285065f454aeaa7fcc1f9dd6f72e92d3423dbe292d06ef36afba6a8db6f9eb
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 4b53ba423fc0b0da3bc8f49188d34370cf49d12e968a13a436ffaa6c849dceddedcf10c7e2b833d7316dbc0045fe487c44891b6eb50b98fb63477f63b8542a0e
|
|
7
|
+
data.tar.gz: ba063539fe384116422310df5a0d3a14c324a0560674932dd39998951d433236fb929a86aae981895adff04a29d71d0780c8fc4a2a9d11fa817359ad56c10ff0
|
data/.rubocop.yml
CHANGED
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
|
@@ -1,42 +1,56 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
osa (0.1
|
|
4
|
+
osa (0.2.1)
|
|
5
5
|
activerecord (~> 6.0)
|
|
6
6
|
faraday (~> 1.1)
|
|
7
|
+
mail (~> 2.7.1)
|
|
7
8
|
public_suffix (~> 4.0)
|
|
9
|
+
sinatra (~> 2.1.0)
|
|
10
|
+
sinatra-contrib (~> 2.1.0)
|
|
8
11
|
sqlite3 (~> 1.4)
|
|
9
12
|
tty-prompt (~> 0.22)
|
|
10
13
|
|
|
11
14
|
GEM
|
|
12
15
|
remote: https://rubygems.org/
|
|
13
16
|
specs:
|
|
14
|
-
activemodel (6.
|
|
15
|
-
activesupport (= 6.
|
|
16
|
-
activerecord (6.
|
|
17
|
-
activemodel (= 6.
|
|
18
|
-
activesupport (= 6.
|
|
19
|
-
activesupport (6.
|
|
17
|
+
activemodel (6.1.3)
|
|
18
|
+
activesupport (= 6.1.3)
|
|
19
|
+
activerecord (6.1.3)
|
|
20
|
+
activemodel (= 6.1.3)
|
|
21
|
+
activesupport (= 6.1.3)
|
|
22
|
+
activesupport (6.1.3)
|
|
20
23
|
concurrent-ruby (~> 1.0, >= 1.0.2)
|
|
21
|
-
i18n (>=
|
|
22
|
-
minitest (
|
|
23
|
-
tzinfo (~>
|
|
24
|
-
zeitwerk (~> 2.
|
|
24
|
+
i18n (>= 1.6, < 2)
|
|
25
|
+
minitest (>= 5.1)
|
|
26
|
+
tzinfo (~> 2.0)
|
|
27
|
+
zeitwerk (~> 2.3)
|
|
25
28
|
ast (2.4.1)
|
|
26
|
-
concurrent-ruby (1.1.
|
|
27
|
-
faraday (1.
|
|
29
|
+
concurrent-ruby (1.1.8)
|
|
30
|
+
faraday (1.3.0)
|
|
31
|
+
faraday-net_http (~> 1.0)
|
|
28
32
|
multipart-post (>= 1.2, < 3)
|
|
29
33
|
ruby2_keywords
|
|
30
|
-
|
|
34
|
+
faraday-net_http (1.0.1)
|
|
35
|
+
i18n (1.8.9)
|
|
31
36
|
concurrent-ruby (~> 1.0)
|
|
32
|
-
|
|
37
|
+
mail (2.7.1)
|
|
38
|
+
mini_mime (>= 0.1.1)
|
|
39
|
+
mini_mime (1.0.2)
|
|
40
|
+
minitest (5.14.3)
|
|
41
|
+
multi_json (1.15.0)
|
|
33
42
|
multipart-post (2.1.1)
|
|
43
|
+
mustermann (1.1.1)
|
|
44
|
+
ruby2_keywords (~> 0.0.1)
|
|
34
45
|
parallel (1.20.0)
|
|
35
46
|
parser (2.7.2.0)
|
|
36
47
|
ast (~> 2.4.1)
|
|
37
48
|
pastel (0.8.0)
|
|
38
49
|
tty-color (~> 0.5)
|
|
39
50
|
public_suffix (4.0.6)
|
|
51
|
+
rack (2.2.3)
|
|
52
|
+
rack-protection (2.1.0)
|
|
53
|
+
rack
|
|
40
54
|
rainbow (3.0.0)
|
|
41
55
|
regexp_parser (1.8.2)
|
|
42
56
|
rexml (3.2.4)
|
|
@@ -55,24 +69,35 @@ GEM
|
|
|
55
69
|
rubocop (>= 0.90.0, < 2.0)
|
|
56
70
|
rubocop-ast (>= 0.4.0)
|
|
57
71
|
ruby-progressbar (1.10.1)
|
|
58
|
-
ruby2_keywords (0.0.
|
|
72
|
+
ruby2_keywords (0.0.4)
|
|
73
|
+
sinatra (2.1.0)
|
|
74
|
+
mustermann (~> 1.0)
|
|
75
|
+
rack (~> 2.2)
|
|
76
|
+
rack-protection (= 2.1.0)
|
|
77
|
+
tilt (~> 2.0)
|
|
78
|
+
sinatra-contrib (2.1.0)
|
|
79
|
+
multi_json
|
|
80
|
+
mustermann (~> 1.0)
|
|
81
|
+
rack-protection (= 2.1.0)
|
|
82
|
+
sinatra (= 2.1.0)
|
|
83
|
+
tilt (~> 2.0)
|
|
59
84
|
sqlite3 (1.4.2)
|
|
60
|
-
|
|
85
|
+
tilt (2.0.10)
|
|
61
86
|
tty-color (0.6.0)
|
|
62
87
|
tty-cursor (0.7.1)
|
|
63
|
-
tty-prompt (0.
|
|
88
|
+
tty-prompt (0.23.0)
|
|
64
89
|
pastel (~> 0.8)
|
|
65
90
|
tty-reader (~> 0.8)
|
|
66
|
-
tty-reader (0.
|
|
91
|
+
tty-reader (0.9.0)
|
|
67
92
|
tty-cursor (~> 0.7)
|
|
68
93
|
tty-screen (~> 0.8)
|
|
69
94
|
wisper (~> 2.0)
|
|
70
95
|
tty-screen (0.8.1)
|
|
71
|
-
tzinfo (
|
|
72
|
-
|
|
96
|
+
tzinfo (2.0.4)
|
|
97
|
+
concurrent-ruby (~> 1.0)
|
|
73
98
|
unicode-display_width (1.7.0)
|
|
74
99
|
wisper (2.0.1)
|
|
75
|
-
zeitwerk (2.4.
|
|
100
|
+
zeitwerk (2.4.2)
|
|
76
101
|
|
|
77
102
|
PLATFORMS
|
|
78
103
|
ruby
|
|
@@ -83,4 +108,4 @@ DEPENDENCIES
|
|
|
83
108
|
rubocop-performance
|
|
84
109
|
|
|
85
110
|
BUNDLED WITH
|
|
86
|
-
2.1.
|
|
111
|
+
2.1.4
|
data/README.md
CHANGED
|
@@ -4,17 +4,18 @@
|
|
|
4
4
|
|
|
5
5
|
### Basics
|
|
6
6
|
|
|
7
|
-
OSA
|
|
8
|
-
|
|
9
|
-
folder.
|
|
7
|
+
OSA asks you to choose your junk folder on first configuration. After the folder is selected, the `scan-junk` command
|
|
8
|
+
allows you to scan the folder to report and delete any unwanted spam. It's important to chose the actual junk folder
|
|
9
|
+
so your Outlook spam filters does not get broken. However, OSA will work with any folder. The processing for each email
|
|
10
|
+
uses the following rules:
|
|
10
11
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
1. If the email is flagged, the email is reported to Spamcop, then deleted and the sender is blacklisted.
|
|
13
|
+
2. If the email's sender is blacklisted, the email is reported to Spamcop, then deleted.
|
|
14
|
+
3. Otherwise, the email is left untouched.
|
|
14
15
|
|
|
15
|
-
*OSA will not touch any folder beside
|
|
16
|
+
*OSA will not touch any folder beside the folder you've chosen.*
|
|
16
17
|
|
|
17
|
-
*It's the user's responsibility to move junk mails to the
|
|
18
|
+
*It's the user's responsibility to move junk mails to the junk folder and flag them to build up the blacklist.*
|
|
18
19
|
|
|
19
20
|
### The blacklist
|
|
20
21
|
|
|
@@ -24,6 +25,13 @@ be blacklisted. However, to prevent millions of users to go blacklisted because
|
|
|
24
25
|
list of free email providers (which includes domains like gmail.com, outlook.com among others). If the sender uses a free
|
|
25
26
|
email provider, the full address is blacklisted.
|
|
26
27
|
|
|
28
|
+
OSA also supports Domain Name System Blacklist. In fact it comes bundled with 3 DNSBL:
|
|
29
|
+
1. [Spamcop Blocking List](https://www.spamcop.net/fom-serve/cache/297.html)
|
|
30
|
+
2. [Spamhaus Block List](https://www.spamhaus.org/sbl)
|
|
31
|
+
3. [Passive Spam Block List](https://psbl.org)
|
|
32
|
+
|
|
33
|
+
You can remove these or add more blacklists, from the database, after you configure OSA.
|
|
34
|
+
|
|
27
35
|
## Installation
|
|
28
36
|
|
|
29
37
|
You can install OSA from RubyGems:
|
|
@@ -58,18 +66,21 @@ osa setup
|
|
|
58
66
|
|
|
59
67
|
Each time you run this command, your previous configuration will be erased, except for your blacklist.
|
|
60
68
|
|
|
61
|
-
Process your
|
|
69
|
+
Process your junk folder:
|
|
62
70
|
|
|
63
71
|
```sh
|
|
64
|
-
osa scan-
|
|
72
|
+
osa scan-junk
|
|
65
73
|
```
|
|
66
74
|
|
|
67
|
-
|
|
75
|
+
OSA also provides you a nice administration dashboard you. You can access the dashboard by running
|
|
68
76
|
|
|
69
77
|
```sh
|
|
70
|
-
osa
|
|
78
|
+
osa dashboard
|
|
71
79
|
```
|
|
72
80
|
|
|
81
|
+
You are now able to access the dashboard on `http://localhost:8080`. You can also change the port of the server by
|
|
82
|
+
providing the `SERVER_PORT` environment variable.
|
|
83
|
+
|
|
73
84
|
## Contributing
|
|
74
85
|
|
|
75
86
|
Bug reports and pull requests are welcome on GitHub at https://github.com/moray95/osa.
|
data/Rakefile
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
|
|
2
|
-
require
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
require 'bundler/gem_tasks'
|
|
3
|
+
require 'rake/testtask'
|
|
3
4
|
|
|
4
5
|
Rake::TestTask.new(:test) do |t|
|
|
5
|
-
t.libs <<
|
|
6
|
-
t.libs <<
|
|
7
|
-
t.test_files = FileList[
|
|
6
|
+
t.libs << 'test'
|
|
7
|
+
t.libs << 'lib'
|
|
8
|
+
t.test_files = FileList['test/**/*_test.rb']
|
|
8
9
|
end
|
|
9
10
|
|
|
10
|
-
task :
|
|
11
|
+
task default: :test
|
data/bin/console
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env ruby
|
|
2
|
-
|
|
3
|
-
require
|
|
4
|
-
require "osa"
|
|
2
|
+
require 'bundler/setup'
|
|
3
|
+
require 'osa'
|
|
5
4
|
|
|
6
5
|
# You can add fixtures and/or initialization code here to make experimenting
|
|
7
6
|
# with your gem easier. You can also use a different console, if you like.
|
|
@@ -10,5 +9,5 @@ require "osa"
|
|
|
10
9
|
# require "pry"
|
|
11
10
|
# Pry.start
|
|
12
11
|
|
|
13
|
-
require
|
|
12
|
+
require 'irb'
|
|
14
13
|
IRB.start(__FILE__)
|
data/bin/osa
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
|
|
3
|
+
#
|
|
4
|
+
# This file was generated by Bundler.
|
|
5
|
+
#
|
|
6
|
+
# The application 'osa' is installed as part of a gem, and
|
|
7
|
+
# this file is here to facilitate running it.
|
|
8
|
+
#
|
|
9
|
+
|
|
10
|
+
require 'pathname'
|
|
11
|
+
ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile',
|
|
12
|
+
Pathname.new(__FILE__).realpath)
|
|
13
|
+
|
|
14
|
+
bundle_binstub = File.expand_path('../bundle', __FILE__)
|
|
15
|
+
|
|
16
|
+
if File.file?(bundle_binstub)
|
|
17
|
+
if /This file was generated by Bundler/.match?(File.read(bundle_binstub, 300))
|
|
18
|
+
load(bundle_binstub)
|
|
19
|
+
else
|
|
20
|
+
abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
|
|
21
|
+
Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
require 'rubygems'
|
|
26
|
+
require 'bundler/setup'
|
|
27
|
+
|
|
28
|
+
load Gem.bin_path('osa', 'osa')
|
data/exe/osa
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
2
3
|
require 'osa/services/setup_service'
|
|
3
4
|
require 'osa/services/auth_service'
|
|
4
5
|
|
|
@@ -8,12 +9,13 @@ case cmd
|
|
|
8
9
|
when 'setup'
|
|
9
10
|
OSA::SetupService.new.setup!
|
|
10
11
|
when 'login'
|
|
11
|
-
OSA::AuthService.login(Config.first || Config.new)
|
|
12
|
+
OSA::AuthService.login(OSA::Config.first || OSA::Config.new)
|
|
12
13
|
when 'scan-junk'
|
|
13
14
|
require 'osa/scripts/scan_junk_folder'
|
|
14
|
-
when '
|
|
15
|
-
require 'osa/scripts/
|
|
15
|
+
when 'dashboard'
|
|
16
|
+
require 'osa/scripts/dashboard_server'
|
|
17
|
+
DashboardServer.start!
|
|
16
18
|
else
|
|
17
|
-
$stderr.puts "Usage: #{File.basename($0)} [setup|login|scan-junk|
|
|
19
|
+
$stderr.puts "Usage: #{File.basename($0)} [setup|login|scan-junk|dashboard]"
|
|
18
20
|
exit 1
|
|
19
21
|
end
|
data/lib/osa.rb
CHANGED
|
@@ -1 +1,2 @@
|
|
|
1
|
-
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
require 'osa/version'
|
|
@@ -15,6 +15,11 @@ module OSA
|
|
|
15
15
|
handle_response(response)
|
|
16
16
|
end
|
|
17
17
|
|
|
18
|
+
def put(*args, **kwargs)
|
|
19
|
+
response = @connection.put(*args, **kwargs)
|
|
20
|
+
handle_response(response)
|
|
21
|
+
end
|
|
22
|
+
|
|
18
23
|
def delete(*args, **kwargs)
|
|
19
24
|
response = @connection.delete(*args, **kwargs)
|
|
20
25
|
handle_response(response)
|
|
@@ -31,7 +36,7 @@ module OSA
|
|
|
31
36
|
if response.status > 299
|
|
32
37
|
raise StandardError, "Request failed with status code: #{response.status}, body: #{response.body}"
|
|
33
38
|
end
|
|
34
|
-
if response.headers['content-type']
|
|
39
|
+
if response.headers['content-type']&.include?('application/json')
|
|
35
40
|
JSON.parse(response.body)
|
|
36
41
|
else
|
|
37
42
|
response.body
|
|
@@ -4,44 +4,60 @@ require 'json'
|
|
|
4
4
|
require 'base64'
|
|
5
5
|
require 'osa/util/paginated'
|
|
6
6
|
require 'osa/clients/http_client'
|
|
7
|
+
require 'active_support/core_ext/numeric/bytes'
|
|
8
|
+
require 'active_support'
|
|
9
|
+
require 'mail'
|
|
7
10
|
|
|
8
11
|
module OSA
|
|
9
|
-
class MSGraphClient
|
|
12
|
+
class MSGraphClient
|
|
13
|
+
URL = 'https://graph.microsoft.com'
|
|
14
|
+
|
|
10
15
|
def initialize(token)
|
|
11
|
-
|
|
16
|
+
@authenticated = HttpClient.new(Faraday.new(
|
|
12
17
|
url: 'https://graph.microsoft.com',
|
|
13
18
|
headers: {
|
|
14
19
|
'authorization' => "Bearer #{token}"
|
|
15
20
|
}
|
|
16
|
-
)
|
|
17
|
-
|
|
21
|
+
))
|
|
22
|
+
|
|
23
|
+
@unauthenticated = HttpClient.new(Faraday.new(
|
|
24
|
+
url: 'https://graph.microsoft.com'
|
|
25
|
+
))
|
|
18
26
|
end
|
|
19
27
|
|
|
20
28
|
def rules
|
|
21
|
-
get('/v1.0/me/mailFolders/inbox/messageRules')
|
|
29
|
+
@authenticated.get('/v1.0/me/mailFolders/inbox/messageRules')
|
|
22
30
|
end
|
|
23
31
|
|
|
24
32
|
def rule(id)
|
|
25
|
-
get("/v1.0/me/mailFolders/inbox/messageRules/#{id}")
|
|
33
|
+
@authenticated.get("/v1.0/me/mailFolders/inbox/messageRules/#{id}")
|
|
26
34
|
end
|
|
27
35
|
|
|
28
36
|
def folders
|
|
29
|
-
Paginated.new(get('/v1.0/me/mailFolders'),
|
|
37
|
+
Paginated.new(@authenticated.get('/v1.0/me/mailFolders'), @authenticated)
|
|
30
38
|
end
|
|
31
39
|
|
|
32
40
|
def mails(folder_id)
|
|
33
|
-
Paginated.new(get("/v1.0/me/mailFolders/#{folder_id}/messages"),
|
|
41
|
+
Paginated.new(@authenticated.get("/v1.0/me/mailFolders/#{folder_id}/messages"), @authenticated)
|
|
34
42
|
end
|
|
35
43
|
|
|
36
44
|
def raw_mail(mail_id)
|
|
37
|
-
get("/v1.0/me/messages/#{mail_id}/$value")
|
|
45
|
+
@authenticated.get("/v1.0/me/messages/#{mail_id}/$value")
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def sender_ip(mail_id)
|
|
49
|
+
content = raw_mail(mail_id)
|
|
50
|
+
mail = Mail.new(content)
|
|
51
|
+
mail.header['x-sender-ip']
|
|
38
52
|
end
|
|
39
53
|
|
|
40
54
|
def forward_mail_as_attachment(mail_id, to)
|
|
41
55
|
raw_mail = self.raw_mail(mail_id)
|
|
42
56
|
forward_message = create_forward_message(mail_id)
|
|
43
|
-
add_email_attachment(forward_message['id'], raw_mail)
|
|
44
57
|
update = {
|
|
58
|
+
body: {
|
|
59
|
+
content: ''
|
|
60
|
+
},
|
|
45
61
|
toRecipients: [
|
|
46
62
|
{
|
|
47
63
|
emailAddress: {
|
|
@@ -51,36 +67,68 @@ module OSA
|
|
|
51
67
|
]
|
|
52
68
|
}
|
|
53
69
|
update_message(forward_message['id'], update)
|
|
70
|
+
add_email_attachment(forward_message['id'], 'email.eml', raw_mail)
|
|
54
71
|
send_message(forward_message['id'])
|
|
55
72
|
end
|
|
56
73
|
|
|
57
74
|
def create_forward_message(mail_id)
|
|
58
|
-
post("/v1.0/me/messages/#{mail_id}/createForward")
|
|
75
|
+
@authenticated.post("/v1.0/me/messages/#{mail_id}/createForward")
|
|
59
76
|
end
|
|
60
77
|
|
|
61
|
-
def add_email_attachment(mail_id, content)
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
post("/v1.0/me/messages/#{mail_id}/attachments", body.to_json, 'content-type': 'application/json')
|
|
78
|
+
def add_email_attachment(mail_id, name, content)
|
|
79
|
+
if content.length < 3.megabytes
|
|
80
|
+
add_small_email_attachment(mail_id, name, content)
|
|
81
|
+
else
|
|
82
|
+
add_large_email_attachment(mail_id, name, content)
|
|
83
|
+
end
|
|
68
84
|
end
|
|
69
85
|
|
|
70
86
|
def delete_mail(mail_id)
|
|
71
|
-
delete("/v1.0/me/messages/#{mail_id}")
|
|
87
|
+
@authenticated.delete("/v1.0/me/messages/#{mail_id}")
|
|
72
88
|
end
|
|
73
89
|
|
|
74
90
|
def update_rule(id, update)
|
|
75
|
-
patch("/v1.0/me/mailFolders/inbox/messageRules/#{id}", update.to_json, 'content-type' => 'application/json')
|
|
91
|
+
@authenticated.patch("/v1.0/me/mailFolders/inbox/messageRules/#{id}", update.to_json, 'content-type' => 'application/json')
|
|
76
92
|
end
|
|
77
93
|
|
|
78
94
|
def update_message(id, update)
|
|
79
|
-
patch("/v1.0/me/messages/#{id}", update.to_json, 'content-type': 'application/json')
|
|
95
|
+
@authenticated.patch("/v1.0/me/messages/#{id}", update.to_json, 'content-type': 'application/json')
|
|
80
96
|
end
|
|
81
97
|
|
|
82
98
|
def send_message(id)
|
|
83
|
-
post("/v1.0/me/messages/#{id}/send")
|
|
99
|
+
@authenticated.post("/v1.0/me/messages/#{id}/send")
|
|
84
100
|
end
|
|
101
|
+
|
|
102
|
+
private
|
|
103
|
+
def add_small_email_attachment(mail_id, name, content)
|
|
104
|
+
body = {
|
|
105
|
+
"@odata.type": '#microsoft.graph.fileAttachment',
|
|
106
|
+
contentBytes: Base64.encode64(content),
|
|
107
|
+
name: name
|
|
108
|
+
}
|
|
109
|
+
@authenticated.post("/v1.0/me/messages/#{mail_id}/attachments", body.to_json, 'content-type': 'application/json')
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def add_large_email_attachment(mail_id, name, content)
|
|
113
|
+
upload_session = create_upload_session(mail_id, name, content.length)
|
|
114
|
+
ranges = upload_session['nextExpectedRanges'].map do |range|
|
|
115
|
+
range.split('-').then { |start, finish| (start.to_i..finish&.to_i) }
|
|
116
|
+
end
|
|
117
|
+
ranges.each do |range|
|
|
118
|
+
current_content = content[range]
|
|
119
|
+
@unauthenticated.put(upload_session['uploadUrl'], current_content[range], 'Content-Range': "bytes #{range.begin}-#{(range.end || content.length) - 1}/#{content.length}")
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def create_upload_session(mail_id, name, size)
|
|
124
|
+
body = {
|
|
125
|
+
AttachmentItem: {
|
|
126
|
+
attachmentType: :file,
|
|
127
|
+
name: name,
|
|
128
|
+
size: size
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
@authenticated.post("/v1.0/me/messages/#{mail_id}/attachments/createUploadSession", body.to_json, 'content-type': 'application/json')
|
|
132
|
+
end
|
|
85
133
|
end
|
|
86
134
|
end
|