-
-
Notifications
You must be signed in to change notification settings - Fork 904
/
base_controller.rb
135 lines (109 loc) · 4.48 KB
/
base_controller.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
class Api::BaseController < ApplicationController
skip_before_action :verify_authenticity_token
after_action :skip_session
private
def name_params
params.permit(:gem_name, :rubygem_name)
end
def gem_name
name_params[:gem_name] || name_params[:rubygem_name]
end
def find_rubygem_by_name
@rubygem = Rubygem.find_by name: gem_name
return if @rubygem
render plain: "This gem could not be found", status: :not_found
end
def enqueue_web_hook_jobs(version)
jobs = version.rubygem.web_hooks.enabled + WebHook.global.enabled
jobs.each do |job|
job.fire(
request.protocol.delete("://"),
request.host_with_port,
version
)
end
end
def verify_api_key_gem_scope
return unless @api_key.rubygem && @api_key.rubygem != @rubygem
render plain: "This API key cannot perform the specified action on this gem.", status: :forbidden
end
def verify_with_otp
otp = request.headers["HTTP_OTP"]
return if @api_key.mfa_authorized?(otp)
prompt_text = otp.present? ? t(:otp_incorrect) : t(:otp_missing)
render plain: prompt_text, status: :unauthorized
end
def verify_mfa_requirement
if @rubygem && !@rubygem.mfa_requirement_satisfied_for?(@api_key.user)
render plain: "Gem requires MFA enabled; You do not have MFA enabled yet.", status: :forbidden
elsif @api_key.mfa_required_not_yet_enabled?
render_mfa_setup_required_error
elsif @api_key.mfa_required_weak_level_enabled?
render_mfa_strong_level_required_error
end
end
def response_with_mfa_warning(response)
message = response
if @api_key.mfa_recommended_not_yet_enabled?
message += <<~WARN.chomp
[WARNING] For protection of your account and gems, we encourage you to set up multi-factor authentication \
at https://rubygems.org/multifactor_auth/new. Your account will be required to have MFA enabled in the future.
WARN
elsif @api_key.mfa_recommended_weak_level_enabled?
message += <<~WARN.chomp
[WARNING] For protection of your account and gems, we encourage you to change your multi-factor authentication \
level to 'UI and gem signin' or 'UI and API' at https://rubygems.org/settings/edit. \
Your account will be required to have MFA enabled on one of these levels in the future.
WARN
end
message
end
def render_mfa_setup_required_error
error = <<~ERROR.chomp
[ERROR] For protection of your account and your gems, you are required to set up multi-factor authentication \
at https://rubygems.org/multifactor_auth/new.
Please read our blog post for more details (https://blog.rubygems.org/2022/08/15/requiring-mfa-on-popular-gems.html).
ERROR
render_forbidden(error)
end
def render_mfa_strong_level_required_error
error = <<~ERROR.chomp
[ERROR] For protection of your account and your gems, you are required to change your MFA level to 'UI and gem signin' or 'UI and API' \
at https://rubygems.org/settings/edit.
Please read our blog post for more details (https://blog.rubygems.org/2022/08/15/requiring-mfa-on-popular-gems.html).
ERROR
render_forbidden(error)
end
def authenticate_with_api_key
params_key = request.headers["Authorization"] || ""
hashed_key = Digest::SHA256.hexdigest(params_key)
@api_key = ApiKey.unexpired.find_by_hashed_key(hashed_key)
return render_unauthorized unless @api_key
set_tags "gemcutter.api_key.owner" => @api_key.owner.to_gid, "gemcutter.user.api_key_id" => @api_key.id
Current.user = @api_key.user
render_soft_deleted_api_key if @api_key.soft_deleted?
end
def verify_user_api_key
render_api_key_forbidden if @api_key.user.blank?
end
def render_unauthorized
render plain: t(:please_sign_up), status: :unauthorized
end
def render_api_key_forbidden
respond_to do |format|
format.any(:all) { render plain: t(:api_key_forbidden), status: :forbidden }
format.json { render json: { error: t(:api_key_forbidden) }, status: :forbidden }
format.yaml { render yaml: { error: t(:api_key_forbidden) }, status: :forbidden }
end
end
def render_soft_deleted_api_key
render plain: "An invalid API key cannot be used. Please delete it and create a new one.", status: :forbidden
end
def skip_session
request.session_options[:skip] = true
end
def render_bad_request(error = "bad request")
error = error.message if error.is_a?(Exception)
render json: { error: error.to_s }, status: :bad_request
end
end