Commit 2fb02f92 authored by Valery Sizov's avatar Valery Sizov

Merge branch 'fix/improve_reset_service_password' into 'master'

Improve invalidation of stored service password if the endpoint URL is changed

A number of issues were found in !1490 and !1558 (triggered by support request 7395)

* It is not possible to set a new URL and a password at the same time (new password is ignored)
* An error occurs on the Service Templates admin pages (prop_updated? was referencing the service's project, which is not defined for templates)
* Passwords are reset on every save in Service Templates admin pages

This should fix these 3 issues by respectively:
* Differentiating a property that has been assigned a new value (regardless of the new value) and a property that has been assigned a new value that is different from the old one
* Providing an alternate implementation to detected updated properties, not relying on the service's project
* Filtering an empty password parameter passed to the Service Templates admin page like on the project service page

See merge request !1583
parents a2f0a365 b4639754
...@@ -39,7 +39,13 @@ class Admin::ServicesController < Admin::ApplicationController ...@@ -39,7 +39,13 @@ class Admin::ServicesController < Admin::ApplicationController
end end
def application_services_params def application_services_params
params.permit(:id, application_services_params = params.permit(:id,
service: Projects::ServicesController::ALLOWED_PARAMS) service: Projects::ServicesController::ALLOWED_PARAMS)
if application_services_params[:service].is_a?(Hash)
Projects::ServicesController::FILTER_BLANK_PARAMS.each do |param|
application_services_params[:service].delete(param) if application_services_params[:service][param].blank?
end
end
application_services_params
end end
end end
...@@ -9,6 +9,10 @@ class Projects::ServicesController < Projects::ApplicationController ...@@ -9,6 +9,10 @@ class Projects::ServicesController < Projects::ApplicationController
:note_events, :send_from_committer_email, :disable_diffs, :external_wiki_url, :note_events, :send_from_committer_email, :disable_diffs, :external_wiki_url,
:notify, :color, :notify, :color,
:server_host, :server_port, :default_irc_uri, :enable_ssl_verification] :server_host, :server_port, :default_irc_uri, :enable_ssl_verification]
# Parameters to ignore if no value is specified
FILTER_BLANK_PARAMS = [:password]
# Authorize # Authorize
before_action :authorize_admin_project! before_action :authorize_admin_project!
before_action :service, only: [:edit, :update, :test] before_action :service, only: [:edit, :update, :test]
...@@ -59,7 +63,9 @@ class Projects::ServicesController < Projects::ApplicationController ...@@ -59,7 +63,9 @@ class Projects::ServicesController < Projects::ApplicationController
def service_params def service_params
service_params = params.require(:service).permit(ALLOWED_PARAMS) service_params = params.require(:service).permit(ALLOWED_PARAMS)
service_params.delete("password") if service_params["password"].blank? FILTER_BLANK_PARAMS.each do |param|
service_params.delete(param) if service_params[param].blank?
end
service_params service_params
end end
end end
...@@ -48,7 +48,7 @@ class BambooService < CiService ...@@ -48,7 +48,7 @@ class BambooService < CiService
end end
def reset_password def reset_password
if prop_updated?(:bamboo_url) if prop_modified?(:bamboo_url) && !prop_updated?(:password)
self.password = nil self.password = nil
end end
end end
......
...@@ -45,7 +45,7 @@ class TeamcityService < CiService ...@@ -45,7 +45,7 @@ class TeamcityService < CiService
end end
def reset_password def reset_password
if prop_updated?(:teamcity_url) if prop_modified?(:teamcity_url) && !prop_updated?(:password)
self.password = nil self.password = nil
end end
end end
......
...@@ -33,6 +33,8 @@ class Service < ActiveRecord::Base ...@@ -33,6 +33,8 @@ class Service < ActiveRecord::Base
after_initialize :initialize_properties after_initialize :initialize_properties
after_commit :reset_updated_properties
belongs_to :project belongs_to :project
has_one :service_hook has_one :service_hook
...@@ -103,6 +105,7 @@ class Service < ActiveRecord::Base ...@@ -103,6 +105,7 @@ class Service < ActiveRecord::Base
# Provide convenient accessor methods # Provide convenient accessor methods
# for each serialized property. # for each serialized property.
# Also keep track of updated properties.
def self.prop_accessor(*args) def self.prop_accessor(*args)
args.each do |arg| args.each do |arg|
class_eval %{ class_eval %{
...@@ -111,19 +114,36 @@ class Service < ActiveRecord::Base ...@@ -111,19 +114,36 @@ class Service < ActiveRecord::Base
end end
def #{arg}=(value) def #{arg}=(value)
updated_properties['#{arg}'] = #{arg} unless updated_properties.include?('#{arg}')
self.properties['#{arg}'] = value self.properties['#{arg}'] = value
end end
} }
end end
end end
# ActiveRecord does not provide a mechanism to track changes in serialized keys. # Returns a hash of the properties that have been assigned a new value since last save,
# This is why we need to perform extra query to do it mannually. # indicating their original values (attr => original value).
# ActiveRecord does not provide a mechanism to track changes in serialized keys,
# so we need a specific implementation for service properties.
# This allows to track changes to properties set with the accessor methods,
# but not direct manipulation of properties hash.
def updated_properties
@updated_properties ||= ActiveSupport::HashWithIndifferentAccess.new
end
def prop_updated?(prop_name) def prop_updated?(prop_name)
relation_name = self.type.underscore # Check if a new value was set.
previous_value = project.send(relation_name).send(prop_name) # The new value may or may not be the same as the old value
return false if previous_value.nil? updated_properties.include?(prop_name)
previous_value != send(prop_name) end
def prop_modified?(prop_name)
# Check if new value was set and it is different from the old value
prop_updated?(prop_name) && updated_properties[prop_name] != send(prop_name)
end
def reset_updated_properties
@updated_properties = nil
end end
def async_execute(data) def async_execute(data)
......
...@@ -116,14 +116,47 @@ describe Service do ...@@ -116,14 +116,47 @@ describe Service do
) )
end end
it "returns false" do it "returns false when the property has not been assigned a new value" do
service.username = "key_changed" service.username = "key_changed"
expect(service.prop_updated?(:bamboo_url)).to be_falsy expect(service.prop_updated?(:bamboo_url)).to be_falsy
end end
it "returns true" do it "returns true when the property has been assigned a different value" do
service.bamboo_url = "http://other.com" service.bamboo_url = "http://example.com"
expect(service.prop_updated?(:bamboo_url)).to be_truthy expect(service.prop_updated?(:bamboo_url)).to be_truthy
end end
it "returns true when the property has been re-assigned the same value" do
service.bamboo_url = 'http://gitlab.com'
expect(service.prop_updated?(:bamboo_url)).to be_truthy
end
end
describe "#prop_modified?" do
let(:service) do
BambooService.create(
project: create(:project),
properties: {
bamboo_url: 'http://gitlab.com',
username: 'mic',
password: "password"
}
)
end
it "returns false when the property has not been assigned a new value" do
service.username = "key_changed"
expect(service.prop_modified?(:bamboo_url)).to be_falsy
end
it "returns true when the property has been assigned a different value" do
service.bamboo_url = "http://example.com"
expect(service.prop_modified?(:bamboo_url)).to be_truthy
end
it "returns false when the property has been re-assigned the same value" do
service.bamboo_url = 'http://gitlab.com'
expect(service.prop_modified?(:bamboo_url)).to be_falsy
end
end end
end end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment