diff --git a/app/controllers/concerns/render_access_tokens.rb b/app/controllers/concerns/render_access_tokens.rb index 43e4686e66f993fec24696d9eb3fb2dec2803cbd..5e4c31b0ba751d6008424fbca3ed329a56887eef 100644 --- a/app/controllers/concerns/render_access_tokens.rb +++ b/app/controllers/concerns/render_access_tokens.rb @@ -29,4 +29,16 @@ def add_pagination_headers(relation) def page (params[:page] || 1).to_i end + + def expiry_ics(tokens) + cal = Icalendar::Calendar.new + tokens.each do |token| + cal.event do |event| + event.dtstart = Icalendar::Values::Date.new(token[:expires_at].delete('-')) + event.dtend = Icalendar::Values::Date.new(token[:expires_at].delete('-')) + event.summary = "Token #{token[:name]} expires today" + end + end + cal.to_ical + end end diff --git a/app/controllers/user_settings/personal_access_tokens_controller.rb b/app/controllers/user_settings/personal_access_tokens_controller.rb index 0bb71966b2141d7d6eea5984469a11f6eb5af678..de1a33647fa38bf262d796ef82dab7a3096cabb1 100644 --- a/app/controllers/user_settings/personal_access_tokens_controller.rb +++ b/app/controllers/user_settings/personal_access_tokens_controller.rb @@ -3,10 +3,12 @@ module UserSettings class PersonalAccessTokensController < ApplicationController include RenderAccessTokens + include FeedTokenHelper feature_category :system_access before_action :check_personal_access_tokens_enabled + prepend_before_action(only: [:index]) { authenticate_sessionless_user!(:ics) } def index set_index_vars @@ -21,6 +23,14 @@ def index format.json do render json: @active_access_tokens end + format.ics do + if params[:feed_token] + response.headers['Content-Type'] = 'text/plain' + render plain: expiry_ics(@active_access_tokens) + else + redirect_to "#{request.path}?feed_token=#{generate_feed_token_with_path(:ics, request.path)}" + end + end end end diff --git a/app/helpers/feed_token_helper.rb b/app/helpers/feed_token_helper.rb index 751a8df4782ac210989f8394eaedc327c1bd63c3..0a44d6c5ee32aefa16b181ef7ba40d0af3c07a2e 100644 --- a/app/helpers/feed_token_helper.rb +++ b/app/helpers/feed_token_helper.rb @@ -2,10 +2,15 @@ module FeedTokenHelper def generate_feed_token(type) + generate_feed_token_with_path(type, current_request.path) + end + + def generate_feed_token_with_path(type, path) feed_token = current_user&.feed_token return unless feed_token - final_path = "#{current_request.path}.#{type}" + final_path = path + final_path += ".#{type}" unless path.ends_with?(".#{type}") digest = OpenSSL::HMAC.hexdigest("SHA256", feed_token, final_path) "#{User::FEED_TOKEN_PREFIX}#{digest}-#{current_user.id}" end diff --git a/doc/user/profile/personal_access_tokens.md b/doc/user/profile/personal_access_tokens.md index 7969b1cf318ab251791318f84e40b7d1db5a5ecd..9cec59b2c0056bbda35d5b630a55f923756ef25f 100644 --- a/doc/user/profile/personal_access_tokens.md +++ b/doc/user/profile/personal_access_tokens.md @@ -180,6 +180,10 @@ Personal access tokens expire on the date you define, at midnight, 00:00 AM UTC. [maximum allowed lifetime for the token](../../administration/settings/account_and_limit_settings.md#limit-the-lifetime-of-access-tokens). If the maximum allowed lifetime is not set, the default expiry date is 365 days from the date of creation. +### Personal access token expiry calendar + +You can subscribe to an iCalendar endpoint which contains events at the expiry date for each token. After signing in, this endpoint is available at `/-/user_settings/personal_access_tokens.ics`. + ### Create a service account personal access token with no expiry date You can [create a personal access token for a service account](../../api/groups.md#create-personal-access-token-for-service-account-user) with no expiry date. These personal access tokens never expire, unlike non-service account personal access tokens. diff --git a/spec/controllers/user_settings/personal_access_tokens_controller_spec.rb b/spec/controllers/user_settings/personal_access_tokens_controller_spec.rb index b1d6fc6f479d9e8bba7b4fe1dc08eeff006cf43f..0487c1f2e2f23f12d368665ede688cf4d5aa70a3 100644 --- a/spec/controllers/user_settings/personal_access_tokens_controller_spec.rb +++ b/spec/controllers/user_settings/personal_access_tokens_controller_spec.rb @@ -106,6 +106,16 @@ def created_token expect(json_response.count).to eq(1) end + it 'returns an iCalendar after redirect for ics format' do + get :index, params: { format: :ics } + + expect(response).to redirect_to(%r{/-/user_settings/personal_access_tokens\?feed_token=}) + + get :index, params: { format: :ics, feed_token: response.location.split('=').last } + + expect(response.body).to include('BEGIN:VCALENDAR') + end + it 'sets available scopes' do expect(assigns(:scopes)).to eq(Gitlab::Auth.available_scopes_for(access_token_user)) end