right_support 2.4.1 → 2.5.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -5,6 +5,8 @@ module RightSupport::Crypto
5
5
 
6
6
  if require_succeeds?('yajl')
7
7
  DefaultEncoding = ::Yajl
8
+ elsif require_succeeds?('oj')
9
+ DefaultEncoding = ::Oj
8
10
  elsif require_succeeds?('json')
9
11
  DefaultEncoding = ::JSON
10
12
  else
@@ -0,0 +1,223 @@
1
+ require 'time'
2
+ require 'date'
3
+
4
+ module RightSupport::Data
5
+ # Utility class that implements true, lossless Ruby-to-JSON serialization.
6
+ # With a few small exceptions, this module can #dump any Ruby object in your
7
+ # VM, and later #load the exact same object from its serialized representation.
8
+ # It can also be used with encoding schemes other than JSON, e.g. msgpack.
9
+ #
10
+ # This class works by transforming Ruby object graphs into an intermediate
11
+ # representation that consists solely of JSON-clean Ruby objects (String,
12
+ # Integer, ...). This intermediate format is dubbed "JSONish", and it can
13
+ # be transformed to JSON and back without losing data or creating ambiguity.
14
+ #
15
+ # If you use the class-level (::load and ::dump) interface to this class,
16
+ # it always uses JSON serialization by default. If you construct an instance,
17
+ # you can control which encoding scheme to use, as well as which Hash key to use
18
+ # to mark a serialized object.
19
+ #
20
+ # === JSONish Object Representation
21
+ # Most Ruby simple types (String, Integer, Float, true/false/nil) are
22
+ # represented by their corresponding JSON type. However, some JSONish
23
+ # values have special meaning:
24
+ # * Strings beginning with a ':' represent a Ruby Symbol
25
+ # * Strings in the ISO 8601 timestamp format represent a Ruby Time
26
+ #
27
+ # To avoid ambiguity due to Ruby Strings that happen to look like a
28
+ # JSONish Time or Symbol, some Strings are "object-escaped," meaning they
29
+ # are represented as a JSON object with _ruby_class:String.
30
+ #
31
+ # Arbitrary Ruby objects are represented as a Hash that contains a special
32
+ # key '_ruby_class'. The other key/value pairs of the hash consist of the
33
+ # object's instance variables. Any object whose state is solely contained
34
+ # in its instance variables is therefore eligible for serialization.
35
+ #
36
+ # JSONish also has special-purpose logic for some Ruby built-ins,
37
+ # allowing them to be serialized even though their state is not contained in
38
+ # instance variables. The following are all serializable built-ins:
39
+ # * Class
40
+ # * Module
41
+ # * String (see below).
42
+ #
43
+ # === Capabilities
44
+ # The serializer can handle any Ruby object that uses instance variables to
45
+ # record state. It cleanly round-trips the following Ruby object types:
46
+ # * Collections (Hash, Array)
47
+ # * JSON-clean data (Numeric, String, true, false, nil)
48
+ # * Symbol
49
+ # * Time
50
+ # * Class
51
+ # * Module
52
+ #
53
+ # === Known Limitations
54
+ # Cannot record the following kinds of state:
55
+ # * Class variables of objects
56
+ # * State that lives outside of instance variables, e.g. IO#fileno
57
+ #
58
+ # Cannot cleanly round-trip the following:
59
+ # * Objects that represent callable code: Proc, Lambda, etc.
60
+ # * High-precision floating point numbers (due to truncation)
61
+ # * Times with timezone other than UTC or precision greater than 1 sec
62
+ # * Hash keys that are anything other than a String (depends on JSON parser)
63
+ # * Hashes that contain a String key whose value is '_ruby_class'
64
+ class Serializer
65
+ if require_succeeds?('yajl')
66
+ Encoder = ::Yajl
67
+ elsif require_succeeds?('oj')
68
+ Encoder = ::Oj
69
+ elsif require_succeeds?('json')
70
+ Encoder = ::JSON
71
+ else
72
+ Encoder = nil
73
+ end unless defined?(Encoder)
74
+
75
+ # Format string to use when sprintf'ing a JSONish Time
76
+ TIME_FORMAT = '%4.4d-%2.2d-%2.2dT%2.2d:%2.2d:%2.2dZ'
77
+ # Pattern to match Strings against for object-escaping
78
+ TIME_PATTERN = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})Z$/
79
+ # Special key used as a pseudo-instance-variable for Class and Module
80
+ CLASS_ESCAPE_KEY = 'name'
81
+ # Special key used as a pseudo-instance-variable for String
82
+ STRING_ESCAPE_KEY = 'value'
83
+
84
+ DEFAULT_OPTIONS = {
85
+ :marker => '_ruby_class',
86
+ :encoder => Encoder
87
+ }
88
+
89
+ def self.load(data)
90
+ self.new.load(data)
91
+ end
92
+
93
+ def self.dump(object)
94
+ self.new.dump(object)
95
+ end
96
+
97
+ # Instantiate a Serializer instance.
98
+ # @option options :encoder [#load, #dump] underlying data-encoder to use. Defaults to any available JSON gem.
99
+ # @option options :marker special object key to use when representing a serialized Ruby object. Defaults to '_ruby_class'.
100
+ def initialize(options={})
101
+ options = DEFAULT_OPTIONS.merge(options)
102
+ @marker = options[:marker]
103
+ @encoder = options[:encoder]
104
+ end
105
+
106
+ # @param [String] data valid JSON document representing a serialized object
107
+ # @return [Object] unserialized Ruby object
108
+ def load(data)
109
+ jsonish = @encoder.load(data)
110
+ jsonish_to_object(jsonish)
111
+ end
112
+
113
+ # @param [Object] object any Ruby object
114
+ # @return [String] JSON document representing the serialized object
115
+ def dump(object)
116
+ jsonish = object_to_jsonish(object)
117
+ @encoder.dump(jsonish)
118
+ end
119
+
120
+ protected
121
+
122
+ # Given an Object, transform it into a JSONish Ruby structure.
123
+ # @param [Object] object any Ruby object
124
+ # @return [Object] JSONish representation of input object
125
+ def object_to_jsonish(object)
126
+ case object
127
+ when String
128
+ if (object =~ /^:/ ) || (object =~ TIME_PATTERN)
129
+ # Strings that look like a Symbol or Time must be object-escaped.
130
+ {@marker => String.name,
131
+ STRING_ESCAPE_KEY => object}
132
+ else
133
+ object
134
+ end
135
+ when Fixnum, Float, TrueClass, FalseClass, NilClass
136
+ object
137
+ when Hash
138
+ object.inject({}) do |result, (k, v)|
139
+ result[object_to_jsonish(k)] = object_to_jsonish(v)
140
+ result
141
+ end
142
+ when Array
143
+ object.map { |e| object_to_jsonish(e) }
144
+ when Symbol
145
+ # Ruby Symbol is represented as a string beginning with ':'
146
+ ":#{object}"
147
+ when Time
148
+ # Ruby Time is represented as an ISO 8601 timestamp in UTC timeztone
149
+ utc = object.utc
150
+ TIME_FORMAT % [utc.year, utc.mon, utc.day, utc.hour, utc.min, utc.sec]
151
+ when Class, Module
152
+ # Ruby Class/Module needs special handling - no instance vars
153
+ { @marker => object.class.name,
154
+ CLASS_ESCAPE_KEY => object.name }
155
+ else
156
+ # Generic serialized object; convert to Hash.
157
+ hash = {}
158
+ hash[@marker] = object.class.name
159
+
160
+ object.instance_variables.each do |var|
161
+ hash[ var[1..-1] ] = object_to_jsonish(object.instance_variable_get(var))
162
+ end
163
+
164
+ hash
165
+ end
166
+ end
167
+
168
+ # Given a JSONish structure, transform it back to a Ruby object.
169
+ # @param [Object] jsonish JSONish Ruby structure
170
+ # @return [Object] unserialized Ruby object
171
+ def jsonish_to_object(jsonish)
172
+ case jsonish
173
+ when String
174
+ if jsonish =~ /^:/
175
+ # JSONish Symbol
176
+ jsonish[1..-1].to_sym
177
+ elsif TIME_PATTERN.match(jsonish)
178
+ # JSONish Time
179
+ Time.parse(jsonish)
180
+ else
181
+ # Normal String
182
+ jsonish
183
+ end
184
+ when Fixnum, Float, TrueClass, FalseClass, NilClass
185
+ jsonish
186
+ when Hash
187
+ if jsonish.key?(@marker) # We have a serialized Ruby object!
188
+ hash = jsonish
189
+ klass = hash.delete(@marker)
190
+ case klass
191
+ when Class.name, Module.name
192
+ # Serialized instance of Ruby Class or Module
193
+ jsonish = hash.delete(CLASS_ESCAPE_KEY).to_const
194
+ when String.name
195
+ # Object-escaped Ruby String
196
+ jsonish = String.new(hash.delete(STRING_ESCAPE_KEY))
197
+ when Time.name
198
+ # Object-escaped Ruby String
199
+ jsonish = Time.at(hash.delete(TIME_ESCAPE_KEY))
200
+ else
201
+ # Generic serialized object
202
+ klass = klass.to_const
203
+ jsonish = klass.allocate
204
+ hash.each_pair do |k, v|
205
+ jsonish.instance_variable_set("@#{k}", jsonish_to_object(v))
206
+ end
207
+ end
208
+
209
+ jsonish
210
+ else # We have a plain old Ruby Hash
211
+ jsonish.inject({}) do |result, (k, v)|
212
+ result[jsonish_to_object(k)] = jsonish_to_object(v)
213
+ result
214
+ end
215
+ end
216
+ when Array
217
+ jsonish.map { |e| jsonish_to_object(e) }
218
+ else
219
+ raise ArgumentError, "Non-JSONish object of type '#{jsonish.class}'"
220
+ end
221
+ end
222
+ end
223
+ end
@@ -29,5 +29,6 @@ module RightSupport
29
29
  end
30
30
  end
31
31
 
32
+ require 'right_support/data/serializer'
32
33
  require 'right_support/data/hash_tools'
33
34
  require 'right_support/data/uuid'
@@ -0,0 +1,144 @@
1
+ # Copyright (c) 2009-2011 RightScale, Inc, All Rights Reserved Worldwide.
2
+ #
3
+ # THIS PROGRAM IS CONFIDENTIAL AND PROPRIETARY TO RIGHTSCALE
4
+ # AND CONSTITUTES A VALUABLE TRADE SECRET. Any unauthorized use,
5
+ # reproduction, modification, or disclosure of this program is
6
+ # strictly prohibited. Any use of this program by an authorized
7
+ # licensee is strictly subject to the terms and conditions,
8
+ # including confidentiality obligations, set forth in the applicable
9
+ # License Agreement between RightScale.com, Inc. and the licensee.
10
+
11
+ module RightSupport::Net
12
+ # Class provides S3 functionality.
13
+ # As part of RightSupport S3Helper does not include Rightscale::S3 and Encryptor modules.
14
+ # This modules must be included in application.
15
+ #
16
+ # Example:
17
+ #
18
+ # require 'right_support'
19
+ # require 'right_aws'
20
+ # require 'encryptor'
21
+ #
22
+ # s3_config = YAML.load_file(File.expand_path("../../config/s3.yml",__FILE__))[ENV['RACK_ENV']]
23
+ # RightSupport::Net::S3Helper.init(s3_config, Rightscale::S3, Encryptor)
24
+ # s3_health = RightSupport::Net::S3Helper.health_check
25
+ class S3Helper
26
+ # Init() is the first method which must be called.
27
+ # This is configuration and integration with S3 and Encryptor
28
+ #
29
+ # @param [Hash] config config for current environment (from YAML config file)
30
+ # creds:
31
+ # aws_access_key_id: String
32
+ # aws_secret_access_key: String
33
+ # bucket_name: String
34
+ # master_secret: String
35
+ # @param [Class] s3 Rightscale::S3 class is used to connect to S3
36
+ # @param [Class] encryptor Class which will be used to encrypt data
37
+ # encrypt() and decrypt() methods must be implemented
38
+ #
39
+ # Init() does not return anything
40
+ def self.init(config, s3, encryptor)
41
+ @config = config
42
+ @s3class = s3
43
+ @encryptor = encryptor
44
+ end
45
+ # Checks S3 credentials syntax in config parameter
46
+ #
47
+ # @return [Boolean] true if s3 creadentials are ok or false if not
48
+ def self.s3_enabled?
49
+ return @s3_enabled if @s3_enabled
50
+ @s3_enabled ||= !config.empty? &&
51
+ !config["creds"].empty? &&
52
+ !config["creds"]["aws_access_key_id"].nil? &&
53
+ config["creds"]["aws_access_key_id"] != "@@AWS_ACCESS_KEY_ID@@"
54
+ end
55
+ # Returns @config parameter
56
+ def self.config
57
+ @config
58
+ end
59
+ # Creates (if does not exist) and return S3 object
60
+ def self.s3
61
+ @s3 ||= @s3class.new config["creds"]["aws_access_key_id"], config["creds"]["aws_secret_access_key"]
62
+ end
63
+ # Creates S3 Bucket object using created in s3() object
64
+ def self.bucket
65
+ #TO DISCUSS: second param 'true' means 'create new bucket'
66
+ #@bucket ||= s3.bucket(config["bucket_name"], true)
67
+ @bucket ||= s3.bucket(config["bucket_name"])
68
+ end
69
+ # Returns Master secret from config
70
+ def self.master_secret
71
+ config["master_secret"]
72
+ end
73
+ # Downloads data from S3 Bucket
74
+ #
75
+ # @param [String] key Name of the bucket key
76
+ # @param [Block] blck Ruby code wich will be done by Bucket object
77
+ #
78
+ # @return [String] S3 bucket's key content in plain/text format
79
+ def self.get(key, &blck)
80
+ return nil unless s3_enabled?
81
+
82
+ object = bucket.key(key, true, &blck)
83
+ return nil if object.nil?
84
+
85
+ # don't decrypt/verify unencrypted values
86
+ return object.data if object.meta_headers["digest"].nil?
87
+
88
+ ciphertext = object.data
89
+ passphrase = "#{key}:#{master_secret}"
90
+ plaintext = @encryptor.decrypt(:key=>passphrase, :value=>ciphertext)
91
+ digest = OpenSSL::HMAC.hexdigest(OpenSSL::Digest::SHA1.new, passphrase, plaintext)
92
+
93
+ if digest == object.meta_headers["digest"]
94
+ return plaintext
95
+ else
96
+ raise "digest for key:#{key} in s3 does not match calculated digest."
97
+ end
98
+ end
99
+ # Upload data to S3 Bucket
100
+ #
101
+ # @param [String] key Name of the bucket key
102
+ # @param [String] plaintext Data which should be saved in the Bucket
103
+ # @param [Block] blck Ruby code wich will be done by Bucket object
104
+ #
105
+ def self.post(key, plaintext, &blck)
106
+ passphrase = "#{key}:#{master_secret}"
107
+ plaintext = "-- no detail --" unless plaintext && plaintext.size > 0
108
+ ciphertext = @encryptor.encrypt(:key=>passphrase, :value=>plaintext)
109
+ digest = OpenSSL::HMAC.hexdigest(OpenSSL::Digest::SHA1.new, passphrase, plaintext)
110
+ bucket.put(key, ciphertext, {"digest" => digest}, &blck)
111
+ end
112
+ # Checks if it is possible to connect to the S3 Bucket.
113
+ #
114
+ # @return [String or True] If test was successful then return true
115
+ # Else return error message
116
+ def self.health_check
117
+ config
118
+ s3
119
+ # test config file
120
+ return 'S3 Config file: Credentials: Syntax error.' unless s3_enabled?
121
+ ["bucket_name", "master_secret"].each do |conf|
122
+ return "S3 Config file: #{conf.upcase}: Syntax error." if config[conf].nil? || config[conf] == '' || config[conf] == "@@#{conf.upcase}@@"
123
+ end
124
+ # test connection
125
+ original_text = 'heath check'
126
+ test_key = 'ping'
127
+ begin
128
+ post(test_key, original_text)
129
+ rescue Exception => e
130
+ return e.message
131
+ end
132
+ begin
133
+ result_text = get(test_key)
134
+ rescue Exception => e
135
+ return e.message
136
+ end
137
+ return 'Sended text and returned one are not equal. Possible master_secret problem' if result_text != original_text
138
+ # retrurn true if there were not errors
139
+ true
140
+ end
141
+
142
+ end
143
+
144
+ end
@@ -36,5 +36,6 @@ require 'right_support/net/lb'
36
36
  require 'right_support/net/request_balancer'
37
37
  require 'right_support/net/ssl'
38
38
  require 'right_support/net/dns'
39
+ require 'right_support/net/s3_helper'
39
40
 
40
41
  RightSupport::Net.extend(RightSupport::Net::AddressHelper)
@@ -7,8 +7,8 @@ spec = Gem::Specification.new do |s|
7
7
  s.required_ruby_version = Gem::Requirement.new(">= 1.8.7")
8
8
 
9
9
  s.name = 'right_support'
10
- s.version = '2.4.1'
11
- s.date = '2012-09-18'
10
+ s.version = '2.5.0'
11
+ s.date = '2012-09-19'
12
12
 
13
13
  s.authors = ['Tony Spataro', 'Sergey Sergyenko', 'Ryan Williamson', 'Lee Kirchhoff', 'Sergey Enin']
14
14
  s.email = 'support@rightscale.com'
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: right_support
3
3
  version: !ruby/object:Gem::Version
4
- hash: 29
4
+ hash: 27
5
5
  prerelease: false
6
6
  segments:
7
7
  - 2
8
- - 4
9
- - 1
10
- version: 2.4.1
8
+ - 5
9
+ - 0
10
+ version: 2.5.0
11
11
  platform: ruby
12
12
  authors:
13
13
  - Tony Spataro
@@ -19,7 +19,7 @@ autorequire:
19
19
  bindir: bin
20
20
  cert_chain: []
21
21
 
22
- date: 2012-09-18 00:00:00 -07:00
22
+ date: 2012-09-19 00:00:00 -07:00
23
23
  default_executable:
24
24
  dependencies: []
25
25
 
@@ -43,6 +43,7 @@ files:
43
43
  - lib/right_support/crypto/signed_hash.rb
44
44
  - lib/right_support/data.rb
45
45
  - lib/right_support/data/hash_tools.rb
46
+ - lib/right_support/data/serializer.rb
46
47
  - lib/right_support/data/uuid.rb
47
48
  - lib/right_support/db.rb
48
49
  - lib/right_support/db/cassandra_model.rb
@@ -62,6 +63,7 @@ files:
62
63
  - lib/right_support/net/lb/round_robin.rb
63
64
  - lib/right_support/net/lb/sticky.rb
64
65
  - lib/right_support/net/request_balancer.rb
66
+ - lib/right_support/net/s3_helper.rb
65
67
  - lib/right_support/net/ssl.rb
66
68
  - lib/right_support/net/ssl/open_ssl_patch.rb
67
69
  - lib/right_support/net/string_encoder.rb