Skip to content

Commit d4a7639

Browse files
committed
Added pipeline-config API material whitelist support
1 parent 80c7cfb commit d4a7639

File tree

56 files changed

+5004
-33
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

56 files changed

+5004
-33
lines changed
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
##########################################################################
2+
# Copyright 2016 ThoughtWorks, Inc.
3+
#
4+
# Licensed under the Apache License, Version 2.0 (the "License");
5+
# you may not use this file except in compliance with the License.
6+
# You may obtain a copy of the License at
7+
#
8+
# http://www.apache.org/licenses/LICENSE-2.0
9+
#
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS,
12+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
# See the License for the specific language governing permissions and
14+
# limitations under the License.
15+
##########################################################################
16+
17+
module ApiV2
18+
module Admin
19+
class PipelinesController < ApiV2::BaseController
20+
before_action :check_pipeline_group_admin_user_and_401
21+
before_action :load_pipeline, only: [:show, :destroy]
22+
before_action :check_if_pipeline_by_same_name_already_exists, :check_group_not_blank, only: [:create]
23+
before_action :check_for_stale_request, :check_for_attempted_pipeline_rename, only: [:update]
24+
25+
def show
26+
if stale?(etag: get_etag_for_pipeline(@pipeline_config.name.to_s))
27+
json = ApiV2::Config::PipelineConfigRepresenter.new(@pipeline_config).to_hash(url_builder: self)
28+
render DEFAULT_FORMAT => json
29+
end
30+
end
31+
32+
def create
33+
result = HttpLocalizedOperationResult.new
34+
get_pipeline_from_request
35+
pipeline_config_service.createPipelineConfig(current_user, @pipeline_config_from_request, result, params[:group])
36+
handle_config_save_or_update_result(result, @pipeline_config_from_request.name.to_s)
37+
if result.isSuccessful
38+
pipeline_pause_service.pause(@pipeline_config_from_request.name.to_s, "Under construction", current_user)
39+
end
40+
end
41+
42+
def update
43+
result = HttpLocalizedOperationResult.new
44+
get_pipeline_from_request
45+
pipeline_config_service.updatePipelineConfig(current_user, @pipeline_config_from_request, get_etag_for_pipeline(params[:pipeline_name]), result)
46+
handle_config_save_or_update_result(result)
47+
end
48+
49+
def destroy
50+
result = HttpLocalizedOperationResult.new
51+
pipeline_config_service.deletePipelineConfig(current_user, @pipeline_config, result)
52+
render_http_operation_result(result)
53+
end
54+
55+
private
56+
57+
def get_pipeline_from_request
58+
@pipeline_config_from_request ||= PipelineConfig.new.tap do |config|
59+
ApiV2::Config::PipelineConfigRepresenter.new(config).from_hash(params[:pipeline], {go_config: go_config_service.getCurrentConfig()})
60+
end
61+
end
62+
63+
def handle_config_save_or_update_result(result, pipeline_name = params[:pipeline_name])
64+
if result.isSuccessful
65+
load_pipeline(pipeline_name)
66+
json = ApiV2::Config::PipelineConfigRepresenter.new(@pipeline_config).to_hash(url_builder: self)
67+
response.etag = [get_etag_for_pipeline(@pipeline_config.name.to_s)]
68+
render DEFAULT_FORMAT => json
69+
else
70+
json = ApiV2::Config::PipelineConfigRepresenter.new(@pipeline_config_from_request).to_hash(url_builder: self)
71+
render_http_operation_result(result, {data: json})
72+
end
73+
end
74+
75+
def check_for_attempted_pipeline_rename
76+
unless CaseInsensitiveString.new(params[:pipeline][:name]) == CaseInsensitiveString.new(params[:pipeline_name])
77+
result = HttpLocalizedOperationResult.new
78+
result.notAcceptable(LocalizedMessage::string("PIPELINE_RENAMING_NOT_ALLOWED"))
79+
render_http_operation_result(result)
80+
end
81+
end
82+
83+
def get_etag_for_pipeline(pipeline_name)
84+
entity_hashing_service.md5ForPipelineConfig(pipeline_name)
85+
end
86+
87+
def check_for_stale_request
88+
return unless stale_request?
89+
90+
result = HttpLocalizedOperationResult.new
91+
result.stale(LocalizedMessage::string("STALE_RESOURCE_CONFIG", 'pipeline', params[:pipeline_name]))
92+
render_http_operation_result(result)
93+
end
94+
95+
def stale_request?
96+
request.env["HTTP_IF_MATCH"] != "\"#{Digest::MD5.hexdigest(get_etag_for_pipeline(params[:pipeline_name]))}\""
97+
end
98+
99+
def load_pipeline(pipeline_name = params[:pipeline_name])
100+
@pipeline_config = pipeline_config_service.getPipelineConfig(pipeline_name)
101+
raise RecordNotFound if @pipeline_config.nil?
102+
end
103+
104+
def check_if_pipeline_by_same_name_already_exists
105+
if (!pipeline_config_service.getPipelineConfig(params[:pipeline_name]).nil?)
106+
result = HttpLocalizedOperationResult.new
107+
result.unprocessableEntity(LocalizedMessage::string("CANNOT_CREATE_PIPELINE_ALREADY_EXISTS", params[:pipeline_name]))
108+
render_http_operation_result(result)
109+
end
110+
end
111+
112+
def check_group_not_blank
113+
if (params[:group].blank?)
114+
result = HttpLocalizedOperationResult.new
115+
result.unprocessableEntity(LocalizedMessage::string("PIPELINE_GROUP_MANDATORY_FOR_PIPELINE_CREATE"))
116+
render_http_operation_result(result)
117+
end
118+
end
119+
end
120+
end
121+
end

server/webapp/WEB-INF/rails.new/app/helpers/api_v2/authentication_helper.rb

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
##########################################################################
2-
# Copyright 2015 ThoughtWorks, Inc.
2+
# Copyright 2016 ThoughtWorks, Inc.
33
#
44
# Licensed under the Apache License, Version 2.0 (the "License");
55
# you may not use this file except in compliance with the License.
@@ -40,6 +40,16 @@ def check_admin_user_and_401
4040
end
4141
end
4242

43+
def check_pipeline_group_admin_user_and_401
44+
groupName = params[:group] || go_config_service.findGroupNameByPipeline(com.thoughtworks.go.config.CaseInsensitiveString.new(params[:pipeline_name]))
45+
return unless security_service.isSecurityEnabled()
46+
unless is_user_an_admin_for_group?(current_user, groupName)
47+
Rails.logger.info("User '#{current_user.getUsername}' attempted to perform an unauthorized action!")
48+
render_unauthorized_error
49+
end
50+
end
51+
52+
4353
def verify_content_type_on_post
4454
if [:put, :post, :patch].include?(request.request_method_symbol) && !request.raw_post.blank? && request.content_mime_type != :json
4555
render ApiV2::BaseController::DEFAULT_FORMAT => { message: "You must specify a 'Content-Type' of 'application/json'" }, status: :unsupported_media_type
@@ -58,5 +68,14 @@ def render_unauthorized_error
5868
render ApiV2::BaseController::DEFAULT_FORMAT => { :message => 'You are not authorized to perform this action.' }, :status => 401
5969
end
6070

71+
private
72+
def is_user_an_admin_for_group?(current_user, group_name)
73+
if go_config_service.groups().hasGroup(group_name)
74+
return security_service.isUserAdminOfGroup(current_user.getUsername, group_name)
75+
else
76+
return security_service.isUserAdmin(current_user)
77+
end
78+
end
79+
6180
end
6281
end

server/webapp/WEB-INF/rails.new/app/presenters/api_v2/agent_representer.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ class AgentRepresenter < ApiV2::BaseRepresenter
4747
property :build_state, exec_context: :decorator
4848
property :resources, exec_context: :decorator
4949
property :environments, exec_context: :decorator
50-
property :errors, exec_context: :decorator, decorator: ApiV1::Config::ErrorRepresenter, skip_parse: true, skip_render: lambda { |object, options| object.empty? }
50+
property :errors, exec_context: :decorator, decorator: ApiV2::Config::ErrorRepresenter, skip_parse: true, skip_render: lambda { |object, options| object.empty? }
5151

5252
def agent_config_state
5353
agent.getAgentConfigStatus()

server/webapp/WEB-INF/rails.new/app/presenters/api_v2/base_representer.rb

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -117,23 +117,5 @@ def with_default_values(hash)
117117
memo
118118
end
119119
end
120-
121-
def from_hash(data, options={})
122-
super(with_default_values(data), options)
123-
end
124-
125-
private
126-
def with_default_values(hash)
127-
hash ||= {}
128-
129-
if hash.respond_to?(:has_key?)
130-
hash = hash.deep_symbolize_keys
131-
end
132-
133-
self.collection_items.inject(hash) do |memo, item|
134-
memo[item] ||= []
135-
memo
136-
end
137-
end
138120
end
139121
end
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
##########################################################################
2+
# Copyright 2016 ThoughtWorks, Inc.
3+
#
4+
# Licensed under the Apache License, Version 2.0 (the "License");
5+
# you may not use this file except in compliance with the License.
6+
# You may obtain a copy of the License at
7+
#
8+
# http://www.apache.org/licenses/LICENSE-2.0
9+
#
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS,
12+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
# See the License for the specific language governing permissions and
14+
# limitations under the License.
15+
##########################################################################
16+
17+
module ApiV2
18+
module Config
19+
class ApprovalRepresenter < ApiV2::BaseRepresenter
20+
alias_method :approval, :represented
21+
22+
error_representer
23+
24+
property :type
25+
property :auth_config, as: :authorization, decorator: ApiV2::Config::StageAuthorizationRepresenter, class: AuthConfig
26+
end
27+
end
28+
29+
end
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
##########################################################################
2+
# Copyright 2016 ThoughtWorks, Inc.
3+
#
4+
# Licensed under the Apache License, Version 2.0 (the "License");
5+
# you may not use this file except in compliance with the License.
6+
# You may obtain a copy of the License at
7+
#
8+
# http://www.apache.org/licenses/LICENSE-2.0
9+
#
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS,
12+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
# See the License for the specific language governing permissions and
14+
# limitations under the License.
15+
##########################################################################
16+
17+
module ApiV2
18+
module Config
19+
class ArtifactRepresenter < ApiV2::BaseRepresenter
20+
alias_method :artifact, :represented
21+
22+
error_representer({"src" => "source", "dest" => "destination"})
23+
24+
ARTIFACT_TYPE_TO_STRING_TYPE_MAP = {
25+
ArtifactType::unit => 'test',
26+
ArtifactType::file => 'build'
27+
}
28+
29+
ARTIFACT_TYPE_TO_ARTIFACT_CLASS_MAP = {
30+
'test' => TestArtifactPlan,
31+
'build' => ArtifactPlan
32+
}
33+
34+
property :src, as: :source
35+
property :dest, as: :destination
36+
property :type, exec_context: :decorator, skip_parse: true
37+
38+
def type
39+
ARTIFACT_TYPE_TO_STRING_TYPE_MAP[artifact.getArtifactType]
40+
end
41+
42+
class << self
43+
def get_class_for_artifact_type(type)
44+
ARTIFACT_TYPE_TO_ARTIFACT_CLASS_MAP[type] || (raise ApiV2::UnprocessableEntity, "Invalid Artifact type: '#{type}'. It has to be one of #{ARTIFACT_TYPE_TO_ARTIFACT_CLASS_MAP.keys.join(', ')}")
45+
end
46+
end
47+
end
48+
end
49+
end
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
##########################################################################
2+
# Copyright 2016 ThoughtWorks, Inc.
3+
#
4+
# Licensed under the Apache License, Version 2.0 (the "License");
5+
# you may not use this file except in compliance with the License.
6+
# You may obtain a copy of the License at
7+
#
8+
# http://www.apache.org/licenses/LICENSE-2.0
9+
#
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS,
12+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
# See the License for the specific language governing permissions and
14+
# limitations under the License.
15+
##########################################################################
16+
17+
module ApiV2
18+
module Config
19+
class EnvironmentVariableRepresenter < ApiV2::BaseRepresenter
20+
alias_method :environment_variable, :represented
21+
22+
error_representer({'encryptedValue' => 'encrypted_value'})
23+
24+
property :isSecure, as: :secure, writable: false
25+
property :name, writable: false
26+
property :value, skip_nil: true, writable: false, exec_context: :decorator
27+
property :encrypted_value, skip_nil: true, writable: false, exec_context: :decorator
28+
property :errors, exec_context: :decorator, decorator: ApiV2::Config::ErrorRepresenter, skip_parse: true, skip_render: lambda { |object, options| object.empty? }
29+
30+
def value
31+
environment_variable.getValueForDisplay() if environment_variable.isPlain
32+
end
33+
34+
def encrypted_value
35+
environment_variable.getValueForDisplay() if environment_variable.isSecure
36+
end
37+
38+
def from_hash(data, options={})
39+
data = data.with_indifferent_access
40+
environment_variable.deserialize(data[:name], data[:value], data[:secure].to_bool, data[:encrypted_value])
41+
environment_variable
42+
end
43+
44+
end
45+
end
46+
end

0 commit comments

Comments
 (0)