forked from rubocop/rubocop
/
require_mfa.rb
145 lines (130 loc) · 4.17 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
# 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 (Multi-Factor Authentication) is
# required for accounts to be able perform privileged operations, such as
# (see RubyGems' documentation for the full list of 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
insert_mfa_required(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 insert_mfa_required(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