osa 0.1.0

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.
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+ require 'osa/util/constants'
3
+ require 'public_suffix'
4
+ require 'osa/util/db'
5
+ require 'osa/util/context'
6
+
7
+ context = OSA::Context.new
8
+
9
+ continue = true
10
+
11
+ while continue
12
+ mails = context.graph_client.mails(context.config.junk_folder_id)
13
+ continue = false
14
+ loop do
15
+ break if mails.nil?
16
+ mails['value'].each do |mail|
17
+ email_address = mail['sender']['emailAddress']['address']
18
+ next if email_address.nil?
19
+ domain = PublicSuffix.domain(email_address.split('@', 2)[1])
20
+
21
+ blacklisted = OSA::Blacklist.where(value: email_address).or(OSA::Blacklist.where(value: domain)).exists?
22
+ if blacklisted
23
+ puts "#{email_address} is blacklisted, reporting and deleting"
24
+ else
25
+ puts "Skipping mail from #{email_address}, its not blacklisted"
26
+ next
27
+ end
28
+
29
+ continue = true
30
+
31
+ puts "forwarding spam from #{email_address}"
32
+ context.graph_client.forward_mail_as_attachment(mail['id'], context.config.spamcop_report_email)
33
+ puts "deleting spam from #{email_address}"
34
+ context.graph_client.delete_mail(mail['id'])
35
+ end
36
+ mails = mails.next
37
+ end
38
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+ require 'uri'
3
+ require 'public_suffix'
4
+ require 'osa/util/db'
5
+ require 'osa/util/context'
6
+
7
+ context = OSA::Context.new
8
+
9
+ loop do
10
+ mails = context.graph_client.mails(context.config.report_folder_id)
11
+ break if mails['value'].empty?
12
+ mails['value'].each do |mail|
13
+ email_address = mail['sender']['emailAddress']['address']
14
+
15
+ puts "forwarding spam from #{email_address}"
16
+ context.graph_client.forward_mail_as_attachment(mail['id'], context.config.spamcop_report_email)
17
+ puts "deleting spam from #{email_address}"
18
+ context.graph_client.delete_mail(mail['id'])
19
+
20
+
21
+ next if email_address.nil?
22
+ domain = PublicSuffix.domain(email_address.split('@', 2)[1])
23
+
24
+ is_free_provider = OSA::EmailProvider.where(value: domain).exists?
25
+ if is_free_provider
26
+ puts "#{email_address} is using a free provider, blacklisting full address"
27
+ OSA::Blacklist.find_or_create_by(value: email_address).save!
28
+ else
29
+ puts "Adding #{domain} to blacklist"
30
+ OSA::Blacklist.find_or_create_by(value: domain).save!
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+ require 'osa/util/db'
3
+ require 'uri'
4
+ require 'tty-prompt'
5
+
6
+ module OSA
7
+ class AuthService
8
+ def initialize(client)
9
+ @client = client
10
+ end
11
+
12
+ def refresh_token(config)
13
+ token = @client.refresh_token(config.refresh_token)
14
+ config.update!(refresh_token: token['refresh_token'])
15
+ token
16
+ end
17
+
18
+ def code_token(code, config)
19
+ token = @client.code_token(code)
20
+ config.update!(refresh_token: token['refresh_token'])
21
+ token
22
+ end
23
+
24
+ def self.login(config)
25
+ query = {
26
+ client_id: CLIENT_ID,
27
+ response_type: 'code',
28
+ redirect_uri: REDIRECT_URL,
29
+ response_mode: 'query',
30
+ scope: SCOPE,
31
+ state: ''
32
+ }
33
+
34
+ query_str = query.reduce('?') do |acc, val|
35
+ key, value = val
36
+ acc + "#{key}=#{URI.encode_www_form_component(value)}&"
37
+ end
38
+
39
+ base_uri = 'https://login.microsoftonline.com/consumers/oauth2/v2.0/authorize'
40
+
41
+ uri = "#{base_uri}#{query_str}"
42
+ puts "Open the following url in your browser and enter the authentication code displayed."
43
+ puts uri
44
+
45
+ prompt = TTY::Prompt.new
46
+ code = prompt.ask('Code:')
47
+
48
+ auth_client = MSAuthClient.new
49
+ auth_service = AuthService.new(auth_client)
50
+
51
+ auth_service.code_token(code, config)
52
+
53
+ puts 'login completed'
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+ require 'osa/util/db'
3
+ require 'osa/services/auth_service'
4
+ require 'tty-prompt'
5
+ require 'osa/util/context'
6
+
7
+ module OSA
8
+ class SetupService
9
+ def setup!
10
+ Config.delete_all
11
+ config = Config.new
12
+ AuthService.login(config)
13
+
14
+ context = Context.new(config)
15
+ folders = context.graph_client.folders.all
16
+ choices = folders.map { |folder|
17
+ [folder['displayName'], folder['id']]
18
+ }.to_h
19
+ prompt = TTY::Prompt.new
20
+ junk_folder_id = prompt.select('Select junk folder:', choices)
21
+ report_folder_id = prompt.select('Select report folder:', choices)
22
+ spamcop_email = prompt.ask('Spamcop report email:') { |q| q.validate :email }
23
+
24
+ config.update! junk_folder_id: junk_folder_id, report_folder_id: report_folder_id, spamcop_report_email: spamcop_email
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OSA
4
+ CLIENT_ID = 'befa4a9e-5d16-4a48-9792-4bd1d125abe8'
5
+ REDIRECT_URL = 'https://storage.googleapis.com/outlook-spam-automator/login.html'
6
+ SCOPE = 'https://graph.microsoft.com/MailboxSettings.ReadWrite https://graph.microsoft.com/MailboxSettings.ReadWrite offline_access'
7
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'osa/clients/ms_auth_client'
4
+ require 'osa/services/auth_service'
5
+ require 'osa/clients/ms_graph_client'
6
+
7
+ module OSA
8
+ class Context
9
+ attr_reader :graph_client
10
+ attr_reader :auth_service
11
+ attr_reader :config
12
+
13
+ def initialize(config=nil)
14
+ @config = config || Config.first
15
+
16
+ if @config.nil?
17
+ $stderr.puts 'OSA not configured'
18
+ exit 1
19
+ end
20
+
21
+ auth_client = MSAuthClient.new
22
+ @auth_service = AuthService.new(auth_client)
23
+ token = auth_service.refresh_token(@config)
24
+
25
+ @graph_client = MSGraphClient.new(token['access_token'])
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+ require 'active_record'
3
+
4
+ ActiveRecord::Base.establish_connection(
5
+ adapter: 'sqlite3',
6
+ database: ENV['DATABASE'] || "#{Dir.pwd}/osa.db"
7
+ )
8
+ root = "#{File.dirname(__FILE__ )}/../"
9
+ ActiveRecord::MigrationContext.new("#{root}/migrations", ActiveRecord::SchemaMigration).migrate
10
+
11
+
12
+ module OSA
13
+ class Blacklist < ActiveRecord::Base
14
+ end
15
+
16
+ class Config < ActiveRecord::Base
17
+ self.table_name = 'config'
18
+ end
19
+
20
+ class EmailProvider < ActiveRecord::Base
21
+ end
22
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OSA
4
+ class Paginated
5
+ def initialize(value, client)
6
+ @value = value
7
+ @client = client
8
+ end
9
+
10
+ def next
11
+ next_page = @value['@odata.nextLink']
12
+ return nil if next_page.nil?
13
+ Paginated.new(@client.get(next_page), @client)
14
+ end
15
+
16
+ def all
17
+ value = @value['value'].dup
18
+ next_page = self.next
19
+ until next_page.nil?
20
+ value += next_page['value']
21
+ next_page = next_page.next
22
+ end
23
+ value
24
+ end
25
+
26
+ private
27
+ def method_missing(symbol, *args)
28
+ @value.send(symbol, *args)
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,3 @@
1
+ module OSA
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,28 @@
1
+ require_relative 'lib/osa/version'
2
+
3
+ Gem::Specification.new do |spec|
4
+ spec.name = 'osa'
5
+ spec.version = OSA::VERSION
6
+ spec.authors = ['Moray Baruh']
7
+ spec.email = ['contact@moraybaruh.com']
8
+
9
+ spec.summary = 'Outlook Spam Automator'
10
+ spec.description = 'Get rid of spam on your Outlook account'
11
+ spec.license = 'MIT'
12
+ spec.required_ruby_version = Gem::Requirement.new(">= 2.7.0")
13
+
14
+ # Specify which files should be added to the gem when it is released.
15
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
16
+ spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
17
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
18
+ end
19
+ spec.bindir = 'exe'
20
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
21
+ spec.require_paths = ['lib']
22
+
23
+ spec.add_dependency 'activerecord', '~> 6.0'
24
+ spec.add_dependency 'faraday', '~> 1.1'
25
+ spec.add_dependency 'public_suffix', '~> 4.0'
26
+ spec.add_dependency 'sqlite3', '~> 1.4'
27
+ spec.add_dependency 'tty-prompt', '~> 0.22'
28
+ end
@@ -0,0 +1,23 @@
1
+ #!/bin/bash
2
+
3
+ set -eu
4
+
5
+ VERSION=$(bundle exec ruby -e 'puts OSA::VERSION')
6
+ echo -n "Releasing ${VERSION}. Continue? (Y/n) "
7
+ read -r CONTINUE
8
+
9
+ if [ "$CONTINUE" != "y" ] && [ "$CONTINUE" != "Y" ]
10
+ then
11
+ echo "Aborting release." >&2
12
+ exit 1
13
+ fi
14
+
15
+ rake release
16
+ echo "Waiting for gem to be available..."
17
+ while ! (gem info -r osa --all | grep "${VERSION}")
18
+ do
19
+ sleep 5
20
+ done
21
+
22
+ docker build . --build-arg "OSA_VERSION=${VERSION}" -t "moray95/osa:${VERSION}"
23
+ docker push "moray95/osa:${VERSION}"
@@ -0,0 +1,14 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <title>Outlook Spam Automator Login</title>
6
+ </head>
7
+ <body>
8
+ <div id="code"></div>
9
+ </body>
10
+ </html>
11
+ <script>
12
+ const urlParams = new URLSearchParams(location.search);
13
+ document.getElementById("code").innerText = "code: " + urlParams.get('code');
14
+ </script>
metadata ADDED
@@ -0,0 +1,146 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: osa
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Moray Baruh
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2020-11-20 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activerecord
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '6.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '6.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: faraday
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.1'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.1'
41
+ - !ruby/object:Gem::Dependency
42
+ name: public_suffix
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '4.0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '4.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: sqlite3
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '1.4'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '1.4'
69
+ - !ruby/object:Gem::Dependency
70
+ name: tty-prompt
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '0.22'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '0.22'
83
+ description: Get rid of spam on your Outlook account
84
+ email:
85
+ - contact@moraybaruh.com
86
+ executables:
87
+ - osa
88
+ extensions: []
89
+ extra_rdoc_files: []
90
+ files:
91
+ - ".dockerignore"
92
+ - ".gitignore"
93
+ - ".rubocop.yml"
94
+ - Dockerfile
95
+ - Gemfile
96
+ - Gemfile.lock
97
+ - LICENSE.txt
98
+ - README.md
99
+ - Rakefile
100
+ - bin/console
101
+ - bin/setup
102
+ - exe/osa
103
+ - lib/osa.rb
104
+ - lib/osa/clients/http_client.rb
105
+ - lib/osa/clients/ms_auth_client.rb
106
+ - lib/osa/clients/ms_graph_client.rb
107
+ - lib/osa/migrations/00001_create_blacklists.rb
108
+ - lib/osa/migrations/00002_create_email_providers.rb
109
+ - lib/osa/migrations/00003_create_config.rb
110
+ - lib/osa/migrations/free-email-providers.txt
111
+ - lib/osa/scripts/scan_junk_folder.rb
112
+ - lib/osa/scripts/scan_report_folder.rb
113
+ - lib/osa/services/auth_service.rb
114
+ - lib/osa/services/setup_service.rb
115
+ - lib/osa/util/constants.rb
116
+ - lib/osa/util/context.rb
117
+ - lib/osa/util/db.rb
118
+ - lib/osa/util/paginated.rb
119
+ - lib/osa/version.rb
120
+ - osa.gemspec
121
+ - release.sh
122
+ - web/login.html
123
+ homepage:
124
+ licenses:
125
+ - MIT
126
+ metadata: {}
127
+ post_install_message:
128
+ rdoc_options: []
129
+ require_paths:
130
+ - lib
131
+ required_ruby_version: !ruby/object:Gem::Requirement
132
+ requirements:
133
+ - - ">="
134
+ - !ruby/object:Gem::Version
135
+ version: 2.7.0
136
+ required_rubygems_version: !ruby/object:Gem::Requirement
137
+ requirements:
138
+ - - ">="
139
+ - !ruby/object:Gem::Version
140
+ version: '0'
141
+ requirements: []
142
+ rubygems_version: 3.1.2
143
+ signing_key:
144
+ specification_version: 4
145
+ summary: Outlook Spam Automator
146
+ test_files: []