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
|
require './spec/support/import_export/configuration_helper'
module ExportFileHelper
include ConfigurationHelper
ObjectWithParent = Struct.new(:object, :parent, :key_found)
def setup_project
project = create(:project, :public, :repository)
create(:release, project: project)
issue = create(:issue, assignees: [user], project: project)
snippet = create(:project_snippet, project: project)
label = create(:label, project: project)
milestone = create(:milestone, project: project)
merge_request = create(:merge_request, source_project: project, milestone: milestone)
commit_status = create(:commit_status, project: project)
create(:label_link, label: label, target: issue)
ci_pipeline = create(:ci_pipeline,
project: project,
sha: merge_request.diff_head_sha,
ref: merge_request.source_branch,
statuses: [commit_status])
create(:ci_build, pipeline: ci_pipeline, project: project)
create(:milestone, project: project)
create(:note, noteable: issue, project: project)
create(:note, noteable: merge_request, project: project)
create(:note, noteable: snippet, project: project)
create(:note_on_commit,
author: user,
project: project,
commit_id: ci_pipeline.sha)
event = create(:event, :created, target: milestone, project: project, author: user, action: 5)
create(:push_event_payload, event: event)
create(:project_member, :maintainer, user: user, project: project)
create(:ci_variable, project: project)
create(:ci_trigger, project: project)
key = create(:deploy_key)
key.projects << project
create(:service, project: project)
create(:project_hook, project: project, token: 'token')
create(:protected_branch, project: project)
project
end
# Expands the compressed file for an exported project into +tmpdir+
def in_directory_with_expanded_export(project)
Dir.mktmpdir do |tmpdir|
export_file = project.export_file.path
_output, exit_status = Gitlab::Popen.popen(%W{tar -zxf #{export_file} -C #{tmpdir}})
yield(exit_status, tmpdir)
end
end
# Recursively finds key/values including +key+ as part of the key, inside a nested hash
def deep_find_with_parent(sensitive_key_word, object, found = nil)
sensitive_key_found = object_contains_key?(object, sensitive_key_word)
# Returns the parent object and the object found containing a sensitive word as part of the key
if sensitive_key_found && object[sensitive_key_found]
ObjectWithParent.new(object[sensitive_key_found], object, sensitive_key_found)
elsif object.is_a?(Enumerable)
# Recursively lookup for keys containing sensitive words in a Hash or Array
object_with_parent = nil
object.find do |*hash_or_array|
object_with_parent = deep_find_with_parent(sensitive_key_word, hash_or_array.last, found)
end
object_with_parent
end
end
# Return true if the hash has a key containing a sensitive word
def object_contains_key?(object, sensitive_key_word)
return false unless object.is_a?(Hash)
object.keys.find { |key| key.include?(sensitive_key_word) }
end
# Returns the offended ObjectWithParent object if a sensitive word is found inside a hash,
# excluding the whitelisted safe hashes.
def find_sensitive_attributes(sensitive_word, project_hash)
loop do
object_with_parent = deep_find_with_parent(sensitive_word, project_hash)
return unless object_with_parent && object_with_parent.object
if is_safe_hash?(object_with_parent.parent, sensitive_word)
# It's in the safe list, remove hash and keep looking
object_with_parent.parent.delete(object_with_parent.key_found)
else
return object_with_parent
end
nil
end
end
# Returns true if it's one of the excluded models in +safe_list+
def is_safe_hash?(parent, sensitive_word)
return false unless parent && safe_list[sensitive_word.to_sym]
# Extra attributes that appear in a model but not in the exported hash.
excluded_attributes = ['type']
safe_list[sensitive_word.to_sym].each do |model|
# Check whether this is a hash attribute inside a model
if model.is_a?(Symbol)
return true if (safe_hashes[model] - parent.keys).empty?
else
return true if safe_model?(model, excluded_attributes, parent)
end
end
false
end
# Compares model attributes with those found in the hash
# and returns true if there is a match, ignoring some excluded attributes.
def safe_model?(model, excluded_attributes, parent)
excluded_attributes += associations_for(model)
parsed_model_attributes = parsed_attributes(model.name.underscore, model.attribute_names)
(parsed_model_attributes - parent.keys - excluded_attributes).empty?
end
def file_permissions(file)
File.lstat(file).mode & 0777
end
end
|