tinypass 0.0.1

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 (85) hide show
  1. checksums.yaml +15 -0
  2. data/.DS_Store +0 -0
  3. data/.gitignore +20 -0
  4. data/.rspec +2 -0
  5. data/Gemfile +4 -0
  6. data/LICENSE.txt +22 -0
  7. data/README.md +32 -0
  8. data/Rakefile +19 -0
  9. data/lib/.DS_Store +0 -0
  10. data/lib/tinypass.rb +48 -0
  11. data/lib/tinypass/.DS_Store +0 -0
  12. data/lib/tinypass/builder.rb +7 -0
  13. data/lib/tinypass/builder/client_builder.rb +40 -0
  14. data/lib/tinypass/builder/client_parser.rb +48 -0
  15. data/lib/tinypass/builder/cookie_parser.rb +25 -0
  16. data/lib/tinypass/builder/json_msg_builder.rb +115 -0
  17. data/lib/tinypass/builder/open_encoder.rb +11 -0
  18. data/lib/tinypass/builder/secure_encoder.rb +15 -0
  19. data/lib/tinypass/builder/security_utils.rb +67 -0
  20. data/lib/tinypass/gateway.rb +104 -0
  21. data/lib/tinypass/offer.rb +23 -0
  22. data/lib/tinypass/policies.rb +5 -0
  23. data/lib/tinypass/policies/discount_policy.rb +25 -0
  24. data/lib/tinypass/policies/policy.rb +25 -0
  25. data/lib/tinypass/policies/pricing_policy.rb +13 -0
  26. data/lib/tinypass/policies/restriction_policy.rb +14 -0
  27. data/lib/tinypass/price_option.rb +66 -0
  28. data/lib/tinypass/resource.rb +17 -0
  29. data/lib/tinypass/token.rb +6 -0
  30. data/lib/tinypass/token/access_token.rb +163 -0
  31. data/lib/tinypass/token/access_token_list.rb +71 -0
  32. data/lib/tinypass/token/access_token_store.rb +59 -0
  33. data/lib/tinypass/token/meter.rb +76 -0
  34. data/lib/tinypass/token/meter_helper.rb +82 -0
  35. data/lib/tinypass/token/token_data.rb +72 -0
  36. data/lib/tinypass/ui.rb +2 -0
  37. data/lib/tinypass/ui/html_widget.rb +29 -0
  38. data/lib/tinypass/ui/purchase_request.rb +34 -0
  39. data/lib/tinypass/utils.rb +34 -0
  40. data/lib/tinypass/version.rb +3 -0
  41. data/spec/.DS_Store +0 -0
  42. data/spec/acceptance/basic_workflow_spec.rb +81 -0
  43. data/spec/integration/.DS_Store +0 -0
  44. data/spec/integration/cases/.DS_Store +0 -0
  45. data/spec/integration/cases/basic_spec.rb +53 -0
  46. data/spec/integration/cases/combo_spec.rb +43 -0
  47. data/spec/integration/cases/metered_reminder_spec.rb +42 -0
  48. data/spec/integration/cases/metered_strict_spec.rb +54 -0
  49. data/spec/integration/cases/metered_views_spec.rb +92 -0
  50. data/spec/integration/client_builder_and_parser_spec.rb +21 -0
  51. data/spec/spec_helper.rb +33 -0
  52. data/spec/support/.DS_Store +0 -0
  53. data/spec/support/acceptance.rb +13 -0
  54. data/spec/support/tinypass_factories.rb +25 -0
  55. data/spec/unit/.DS_Store +0 -0
  56. data/spec/unit/builder/.DS_Store +0 -0
  57. data/spec/unit/builder/client_builder_spec.rb +23 -0
  58. data/spec/unit/builder/client_parser_spec.rb +27 -0
  59. data/spec/unit/builder/cookie_parser_spec.rb +39 -0
  60. data/spec/unit/builder/json_msg_builder_spec.rb +73 -0
  61. data/spec/unit/builder/open_encoder_spec.rb +15 -0
  62. data/spec/unit/builder/secure_encoder_spec.rb +23 -0
  63. data/spec/unit/builder/security_utils_spec.rb +42 -0
  64. data/spec/unit/gateway_spec.rb +80 -0
  65. data/spec/unit/offer_spec.rb +31 -0
  66. data/spec/unit/policies/.DS_Store +0 -0
  67. data/spec/unit/policies/discount_policy_spec.rb +40 -0
  68. data/spec/unit/policies/pricing_policy_spec.rb +23 -0
  69. data/spec/unit/policies/restriction_policy_spec.rb +35 -0
  70. data/spec/unit/price_option_spec.rb +109 -0
  71. data/spec/unit/resource_spec.rb +24 -0
  72. data/spec/unit/tinypass_spec.rb +51 -0
  73. data/spec/unit/token/.DS_Store +0 -0
  74. data/spec/unit/token/access_token_list_spec.rb +94 -0
  75. data/spec/unit/token/access_token_spec.rb +267 -0
  76. data/spec/unit/token/access_token_store_spec.rb +93 -0
  77. data/spec/unit/token/meter_helper_spec.rb +103 -0
  78. data/spec/unit/token/meter_spec.rb +66 -0
  79. data/spec/unit/token/token_data_spec.rb +66 -0
  80. data/spec/unit/ui/.DS_Store +0 -0
  81. data/spec/unit/ui/html_widget_spec.rb +89 -0
  82. data/spec/unit/ui/purchase_request_spec.rb +46 -0
  83. data/spec/unit/utils_spec.rb +57 -0
  84. data/tinypass.gemspec +35 -0
  85. metadata +330 -0
@@ -0,0 +1,71 @@
1
+ module Tinypass
2
+ class AccessTokenList
3
+ include Enumerable
4
+
5
+ MAX = 20
6
+
7
+ def initialize(input_tokens = nil)
8
+ @tokens_hash = {}
9
+ input_tokens = Array(input_tokens)
10
+
11
+ input_tokens.each { |token| self << token }
12
+ end
13
+
14
+ def tokens
15
+ @tokens_hash.values
16
+ end
17
+ alias_method :access_tokens, :tokens
18
+
19
+ def [](rid)
20
+ @tokens_hash[rid.to_s]
21
+ end
22
+
23
+ def <<(token)
24
+ key = token.token_data.rid
25
+
26
+ @tokens_hash[key] = token
27
+ shift until size <= MAX
28
+
29
+ self[key]
30
+ end
31
+ alias_method :add, :<<
32
+
33
+ def push(*args)
34
+ args.each do |token|
35
+ self << token
36
+ end
37
+ end
38
+
39
+ def add_all(tokens)
40
+ self.push(*tokens)
41
+ end
42
+
43
+ def include?(rid)
44
+ rid = rid.to_s
45
+ @tokens_hash.has_key?(rid)
46
+ end
47
+ alias_method :contains?, :include?
48
+
49
+ def each(*args, &block)
50
+ tokens.each(*args, &block)
51
+ end
52
+
53
+ def length
54
+ tokens.size
55
+ end
56
+ alias_method :size, :length
57
+
58
+ def empty?
59
+ @tokens_hash.empty?
60
+ end
61
+
62
+ def delete(rid)
63
+ @tokens_hash.delete(rid)
64
+ end
65
+ alias_method :remove, :delete
66
+
67
+ def shift
68
+ delete(@tokens_hash.keys.first)
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,59 @@
1
+ require 'uri'
2
+
3
+ require 'tinypass/token/access_token.rb'
4
+ require 'tinypass/token/token_data.rb'
5
+
6
+ module Tinypass
7
+ class AccessTokenStore
8
+ attr_reader :tokens, :raw_cookie
9
+
10
+ def initialize(config = nil)
11
+ @config = config
12
+ @tokens = AccessTokenList.new
13
+ end
14
+
15
+ def load_tokens_from_cookie(cookies, name = nil)
16
+ name ||= Config.token_cookie_name(Tinypass.aid)
17
+ @raw_cookie = cookies.respond_to?(:to_str) ? cookies : cookies[name]
18
+
19
+ if @raw_cookie
20
+ @tokens = ClientParser.new.parse_access_tokens(URI.unescape(raw_cookie))
21
+ end
22
+ end
23
+
24
+ def get_access_token(rid)
25
+ rid = rid.to_s
26
+ return tokens[rid] if tokens[rid]
27
+
28
+ token = AccessToken.new(rid, -1)
29
+
30
+ if tokens.size == 0
31
+ token.access_state = AccessState::NO_TOKENS_FOUND
32
+ else
33
+ token.access_state = AccessState::RID_NOT_FOUND
34
+ end
35
+
36
+ return token
37
+ end
38
+
39
+ def has_token?(rid)
40
+ tokens.contains?(rid.to_s)
41
+ end
42
+
43
+ def find_active_token(regexp)
44
+ tokens.each do |token|
45
+ return token if token.rid =~ regexp && !token.expired?
46
+ end
47
+
48
+ nil
49
+ end
50
+
51
+ protected
52
+
53
+ def clean_expired_tokens
54
+ @tokens.dup.each do |token|
55
+ @tokens.delete(token.rid) if token.expired? || (token.metered? && token.trial_dead?)
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,76 @@
1
+ module Tinypass
2
+ class Meter
3
+ def initialize(access_token)
4
+ @access_token = access_token
5
+ end
6
+
7
+ def self.create_view_based(rid, max_views, trial_period)
8
+ access_token = AccessToken.new(rid)
9
+ end_time = Utils.parse_loose_period_in_secs(trial_period) + Time.now.to_i
10
+
11
+ access_token.token_data[TokenData::METER_TYPE] = TokenData::METER_REMINDER
12
+ access_token.token_data[TokenData::METER_TRIAL_MAX_ACCESS_ATTEMPTS] = max_views
13
+ access_token.token_data[TokenData::METER_TRIAL_ACCESS_ATTEMPTS] = 0
14
+ access_token.token_data[TokenData::METER_TRIAL_ENDTIME] = end_time
15
+ access_token.token_data[TokenData::METER_LOCKOUT_ENDTIME] = end_time
16
+
17
+ new(access_token)
18
+ end
19
+
20
+ def self.create_time_based(rid, trial_period, lockout_period)
21
+ access_token = AccessToken.new(rid)
22
+ trial_end_time = Utils::parse_loose_period_in_secs(trial_period) + Time.now.to_i
23
+ lockout_end_time = trial_end_time + Utils::parse_loose_period_in_secs(lockout_period)
24
+
25
+ access_token.token_data[TokenData::METER_TYPE] = TokenData::METER_REMINDER
26
+ access_token.token_data[TokenData::METER_TRIAL_ENDTIME] = trial_end_time
27
+ access_token.token_data[TokenData::METER_LOCKOUT_ENDTIME] = lockout_end_time
28
+
29
+ new(access_token)
30
+ end
31
+
32
+ def increment
33
+ data[TokenData::METER_TRIAL_ACCESS_ATTEMPTS] = trial_view_count + 1
34
+ end
35
+
36
+ def trial_period_active?
37
+ @access_token.trial_period_active?
38
+ end
39
+
40
+ def lockout_period_active?
41
+ @access_token.lockout_period_active?
42
+ end
43
+
44
+ def data
45
+ @access_token.token_data
46
+ end
47
+
48
+ def view_based?
49
+ @access_token.meter_view_based?
50
+ end
51
+
52
+ def trial_view_count
53
+ @access_token.trial_view_count
54
+ end
55
+
56
+ def trial_view_limit
57
+ @access_token.trial_view_limit
58
+ end
59
+
60
+ def trial_dead?
61
+ @access_token.trial_dead?
62
+ end
63
+
64
+ def meter_type
65
+ @access_token.meter_type
66
+ end
67
+
68
+ def trial_end_time_secs
69
+ @access_token.trial_end_time_secs
70
+ end
71
+
72
+ def lockout_end_time_secs
73
+ @access_token.lockout_end_time_secs
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,82 @@
1
+ module Tinypass
2
+ module MeterHelper
3
+ extend self
4
+
5
+ def create_view_based(name, max_views, within_period)
6
+ Meter.create_view_based(name, max_views, within_period)
7
+ end
8
+
9
+ def create_time_based(name, trial_period, lockout_period)
10
+ Meter.create_time_based(name, trial_period, lockout_period)
11
+ end
12
+
13
+ def load_meter_from_cookie(meter_name, cookies)
14
+ # NOTE: This method expects `meter_name` to be both the cookie name and the meter name
15
+ # aka rid.
16
+
17
+ store = AccessTokenStore.new
18
+ store.load_tokens_from_cookie(cookies, meter_name)
19
+
20
+ return unless store.has_token?(meter_name)
21
+
22
+ token = store.get_access_token(meter_name)
23
+ meter = Meter.new(token)
24
+
25
+ return if meter.trial_dead?
26
+ meter
27
+ end
28
+
29
+ def load_meter_from_serialized_data(string)
30
+ store = AccessTokenStore.new
31
+ store.load_tokens_from_cookie(string)
32
+ token = store.tokens.first
33
+
34
+ return if token.nil?
35
+
36
+ meter = Meter.new(token)
37
+
38
+ return if meter.trial_dead?
39
+ meter
40
+ end
41
+
42
+ def serialize(meter, builder_config = '')
43
+ token = AccessToken.new(meter.data)
44
+ builder = ClientBuilder.new(builder_config)
45
+ builder.build_access_tokens(token)
46
+ end
47
+
48
+ def serialize_to_json(meter)
49
+ serialize(meter, ClientBuilder::OPEN_ENC)
50
+ end
51
+
52
+ def deserialize(string)
53
+ parser = ClientParser.new
54
+ list = parser.parse_access_tokens(string)
55
+ token = list.first
56
+
57
+ return if token.nil?
58
+
59
+ Meter.new(token)
60
+ end
61
+
62
+ def generate_cookie_embed_script(name, meter)
63
+ if meter.lockout_period_active?
64
+ expires = meter.lockout_end_time_secs + 60
65
+ else
66
+ expires = Time.now.to_i + 60 * 60 * 24 * 90
67
+ end
68
+ expires_string = Time.at(expires).utc
69
+
70
+ "<script>
71
+ document.cookie='#{ generate_local_cookie(name, meter) }; path=/; expires=#{ expires_string };';
72
+ </script>"
73
+ end
74
+
75
+ private
76
+
77
+ def generate_local_cookie(name, meter)
78
+ cookie_value = URI::escape(serialize(meter))
79
+ "#{ name }=#{ cookie_value }"
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,72 @@
1
+ module Tinypass
2
+ class TokenData
3
+ MARK_YEAR_MILLIS = 1293858000000
4
+
5
+ METER_REMINDER = 10
6
+ METER_STRICT = 20
7
+
8
+ METER_TRIAL_ENDTIME = 'mtet'
9
+ METER_TRIAL_ACCESS_PERIOD = 'mtap'
10
+
11
+ METER_LOCKOUT_ENDTIME = 'mlet'
12
+ METER_LOCKOUT_PERIOD = 'mlp'
13
+
14
+ METER_TRIAL_MAX_ACCESS_ATTEMPTS = 'mtma'
15
+ METER_TRIAL_ACCESS_ATTEMPTS = 'mtaa'
16
+ METER_TYPE = 'mt'
17
+
18
+ ACCESS_ID = 'id'
19
+
20
+ RID = 'rid'
21
+ UID = 'uid'
22
+ EX = 'ex'
23
+ EARLY_EX = 'eex'
24
+ IPS = 'ips'
25
+
26
+ def initialize(data = {})
27
+ @data = data
28
+ end
29
+
30
+ def rid
31
+ @data[RID]
32
+ end
33
+
34
+ def [](key)
35
+ key = key.to_s
36
+ @data[key]
37
+ end
38
+
39
+ def []=(key, value)
40
+ key = key.to_s
41
+ @data[key] = value
42
+ end
43
+
44
+ def values
45
+ @data
46
+ end
47
+
48
+ def fetch(*args)
49
+ args[0] = args[0].to_s
50
+ @data.fetch(*args)
51
+ end
52
+
53
+ def merge(hash)
54
+ stringified_hash = {}
55
+ hash.keys.each do |key|
56
+ stringified_hash[key.to_s] = hash[key]
57
+ end
58
+
59
+ @data.merge!(stringified_hash)
60
+ end
61
+ alias_method :add_fields, :merge
62
+
63
+ def size
64
+ @data.size
65
+ end
66
+
67
+ def self.convert_to_epoch_seconds(seconds_from_now)
68
+ seconds_from_now /= 1000 if seconds_from_now > MARK_YEAR_MILLIS
69
+ seconds_from_now
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,2 @@
1
+ require 'tinypass/ui/html_widget'
2
+ require 'tinypass/ui/purchase_request'
@@ -0,0 +1,29 @@
1
+ module Tinypass
2
+ class HtmlWidget
3
+ def create_button_html(request)
4
+ options = request.options.dup || {}
5
+ rid = request.primary_offer.resource.rid
6
+ builder = ClientBuilder.new
7
+ rdata = builder.build_purchase_request(request).gsub('"', '\"')
8
+
9
+ html = "<tp:request type=\"purchase\" rid=\"#{ rid }\"" <<
10
+ " url=\"#{ Config.endpoint + Config::CONTEXT }\"" <<
11
+ " rdata=\"#{ rdata }\" aid=\"#{ Tinypass.aid }\"" <<
12
+ " cn=\"#{ Config.token_cookie_name }\" v=\"#{ Config::VERSION }\""
13
+
14
+ html << " oncheckaccess=\"#{ request.callback }\"" if request.callback
15
+
16
+ if options['button.html']
17
+ custom = options['button.html'].gsub('"', '&quot;')
18
+ html << " custom=\"#{ custom }\""
19
+ elsif options['button.link']
20
+ link = options['button.link'].gsub('"', '&quot;')
21
+ html << " link=\"#{ link }\""
22
+ end
23
+
24
+ html << "></tp:request>"
25
+
26
+ html
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,34 @@
1
+ module Tinypass
2
+ class PurchaseRequest
3
+ attr_accessor :primary_offer, :secondary_offer, :options, :callback, :client_ip, :user_ref
4
+
5
+ def initialize(offer, options = {})
6
+ @primary_offer, @options = offer, options
7
+ end
8
+
9
+ def generate_tag
10
+ widget = HtmlWidget.new
11
+ widget.create_button_html(self)
12
+ end
13
+
14
+ def generate_link(return_url, cancel_url)
15
+ self.options['return_url'] = return_url if return_url
16
+ self.options['cancel_url'] = cancel_url if cancel_url
17
+
18
+ builder = ClientBuilder.new
19
+ ticket_string = builder.build_purchase_request(self)
20
+
21
+ Config.endpoint + Config::CONTEXT + "/jsapi/auth.js?aid=#{ Tinypass.aid }&r=#{ ticket_string }"
22
+ end
23
+
24
+ def client_ip=(value)
25
+ value.strip! if value
26
+ @client_ip = value
27
+ end
28
+
29
+ def user_ref=(value)
30
+ value.strip! if value
31
+ @user_ref = value
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,34 @@
1
+ module Tinypass
2
+ module Utils
3
+ extend self
4
+
5
+ def valid_ip?(ip)
6
+ ip && ip =~ /\d{1,3}\.\d{1,3}\.\d{1,3}.\d{1,3}/
7
+ end
8
+
9
+ def parse_loose_period_in_msecs(period)
10
+ period = period.to_s
11
+ return period.to_i if period.to_i.to_s == period
12
+
13
+ if matches = /(\d+)\s*(\w+)/.match(period)
14
+ number = matches[1].to_i
15
+ string = matches[2]
16
+
17
+ return number if string.start_with?("ms")
18
+ return number * 1000 if string.start_with?("s")
19
+ return number * 1000 * 60 if string.start_with?("mi")
20
+ return number * 1000 * 60 * 60 if string.start_with?("h")
21
+ return number * 1000 * 60 * 60 * 24 if string.start_with?("d")
22
+ return number * 1000 * 60 * 60 * 24 * 7 if string.start_with?("w")
23
+ return number * 1000 * 60 * 60 * 24 * 30 if string.start_with?("mo")
24
+ return number * 1000 * 60 * 60 * 24 * 365 if string.start_with?("y")
25
+ end
26
+
27
+ raise ArgumentError.new("Cannot parse the specified period: #{ period }")
28
+ end
29
+
30
+ def parse_loose_period_in_secs(period)
31
+ parse_loose_period_in_msecs(period) / 1000
32
+ end
33
+ end
34
+ end