opensrs 0.3.4 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
data/Rakefile CHANGED
@@ -1,40 +1 @@
1
- require 'rubygems'
2
- require 'bundler'
3
-
4
- begin
5
- Bundler.setup(:default, :development)
6
- rescue Bundler::BundlerError => e
7
- $stderr.puts e.message
8
- $stderr.puts "Run `bundle install` to install missing gems"
9
- exit e.status_code
10
- end
11
-
12
- $LOAD_PATH.unshift("lib")
13
-
14
- require 'rake'
15
- require 'opensrs'
16
-
17
- begin
18
- require 'jeweler'
19
-
20
- Jeweler::Tasks.new do |gem|
21
- gem.name = "opensrs"
22
- gem.version = OpenSRS::Version::VERSION
23
- gem.summary = "Provides support to utilize the OpenSRS API with Ruby/Rails."
24
- gem.description = "Provides support to utilize the OpenSRS API with Ruby/Rails."
25
- gem.email = "jdelsman@voxxit.com"
26
- gem.homepage = "http://github.com/voxxit/opensrs"
27
- gem.license = "MIT"
28
- gem.authors = ["Josh Delsman"]
29
-
30
- # Requirements are in Gemfile
31
- end
32
- rescue LoadError
33
- puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
34
- end
35
-
36
- require 'rspec/core/rake_task'
37
-
38
- RSpec::Core::RakeTask.new(:spec)
39
-
40
- task :default => :spec
1
+ require 'bundler/gem_tasks'
data/SECURITY.md ADDED
@@ -0,0 +1,12 @@
1
+ # Security Policy
2
+
3
+ ## Supported Versions
4
+
5
+ | Version | Supported |
6
+ | ------- | ------------------ |
7
+ | 0.4.x | :white_check_mark: |
8
+ | < 0.4.x | :x: |
9
+
10
+ ## Reporting a Vulnerability
11
+
12
+ Please encyrpt your message to me at https://keybase.io/encrypt#jdelsman, then send to j@srv.im. Since this is an unsupported open-source project, I will get to it as quickly as I can.
data/bin/console ADDED
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env ruby
2
+ require 'bundler/setup'
3
+ require 'opensrs'
4
+
5
+ require 'pry'
6
+ Pry.start
7
+
8
+ require 'irb'
9
+ IRB.start
data/bin/setup ADDED
@@ -0,0 +1,7 @@
1
+ #!/bin/bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+
5
+ bundle install
6
+
7
+ # Do any other automated setup that you need to do here
data/lib/opensrs.rb CHANGED
@@ -1,8 +1,9 @@
1
- require File.dirname(__FILE__) + '/opensrs/xml_processor'
2
- require File.dirname(__FILE__) + '/opensrs/xml_processor/libxml.rb'
3
- require File.dirname(__FILE__) + '/opensrs/server.rb'
4
- require File.dirname(__FILE__) + '/opensrs/response.rb'
5
- require File.dirname(__FILE__) + '/opensrs/version.rb'
1
+ require 'opensrs/xml_processor'
2
+ require 'opensrs/server'
3
+ require 'opensrs/response'
4
+ require 'opensrs/sanitizable_string'
5
+ require 'opensrs/version'
6
6
 
7
+ # OpenSRS
7
8
  module OpenSRS
8
- end
9
+ end
@@ -1,29 +1,35 @@
1
1
  module OpenSRS
2
+ # Response
2
3
  class Response
3
4
  attr_reader :request_xml, :response_xml
4
5
  attr_accessor :response, :success
5
-
6
+
6
7
  def initialize(parsed_response, request_xml, response_xml)
7
8
  @response = parsed_response
8
9
  @request_xml = request_xml
9
10
  @response_xml = response_xml
10
11
  @success = success?
11
12
  end
12
-
13
+
13
14
  # We need to return the error message unless the
14
15
  # response is successful.
15
16
  def errors
16
- if !success?
17
- if response["response_text"] and response["response_code"]
18
- "#{response["response_text"]} (Code #{response["response_code"]})"
19
- else
20
- "Unknown error"
21
- end
22
- end
17
+ return if success?
18
+
19
+ msg = @response['response_text']
20
+ code = @response['response_code']
21
+
22
+ msg && code ? "#{msg} (Code #{code})" : 'Unknown error'
23
23
  end
24
-
24
+
25
+ # If 'is_success' is returned, the API is letting us know that they
26
+ # will explicitly tell us whether something has succeeded or not.
27
+ #
28
+ # Otherwise, just assume it failed.
25
29
  def success?
26
- response["is_success"] ? response["is_success"].to_s == "1" : true
30
+ return false unless @response['is_success']
31
+
32
+ @response['is_success'] == '1'
27
33
  end
28
34
  end
29
- end
35
+ end
@@ -0,0 +1,21 @@
1
+ require 'delegate'
2
+
3
+ module OpenSRS
4
+ # SanitizableString
5
+ class SanitizableString < SimpleDelegator
6
+ @enable_sanitization = false
7
+
8
+ class << self
9
+ attr_accessor :enable_sanitization
10
+ end
11
+
12
+ def initialize(original_string, sanitized_string)
13
+ super(original_string)
14
+ @sanitized_string = sanitized_string
15
+ end
16
+
17
+ def sanitized
18
+ self.class.enable_sanitization ? @sanitized_string : self
19
+ end
20
+ end
21
+ end
@@ -1,37 +1,53 @@
1
- require "uri"
2
- require "net/https"
3
- require "digest/md5"
4
- require "openssl"
1
+ require 'uri'
2
+ require 'net/https'
3
+ require 'digest/md5'
4
+ require 'openssl'
5
5
 
6
6
  module OpenSRS
7
- class BadResponse < StandardError; end
8
- class TimeoutError < StandardError; end
7
+ class OpenSRSError < StandardError; end
9
8
 
9
+ class BadResponse < OpenSRSError; end
10
+
11
+ class ConnectionError < OpenSRSError; end
12
+
13
+ class TimeoutError < ConnectionError; end
14
+
15
+ # Server
10
16
  class Server
11
- attr_accessor :server, :username, :password, :key, :timeout, :open_timeout
17
+ attr_accessor :server, :username, :password, :key, :timeout, :open_timeout, :logger, :proxy
12
18
 
13
19
  def initialize(options = {})
14
- @server = URI.parse(options[:server] || "https://rr-n1-tor.opensrs.net:55443/")
20
+ @server = URI.parse(options[:server] || 'https://rr-n1-tor.opensrs.net:55443/')
15
21
  @username = options[:username]
16
22
  @password = options[:password]
17
23
  @key = options[:key]
18
24
  @timeout = options[:timeout]
19
- @open_timeout = options[:open_timeout]
25
+ @open_timeout = options[:open_timeout]
26
+ @logger = options[:logger]
27
+ @proxy = URI.parse(options[:proxy]) if options[:proxy]
28
+ @sanitize_request = options[:sanitize_request]
29
+
30
+ OpenSRS::SanitizableString.enable_sanitization = @sanitize_request
20
31
  end
21
32
 
22
33
  def call(data = {})
23
- xml = xml_processor.build({ :protocol => "XCP" }.merge!(data))
34
+ xml = xml_processor.build({ protocol: 'XCP' }.merge!(data))
35
+ log('Request', xml.sanitized, data)
24
36
 
25
37
  begin
26
38
  response = http.post(server_path, xml, headers(xml))
39
+ log('Response', response.body, data)
27
40
  rescue Net::HTTPBadResponse
28
- raise OpenSRS::BadResponse, "Received a bad response from OpenSRS. Please check that your IP address is added to the whitelist, and try again."
41
+ raise OpenSRS::BadResponse,
42
+ 'Received a bad response from OpenSRS. Please check that your IP address is added to the whitelist, and try again.'
29
43
  end
30
44
 
31
45
  parsed_response = xml_processor.parse(response.body)
32
- return OpenSRS::Response.new(parsed_response, xml, response.body)
33
- rescue Timeout::Error => err
34
- raise OpenSRS::TimeoutError, err
46
+ OpenSRS::Response.new(parsed_response, xml.sanitized, response.body)
47
+ rescue Timeout::Error => e
48
+ raise OpenSRS::TimeoutError, e
49
+ rescue Errno::ECONNRESET, Errno::ECONNREFUSED => e
50
+ raise OpenSRS::ConnectionError, e
35
51
  end
36
52
 
37
53
  def xml_processor
@@ -39,37 +55,49 @@ module OpenSRS
39
55
  end
40
56
 
41
57
  def self.xml_processor=(name)
42
- require File.dirname(__FILE__) + "/xml_processor/#{name.to_s.downcase}"
43
- @@xml_processor = OpenSRS::XmlProcessor.const_get("#{name.to_s.capitalize}")
58
+ require "opensrs/xml_processor/#{name.to_s.downcase}"
59
+ @@xml_processor = OpenSRS::XmlProcessor.const_get(name.to_s.capitalize.to_s)
44
60
  end
45
61
 
46
- OpenSRS::Server.xml_processor = :libxml
47
-
48
62
  private
49
63
 
50
64
  def headers(request)
51
- { "Content-Length" => request.length.to_s,
52
- "Content-Type" => "text/xml",
53
- "X-Username" => username,
54
- "X-Signature" => signature(request)
65
+ {
66
+ 'Content-Length' => request.length.to_s,
67
+ 'Content-Type' => 'text/xml',
68
+ 'X-Username' => username,
69
+ 'X-Signature' => signature(request)
55
70
  }
56
71
  end
57
72
 
58
73
  def signature(request)
59
- signature = Digest::MD5.hexdigest(request + key)
60
- signature = Digest::MD5.hexdigest(signature + key)
61
- signature
74
+ Digest::MD5.hexdigest(Digest::MD5.hexdigest(request + key) + key)
62
75
  end
63
76
 
64
77
  def http
65
- http = Net::HTTP.new(server.host, server.port)
66
- http.use_ssl = (server.scheme == "https")
78
+ http = if @proxy
79
+ Net::HTTP.new(server.host, server.port, @proxy.host, @proxy.port, @proxy.user, @proxy.password)
80
+ else
81
+ Net::HTTP.new(server.host, server.port)
82
+ end
83
+
84
+ http.use_ssl = (server.scheme == 'https')
67
85
  http.verify_mode = OpenSSL::SSL::VERIFY_NONE
68
86
  http.read_timeout = http.open_timeout = @timeout if @timeout
69
87
  http.open_timeout = @open_timeout if @open_timeout
70
88
  http
71
89
  end
72
90
 
91
+ def log(type, data, options = {})
92
+ return unless logger
93
+
94
+ message = "[OpenSRS] #{type} XML"
95
+ message = "#{message} for #{options[:object]} #{options[:action]}" if options[:object] && options[:action]
96
+
97
+ line = [message, data].join("\n")
98
+ logger.info(line)
99
+ end
100
+
73
101
  def server_path
74
102
  server.path.empty? ? '/' : server.path
75
103
  end
@@ -1,5 +1,3 @@
1
1
  module OpenSRS
2
- class Version
3
- VERSION = "0.3.4"
4
- end
2
+ VERSION = '0.4.0'.freeze
5
3
  end
@@ -1,59 +1,59 @@
1
1
  module OpenSRS
2
-
2
+ # XmlProcessor
3
3
  class XmlProcessor
4
-
5
4
  # Parses the main data block from OpenSRS and discards
6
5
  # the rest of the response.
7
6
  def self.parse(response)
8
7
  data_block = data_block_element(response)
9
8
 
10
- raise ArgumentError.new("No data found in document") if !data_block
9
+ raise ArgumentError, 'No data found in document' unless data_block
11
10
 
12
- return decode_data(data_block)
11
+ decode_data(data_block)
13
12
  end
14
13
 
15
- protected
16
-
17
14
  # Encodes individual elements, and their child elements, for the root XML document.
18
15
  def self.encode_data(data, container = nil)
19
- case data.class.to_s
20
- when "Array" then return encode_dt_array(data, container)
21
- when "Hash" then return encode_dt_assoc(data, container)
22
- when "String", "Numeric", "Date", "Time", "Symbol", "NilClass"
23
- return data.to_s
16
+ case data
17
+ when Array
18
+ encode_dt_array(data, container)
19
+ when Hash
20
+ encode_dt_assoc(data, container)
21
+ when String, Numeric, Date, Time, Symbol, NilClass
22
+ data.to_s
24
23
  else
25
- return data.inspect
24
+ data.inspect
26
25
  end
27
-
28
- return nil
29
26
  end
30
27
 
31
28
  def self.encode_dt_array(data, container)
32
- dt_array = new_element(:dt_array, container)
33
-
34
- data.each_with_index do |item, index|
35
- item_node = new_element(:item, container)
36
- item_node["key"] = index.to_s
37
- item_node << encode_data(item, item_node)
38
-
39
- dt_array << item_node
40
- end
41
-
42
- return dt_array
29
+ build_element(:dt_array, data, container)
43
30
  end
44
31
 
45
32
  def self.encode_dt_assoc(data, container)
46
- dt_assoc = new_element(:dt_assoc, container)
33
+ build_element(:dt_assoc, data, container)
34
+ end
35
+
36
+ def self.build_element(type, data, container)
37
+ element = new_element(type, container)
47
38
 
48
- data.each do |key, value|
39
+ # if array, item = the item
40
+ # if hash, item will be array of the key & value
41
+ data.each_with_index do |item, index|
49
42
  item_node = new_element(:item, container)
50
- item_node["key"] = key.to_s
51
- item_node << encode_data(value, item_node)
43
+ item_node['key'] = item.is_a?(Array) ? item[0].to_s : index.to_s
44
+
45
+ value = item.is_a?(Array) ? item[1] : item
52
46
 
53
- dt_assoc << item_node
47
+ encoded_data = encode_data(value, item_node)
48
+ if encoded_data.is_a?(String)
49
+ item_node.content = encoded_data
50
+ else
51
+ item_node << encoded_data
52
+ end
53
+ element << item_node
54
54
  end
55
55
 
56
- return dt_assoc
56
+ element
57
57
  end
58
58
 
59
59
  # Recursively decodes individual data elements from OpenSRS
@@ -61,17 +61,16 @@ module OpenSRS
61
61
  def self.decode_data(data)
62
62
  data.each do |element|
63
63
  case element.name
64
- when "dt_array"
64
+ when 'dt_array'
65
65
  return decode_dt_array_data(element)
66
- when "dt_assoc"
66
+ when 'dt_assoc'
67
67
  return decode_dt_assoc_data(element)
68
- when "text", "item", "dt_scalar"
68
+ when 'text', 'item', 'dt_scalar'
69
69
  next if element.content.strip.empty?
70
+
70
71
  return element.content.strip
71
72
  end
72
73
  end
73
74
  end
74
-
75
75
  end
76
-
77
- end
76
+ end
@@ -1,60 +1,81 @@
1
- require "libxml"
1
+ begin
2
+ require 'libxml'
3
+ rescue LoadError => e
4
+ warn 'Cannot find `libxml` gem. Please install it before using it as the XML processor'
5
+ raise e
6
+ end
2
7
 
3
8
  module OpenSRS
4
- class XmlProcessor::Libxml < OpenSRS::XmlProcessor
5
- include ::LibXML::XML
6
-
7
- # First, builds REXML elements for the inputted data. Then, it will
8
- # go ahead and build the entire XML document to send to OpenSRS.
9
- def self.build(data)
10
- xml = Document.new
11
- xml.root = envelope = Node.new("OPS_envelope")
12
-
13
- envelope << header = Node.new("header")
14
- envelope << body = Node.new("body")
15
- header << Node.new("version", "0.9")
16
- body << data_block = Node.new("data_block")
17
-
18
- data_block << encode_data(data, data_block)
19
-
20
- return xml.to_s
21
- end
9
+ class XmlProcessor
10
+ # Libxml
11
+ class Libxml < OpenSRS::XmlProcessor
12
+ include ::LibXML::XML
22
13
 
23
- protected
14
+ # First, builds REXML elements for the inputted data. Then, it will
15
+ # go ahead and build the entire XML document to send to OpenSRS.
16
+ def self.build(data)
17
+ xml = Document.new
18
+ xml.root = envelope = Node.new('OPS_envelope')
24
19
 
25
- def self.data_block_element(response)
26
- doc = Parser.string(response).parse
27
- return doc.find("//OPS_envelope/body/data_block/*")
28
- end
20
+ envelope << header = Node.new('header')
21
+ envelope << body = Node.new('body')
22
+ header << Node.new('version', '0.9')
23
+ body << data_block = Node.new('data_block')
29
24
 
30
- def self.decode_dt_array_data(element)
31
- dt_array = []
25
+ data_block << encode_data(data, data_block)
32
26
 
33
- element.children.each do |item|
34
- next if item.empty?
35
- dt_array[item.attributes["key"].to_i] = decode_data(item)
27
+ OpenSRS::SanitizableString.new(xml.to_s, sanitize(xml).to_s)
36
28
  end
37
29
 
38
- return dt_array
39
- end
30
+ def self.sanitize(doc)
31
+ # Before changing the iteration through the nodes, read:
32
+ # https://www.rubydoc.info/gems/libxml-ruby/LibXML/XML/Document#find-instance_method
40
33
 
41
- def self.decode_dt_assoc_data(element)
42
- dt_assoc = {}
34
+ username_nodes = doc.find("//item[@key='reg_username']")
35
+ username_nodes.each { |node| node.content = 'FILTERED' }
43
36
 
44
- element.children.each do |item|
45
- next if item.content.strip.empty?
46
- dt_assoc[item.attributes["key"]] = decode_data(item)
37
+ password_nodes = doc.find("//item[@key='reg_password']")
38
+ password_nodes.each { |node| node.content = 'FILTERED' }
39
+
40
+ doc
47
41
  end
42
+ private_class_method :sanitize
48
43
 
49
- return dt_assoc
50
- end
44
+ def self.data_block_element(response)
45
+ doc = Parser.string(response).parse
46
+ doc.find('//OPS_envelope/body/data_block/*')
47
+ end
51
48
 
52
- # Accepts two parameters but uses only one; to keep the interface same as other xml parser classes
53
- # Is that a side effect of Template pattern?
54
- #
55
- def self.new_element(element_name, container)
56
- return Node.new(element_name.to_s)
57
- end
49
+ def self.decode_dt_array_data(element)
50
+ dt_array = []
51
+
52
+ element.children.each do |item|
53
+ next if item.empty?
54
+
55
+ dt_array[item.attributes['key'].to_i] = decode_data(item)
56
+ end
58
57
 
58
+ dt_array
59
+ end
60
+
61
+ def self.decode_dt_assoc_data(element)
62
+ dt_assoc = {}
63
+
64
+ element.children.each do |item|
65
+ next if item.content.strip.empty?
66
+
67
+ dt_assoc[item.attributes['key']] = decode_data(item)
68
+ end
69
+
70
+ dt_assoc
71
+ end
72
+
73
+ # Accepts two parameters but uses only one; to keep the interface same as other xml parser classes
74
+ # Is that a side effect of Template pattern?
75
+ #
76
+ def self.new_element(element_name, _container)
77
+ Node.new(element_name.to_s)
78
+ end
79
+ end
59
80
  end
60
- end
81
+ end