opensrs 0.3.4 → 0.4.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.
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