castle-rb 2.2.0 → 2.3.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.
Files changed (42) hide show
  1. checksums.yaml +2 -5
  2. data/README.md +1 -1
  3. data/lib/castle-rb.rb +2 -19
  4. data/lib/castle.rb +42 -0
  5. data/lib/castle/api.rb +48 -0
  6. data/lib/castle/client.rb +43 -0
  7. data/lib/castle/configuration.rb +39 -0
  8. data/lib/{castle-rb/support → castle}/cookie_store.rb +9 -5
  9. data/lib/castle/errors.rb +27 -0
  10. data/lib/castle/extractors/client_id.rb +28 -0
  11. data/lib/castle/extractors/headers.rb +36 -0
  12. data/lib/castle/extractors/ip.rb +16 -0
  13. data/lib/castle/headers.rb +39 -0
  14. data/lib/castle/request.rb +34 -0
  15. data/lib/castle/response.rb +43 -0
  16. data/lib/castle/support.rb +11 -0
  17. data/lib/{castle-rb → castle}/support/padrino.rb +1 -1
  18. data/lib/{castle-rb → castle}/support/rails.rb +2 -0
  19. data/lib/{castle-rb → castle}/support/sinatra.rb +1 -1
  20. data/lib/castle/system.rb +36 -0
  21. data/lib/castle/version.rb +5 -0
  22. data/spec/lib/castle/api_spec.rb +54 -0
  23. data/spec/lib/castle/client_spec.rb +64 -0
  24. data/spec/lib/castle/configuration_spec.rb +98 -0
  25. data/spec/lib/castle/extractors/client_id_spec.rb +39 -0
  26. data/spec/lib/castle/extractors/headers_spec.rb +25 -0
  27. data/spec/lib/castle/extractors/ip_spec.rb +20 -0
  28. data/spec/lib/castle/headers_spec.rb +82 -0
  29. data/spec/lib/castle/request_spec.rb +37 -0
  30. data/spec/lib/castle/response_spec.rb +71 -0
  31. data/spec/lib/castle/system_spec.rb +70 -0
  32. data/spec/lib/castle/version_spec.rb +9 -0
  33. data/spec/lib/castle_spec.rb +73 -0
  34. data/spec/spec_helper.rb +9 -3
  35. metadata +49 -76
  36. data/lib/castle-rb/api.rb +0 -94
  37. data/lib/castle-rb/client.rb +0 -67
  38. data/lib/castle-rb/configuration.rb +0 -48
  39. data/lib/castle-rb/errors.rb +0 -15
  40. data/lib/castle-rb/version.rb +0 -3
  41. data/spec/api_spec.rb +0 -31
  42. data/spec/client_spec.rb +0 -55
data/lib/castle-rb/api.rb DELETED
@@ -1,94 +0,0 @@
1
- module Castle
2
- class API
3
- attr_accessor :http, :headers
4
-
5
- def initialize(cookie_id, ip, headers)
6
- @http = Net::HTTP.new(Castle.config.api_endpoint.host,
7
- Castle.config.api_endpoint.port)
8
-
9
- @http.read_timeout = Castle.config.request_timeout
10
-
11
- if Castle.config.api_endpoint.scheme == 'https'
12
- @http.use_ssl = true
13
- @http.verify_mode = OpenSSL::SSL::VERIFY_PEER
14
- end
15
-
16
- @headers = {
17
- "Content-Type" => "application/json",
18
- "X-Castle-Cookie-Id" => cookie_id,
19
- "X-Castle-Ip" => ip,
20
- "X-Castle-Headers" => headers,
21
- "X-Castle-Client-User-Agent" => JSON.generate(client_user_agent),
22
- "X-Castle-Source" => Castle.config.source_header,
23
- "User-Agent" => "Castle/v1 RubyBindings/#{Castle::VERSION}"
24
- }
25
-
26
- @headers.delete_if { |k, v| v.nil? }
27
- end
28
-
29
- def request(endpoint, args, method: :post)
30
- http_method = method.to_s.capitalize
31
- req = Net::HTTP.const_get(http_method).new(
32
- "#{Castle.config.api_endpoint.path}/#{endpoint}", @headers)
33
- req.basic_auth("", Castle.config.api_secret)
34
- req.body = args.to_json unless http_method == 'Get'
35
-
36
- begin
37
- response = @http.request(req)
38
- rescue Timeout::Error, Errno::EINVAL, Errno::ECONNRESET, EOFError,
39
- Net::HTTPBadResponse, Net::HTTPHeaderSyntaxError,
40
- Net::ProtocolError, Net::ReadTimeout => e
41
- raise Castle::RequestError, 'Castle API connection error'
42
- end
43
-
44
- case response.code.to_i
45
- when 200..299
46
- # OK
47
- when 400
48
- raise Castle::BadRequestError, response[:message]
49
- when 401
50
- raise Castle::UnauthorizedError, response[:message]
51
- when 403
52
- raise Castle::ForbiddenError, response[:message]
53
- when 404
54
- raise Castle::NotFoundError, response[:message]
55
- when 419
56
- raise Castle::UserUnauthorizedError, response[:message]
57
- when 422
58
- raise Castle::InvalidParametersError, response[:message]
59
- else
60
- raise Castle::ApiError, response[:message]
61
- end
62
-
63
- if response.body.nil? || response.body.empty?
64
- {}
65
- else
66
- begin
67
- JSON.parse(response.body, :symbolize_names => true)
68
- rescue JSON::ParserError
69
- raise Castle::ApiError, 'Invalid response from Castle API'
70
- end
71
- end
72
- end
73
-
74
- def client_user_agent
75
- @uname ||= get_uname
76
- lang_version = "#{RUBY_VERSION} p#{RUBY_PATCHLEVEL} (#{RUBY_RELEASE_DATE})"
77
-
78
- {
79
- :bindings_version => Castle::VERSION,
80
- :lang => 'ruby',
81
- :lang_version => lang_version,
82
- :platform => RUBY_PLATFORM,
83
- :publisher => 'castle',
84
- :uname => @uname
85
- }
86
- end
87
-
88
- def get_uname
89
- `uname -a 2>/dev/null`.strip if RUBY_PLATFORM =~ /linux|darwin/i
90
- rescue Errno::ENOMEM # couldn't create subprocess
91
- "uname lookup failed"
92
- end
93
- end
94
- end
@@ -1,67 +0,0 @@
1
- module Castle
2
- class Client
3
- attr_accessor :do_not_track, :api
4
-
5
- def initialize(request, response)
6
- cookie_id = extract_cookie(request, response)['__cid'] || ''
7
- ip = request.ip
8
- headers = header_string(request)
9
-
10
- @api = API.new(cookie_id, ip, headers)
11
- end
12
-
13
- def fetch_review(id)
14
- @api.request("reviews/#{id}", nil, method: :get)
15
- end
16
-
17
- def identify(args)
18
- @api.request('identify', args) unless do_not_track?
19
- end
20
-
21
- def authenticate(args)
22
- @api.request('authenticate', args)
23
- end
24
-
25
- def track(args)
26
- @api.request('track', args) unless do_not_track?
27
- end
28
-
29
- def do_not_track!
30
- @do_not_track = true
31
- end
32
-
33
- def track!
34
- @do_not_track = false
35
- end
36
-
37
- def do_not_track?
38
- !!@do_not_track
39
- end
40
-
41
-
42
- private
43
-
44
- # Extract the cookie set by the Castle Javascript
45
- def extract_cookie(request, response)
46
- if response.class.name == 'ActionDispatch::Cookies::CookieJar'
47
- Castle::CookieStore::Rack.new(response)
48
- else
49
- Castle::CookieStore::Base.new(request, response)
50
- end
51
- end
52
-
53
- # Serialize HTTP headers
54
- def header_string(request)
55
- scrub_headers = ['Cookie']
56
-
57
- headers = request.env.keys.grep(/^HTTP_/).map do |header|
58
- name = header.gsub(/^HTTP_/, '').split('_').map(&:capitalize).join('-')
59
- unless scrub_headers.include?(name)
60
- { name => request.env[header] }
61
- end
62
- end.compact.inject(:merge)
63
-
64
- JSON.generate(headers || {})
65
- end
66
- end
67
- end
@@ -1,48 +0,0 @@
1
- module Castle
2
- class << self
3
- def configure(config_hash=nil)
4
- if config_hash
5
- config_hash.each do |k,v|
6
- config.send("#{k}=", v)
7
- end
8
- end
9
-
10
- yield(config) if block_given?
11
- end
12
-
13
- def config
14
- @configuration ||= Castle::Configuration.new
15
- end
16
-
17
- def api_secret=(api_secret)
18
- config.api_secret = api_secret
19
- end
20
- end
21
-
22
- class Configuration
23
- attr_accessor :request_timeout
24
- attr_accessor :source_header
25
-
26
- def initialize
27
- self.request_timeout = 30.0
28
- self.api_endpoint =
29
- ENV['CASTLE_API_ENDPOINT'] || 'https://api.castle.io/v1'
30
- end
31
-
32
- def api_secret
33
- ENV['CASTLE_API_SECRET'] || @_api_secret || ''
34
- end
35
-
36
- def api_secret=(value)
37
- @_api_secret = value
38
- end
39
-
40
- def api_endpoint
41
- @_api_endpoint
42
- end
43
-
44
- def api_endpoint=(value)
45
- @_api_endpoint = URI(value)
46
- end
47
- end
48
- end
@@ -1,15 +0,0 @@
1
- class Castle::Error < Exception; end
2
-
3
- class Castle::RequestError < Castle::Error; end
4
- class Castle::SecurityError < Castle::Error; end
5
- class Castle::ConfigurationError < Castle::Error; end
6
-
7
- class Castle::ApiError < Castle::Error; end
8
-
9
- class Castle::BadRequestError < Castle::ApiError; end
10
- class Castle::ForbiddenError < Castle::ApiError; end
11
- class Castle::NotFoundError < Castle::ApiError; end
12
- class Castle::UserUnauthorizedError < Castle::ApiError; end
13
- class Castle::InvalidParametersError < Castle::ApiError; end
14
-
15
- class Castle::UnauthorizedError < Castle::ApiError; end
@@ -1,3 +0,0 @@
1
- module Castle
2
- VERSION = "2.2.0"
3
- end
data/spec/api_spec.rb DELETED
@@ -1,31 +0,0 @@
1
- require 'spec_helper'
2
-
3
- describe Castle::API do
4
- let(:api) { Castle::API.new('abcd', '1.2.3.4', '{}') }
5
-
6
- it 'handles timeout' do
7
- stub_request(:any, /api.castle.io/).to_timeout
8
- expect {
9
- api.request('authenticate', user_id: '1234')
10
- }.to raise_error(Castle::RequestError)
11
- end
12
-
13
- it 'handles non-OK response code' do
14
- stub_request(:any, /api.castle.io/).to_return(status: 400)
15
- expect {
16
- api.request('authenticate', user_id: '1234')
17
- }.to raise_error(Castle::BadRequestError)
18
- end
19
-
20
- it 'handles custom API endpoint' do
21
- stub_request(:any, /new.herokuapp.com/)
22
-
23
- api_endpoint = 'http://new.herokuapp.com:3000/v2'
24
- Castle.config.api_endpoint = api_endpoint
25
- uri = URI(api_endpoint)
26
-
27
- api.request('authenticate', user_id: '1234')
28
- assert_requested :post,
29
- "#{api_endpoint.gsub(/new/, ":secret@new")}/authenticate", times: 1
30
- end
31
- end
data/spec/client_spec.rb DELETED
@@ -1,55 +0,0 @@
1
- require 'spec_helper'
2
-
3
- class Request < Rack::Request
4
- def delegate?; false; end
5
- end
6
-
7
- describe Castle::Client do
8
- let(:ip) {'1.2.3.4' }
9
- let(:cookie_id) { 'abcd' }
10
- let(:env) do
11
- Rack::MockRequest.env_for('/',
12
- 'HTTP_X_FORWARDED_FOR' => '1.2.3.4',
13
- 'HTTP_COOKIE' => "__cid=#{cookie_id};other=efgh"
14
- )
15
- end
16
- let(:request) { Request.new(env) }
17
- let(:client) { Castle::Client.new(request, nil) }
18
- let(:review_id) { '12356789' }
19
-
20
- it 'parses the request' do
21
- expect(Castle::API).to receive(:new).with(cookie_id, ip,
22
- "{\"X-Forwarded-For\":\"#{ip}\"}").and_call_original
23
-
24
- client = Castle::Client.new(request, nil)
25
- client.authenticate({name: '$login.succeeded', user_id: '1234'})
26
- end
27
-
28
- it 'identifies' do
29
- client.identify( user_id: '1234', traits: { name: 'Jo' })
30
- assert_requested :post, 'https://:secret@api.castle.io/v1/identify',
31
- times: 1,
32
- body: { user_id: '1234', traits: { name: 'Jo' } }
33
- end
34
-
35
- it 'authenticates' do
36
- client.authenticate(name: '$login.succeeded', user_id: '1234')
37
- assert_requested :post, 'https://:secret@api.castle.io/v1/authenticate',
38
- times: 1,
39
- body: { name: '$login.succeeded', user_id: "1234" }
40
- end
41
-
42
- it 'tracks' do
43
- client.track(name: '$login.succeeded', user_id: '1234')
44
- assert_requested :post, 'https://:secret@api.castle.io/v1/track',
45
- times: 1,
46
- body: { name: '$login.succeeded', user_id: "1234" }
47
- end
48
-
49
- it 'fetches review' do
50
- client.fetch_review(review_id)
51
-
52
- assert_requested :get, "https://:secret@api.castle.io/v1/reviews/#{review_id}",
53
- times: 1
54
- end
55
- end