diff --git a/app/helpers/access_tokens_helper.rb b/app/helpers/access_tokens_helper.rb index f05c764bfb40e12e0584650a9928f0fbcfaba98b..66b5256a18b185c56c5f5dbb602028031effaf4e 100644 --- a/app/helpers/access_tokens_helper.rb +++ b/app/helpers/access_tokens_helper.rb @@ -36,6 +36,8 @@ def tokens_app_data end def expires_at_field_data + return {} unless Gitlab::CurrentSettings.require_personal_access_token_expiry? + { min_date: 1.day.from_now.iso8601 } diff --git a/app/helpers/application_settings_helper.rb b/app/helpers/application_settings_helper.rb index 5ba028a3b0324b2c5fb011cc0f733548e173f0fa..e14186b6929bda69fd25db8b5eb8797f10c17f7d 100644 --- a/app/helpers/application_settings_helper.rb +++ b/app/helpers/application_settings_helper.rb @@ -8,6 +8,7 @@ module ApplicationSettingsHelper :password_authentication_enabled_for_web?, :akismet_enabled?, :spam_check_endpoint_enabled?, + :require_personal_access_token_expiry?, to: :'Gitlab::CurrentSettings.current_application_settings' def user_oauth_applications? @@ -526,7 +527,8 @@ def visible_attributes :downstream_pipeline_trigger_limit_per_project_user_sha, :asciidoc_max_includes, :ai_action_api_rate_limit, - :code_suggestions_api_rate_limit + :code_suggestions_api_rate_limit, + :require_personal_access_token_expiry ].tap do |settings| unless Gitlab.com? settings << :deactivate_dormant_users diff --git a/app/models/application_setting_implementation.rb b/app/models/application_setting_implementation.rb index 2d14747edff4e10823b23561929cd33b511afaa9..a7243dbab653f333da1c2544434a05afa3f8e34e 100644 --- a/app/models/application_setting_implementation.rb +++ b/app/models/application_setting_implementation.rb @@ -289,7 +289,8 @@ def defaults # rubocop:disable Metrics/AbcSize user_starred_projects_api_limit: 100, nuget_skip_metadata_url_validation: false, ai_action_api_rate_limit: 160, - code_suggestions_api_rate_limit: 60 + code_suggestions_api_rate_limit: 60, + require_personal_access_token_expiry: true }.tap do |hsh| hsh.merge!(non_production_defaults) unless Rails.env.production? end diff --git a/app/models/personal_access_token.rb b/app/models/personal_access_token.rb index 4135b398b09827f675d2e49f3fc36d64ca0cbf60..9c98ac21b7bff52c35f2d2b797a0ec0482dcf5d9 100644 --- a/app/models/personal_access_token.rb +++ b/app/models/personal_access_token.rb @@ -104,7 +104,7 @@ def prefix_from_application_current_settings end def allow_expires_at_to_be_empty? - false + !Gitlab::CurrentSettings.require_personal_access_token_expiry? end def expires_at_before_instance_max_expiry_date diff --git a/app/services/personal_access_tokens/create_service.rb b/app/services/personal_access_tokens/create_service.rb index 095cfadf02c80bf491ea3a9abf4a3022ed0ce7e4..a8bc922e8a288bd74eb89aa792a2ce5af9775c30 100644 --- a/app/services/personal_access_tokens/create_service.rb +++ b/app/services/personal_access_tokens/create_service.rb @@ -41,7 +41,11 @@ def personal_access_token_params end def pat_expiration - params[:expires_at].presence || max_expiry_date + return params[:expires_at] if params[:expires_at].present? + + return max_expiry_date if Gitlab::CurrentSettings.require_personal_access_token_expiry? + + nil end def max_expiry_date diff --git a/app/services/resource_access_tokens/create_service.rb b/app/services/resource_access_tokens/create_service.rb index 9e195184f47a682d121f10a89c07d7106b22d776..d6931d997cee372f15add69a4df457315e9e9c41 100644 --- a/app/services/resource_access_tokens/create_service.rb +++ b/app/services/resource_access_tokens/create_service.rb @@ -117,7 +117,13 @@ def create_membership(resource, user, access_level) end def pat_expiration - params[:expires_at].presence || PersonalAccessToken::MAX_PERSONAL_ACCESS_TOKEN_LIFETIME_IN_DAYS.days.from_now + return params[:expires_at] if params[:expires_at].present? + + if Gitlab::CurrentSettings.require_personal_access_token_expiry? + return PersonalAccessToken::MAX_PERSONAL_ACCESS_TOKEN_LIFETIME_IN_DAYS.days.from_now + end + + nil end def log_event(token) diff --git a/app/views/admin/application_settings/_account_and_limit.html.haml b/app/views/admin/application_settings/_account_and_limit.html.haml index 8708d2afc0e6be123bd2927340241a532eee4acf..33137daa3c4cafbd44c733daa2e98b98c7b91ff5 100644 --- a/app/views/admin/application_settings/_account_and_limit.html.haml +++ b/app/views/admin/application_settings/_account_and_limit.html.haml @@ -29,6 +29,11 @@ = f.gitlab_ui_checkbox_component :remember_me_enabled, _('Allow users to extend their session'), help_text: _("Users can select 'Remember me' on sign-in to keep their session active beyond the session duration. %{link_start}Learn more%{link_end}.").html_safe % { link_start: remember_me_help_link_start, link_end: '</a>'.html_safe } = render_if_exists 'admin/application_settings/git_two_factor_session_expiry', form: f + %h5 + = _('Personal/project/group access token expiration') + .form-group + = f.gitlab_ui_checkbox_component :require_personal_access_token_expiry, _('Require expiration date'), + help_text: _('When enabled, a user will be required to enter in an expiration date when creating an access token. Changes will not affect existing token expiration dates.') = render_if_exists 'admin/application_settings/personal_access_token_expiration_policy', form: f = render_if_exists 'admin/application_settings/service_access_tokens_expiration_enforced', form: f = render_if_exists 'admin/application_settings/ssh_key_expiration_policy', form: f diff --git a/db/migrate/20240702181131_add_require_pat_expiry_to_application_settings.rb b/db/migrate/20240702181131_add_require_pat_expiry_to_application_settings.rb new file mode 100644 index 0000000000000000000000000000000000000000..5ecc89fa2172e5e5fcbf3c7c75892f93fc97a6b4 --- /dev/null +++ b/db/migrate/20240702181131_add_require_pat_expiry_to_application_settings.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +class AddRequirePatExpiryToApplicationSettings < Gitlab::Database::Migration[2.2] + milestone '17.2' + + def change + add_column :application_settings, :require_personal_access_token_expiry, :boolean, default: true, null: false + end +end diff --git a/db/schema_migrations/20240702181131 b/db/schema_migrations/20240702181131 new file mode 100644 index 0000000000000000000000000000000000000000..4b8ba284bd507940a6c4bef969bb25d37449ca4b --- /dev/null +++ b/db/schema_migrations/20240702181131 @@ -0,0 +1 @@ +ce07813b40454e38bdcc467c3c313573f1944848974f39d4ceea2c299714c6ac \ No newline at end of file diff --git a/db/structure.sql b/db/structure.sql index 5d6744e3c0c54af6fd6694d388e4bde95dbc06fb..e74005aa661cb868ade96bbdc26a72d3a95e7440 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -5769,6 +5769,7 @@ CREATE TABLE application_settings ( code_creation jsonb DEFAULT '{}'::jsonb NOT NULL, code_suggestions_api_rate_limit integer DEFAULT 60 NOT NULL, ai_action_api_rate_limit integer DEFAULT 160 NOT NULL, + require_personal_access_token_expiry boolean DEFAULT true NOT NULL, CONSTRAINT app_settings_container_reg_cleanup_tags_max_list_size_positive CHECK ((container_registry_cleanup_tags_service_max_list_size >= 0)), CONSTRAINT app_settings_dep_proxy_ttl_policies_worker_capacity_positive CHECK ((dependency_proxy_ttl_group_policy_worker_capacity >= 0)), CONSTRAINT app_settings_ext_pipeline_validation_service_url_text_limit CHECK ((char_length(external_pipeline_validation_service_url) <= 255)), diff --git a/ee/lib/ee/gitlab/personal_access_tokens/service_account_token_validator.rb b/ee/lib/ee/gitlab/personal_access_tokens/service_account_token_validator.rb index 494a9b068956555e441e69fe7e21c4626d9848ac..df560eb83a8e6d2514843ef8abd18b2decec0b1c 100644 --- a/ee/lib/ee/gitlab/personal_access_tokens/service_account_token_validator.rb +++ b/ee/lib/ee/gitlab/personal_access_tokens/service_account_token_validator.rb @@ -11,7 +11,9 @@ def initialize(service_account_user) attr_accessor :service_account_user def expiry_enforced? - return true unless License.feature_available?(:service_accounts) && service_account_user.service_account? + unless License.feature_available?(:service_accounts) && service_account_user.service_account? + return ::Gitlab::CurrentSettings.require_personal_access_token_expiry? + end if saas? return true unless service_account_scoped_to_group diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 061390a3cb11ea2014f0f1271fdbb3ecd22b421d..797d041a30cb71b03c9e9bee0b8f042e2e65c889 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -38378,6 +38378,9 @@ msgstr "" msgid "Personal projects limit:" msgstr "" +msgid "Personal/project/group access token expiration" +msgstr "" + msgid "PersonalProject|Learn to move a project to a group" msgstr "" @@ -59811,6 +59814,9 @@ msgstr "" msgid "When enabled, SSH keys with no expiry date or an invalid expiration date are no longer accepted. Leave blank for no limit." msgstr "" +msgid "When enabled, a user will be required to enter in an expiration date when creating an access token. Changes will not affect existing token expiration dates." +msgstr "" + msgid "When enabled, cleanup policies execute faster but put more load on Redis." msgstr "" diff --git a/spec/helpers/access_tokens_helper_spec.rb b/spec/helpers/access_tokens_helper_spec.rb index 576045b22d4bca74d9c81cd66cc550add64677bb..a013521d54357dd003bc21d32ac0e52602129977 100644 --- a/spec/helpers/access_tokens_helper_spec.rb +++ b/spec/helpers/access_tokens_helper_spec.rb @@ -2,7 +2,7 @@ require "spec_helper" -RSpec.describe AccessTokensHelper do +RSpec.describe AccessTokensHelper, feature_category: :system_access do describe "#scope_description" do using RSpec::Parameterized::TableSyntax @@ -72,6 +72,16 @@ min_date: 1.day.from_now.iso8601 }) end + + context 'when require_personal_access_token_expiry is true' do + before do + stub_application_setting(require_personal_access_token_expiry: false) + end + + it 'returns an empty hash' do + expect(helper.expires_at_field_data).to eq({}) + end + end end describe '#show_token_expiration_banner?' do