forked from rubocop/rubocop
/
require_mfa.rb
146 lines (131 loc) · 4.1 KB
/
require_mfa.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
136
137
138
139
140
141
142
143
144
145
146
# frozen_string_literal: true
module RuboCop
module Cop
module Gemspec
# Requires a gemspec to have `rubygems_mfa_required` metadata set.
#
# This setting tells RubyGems that MFA is required for accounts to
# be able perform any of these privileged operations:
#
# * gem push
# * gem yank
# * gem owner --add/remove
# * adding or removing owners using gem ownership page
#
# This helps make your gem more secure, as users can be more
# confident that gem updates were pushed by maintainers.
#
# @example
#
# # bad
# Gem::Specification.new do |spec|
# # no `rubygems_mfa_required` metadata specified
# end
#
# # good
# Gem::Specification.new do |spec|
# spec.metadata = {
# 'rubygems_mfa_required' => 'true'
# }
# end
#
# # good
# Gem::Specification.new do |spec|
# spec.metadata['rubygems_mfa_required'] = 'true'
# end
#
# # bad
# Gem::Specification.new do |spec|
# spec.metadata = {
# 'rubygems_mfa_required' => 'false'
# }
# end
#
# # good
# Gem::Specification.new do |spec|
# spec.metadata = {
# 'rubygems_mfa_required' => 'true'
# }
# end
#
# # bad
# Gem::Specification.new do |spec|
# spec.metadata['rubygems_mfa_required'] = 'false'
# end
#
# # good
# Gem::Specification.new do |spec|
# spec.metadata['rubygems_mfa_required'] = 'true'
# end
#
class RequireMFA < Base
include GemspecHelp
extend AutoCorrector
MSG = "`metadata['rubygems_mfa_required']` must be set to `'true'`."
# @!method metadata(node)
def_node_matcher :metadata, <<~PATTERN
`{
(send _ :metadata= $_)
(send (send _ :metadata) :[]= (str "rubygems_mfa_required") $_)
}
PATTERN
# @!method rubygems_mfa_required(node)
def_node_search :rubygems_mfa_required, <<~PATTERN
(pair (str "rubygems_mfa_required") $_)
PATTERN
# @!method true_string?(node)
def_node_matcher :true_string?, <<~PATTERN
(str "true")
PATTERN
def on_block(node) # rubocop:disable Metrics/MethodLength
gem_specification(node) do |block_var|
metadata_value = metadata(node)
mfa_value = mfa_value(metadata_value)
if mfa_value
unless true_string?(mfa_value)
add_offense(mfa_value) do |corrector|
change_value(corrector, mfa_value)
end
end
else
add_offense(node) do |corrector|
autocorrect(corrector, node, block_var, metadata_value)
end
end
end
end
private
def mfa_value(metadata_value)
return unless metadata_value
return metadata_value if metadata_value.str_type?
rubygems_mfa_required(metadata_value).first
end
def autocorrect(corrector, node, block_var, metadata)
if metadata
return unless metadata.hash_type?
correct_metadata(corrector, metadata)
else
correct_missing_metadata(corrector, node, block_var)
end
end
def correct_metadata(corrector, metadata)
if metadata.pairs.any?
corrector.insert_after(metadata.pairs.last, ",\n'rubygems_mfa_required' => 'true'")
else
corrector.insert_before(metadata.loc.end, "'rubygems_mfa_required' => 'true'")
end
end
def correct_missing_metadata(corrector, node, block_var)
corrector.insert_before(node.loc.end, <<~RUBY)
#{block_var}.metadata = {
'rubygems_mfa_required' => 'true'
}
RUBY
end
def change_value(corrector, value)
corrector.replace(value, "'true'")
end
end
end
end
end