Adding existing topic to project redirects to 500 page due to case sensitivity
Summary
If you try to add a topic to a project that was previously added with different capitalization (e.g., "smoketest" vs. "SmokeTest"), it results in a redirect to a 500 error page.
GitLab versions: at least 16.0.7 - 16.4.0-pre, could go further back
Steps to reproduce
- Create a project
- Navigate to project -> Settings -> General
- Add lowercase topic "smoketest"
- Save topics -> OK
- Add another CamelCase topic with same name: "SmokeTest"
- Save topics
After the last step, you are redirected to a 500 error page.
Example Project
What is the current bug behavior?
Redirect to a 500 error page.
What is the expected correct behavior?
Don't redirect. Display an error banner on the settings page with "Name has already been taken" message. In the Admin Area, this is the behavior when you try to add a topic which already has a case-insensitive match:
Relevant logs and/or screenshots
Log from 500 error:
ActiveRecord::RecordNotUnique (PG::UniqueViolation: ERROR: duplicate key value violates unique constraint "index_project_topics_on_project_id_and_topic_id"
DETAIL: Key (project_id, topic_id)=(41, 1) already exists.
):
lib/gitlab/database/load_balancing/connection_proxy.rb:127:in `public_send'
lib/gitlab/database/load_balancing/connection_proxy.rb:127:in `block in write_using_load_balancer'
lib/gitlab/database/load_balancing/load_balancer.rb:133:in `block in read_write'
lib/gitlab/database/load_balancing/load_balancer.rb:204:in `retry_with_backoff'
lib/gitlab/database/load_balancing/load_balancer.rb:122:in `read_write'
lib/gitlab/database/load_balancing/connection_proxy.rb:126:in `write_using_load_balancer'
lib/gitlab/database/load_balancing/connection_proxy.rb:61:in `block (2 levels) in <class:ConnectionProxy>'
lib/gitlab/database/load_balancing/connection_proxy.rb:127:in `public_send'
lib/gitlab/database/load_balancing/connection_proxy.rb:127:in `block in write_using_load_balancer'
lib/gitlab/database/load_balancing/load_balancer.rb:133:in `block in read_write'
lib/gitlab/database/load_balancing/load_balancer.rb:204:in `retry_with_backoff'
lib/gitlab/database/load_balancing/load_balancer.rb:122:in `read_write'
lib/gitlab/database/load_balancing/connection_proxy.rb:126:in `write_using_load_balancer'
lib/gitlab/database/load_balancing/connection_proxy.rb:78:in `transaction'
app/models/concerns/cross_database_modification.rb:92:in `block in transaction'
lib/gitlab/database/load_balancing/connection_proxy.rb:127:in `public_send'
lib/gitlab/database/load_balancing/connection_proxy.rb:127:in `block in write_using_load_balancer'
lib/gitlab/database/load_balancing/load_balancer.rb:133:in `block in read_write'
lib/gitlab/database/load_balancing/load_balancer.rb:204:in `retry_with_backoff'
lib/gitlab/database/load_balancing/load_balancer.rb:122:in `read_write'
lib/gitlab/database/load_balancing/connection_proxy.rb:126:in `write_using_load_balancer'
lib/gitlab/database/load_balancing/connection_proxy.rb:78:in `transaction'
lib/gitlab/database.rb:385:in `block in transaction'
lib/gitlab/database.rb:384:in `transaction'
app/models/concerns/cross_database_modification.rb:83:in `transaction'
app/models/concerns/cross_database_modification.rb:92:in `block in transaction'
lib/gitlab/database/load_balancing/connection_proxy.rb:127:in `public_send'
lib/gitlab/database/load_balancing/connection_proxy.rb:127:in `block in write_using_load_balancer'
lib/gitlab/database/load_balancing/load_balancer.rb:133:in `block in read_write'
lib/gitlab/database/load_balancing/load_balancer.rb:204:in `retry_with_backoff'
lib/gitlab/database/load_balancing/load_balancer.rb:122:in `read_write'
lib/gitlab/database/load_balancing/connection_proxy.rb:126:in `write_using_load_balancer'
lib/gitlab/database/load_balancing/connection_proxy.rb:78:in `transaction'
lib/gitlab/database.rb:385:in `block in transaction'
lib/gitlab/database.rb:384:in `transaction'
app/models/concerns/cross_database_modification.rb:83:in `transaction'
app/models/project.rb:3256:in `update_topics'
app/models/project.rb:3244:in `save_topics'
lib/gitlab/database/load_balancing/connection_proxy.rb:127:in `public_send'
lib/gitlab/database/load_balancing/connection_proxy.rb:127:in `block in write_using_load_balancer'
lib/gitlab/database/load_balancing/load_balancer.rb:133:in `block in read_write'
lib/gitlab/database/load_balancing/load_balancer.rb:204:in `retry_with_backoff'
lib/gitlab/database/load_balancing/load_balancer.rb:122:in `read_write'
lib/gitlab/database/load_balancing/connection_proxy.rb:126:in `write_using_load_balancer'
lib/gitlab/database/load_balancing/connection_proxy.rb:78:in `transaction'
lib/gitlab/database/load_balancing/connection_proxy.rb:127:in `public_send'
lib/gitlab/database/load_balancing/connection_proxy.rb:127:in `block in write_using_load_balancer'
lib/gitlab/database/load_balancing/load_balancer.rb:133:in `block in read_write'
lib/gitlab/database/load_balancing/load_balancer.rb:204:in `retry_with_backoff'
lib/gitlab/database/load_balancing/load_balancer.rb:122:in `read_write'
lib/gitlab/database/load_balancing/connection_proxy.rb:126:in `write_using_load_balancer'
lib/gitlab/database/load_balancing/connection_proxy.rb:78:in `transaction'
app/services/projects/update_service.rb:34:in `execute'
app/controllers/projects_controller.rb:114:in `update'
app/controllers/application_controller.rb:517:in `set_current_admin'
lib/gitlab/session.rb:11:in `with_session'
app/controllers/application_controller.rb:508:in `set_session_storage'
lib/gitlab/i18n.rb:107:in `with_locale'
lib/gitlab/i18n.rb:113:in `with_user_locale'
app/controllers/application_controller.rb:499:in `set_locale'
app/controllers/application_controller.rb:492:in `set_current_context'
lib/gitlab/metrics/elasticsearch_rack_middleware.rb:16:in `call'
lib/gitlab/middleware/memory_report.rb:13:in `call'
lib/gitlab/middleware/speedscope.rb:13:in `call'
lib/gitlab/database/load_balancing/rack_middleware.rb:23:in `call'
lib/gitlab/middleware/rails_queue_duration.rb:33:in `call'
lib/gitlab/metrics/rack_middleware.rb:16:in `block in call'
lib/gitlab/metrics/web_transaction.rb:46:in `run'
lib/gitlab/metrics/rack_middleware.rb:16:in `call'
lib/gitlab/jira/middleware.rb:19:in `call'
lib/gitlab/middleware/go.rb:20:in `call'
lib/gitlab/etag_caching/middleware.rb:21:in `call'
lib/gitlab/middleware/query_analyzer.rb:11:in `block in call'
lib/gitlab/database/query_analyzer.rb:37:in `within'
lib/gitlab/middleware/query_analyzer.rb:11:in `call'
lib/gitlab/middleware/multipart.rb:173:in `call'
lib/gitlab/middleware/read_only/controller.rb:50:in `call'
lib/gitlab/middleware/read_only.rb:18:in `call'
lib/gitlab/middleware/same_site_cookies.rb:27:in `call'
lib/gitlab/middleware/basic_health_check.rb:25:in `call'
lib/gitlab/middleware/handle_malformed_strings.rb:21:in `call'
lib/gitlab/middleware/handle_ip_spoof_attack_error.rb:25:in `call'
lib/gitlab/middleware/request_context.rb:15:in `call'
lib/gitlab/middleware/webhook_recursion_detection.rb:15:in `call'
config/initializers/fix_local_cache_middleware.rb:11:in `call'
lib/gitlab/middleware/compressed_json.rb:44:in `call'
lib/gitlab/middleware/rack_multipart_tempfile_factory.rb:19:in `call'
lib/gitlab/middleware/sidekiq_web_static.rb:20:in `call'
lib/gitlab/metrics/requests_rack_middleware.rb:79:in `call'
lib/gitlab/middleware/release_env.rb:13:in `call'
Possible fixes
Add uniqueness validation in app/models/projects/project_topic.rb
Implementation guide
Add uniqueness validation for project_id
in scope of topic_id
Edited by Thong Kuah