Skip to content

Commit a6385dd

Browse files
GbArcvitalie
authored andcommitted
New user journey dev (#1278)
1 parent e3ffe60 commit a6385dd

File tree

28 files changed

+802
-26
lines changed

28 files changed

+802
-26
lines changed

lib/travis/api/app/endpoint/authorization.rb

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,9 +106,15 @@ class Authorization < Endpoint
106106
get '/handshake/?:provider?' do
107107
method = org? ? :handshake : :vcs_handshake
108108
params[:provider] ||= 'github'
109+
params[:signup] ||= false
109110
send(method) do |user, token, redirect_uri|
110111
if target_ok? redirect_uri
112+
user[:installation] = params[:installation_id]
111113
content_type :html
114+
if params[:setup_action] && params[:setup_action] == 'install' && params[:provider] == 'github'
115+
redirect_uri = redirect_uri + "?installation_id=#{params[:installation_id]}"
116+
redirect_uri = "#{Travis.config.vcs_redirects.web_url}#{Travis.config.vcs_redirects[params[:provider]]}?installation_id=#{params[:installation_id]}"
117+
end
112118
data = { user: user, token: token, uri: redirect_uri }
113119
erb(:post_payload, locals: data)
114120
else
@@ -214,6 +220,10 @@ def remote_vcs_user
214220

215221
def vcs_handshake
216222
if params[:code]
223+
if params[:setup_action] && (params[:setup_action] == 'update' || params[:setup_action] == 'install') && params[:provider] && !params[:state]
224+
redirect to("#{Travis.config.vcs_redirects.web_url}#{Travis.config.vcs_redirects[params[:provider]]}?installation_id=#{params[:installation_id]}")
225+
end
226+
217227
unless state_ok?(params[:state], params[:provider])
218228
handle_invalid_response
219229
return
@@ -239,7 +249,8 @@ def vcs_handshake
239249
vcs_data = remote_vcs_user.auth_request(
240250
provider: params[:provider],
241251
state: state,
242-
redirect_uri: oauth_endpoint
252+
redirect_uri: oauth_endpoint,
253+
signup: params[:signup]
243254
)
244255

245256
response.set_cookie(cookie_name(params[:provider]), value: state, httponly: true)

lib/travis/api/v3/billing_client.rb

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ def allowance(owner_type, owner_id)
1313
response = connection(timeout: ALLOWANCE_TIMEOUT).get("/usage/#{owner_type.downcase}s/#{owner_id}/allowance")
1414
return BillingClient.default_allowance_response unless response.status == 200
1515

16-
Travis::API::V3::Models::Allowance.new(2, owner_id, response.body)
16+
Travis::API::V3::Models::Allowance.new(response.body.fetch('subscription_type', 2), owner_id, response.body)
1717
end
1818

1919
def authorize_build(repo, sender_id, jobs)
@@ -27,7 +27,11 @@ def self.default_allowance_response(id = 0)
2727
"private_repos" => false,
2828
"concurrency_limit" => 1,
2929
"user_usage" => false,
30-
"pending_user_licenses" => false
30+
"pending_user_licenses" => false,
31+
"payment_changes_block_captcha" => false,
32+
"payment_changes_block_credit" => false,
33+
"credit_card_block_duration" => 0,
34+
"captcha_block_duration" => 0
3135
}.freeze)
3236
end
3337

@@ -239,6 +243,14 @@ def cancel_v2_subscription(id, reason_data)
239243
handle_subscription_response(response)
240244
end
241245

246+
def usage_stats(owners)
247+
data = connection.post("/usage/stats", owners: owners, query: 'paid_plan_count')
248+
data = data&.body
249+
data.fetch('paid_plans').to_i > 0 if data && data['paid_plans']
250+
rescue
251+
false
252+
end
253+
242254
private
243255

244256
def handle_subscription_response(response)

lib/travis/api/v3/models/allowance.rb

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
module Travis::API::V3
22
class Models::Allowance
3-
attr_reader :subscription_type, :public_repos, :private_repos, :concurrency_limit, :user_usage, :pending_user_licenses, :id
3+
attr_reader :subscription_type, :public_repos, :private_repos, :concurrency_limit, :user_usage, :pending_user_licenses, :id,
4+
:payment_changes_block_credit, :payment_changes_block_captcha, :credit_card_block_duration, :captcha_block_duration
45

56
def initialize(subscription_type, owner_id, attributes = {})
67
@subscription_type = subscription_type
@@ -11,6 +12,10 @@ def initialize(subscription_type, owner_id, attributes = {})
1112
@concurrency_limit = attributes.fetch('concurrency_limit', nil)
1213
@user_usage = attributes.fetch('user_usage', nil)
1314
@pending_user_licenses = attributes.fetch('pending_user_licenses', nil)
15+
@payment_changes_block_captcha = attributes.fetch('payment_changes_block_captcha', nil)
16+
@payment_changes_block_credit = attributes.fetch('payment_changes_block_credit', nil)
17+
@credit_card_block_duration = attributes.fetch('credit_card_block_duration', nil)
18+
@captcha_block_duration = attributes.fetch('captcha_block_duration', nil)
1419
end
1520
end
1621
end

lib/travis/api/v3/models/storage.rb

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
require 'redis'
2+
3+
module Travis::API::V3
4+
class Models::Storage
5+
6+
attr_reader :id, :value
7+
8+
def initialize(attrs)
9+
puts "ATTRS: #{attrs.inspect}"
10+
@id = attrs.fetch(:id)
11+
@value = attrs[:value]
12+
end
13+
14+
def public?
15+
true
16+
end
17+
18+
def get
19+
puts "ID: #{id.inspect}, VAL: #{value.inspect}"
20+
@value = Travis.redis.get(id) || 0
21+
22+
23+
puts "ID: #{id.inspect}, VAL: #{value.inspect}"
24+
self
25+
end
26+
27+
def create
28+
Travis.redis.set(id, value)
29+
self
30+
end
31+
32+
def delete
33+
Travis.redis.del(id)
34+
@value = 0
35+
self
36+
end
37+
end
38+
end

lib/travis/api/v3/queries/storage.rb

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
module Travis::API::V3
2+
class Queries::Storage < Query
3+
params :user, :id,:value, prefix: :storage
4+
5+
PERMITTED_OPTIONS = [:billing_wizard_state]
6+
7+
def find
8+
Models::Storage.new(id: option_id).get if valid?
9+
end
10+
11+
def update
12+
Models::Storage.new(id: option_id, value: value).create if valid?
13+
end
14+
15+
def delete
16+
Models::Storage.new(id: option_id).delete if valid?
17+
end
18+
19+
private
20+
def option_id
21+
"#{user}::storage::#{id}"
22+
end
23+
24+
def valid?
25+
PERMITTED_OPTIONS.include? id.to_sym
26+
end
27+
28+
29+
def user
30+
params['user.id']
31+
end
32+
33+
def id
34+
params['id']
35+
end
36+
37+
def value
38+
params['value']
39+
end
40+
41+
def redis
42+
Travis.redis
43+
end
44+
end
45+
end

lib/travis/api/v3/queries/subscription.rb

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,31 @@
11
module Travis::API::V3
22
class Queries::Subscription < Query
3+
def update_payment_details(user_id)
4+
recaptcha_redis_key = "recaptcha_attempts_v1_#{params['subscription.id']}"
5+
count = Travis.redis.get(recaptcha_redis_key)&.to_i
6+
count = count.nil? ? 0 : count
7+
if count > captcha_max_failed_attempts
8+
raise ClientError, 'Error verifying reCAPTCHA, you have exausted your attempts, please wait.'
9+
end
10+
11+
result = recaptcha_client.verify(params['captcha_token'])
12+
unless result
13+
if count == 0
14+
Travis.redis.setex(recaptcha_redis_key, captcha_block_duration, count + 1)
15+
else
16+
ttl = Travis.redis.ttl(recaptcha_redis_key)
17+
Travis.redis.setex(recaptcha_redis_key, ttl, count + 1)
18+
end
19+
raise ClientError, 'Error verifying reCAPTCHA, please try again.'
20+
end
21+
22+
address_data = params.dup.tap { |h| h.delete('subscription.id') }
23+
address_data = address_data.tap { |h| h.delete('token') }
24+
client = BillingClient.new(user_id)
25+
client.update_address(params['subscription.id'], address_data) unless address_data.empty?
26+
client.update_creditcard(params['subscription.id'], params['token']) if params.key?('token')
27+
end
28+
329
def update_address(user_id)
430
address_data = params.dup.tap { |h| h.delete('subscription.id') }
531
client = BillingClient.new(user_id)
@@ -42,5 +68,19 @@ def pay(user_id)
4268
client = BillingClient.new(user_id)
4369
client.pay(params['subscription.id'])
4470
end
71+
72+
private
73+
74+
def recaptcha_client
75+
@recaptcha_client ||= RecaptchaClient.new
76+
end
77+
78+
def captcha_block_duration
79+
Travis.config.antifraud.captcha_block_duration
80+
end
81+
82+
def captcha_max_failed_attempts
83+
Travis.config.antifraud.captcha_max_failed_attempts
84+
end
4585
end
4686
end

lib/travis/api/v3/queries/user.rb

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,27 @@ def find_by_email(email)
2323
end
2424
end
2525

26+
def collaborator?(id)
27+
user = Models::User.find_by_id(id) if id
28+
return false unless user
29+
30+
owners=[]
31+
user.organizations.each do |org|
32+
owners << {
33+
:id => org.id,
34+
:type => 'Organization'
35+
}
36+
end
37+
Models::Repository.where(id: user.shared_repositories_ids).uniq.pluck(:owner_id, :owner_type).each do |owner|
38+
owners << {
39+
:id => owner[0],
40+
:type =>owner[1]
41+
}
42+
end
43+
client = BillingClient.new(id)
44+
client.usage_stats(owners)
45+
end
46+
2647
def sync(user)
2748
raise AlreadySyncing if user.is_syncing?
2849
if Travis::Features.user_active?(:use_vcs, user) || !user.github?

lib/travis/api/v3/queries/v2_subscription.rb

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,32 @@ module Travis::API::V3
22
class Queries::V2Subscription < Query
33
params :enabled, :threshold, :amount
44

5+
def update_payment_details(user_id)
6+
recaptcha_redis_key = "recaptcha_attempts_v2_#{params['subscription.id']}"
7+
count = Travis.redis.get(recaptcha_redis_key)&.to_i
8+
count = count.nil? ? 0 : count
9+
if count > captcha_max_failed_attempts
10+
raise ClientError, 'Error verifying reCAPTCHA, you have exausted your attempts, please wait.'
11+
end
12+
13+
result = recaptcha_client.verify(params['captcha_token'])
14+
unless result
15+
if count == 0
16+
Travis.redis.setex(recaptcha_redis_key, captcha_block_duration, count + 1)
17+
else
18+
ttl = Travis.redis.ttl(recaptcha_redis_key)
19+
Travis.redis.setex(recaptcha_redis_key, ttl, count + 1)
20+
end
21+
raise ClientError, 'Error verifying reCAPTCHA, please try again.'
22+
end
23+
24+
address_data = params.dup.tap { |h| h.delete('subscription.id') }
25+
address_data = address_data.tap { |h| h.delete('token') }
26+
client = BillingClient.new(user_id)
27+
client.update_v2_address(params['subscription.id'], address_data) unless address_data.empty?
28+
client.update_v2_creditcard(params['subscription.id'], params['token'], params['fingerprint']) if params.key?('token')
29+
end
30+
531
def update_address(user_id)
632
address_data = params.dup.tap { |h| h.delete('subscription.id') }
733
client = BillingClient.new(user_id)
@@ -60,5 +86,19 @@ def update_auto_refill(user_id, addon_id)
6086
client = BillingClient.new(user_id)
6187
client.update_auto_refill(addon_id, threshold, amount)
6288
end
89+
90+
private
91+
92+
def recaptcha_client
93+
@recaptcha_client ||= RecaptchaClient.new
94+
end
95+
96+
def captcha_block_duration
97+
Travis.config.antifraud.captcha_block_duration
98+
end
99+
100+
def captcha_max_failed_attempts
101+
Travis.config.antifraud.captcha_max_failed_attempts
102+
end
63103
end
64104
end

lib/travis/api/v3/recaptcha_client.rb

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
module Travis::API::V3
2+
class RecaptchaClient
3+
class ConfigurationError < StandardError; end
4+
5+
def verify(token)
6+
response = connection.post('/recaptcha/api/siteverify') do |req|
7+
req.headers['Content-Type'] = 'application/x-www-form-urlencoded'
8+
req.body = URI.encode_www_form({ secret: recaptcha_secret, response: token })
9+
end
10+
handle_errors_and_respond(response) { |r| r['success'] }
11+
end
12+
13+
private
14+
15+
def handle_errors_and_respond(response)
16+
case response.status
17+
when 200, 201
18+
yield(JSON.parse(response.body)) if block_given?
19+
when 204
20+
true
21+
else
22+
raise Travis::API::V3::ServerError, 'ReCaptcha system error'
23+
end
24+
end
25+
26+
def connection
27+
@connection ||= Faraday.new(url: recaptcha_url, ssl: { ca_path: '/usr/lib/ssl/certs' }) do |conn|
28+
conn.use OpenCensus::Trace::Integrations::FaradayMiddleware if Travis::Api::App::Middleware::OpenCensus.enabled?
29+
conn.adapter Faraday.default_adapter
30+
end
31+
end
32+
33+
def recaptcha_url
34+
Travis.config.recaptcha.endpoint || raise(ConfigurationError, 'No recaptcha url configured')
35+
end
36+
37+
def recaptcha_secret
38+
Travis.config.recaptcha.secret || raise(ConfigurationError, 'No recaptcha secret configured')
39+
end
40+
end
41+
end
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
module Travis::API::V3
22
class Renderer::Allowance < ModelRenderer
33
representation(:minimal, :id)
4-
representation(:standard, :subscription_type, :public_repos, :private_repos, :concurrency_limit, :user_usage, :pending_user_licenses, :id)
4+
representation(:standard, :subscription_type, :public_repos, :private_repos, :concurrency_limit, :user_usage, :pending_user_licenses,
5+
:payment_changes_block_credit, :payment_changes_block_captcha, :credit_card_block_duration, :captcha_block_duration, :id)
56
end
67
end

0 commit comments

Comments
 (0)