postmark-inbound 0.0.1

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
+ SHA1:
3
+ metadata.gz: d30cef83d4df28de873f3d1d929ee705f27a86b4
4
+ data.tar.gz: e915c307737eccfcae23d32e4abaa332d9576237
5
+ SHA512:
6
+ metadata.gz: 6d9eff2ee5a31ef257445750f70a8c8b1c652787463272bc30ecc7c9995cd9486e6fc80472fad47c22a254acf1866b4d6829de55534ece6235a17264b3cf46a4
7
+ data.tar.gz: 485870ff33fd174d41d147c1ccaeefd519099d2fe1e14b64086ac27c2a9c1c85c6a5bf73e385e3cd46fd50c64287f114bf4c2b31395afa88da4c801707f72f78
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2016 Ken J.
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.md ADDED
@@ -0,0 +1,79 @@
1
+ # postmark-inbound
2
+
3
+ [![Gem Version](https://badge.fury.io/rb/postmark-inbound.svg)](http://badge.fury.io/rb/postmark-inbound) [![Code Climate](https://codeclimate.com/github/kenjij/postmark-inbound/badges/gpa.svg)](https://codeclimate.com/github/kenjij/postmark-inbound) [![security](https://hakiri.io/github/kenjij/postmark-inbound/master.svg)](https://hakiri.io/github/kenjij/postmark-inbound/master)
4
+
5
+ A Ruby server for Postmark inbound webhook.
6
+
7
+ ## Requirements
8
+
9
+ - Ruby 2.1 <=
10
+ - Kajiki 1.1 <=
11
+ - Sinatra 1.4 <=
12
+
13
+ ## Getting Started
14
+
15
+ ### Install
16
+
17
+ ```
18
+ $ gem install postmark-inbound
19
+ ```
20
+
21
+ ### Configure
22
+
23
+ Create a configuration file following the example below.
24
+
25
+ ```ruby
26
+ # Configure application logging
27
+ PINS.logger = Logger.new(STDOUT)
28
+ PINS.logger.level = Logger::DEBUG
29
+
30
+ PINS::Config.setup do |c|
31
+ # User custom data
32
+ c.user = {my_data1: 'Something', my_data2: 'Somethingelse'}
33
+ # If any number of strings are set, it will require a matching "?auth=" parameter in the incoming request
34
+ c.auth_tokens = [
35
+ 'adc9c81e23cd4ed785038028e69f7c54',
36
+ '4e1bca06d99f477f89c110e12e45f8f4'
37
+ ]
38
+ # HTTP server (Sinatra) settings
39
+ c.dump_errors = true
40
+ c.logging = true
41
+ end
42
+
43
+ # Add and register handler blocks; all handlers will be called for every incoming request
44
+ PINS::Handler.add(:my_handler1) do |h|
45
+ h.set_block do |pin|
46
+ break unless pin[:originalrecipient] == 'myinbox@pm.example.com'
47
+ puts "It's a match!"
48
+ # Do something more
49
+ end
50
+ end
51
+
52
+ PINS::Handler.add(:my_handler2) do |h|
53
+ h.set_block do |pin|
54
+ break unless pin[:spam_status]
55
+ puts "We've got a spam."
56
+ # Do something more
57
+ end
58
+ end
59
+ ```
60
+
61
+ ### Use
62
+
63
+ To see help:
64
+
65
+ ```
66
+ $ pin-server -h
67
+ Usage: pin-server [options] {start|stop}
68
+ -c, --config=<s> Load config from file
69
+ -d, --daemonize Run in the background
70
+ -l, --log=<s> Log output to file
71
+ -P, --pid=<s> Store PID to file
72
+ -p, --port=<i> Use port (default: 4567)
73
+ ```
74
+
75
+ The minimum to start a server:
76
+
77
+ ```
78
+ $ pin-server -c config.rb start
79
+ ```
data/bin/pin-server ADDED
@@ -0,0 +1,20 @@
1
+ #!/usr/bin/env ruby
2
+ require 'kajiki'
3
+ require 'postmark-inbound'
4
+
5
+
6
+ opts = Kajiki.preset_options(:server, {config: true})
7
+
8
+ Kajiki.run(opts) do |cmd|
9
+ case cmd
10
+ when 'start'
11
+ PINS::Config.load_config(opts[:config]) if opts[:config]
12
+ require 'postmark-inbound/server'
13
+ PINS.logger.warn('Postmark Inbound Server starting...')
14
+ Rack::Server.start({
15
+ app: PINS::Server.new,
16
+ Host: opts[:address],
17
+ Port: opts[:port]
18
+ })
19
+ end
20
+ end
@@ -0,0 +1,41 @@
1
+ require 'postmark-inbound/handler'
2
+
3
+ module PINS
4
+
5
+ class Config
6
+
7
+ # Load Ruby config file
8
+ # @param path [String] config file
9
+ def self.load_config(path)
10
+ raise 'config file missing' unless path
11
+ PINS.logger.debug("Loading config file: #{path}")
12
+ require File.expand_path(path)
13
+ PINS.logger.info('Config.load_config done.')
14
+ end
15
+
16
+ # Returns the shared instance
17
+ # @return [PINS::Config]
18
+ def self.shared
19
+ @shared_config ||= Config.new
20
+ end
21
+
22
+ # Call this from your config file
23
+ def self.setup(&block)
24
+ yield Config.shared
25
+ PINS.logger.debug('Config.setup block executed.')
26
+ end
27
+
28
+ attr_accessor :user
29
+ attr_accessor :auth_tokens
30
+ attr_accessor :dump_errors
31
+ attr_accessor :logging
32
+
33
+ def initialize
34
+ @auth_tokens = []
35
+ @dump_errors = false
36
+ @logging = false
37
+ end
38
+
39
+ end
40
+
41
+ end
@@ -0,0 +1,55 @@
1
+ module PINS
2
+
3
+ class Handler
4
+
5
+ # Call as often as necessary to add handlers, usually from the config file; each call creates a PINS::Handler object
6
+ # @param name [Symbol] or a String
7
+ def self.add(name)
8
+ @catalog ||= {}
9
+ if @catalog[name]
10
+ PINS.logger.warn("Handler not added, duplicate name: #{name}")
11
+ return nil
12
+ end
13
+ handler = Handler.new(name)
14
+ yield handler if block_given?
15
+ @catalog[name] = handler
16
+ PINS.logger.info("Added handler: #{name}")
17
+ end
18
+
19
+ # @return [Hash] containing all the handlers
20
+ def self.catalog
21
+ return @catalog
22
+ end
23
+
24
+ # Run the handlers, typically called by the server
25
+ # @param pin [Hash] from Postmark inbound webhook JSON
26
+ def self.run(pin, names = Handler.catalog.keys)
27
+ PINS.logger.info('Running handlers...')
28
+ names.each do |name|
29
+ (Handler.catalog)[name].run(pin)
30
+ end
31
+ PINS.logger.info('Done running handlers.')
32
+ end
33
+
34
+ attr_reader :myname
35
+
36
+ def initialize(name)
37
+ PINS.logger.debug("Initializing handler: #{name}")
38
+ @myname = name
39
+ end
40
+
41
+ # When adding a handler, call this to register a block
42
+ def set_block(&block)
43
+ PINS.logger.debug("Registering block for handler: #{}")
44
+ @block = block
45
+ end
46
+
47
+ def run(obj)
48
+ PINS.logger.warn("No block to execute for handler: #{myname}") unless @block
49
+ PINS.logger.debug("Running handler: #{myname}")
50
+ @block.call(obj)
51
+ end
52
+
53
+ end
54
+
55
+ end
@@ -0,0 +1,99 @@
1
+ require 'json'
2
+ require 'time'
3
+
4
+ module PINS
5
+
6
+ module Helper
7
+
8
+ def parse_json(json)
9
+ obj = JSON.parse(json)
10
+ return parse_pm(keys_to_sym(obj)).merge!(catch_pm_headers(obj[:headers]))
11
+ end
12
+
13
+ # Convert all Hash keys to lowercase symbols
14
+ # @param obj [Object] any Ruby object
15
+ def keys_to_sym(obj)
16
+ case obj
17
+ when Array
18
+ obj.each do |v|
19
+ keys_to_sym(v)
20
+ end
21
+ when Hash
22
+ obj.keys.each do |k|
23
+ if k.class == String
24
+ obj[k.downcase.to_sym] = keys_to_sym(obj.delete(k))
25
+ end
26
+ end
27
+ end
28
+ return obj
29
+ end
30
+
31
+ # Additional parsing/conversions
32
+ def parse_pm(hash)
33
+ hash[:date] = Time.parse(hash[:date]) if hash[:date]
34
+ return hash
35
+ end
36
+
37
+ # Extract Postmark related headers into a Hash
38
+ # @param headers [Array]
39
+ # @return [Hash]
40
+ def catch_pm_headers(headers)
41
+ return nil unless Array === headers
42
+ caught = {}
43
+ headers.each do |h|
44
+ case h[:name]
45
+ when 'X-Spam-Status'
46
+ caught[:spam_status] = h[:value].downcase.start_with?('yes')
47
+ when 'X-Spam-Score'
48
+ caught[:spam_score] = h[:value].to_f
49
+ when 'Received-SPF'
50
+ caught[:received_spf_status] = (h[:value].split)[0].downcase
51
+ end
52
+ end
53
+ return caught
54
+ end
55
+
56
+ def lookup_email_headers(headers, name)
57
+ end
58
+
59
+ # Convert object into JSON, optionally pretty-format
60
+ # @param obj [Object] any Ruby object
61
+ # @param opts [Hash] any JSON options
62
+ # @return [String] JSON string
63
+ def json_with_object(obj, pretty: true, opts: nil)
64
+ return '{}' if obj.nil?
65
+ if pretty
66
+ opts = {
67
+ indent: ' ',
68
+ space: ' ',
69
+ object_nl: "\n",
70
+ array_nl: "\n"
71
+ }
72
+ end
73
+ JSON.fast_generate(json_format_value(obj), opts)
74
+ end
75
+
76
+ # Return Ruby object/value to JSON standard format
77
+ # @param val [Object]
78
+ # @return [Object]
79
+ def json_format_value(val)
80
+ case val
81
+ when Array
82
+ val.map { |v| json_format_value(v) }
83
+ when Hash
84
+ val.reduce({}) { |h, (k, v)| h.merge({k => json_format_value(v)}) }
85
+ when String
86
+ val.encode!('UTF-8', {invalid: :replace, undef: :replace})
87
+ when Time
88
+ val.utc.iso8601
89
+ else
90
+ val
91
+ end
92
+ end
93
+
94
+ end
95
+
96
+ end
97
+
98
+
99
+ [{'A' => 1, 'Base' => 'Text', 'He' => {'llo' => 2, 'NONE' => 0}, 'aRRAy' => [1,2,{a: 1, 'BB' => "two"}]}]
@@ -0,0 +1,24 @@
1
+ require 'logger'
2
+
3
+
4
+ module PINS
5
+
6
+ def self.logger=(logger)
7
+ @logger = logger
8
+ end
9
+
10
+ def self.logger
11
+ @logger ||= NullLogger.new()
12
+ end
13
+
14
+ class NullLogger < Logger
15
+
16
+ def initialize(*args)
17
+ end
18
+
19
+ def add(*args, &block)
20
+ end
21
+
22
+ end
23
+
24
+ end
@@ -0,0 +1,57 @@
1
+ require 'sinatra/base'
2
+ require 'postmark-inbound/helper'
3
+
4
+ module PINS
5
+
6
+ # The Sinatra server
7
+ class Server < Sinatra::Base
8
+
9
+ helpers Helper
10
+
11
+ configure do
12
+ set :environment, :production
13
+ disable :static
14
+ c = Config.shared
15
+ set :dump_errors, c.dump_errors
16
+ set :logging, c.logging
17
+ PINS.logger.info('Sinatra server configured.')
18
+ end
19
+
20
+ before do
21
+ tokens = Config.shared.auth_tokens
22
+ halt 401 unless tokens.empty? || tokens.include?(params[:auth])
23
+ end
24
+
25
+ post '/' do
26
+ PINS.logger.info('Incoming request received.')
27
+ PINS.logger.debug("Body size: #{request.content_length} bytes")
28
+ request.body.rewind
29
+ Handler.run(parse_json(request.body.read))
30
+ body ''
31
+ end
32
+
33
+ not_found do
34
+ PINS.logger.info('Invalid request.')
35
+ PINS.logger.debug("Request method and path: #{request.request_method} #{request.path}")
36
+ json_with_object({message: 'Huh, nothing here.'})
37
+ end
38
+
39
+ error 401 do
40
+ PINS.logger.info(params[:auth] ? 'Invalid auth token provided.' : 'Missing auth token.')
41
+ PINS.logger.debug("Provided auth token: #{params[:auth]}") if params[:auth]
42
+ json_with_object({message: 'Oops, need a valid auth.'})
43
+ end
44
+
45
+ error do
46
+ status 500
47
+ err = env['sinatra.error']
48
+ PINS.logger.error "#{err.class.name} - #{err}"
49
+ json_with_object({message: 'Yikes, internal error.'})
50
+ end
51
+
52
+ after do
53
+ content_type 'application/json'
54
+ end
55
+
56
+ end
57
+ end
@@ -0,0 +1,5 @@
1
+ module PINS
2
+
3
+ Version = '0.0.1'
4
+
5
+ end
@@ -0,0 +1,2 @@
1
+ require 'postmark-inbound/logger'
2
+ require 'postmark-inbound/config'
@@ -0,0 +1,23 @@
1
+ $LOAD_PATH.unshift(File.expand_path('../lib', __FILE__))
2
+ require 'postmark-inbound/version'
3
+
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = 'postmark-inbound'
7
+ s.version = PINS::Version
8
+ s.authors = ['Ken J.']
9
+ s.email = ['kenjij@gmail.com']
10
+ s.summary = %q{A Ruby server for Postmark inbound webhook.}
11
+ s.description = %q{A programable Ruby server for Postmark inbound webhook. For example, trigger notifications or automated response.}
12
+ s.homepage = 'https://github.com/kenjij/postmark-inbound'
13
+ s.license = 'MIT'
14
+
15
+ s.files = `git ls-files`.split($/)
16
+ s.executables = s.files.grep(%r{^bin/}) { |f| File.basename(f) }
17
+ s.require_paths = ["lib"]
18
+
19
+ s.required_ruby_version = '>= 2.1.0'
20
+
21
+ s.add_runtime_dependency 'kajiki', '~> 1.1'
22
+ s.add_runtime_dependency 'sinatra', '~> 1.4'
23
+ end
metadata ADDED
@@ -0,0 +1,85 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: postmark-inbound
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Ken J.
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2016-12-27 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: kajiki
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.1'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.1'
27
+ - !ruby/object:Gem::Dependency
28
+ name: sinatra
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.4'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.4'
41
+ description: A programable Ruby server for Postmark inbound webhook. For example,
42
+ trigger notifications or automated response.
43
+ email:
44
+ - kenjij@gmail.com
45
+ executables:
46
+ - pin-server
47
+ extensions: []
48
+ extra_rdoc_files: []
49
+ files:
50
+ - LICENSE
51
+ - README.md
52
+ - bin/pin-server
53
+ - lib/postmark-inbound.rb
54
+ - lib/postmark-inbound/config.rb
55
+ - lib/postmark-inbound/handler.rb
56
+ - lib/postmark-inbound/helper.rb
57
+ - lib/postmark-inbound/logger.rb
58
+ - lib/postmark-inbound/server.rb
59
+ - lib/postmark-inbound/version.rb
60
+ - postmark-inbound.gemspec
61
+ homepage: https://github.com/kenjij/postmark-inbound
62
+ licenses:
63
+ - MIT
64
+ metadata: {}
65
+ post_install_message:
66
+ rdoc_options: []
67
+ require_paths:
68
+ - lib
69
+ required_ruby_version: !ruby/object:Gem::Requirement
70
+ requirements:
71
+ - - ">="
72
+ - !ruby/object:Gem::Version
73
+ version: 2.1.0
74
+ required_rubygems_version: !ruby/object:Gem::Requirement
75
+ requirements:
76
+ - - ">="
77
+ - !ruby/object:Gem::Version
78
+ version: '0'
79
+ requirements: []
80
+ rubyforge_project:
81
+ rubygems_version: 2.4.8
82
+ signing_key:
83
+ specification_version: 4
84
+ summary: A Ruby server for Postmark inbound webhook.
85
+ test_files: []