smyte 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: e49d6787f785de76ce55f896fea08e2cfbc98ed2
4
+ data.tar.gz: 187e626f692fc20205f284fe398be27408d3f25e
5
+ SHA512:
6
+ metadata.gz: ecf2767e5f099f9ffc8e4a9f70f443bd9709c58347457230c6097a2ba5f7f37bbeedd2617d3d44786fd890622b099b5f3c003ba71d98ab5ecd5c55b07fef4102
7
+ data.tar.gz: 848e86930a5c87cdcaba635d86992524d75e08efc56775c734bfe7606f1e2619258ca6c1c3be5128af4147c44491908fb50eb8065e57ebda756d42e7b36883e2
@@ -0,0 +1,10 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ *.gem
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --require spec_helper
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in smyte.gemspec
4
+ gemspec
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2016 Brian Leonard
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
13
+ all 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
21
+ THE SOFTWARE.
@@ -0,0 +1,77 @@
1
+ # Smyte
2
+
3
+ [Smyte](https://www.smyte.com) is a fraud-checking service. This gem makes it a bit easier to work with their [API](https://docs.smyte.com/).
4
+
5
+ ## Setup
6
+
7
+ `gem install smyte`
8
+
9
+ ```ruby
10
+ require 'smyte'
11
+
12
+ Smyte.enabled = true
13
+ Smyte.api_key = '123456789'
14
+ Smyte.api_secret = 'qwertyuiopasdfghjkl'
15
+ Smyte.webhook_secret = 'zxcvbnmpoiuytrewq'
16
+ ```
17
+
18
+ ## Sending data to Smyte
19
+
20
+ Uses this [API](https://docs.smyte.com/docs/sending-data-to-smyte).
21
+
22
+ ```ruby
23
+ payload = {
24
+ data: {
25
+ custom_id: 1
26
+ },
27
+ http_request: {
28
+ headers: {
29
+ "X-Real-IP" => "5.6.7.8"
30
+ },
31
+ path: "/login"
32
+ },
33
+ session: {
34
+ id: 'sessionguid',
35
+ action: {
36
+ user_id: 2
37
+ }
38
+ },
39
+ timestamp: Time.now
40
+ }
41
+
42
+ Smyte.action('my_event', payload)
43
+
44
+ ```
45
+
46
+ ## Receiving classification results
47
+
48
+ Uses this [API](https://docs.smyte.com/docs/receiving-classification-results).
49
+
50
+ ```ruby
51
+ # payload same as before (or other, of course)
52
+ classification = Smyte.classify('my_event', payload)
53
+
54
+ puts classification.action # will be :block, :review, or :allow
55
+ puts classification.label_report # for example on block ["exp:high_fail_rate_payment", "cc_country_mismatch"]
56
+
57
+ ```
58
+
59
+ ## Webhooks
60
+
61
+ Uses this [API](https://docs.smyte.com/docs/webhooks).
62
+ Smyte does a POST to your endpoint
63
+
64
+ ```ruby
65
+ # for example in a Rails controller action
66
+ secret = request.headers["X-Smyte-Signature"]
67
+ notification = ::Smyte.webhook(secret, params)
68
+ notification.items.each do |item|
69
+ puts item.action # will be :block, :review, or :allow
70
+ puts item.type # for example "user"
71
+ puts item.id # user id
72
+ puts item.label_report # for example on block ["bad_user"]
73
+ end
74
+
75
+ ```
76
+
77
+
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,45 @@
1
+ require "smyte/version"
2
+ require "forwardable"
3
+
4
+ module Smyte
5
+
6
+ autoload :Classification, 'smyte/classification'
7
+ autoload :Client, 'smyte/client'
8
+ autoload :Config, 'smyte/config'
9
+ autoload :LabelReporter, 'smyte/label_reporter'
10
+ autoload :Notification, 'smyte/notification'
11
+
12
+ class << self
13
+ extend Forwardable
14
+
15
+ def_delegators :config, :logger=, :logger,
16
+ :enabled=, :enabled, :enabled?,
17
+ :api_key=, :api_key,
18
+ :api_secret=, :api_secret,
19
+ :webhook_secret=, :webhook_secret
20
+
21
+ def_delegators :client, :action, :classify
22
+
23
+
24
+ def webhook(secret, response)
25
+ ::Smyte::Notification.parse(secret, response)
26
+ end
27
+
28
+ protected
29
+
30
+ def reset
31
+ # used by tests
32
+ @config = nil
33
+ end
34
+
35
+ def config
36
+ @config ||= ::Smyte::Config.new
37
+ end
38
+
39
+ def client
40
+ ::Smyte::Client.new
41
+ end
42
+
43
+ end
44
+
45
+ end
@@ -0,0 +1,63 @@
1
+ module Smyte
2
+ class Classification
3
+ include ::Smyte::LabelReporter
4
+
5
+ def self.allowed
6
+ new('verdict' => "ALLOW", 'labels' => {})
7
+ end
8
+
9
+ attr_reader :response
10
+
11
+ def initialize(response)
12
+ @response = response
13
+ end
14
+
15
+ protected
16
+
17
+ def parse_labels
18
+ out = []
19
+ labels = response["labels"] || {}
20
+ labels.each do |name, attributes|
21
+ out << Smyte::Classification::Label.new(name, attributes)
22
+ end
23
+ out
24
+ end
25
+
26
+ def parse_allow?
27
+ response["verdict"] == "ALLOW"
28
+ end
29
+
30
+ class Label
31
+ attr_reader :name, :response
32
+ def initialize(name, response)
33
+ @name = name
34
+ @response = response
35
+ end
36
+
37
+ # returns :block, :review, :allow, :unknown
38
+ def action
39
+ @action ||= calculate_action
40
+ end
41
+
42
+ def experimental?
43
+ !response["enabled"]
44
+ end
45
+
46
+ protected
47
+
48
+ def calculate_action
49
+ return :allow if response["verdict"] == "ALLOW"
50
+
51
+ if response["verdict"] == "BLOCK"
52
+ if experimental?
53
+ return :review
54
+ else
55
+ return :block
56
+ end
57
+ end
58
+
59
+ return :unknown
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,80 @@
1
+ require 'faraday'
2
+ require 'json'
3
+ require 'logger'
4
+
5
+ module Smyte
6
+ class Client
7
+ attr_reader :api_key, :api_secret, :logger, :enabled
8
+
9
+ def initialize(options=nil)
10
+ options ||= {}
11
+ [:api_key, :api_secret, :logger, :enabled].each do |key|
12
+ if options.has_key?(key)
13
+ value = options[key]
14
+ else
15
+ value = Smyte.send(key)
16
+ end
17
+ instance_variable_set("@#{key}", value)
18
+ end
19
+ end
20
+
21
+ def action(event_name, payload)
22
+ return true if !enabled
23
+
24
+ process(event_name, payload, "/v2/action")
25
+ true # any success is just true - nothing else to know
26
+ end
27
+
28
+ def classify(event_name, payload)
29
+ return Smyte::Classification.allowed if !enabled
30
+
31
+ response = process(event_name, payload, "/v2/action/classify")
32
+ Smyte::Classification.new(response)
33
+ end
34
+
35
+ protected
36
+
37
+ def connection
38
+ return @connection if @connection
39
+ options = {
40
+ url: 'https://api.smyte.com',
41
+ headers: {
42
+ 'Content-Type' => 'application/json'
43
+ }
44
+ }
45
+ @connection = Faraday.new(options) do |faraday|
46
+ # faraday.response :logger # log requests to STDOUT
47
+ faraday.adapter Faraday.default_adapter
48
+ end
49
+ @connection.basic_auth(api_key, api_secret)
50
+ @connection
51
+ end
52
+
53
+ def process(event_name, payload, path)
54
+ payload ||= {}
55
+ if payload.has_key?('name')
56
+ payload['name'] = event_name
57
+ else
58
+ payload[:name] = event_name
59
+ end
60
+
61
+ response = connection.post do |req|
62
+ req.url path
63
+ req.body = payload.to_json
64
+ end
65
+
66
+ if response.success?
67
+ return JSON.parse(response.body)
68
+ else
69
+ hash = JSON.parse(response.body) rescue nil
70
+ error = "Smyte Error (#{response.status})"
71
+ if hash && hash["message"]
72
+ error << ": "
73
+ error << hash["message"]
74
+ error << "\n"
75
+ end
76
+ raise error
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,34 @@
1
+ module Smyte
2
+ class Config
3
+ attr_accessor :logger
4
+ attr_writer :api_key, :api_secret, :webhook_secret
5
+
6
+ def api_key
7
+ return @api_key if @api_key
8
+ raise "Smyte api_key not set"
9
+ end
10
+
11
+ def api_secret
12
+ return @api_secret if @api_secret
13
+ raise "Smyte api_secret not set"
14
+ end
15
+
16
+ def webhook_secret
17
+ return @webhook_secret if @webhook_secret
18
+ raise "Smyte webhook_secret not set"
19
+ end
20
+
21
+ def enabled=input
22
+ @enabled = !!(input.to_s =~ /^(t|1|y|ok)/)
23
+ end
24
+
25
+ def enabled
26
+ return true if @enabled.nil?
27
+ @enabled
28
+ end
29
+
30
+ def enabled?
31
+ enabled
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,60 @@
1
+ module Smyte
2
+ module LabelReporter
3
+ # class needs to define parse_labels and parse_allow?
4
+ # which returns objects that have an action
5
+
6
+ # returns :block, :review, :allow, :unknown
7
+ def action
8
+ @action ||= calculate_action
9
+ end
10
+
11
+ def labels
12
+ @labels ||= parse_labels
13
+ end
14
+
15
+ def label_actions
16
+ return @label_actions if @label_actions
17
+ found = Hash.new { |hash, key| hash[key] = [] }
18
+ labels.each do |label|
19
+ found[label.action] << label
20
+ end
21
+
22
+ @label_actions = found
23
+ end
24
+
25
+ def label_report
26
+ return @label_report if @label_report
27
+ out = []
28
+
29
+ labels.each do |label|
30
+ case label.action
31
+ when :block, :review
32
+ out << label.name
33
+ end
34
+ end
35
+
36
+ @label_report = out
37
+ end
38
+
39
+ def reset_labels
40
+ remove_instance_variable(:@label_report) if defined?(@label_report)
41
+ remove_instance_variable(:@label_actions) if defined?(@label_actions)
42
+ remove_instance_variable(:@labels) if defined?(@labels)
43
+ remove_instance_variable(:@action) if defined?(@action)
44
+ end
45
+
46
+ protected
47
+
48
+ def calculate_action
49
+ return :allow if respond_to?(:parse_allow?, true) && parse_allow?
50
+
51
+ if label_actions[:block].size > 0
52
+ return :block
53
+ elsif label_actions[:review].size > 0
54
+ return :review
55
+ else
56
+ return :unknown
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,109 @@
1
+ module Smyte
2
+ class Notification
3
+ def self.parse(webhook_secret, response)
4
+ if webhook_secret != Smyte.webhook_secret
5
+ raise "invalid webhook_secret: #{webhook_secret}"
6
+ end
7
+ Smyte::Notification.new(response)
8
+ end
9
+
10
+ attr_reader :response
11
+
12
+ def initialize(response)
13
+ @response = response
14
+ end
15
+
16
+ def items
17
+ @items ||= parse_items
18
+ end
19
+
20
+ protected
21
+
22
+ def parse_items
23
+ out = {}
24
+ items = response["items"] || []
25
+ items.each do |hash|
26
+ key = hash["item"]
27
+ next unless key
28
+ if out[key]
29
+ out[key].send(:add_response, hash)
30
+ else
31
+ out[key] = Smyte::Notification::Item.new(hash)
32
+ end
33
+ end
34
+
35
+ out.values
36
+ end
37
+
38
+ class Item
39
+ include ::Smyte::LabelReporter
40
+
41
+ attr_reader :responses
42
+
43
+ def initialize(first_response)
44
+ @responses = [first_response]
45
+ end
46
+
47
+ def key
48
+ @item ||= responses.first["item"]
49
+ end
50
+
51
+ def type
52
+ @type ||= key.split("/").first
53
+ end
54
+
55
+ def id
56
+ @id ||= key.split("/").last
57
+ end
58
+
59
+ protected
60
+
61
+ def add_response(response)
62
+ reset_labels
63
+ responses << response
64
+ responses
65
+ end
66
+
67
+ def parse_labels
68
+ out = []
69
+ responses.each do |hash|
70
+ out << Smyte::Notification::Label.new(hash)
71
+ end
72
+ out
73
+ end
74
+ end
75
+
76
+ class Label
77
+ attr_reader :name, :response
78
+ def initialize(response)
79
+ @name = response["labelName"]
80
+ @response = response
81
+ end
82
+
83
+ # returns :block, :review, :allow, :unknown
84
+ def action
85
+ @action ||= calculate_action
86
+ end
87
+
88
+ def experimental?
89
+ response["labelType"] == "PENDING"
90
+ end
91
+
92
+ protected
93
+
94
+ def calculate_action
95
+ case response["labelType"]
96
+ when "ALLOW"
97
+ return :allow
98
+ when "PENDING"
99
+ return :review
100
+ when "ADDED", "BLOCK"
101
+ return :block
102
+ else
103
+ return :unknown
104
+ end
105
+ end
106
+ end
107
+
108
+ end
109
+ end
@@ -0,0 +1,3 @@
1
+ module Smyte
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,30 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'smyte/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "smyte"
8
+ spec.version = Smyte::VERSION
9
+ spec.authors = ["Brian Leonard"]
10
+ spec.email = ["brian@bleonard.com"]
11
+
12
+ spec.summary = %q{Talk to Smyte service}
13
+ spec.description = %q{Smyte does fraud detection}
14
+ spec.homepage = "https://www.taskrabbit.com"
15
+ spec.license = "MIT"
16
+
17
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
18
+ spec.bindir = "exe"
19
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
20
+ spec.require_paths = ["lib"]
21
+
22
+ spec.add_dependency "faraday"
23
+
24
+ spec.add_development_dependency "bundler", "~> 1.10"
25
+ spec.add_development_dependency "rake", "~> 10.0"
26
+ spec.add_development_dependency "rspec", "~> 3.4"
27
+ spec.add_development_dependency "byebug", "~> 3.5.1"
28
+ spec.add_development_dependency "vcr", "~> 3.0.1"
29
+ spec.add_development_dependency "webmock", "~> 1.24.3"
30
+ end
metadata ADDED
@@ -0,0 +1,157 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: smyte
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Brian Leonard
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2016-04-26 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: faraday
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
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: bundler
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.10'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.10'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '10.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '10.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '3.4'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '3.4'
69
+ - !ruby/object:Gem::Dependency
70
+ name: byebug
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: 3.5.1
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: 3.5.1
83
+ - !ruby/object:Gem::Dependency
84
+ name: vcr
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: 3.0.1
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: 3.0.1
97
+ - !ruby/object:Gem::Dependency
98
+ name: webmock
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: 1.24.3
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: 1.24.3
111
+ description: Smyte does fraud detection
112
+ email:
113
+ - brian@bleonard.com
114
+ executables: []
115
+ extensions: []
116
+ extra_rdoc_files: []
117
+ files:
118
+ - ".gitignore"
119
+ - ".rspec"
120
+ - Gemfile
121
+ - LICENSE.txt
122
+ - README.md
123
+ - Rakefile
124
+ - lib/smyte.rb
125
+ - lib/smyte/classification.rb
126
+ - lib/smyte/client.rb
127
+ - lib/smyte/config.rb
128
+ - lib/smyte/label_reporter.rb
129
+ - lib/smyte/notification.rb
130
+ - lib/smyte/version.rb
131
+ - smyte.gemspec
132
+ homepage: https://www.taskrabbit.com
133
+ licenses:
134
+ - MIT
135
+ metadata: {}
136
+ post_install_message:
137
+ rdoc_options: []
138
+ require_paths:
139
+ - lib
140
+ required_ruby_version: !ruby/object:Gem::Requirement
141
+ requirements:
142
+ - - ">="
143
+ - !ruby/object:Gem::Version
144
+ version: '0'
145
+ required_rubygems_version: !ruby/object:Gem::Requirement
146
+ requirements:
147
+ - - ">="
148
+ - !ruby/object:Gem::Version
149
+ version: '0'
150
+ requirements: []
151
+ rubyforge_project:
152
+ rubygems_version: 2.2.3
153
+ signing_key:
154
+ specification_version: 4
155
+ summary: Talk to Smyte service
156
+ test_files: []
157
+ has_rdoc: