Commit d40446cd authored by Peter Hegman's avatar Peter Hegman Committed by Lin Jen-Shin

Hide feed, incoming email, and static object tokens by default

parent 073b67bc
<script>
import InputCopyToggleVisibility from '~/vue_shared/components/form/input_copy_toggle_visibility.vue';
export default {
components: { InputCopyToggleVisibility },
props: {
token: {
type: String,
required: true,
},
inputId: {
type: String,
required: true,
},
inputLabel: {
type: String,
required: true,
},
copyButtonTitle: {
type: String,
required: true,
},
},
computed: {
formInputGroupProps() {
return { id: this.inputId };
},
},
};
</script>
<template>
<div class="row">
<div class="col-lg-12">
<hr />
</div>
<div class="col-lg-4">
<h4 class="gl-mt-0"><slot name="title"></slot></h4>
<slot name="description"></slot>
</div>
<div class="col-lg-8">
<input-copy-toggle-visibility
:label="inputLabel"
:label-for="inputId"
:form-input-group-props="formInputGroupProps"
:value="token"
:copy-button-title="copyButtonTitle"
>
<template #description>
<slot name="input-description"></slot>
</template>
</input-copy-toggle-visibility>
</div>
</div>
</template>
<script>
import { GlSprintf, GlLink } from '@gitlab/ui';
import { pickBy } from 'lodash';
import { s__ } from '~/locale';
import { FEED_TOKEN, INCOMING_EMAIL_TOKEN, STATIC_OBJECT_TOKEN } from '../constants';
import Token from './token.vue';
export default {
i18n: {
canNotAccessOtherData: s__('AccessTokens|It cannot be used to access any other data.'),
[FEED_TOKEN]: {
label: s__('AccessTokens|Feed token'),
copyButtonTitle: s__('AccessTokens|Copy feed token'),
description: s__(
'AccessTokens|Your feed token authenticates you when your RSS reader loads a personalized RSS feed or when your calendar application loads a personalized calendar. It is visible in those feed URLs.',
),
inputDescription: s__(
'AccessTokens|Keep this token secret. Anyone who has it can read activity and issue RSS feeds or your calendar feed as if they were you. If that happens, %{linkStart}reset this token%{linkEnd}.',
),
resetConfirmMessage: s__(
'AccessTokens|Are you sure? Any RSS or calendar URLs currently in use will stop working.',
),
},
[INCOMING_EMAIL_TOKEN]: {
label: s__('AccessTokens|Incoming email token'),
copyButtonTitle: s__('AccessTokens|Copy incoming email token'),
description: s__(
'AccessTokens|Your incoming email token authenticates you when you create a new issue by email, and is included in your personal project-specific email addresses.',
),
inputDescription: s__(
'AccessTokens|Keep this token secret. Anyone who has it can create issues as if they were you. If that happens, %{linkStart}reset this token%{linkEnd}.',
),
resetConfirmMessage: s__(
'AccessTokens|Are you sure? Any issue email addresses currently in use will stop working.',
),
},
[STATIC_OBJECT_TOKEN]: {
label: s__('AccessTokens|Static object token'),
copyButtonTitle: s__('AccessTokens|Copy static object token'),
description: s__(
'AccessTokens|Your static object token authenticates you when repository static objects (such as archives or blobs) are served from an external storage.',
),
inputDescription: s__(
'AccessTokens|Keep this token secret. Anyone who has it can access repository static objects as if they were you. If that ever happens, %{linkStart}reset this token%{linkEnd}.',
),
resetConfirmMessage: s__('AccessTokens|Are you sure?'),
},
},
htmlAttributes: {
[FEED_TOKEN]: {
inputId: 'feed_token',
containerTestId: 'feed-token-container',
},
[INCOMING_EMAIL_TOKEN]: {
inputId: 'incoming_email_token',
containerTestId: 'incoming-email-token-container',
},
[STATIC_OBJECT_TOKEN]: {
inputId: 'static_object_token',
containerTestId: 'static-object-token-container',
},
},
components: { Token, GlSprintf, GlLink },
inject: ['tokenTypes'],
computed: {
enabledTokenTypes() {
return pickBy(this.tokenTypes, (tokenData, tokenType) => {
return (
tokenData?.enabled &&
this.$options.i18n[tokenType] &&
this.$options.htmlAttributes[tokenType]
);
});
},
},
};
</script>
<template>
<div>
<token
v-for="(tokenData, tokenType) in enabledTokenTypes"
:key="tokenType"
:token="tokenData.token"
:input-id="$options.htmlAttributes[tokenType].inputId"
:input-label="$options.i18n[tokenType].label"
:copy-button-title="$options.i18n[tokenType].copyButtonTitle"
:data-testid="$options.htmlAttributes[tokenType].containerTestId"
>
<template #title>{{ $options.i18n[tokenType].label }}</template>
<template #description>
<p>{{ $options.i18n[tokenType].description }}</p>
<p>{{ $options.i18n.canNotAccessOtherData }}</p>
</template>
<template #input-description>
<gl-sprintf :message="$options.i18n[tokenType].inputDescription">
<template #link="{ content }">
<gl-link
:href="tokenData.resetPath"
:data-confirm="$options.i18n[tokenType].resetConfirmMessage"
data-method="put"
>{{ content }}</gl-link
>
</template>
</gl-sprintf>
</template>
</token>
</div>
</template>
// Token types
export const FEED_TOKEN = 'feedToken';
export const INCOMING_EMAIL_TOKEN = 'incomingEmailToken';
export const STATIC_OBJECT_TOKEN = 'staticObjectToken';
import Vue from 'vue'; import Vue from 'vue';
import createFlash from '~/flash'; import createFlash from '~/flash';
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
import { parseRailsFormFields } from '~/lib/utils/forms'; import { parseRailsFormFields } from '~/lib/utils/forms';
import { __ } from '~/locale'; import { __ } from '~/locale';
import ExpiresAtField from './components/expires_at_field.vue'; import ExpiresAtField from './components/expires_at_field.vue';
import TokensApp from './components/tokens_app.vue';
import { FEED_TOKEN, INCOMING_EMAIL_TOKEN, STATIC_OBJECT_TOKEN } from './constants';
export const initExpiresAtField = () => { export const initExpiresAtField = () => {
const el = document.querySelector('.js-access-tokens-expires-at'); const el = document.querySelector('.js-access-tokens-expires-at');
...@@ -81,3 +85,29 @@ export const initProjectsField = () => { ...@@ -81,3 +85,29 @@ export const initProjectsField = () => {
return null; return null;
}; };
export const initTokensApp = () => {
const el = document.getElementById('js-tokens-app');
if (!el) return false;
const tokensData = convertObjectPropsToCamelCase(JSON.parse(el.dataset.tokensData), {
deep: true,
});
const tokenTypes = {
[FEED_TOKEN]: tokensData[FEED_TOKEN],
[INCOMING_EMAIL_TOKEN]: tokensData[INCOMING_EMAIL_TOKEN],
[STATIC_OBJECT_TOKEN]: tokensData[STATIC_OBJECT_TOKEN],
};
return new Vue({
el,
provide: {
tokenTypes,
},
render(createElement) {
return createElement(TokensApp);
},
});
};
import { initExpiresAtField, initProjectsField } from '~/access_tokens'; import { initExpiresAtField, initProjectsField, initTokensApp } from '~/access_tokens';
initExpiresAtField(); initExpiresAtField();
initProjectsField(); initProjectsField();
initTokensApp();
# frozen_string_literal: true # frozen_string_literal: true
module AccessTokensHelper module AccessTokensHelper
include AccountsHelper
include ApplicationHelper
def scope_description(prefix) def scope_description(prefix)
prefix == :project_access_token ? [:doorkeeper, :project_access_token_scope_desc] : [:doorkeeper, :scope_desc] prefix == :project_access_token ? [:doorkeeper, :project_access_token_scope_desc] : [:doorkeeper, :scope_desc]
end end
def tokens_app_data
{
feed_token: {
enabled: !Gitlab::CurrentSettings.disable_feed_token,
token: current_user.feed_token,
reset_path: reset_feed_token_profile_path
},
incoming_email_token: {
enabled: incoming_email_token_enabled?,
token: current_user.enabled_incoming_email_token,
reset_path: reset_incoming_email_token_profile_path
},
static_object_token: {
enabled: static_objects_external_storage_enabled?,
token: current_user.enabled_static_object_token,
reset_path: reset_static_object_token_profile_path
}
}.to_json
end
end end
...@@ -1794,7 +1794,7 @@ class User < ApplicationRecord ...@@ -1794,7 +1794,7 @@ class User < ApplicationRecord
# we do this on read since migrating all existing users is not a feasible # we do this on read since migrating all existing users is not a feasible
# solution. # solution.
def feed_token def feed_token
Gitlab::CurrentSettings.disable_feed_token ? nil : ensure_feed_token! ensure_feed_token! unless Gitlab::CurrentSettings.disable_feed_token
end end
# Each existing user needs to have a `static_object_token`. # Each existing user needs to have a `static_object_token`.
...@@ -1804,6 +1804,14 @@ class User < ApplicationRecord ...@@ -1804,6 +1804,14 @@ class User < ApplicationRecord
ensure_static_object_token! ensure_static_object_token!
end end
def enabled_static_object_token
static_object_token if Gitlab::CurrentSettings.static_objects_external_storage_enabled?
end
def enabled_incoming_email_token
incoming_email_token if Gitlab::IncomingEmail.supports_issue_creation?
end
def sync_attribute?(attribute) def sync_attribute?(attribute)
return true if ldap_user? && attribute == :email return true if ldap_user? && attribute == :email
......
...@@ -32,62 +32,64 @@ ...@@ -32,62 +32,64 @@
type_plural: type_plural, type_plural: type_plural,
active_tokens: @active_personal_access_tokens, active_tokens: @active_personal_access_tokens,
revoke_route_helper: ->(token) { revoke_profile_personal_access_token_path(token) } revoke_route_helper: ->(token) { revoke_profile_personal_access_token_path(token) }
- if Feature.enabled?(:hide_access_tokens)
#js-tokens-app{ data: { tokens_data: tokens_app_data } }
- else
- unless Gitlab::CurrentSettings.disable_feed_token
.col-lg-12
%hr
.row.gl-mt-3.js-search-settings-section
.col-lg-4.profile-settings-sidebar
%h4.gl-mt-0
= s_('AccessTokens|Feed token')
%p
= s_('AccessTokens|Your feed token authenticates you when your RSS reader loads a personalized RSS feed or when your calendar application loads a personalized calendar. It is visible in those feed URLs.')
%p
= s_('AccessTokens|It cannot be used to access any other data.')
.col-lg-8.feed-token-reset
= label_tag :feed_token, s_('AccessTokens|Feed token'), class: 'label-bold'
= text_field_tag :feed_token, current_user.feed_token, class: 'form-control gl-form-input js-select-on-focus', readonly: true
%p.form-text.text-muted
- reset_link = link_to s_('AccessTokens|reset this token'), [:reset, :feed_token, :profile], method: :put, data: { confirm: s_('AccessTokens|Are you sure? Any RSS or calendar URLs currently in use will stop working.'), testid: :reset_feed_token_link }
- reset_message = s_('AccessTokens|Keep this token secret. Anyone who has it can read activity and issue RSS feeds or your calendar feed as if they were you. If that happens, %{link_reset_it}.') % { link_reset_it: reset_link }
= reset_message.html_safe
- unless Gitlab::CurrentSettings.disable_feed_token - if incoming_email_token_enabled?
.col-lg-12 .col-lg-12
%hr %hr
.row.gl-mt-3.js-search-settings-section .row.gl-mt-3.js-search-settings-section
.col-lg-4.profile-settings-sidebar .col-lg-4.profile-settings-sidebar
%h4.gl-mt-0 %h4.gl-mt-0
= s_('AccessTokens|Feed token') = s_('AccessTokens|Incoming email token')
%p %p
= s_('AccessTokens|Your feed token authenticates you when your RSS reader loads a personalized RSS feed or when your calendar application loads a personalized calendar. It is visible in those feed URLs.') = s_('AccessTokens|Your incoming email token authenticates you when you create a new issue by email, and is included in your personal project-specific email addresses.')
%p %p
= s_('AccessTokens|It cannot be used to access any other data.') = s_('AccessTokens|It cannot be used to access any other data.')
.col-lg-8.feed-token-reset .col-lg-8.incoming-email-token-reset
= label_tag :feed_token, s_('AccessTokens|Feed token'), class: 'label-bold' = label_tag :incoming_email_token, s_('AccessTokens|Incoming email token'), class: 'label-bold'
= text_field_tag :feed_token, current_user.feed_token, class: 'form-control gl-form-input js-select-on-focus', readonly: true = text_field_tag :incoming_email_token, current_user.incoming_email_token, class: 'form-control gl-form-input js-select-on-focus', readonly: true
%p.form-text.text-muted %p.form-text.text-muted
- reset_link = link_to s_('AccessTokens|reset this token'), [:reset, :feed_token, :profile], method: :put, data: { confirm: s_('AccessTokens|Are you sure? Any RSS or calendar URLs currently in use will stop working.'), testid: :reset_feed_token_link } - reset_link = link_to s_('AccessTokens|reset this token'), [:reset, :incoming_email_token, :profile], method: :put, data: { confirm: s_('AccessTokens|Are you sure? Any issue email addresses currently in use will stop working.'), testid: :reset_email_token_link }
- reset_message = s_('AccessTokens|Keep this token secret. Anyone who has it can read activity and issue RSS feeds or your calendar feed as if they were you. If that happens, %{link_reset_it}.') % { link_reset_it: reset_link } - reset_message = s_('AccessTokens|Keep this token secret. Anyone who has it can create issues as if they were you. If that happens, %{link_reset_it}.') % { link_reset_it: reset_link }
= reset_message.html_safe = reset_message.html_safe
- if incoming_email_token_enabled? - if static_objects_external_storage_enabled?
.col-lg-12 .col-lg-12
%hr %hr
.row.gl-mt-3.js-search-settings-section .row.gl-mt-3.js-search-settings-section
.col-lg-4.profile-settings-sidebar .col-lg-4
%h4.gl-mt-0 %h4.gl-mt-0
= s_('AccessTokens|Incoming email token') = s_('AccessTokens|Static object token')
%p %p
= s_('AccessTokens|Your incoming email token authenticates you when you create a new issue by email, and is included in your personal project-specific email addresses.') = s_('AccessTokens|Your static object token authenticates you when repository static objects (such as archives or blobs) are served from an external storage.')
%p %p
= s_('AccessTokens|It cannot be used to access any other data.') = s_('AccessTokens|It cannot be used to access any other data.')
.col-lg-8.incoming-email-token-reset .col-lg-8
= label_tag :incoming_email_token, s_('AccessTokens|Incoming email token'), class: 'label-bold' = label_tag :static_object_token, s_('AccessTokens|Static object token'), class: "label-bold"
= text_field_tag :incoming_email_token, current_user.incoming_email_token, class: 'form-control gl-form-input js-select-on-focus', readonly: true = text_field_tag :static_object_token, current_user.static_object_token, class: 'form-control gl-form-input', readonly: true, onclick: 'this.select()'
%p.form-text.text-muted %p.form-text.text-muted
- reset_link = link_to s_('AccessTokens|reset this token'), [:reset, :incoming_email_token, :profile], method: :put, data: { confirm: s_('AccessTokens|Are you sure? Any issue email addresses currently in use will stop working.'), testid: :reset_email_token_link } - reset_link = url_for [:reset, :static_object_token, :profile]
- reset_message = s_('AccessTokens|Keep this token secret. Anyone who has it can create issues as if they were you. If that happens, %{link_reset_it}.') % { link_reset_it: reset_link } - reset_link_start = '<a data-confirm="%{confirm}" rel="nofollow" data-method="put" href="%{url}">'.html_safe % { confirm: s_('AccessTokens|Are you sure?'), url: reset_link }
= reset_message.html_safe - reset_link_end = '</a>'.html_safe
- reset_message = s_('AccessTokens|Keep this token secret. Anyone who has it can access repository static objects as if they were you. If that ever happens, %{reset_link_start}reset this token%{reset_link_end}.') % { reset_link_start: reset_link_start, reset_link_end: reset_link_end }
- if static_objects_external_storage_enabled? = reset_message.html_safe
.col-lg-12
%hr
.row.gl-mt-3.js-search-settings-section
.col-lg-4
%h4.gl-mt-0
= s_('AccessTokens|Static object token')
%p
= s_('AccessTokens|Your static object token authenticates you when repository static objects (such as archives or blobs) are served from an external storage.')
%p
= s_('AccessTokens|It cannot be used to access any other data.')
.col-lg-8
= label_tag :static_object_token, s_('AccessTokens|Static object token'), class: "label-bold"
= text_field_tag :static_object_token, current_user.static_object_token, class: 'form-control gl-form-input', readonly: true, onclick: 'this.select()'
%p.form-text.text-muted
- reset_link = url_for [:reset, :static_object_token, :profile]
- reset_link_start = '<a data-confirm="%{confirm}" rel="nofollow" data-method="put" href="%{url}">'.html_safe % { confirm: s_('AccessTokens|Are you sure?'), url: reset_link }
- reset_link_end = '</a>'.html_safe
- reset_message = s_('AccessTokens|Keep this token secret. Anyone who has it can access repository static objects as if they were you. If that ever happens, %{reset_link_start}reset this token%{reset_link_end}.') % { reset_link_start: reset_link_start, reset_link_end: reset_link_end }
= reset_message.html_safe
---
name: hide_access_tokens
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/76280
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/347490
milestone: '14.6'
type: development
group: group::access
default_enabled: false
...@@ -1805,6 +1805,15 @@ msgstr "" ...@@ -1805,6 +1805,15 @@ msgstr ""
msgid "AccessTokens|Are you sure? Any issue email addresses currently in use will stop working." msgid "AccessTokens|Are you sure? Any issue email addresses currently in use will stop working."
msgstr "" msgstr ""
msgid "AccessTokens|Copy feed token"
msgstr ""
msgid "AccessTokens|Copy incoming email token"
msgstr ""
msgid "AccessTokens|Copy static object token"
msgstr ""
msgid "AccessTokens|Created" msgid "AccessTokens|Created"
msgstr "" msgstr ""
...@@ -1817,12 +1826,21 @@ msgstr "" ...@@ -1817,12 +1826,21 @@ msgstr ""
msgid "AccessTokens|It cannot be used to access any other data." msgid "AccessTokens|It cannot be used to access any other data."
msgstr "" msgstr ""
msgid "AccessTokens|Keep this token secret. Anyone who has it can access repository static objects as if they were you. If that ever happens, %{linkStart}reset this token%{linkEnd}."
msgstr ""
msgid "AccessTokens|Keep this token secret. Anyone who has it can access repository static objects as if they were you. If that ever happens, %{reset_link_start}reset this token%{reset_link_end}." msgid "AccessTokens|Keep this token secret. Anyone who has it can access repository static objects as if they were you. If that ever happens, %{reset_link_start}reset this token%{reset_link_end}."
msgstr "" msgstr ""
msgid "AccessTokens|Keep this token secret. Anyone who has it can create issues as if they were you. If that happens, %{linkStart}reset this token%{linkEnd}."
msgstr ""
msgid "AccessTokens|Keep this token secret. Anyone who has it can create issues as if they were you. If that happens, %{link_reset_it}." msgid "AccessTokens|Keep this token secret. Anyone who has it can create issues as if they were you. If that happens, %{link_reset_it}."
msgstr "" msgstr ""
msgid "AccessTokens|Keep this token secret. Anyone who has it can read activity and issue RSS feeds or your calendar feed as if they were you. If that happens, %{linkStart}reset this token%{linkEnd}."
msgstr ""
msgid "AccessTokens|Keep this token secret. Anyone who has it can read activity and issue RSS feeds or your calendar feed as if they were you. If that happens, %{link_reset_it}." msgid "AccessTokens|Keep this token secret. Anyone who has it can read activity and issue RSS feeds or your calendar feed as if they were you. If that happens, %{link_reset_it}."
msgstr "" msgstr ""
......
...@@ -63,11 +63,24 @@ RSpec.describe 'Profile account page', :js do ...@@ -63,11 +63,24 @@ RSpec.describe 'Profile account page', :js do
end end
describe 'when I reset feed token' do describe 'when I reset feed token' do
before do it 'resets feed token with `hide_access_tokens` feature flag enabled' do
visit profile_personal_access_tokens_path visit profile_personal_access_tokens_path
within('[data-testid="feed-token-container"]') do
previous_token = find_field('Feed token').value
accept_confirm { click_link('reset this token') }
click_button('Click to reveal')
expect(find_field('Feed token').value).not_to eq(previous_token)
end
end end
it 'resets feed token' do it 'resets feed token with `hide_access_tokens` feature flag disabled' do
stub_feature_flags(hide_access_tokens: false)
visit profile_personal_access_tokens_path
within('.feed-token-reset') do within('.feed-token-reset') do
previous_token = find("#feed_token").value previous_token = find("#feed_token").value
...@@ -82,10 +95,26 @@ RSpec.describe 'Profile account page', :js do ...@@ -82,10 +95,26 @@ RSpec.describe 'Profile account page', :js do
before do before do
allow(Gitlab.config.incoming_email).to receive(:enabled).and_return(true) allow(Gitlab.config.incoming_email).to receive(:enabled).and_return(true)
stub_feature_flags(bootstrap_confirmation_modals: false) stub_feature_flags(bootstrap_confirmation_modals: false)
end
it 'resets incoming email token with `hide_access_tokens` feature flag enabled' do
visit profile_personal_access_tokens_path visit profile_personal_access_tokens_path
within('[data-testid="incoming-email-token-container"]') do
previous_token = find_field('Incoming email token').value
accept_confirm { click_link('reset this token') }
click_button('Click to reveal')
expect(find_field('Incoming email token').value).not_to eq(previous_token)
end
end end
it 'resets incoming email token' do it 'resets incoming email token with `hide_access_tokens` feature flag disabled' do
stub_feature_flags(hide_access_tokens: false)
visit profile_personal_access_tokens_path
within('.incoming-email-token-reset') do within('.incoming-email-token-reset') do
previous_token = find('#incoming_email_token').value previous_token = find('#incoming_email_token').value
......
...@@ -18,10 +18,6 @@ RSpec.describe 'Profile > Personal Access Tokens', :js do ...@@ -18,10 +18,6 @@ RSpec.describe 'Profile > Personal Access Tokens', :js do
find("#created-personal-access-token").value find("#created-personal-access-token").value
end end
def feed_token
find("#feed_token").value
end
def feed_token_description def feed_token_description
"Your feed token authenticates you when your RSS reader loads a personalized RSS feed or when your calendar application loads a personalized calendar. It is visible in those feed URLs." "Your feed token authenticates you when your RSS reader loads a personalized RSS feed or when your calendar application loads a personalized calendar. It is visible in those feed URLs."
end end
...@@ -136,12 +132,24 @@ RSpec.describe 'Profile > Personal Access Tokens', :js do ...@@ -136,12 +132,24 @@ RSpec.describe 'Profile > Personal Access Tokens', :js do
describe "feed token" do describe "feed token" do
context "when enabled" do context "when enabled" do
it "displays feed token" do it "displays feed token with `hide_access_tokens` feature flag enabled" do
allow(Gitlab::CurrentSettings).to receive(:disable_feed_token).and_return(false) allow(Gitlab::CurrentSettings).to receive(:disable_feed_token).and_return(false)
visit profile_personal_access_tokens_path visit profile_personal_access_tokens_path
expect(feed_token).to eq(user.feed_token) within('[data-testid="feed-token-container"]') do
click_button('Click to reveal')
expect(page).to have_field('Feed token', with: user.feed_token)
expect(page).to have_content(feed_token_description)
end
end
it "displays feed token with `hide_access_tokens` feature flag disabled" do
stub_feature_flags(hide_access_tokens: false)
allow(Gitlab::CurrentSettings).to receive(:disable_feed_token).and_return(false)
visit profile_personal_access_tokens_path
expect(page).to have_field('Feed token', with: user.feed_token)
expect(page).to have_content(feed_token_description) expect(page).to have_content(feed_token_description)
end end
end end
...@@ -151,8 +159,8 @@ RSpec.describe 'Profile > Personal Access Tokens', :js do ...@@ -151,8 +159,8 @@ RSpec.describe 'Profile > Personal Access Tokens', :js do
allow(Gitlab::CurrentSettings).to receive(:disable_feed_token).and_return(true) allow(Gitlab::CurrentSettings).to receive(:disable_feed_token).and_return(true)
visit profile_personal_access_tokens_path visit profile_personal_access_tokens_path
expect(page).to have_no_content(feed_token_description) expect(page).not_to have_content(feed_token_description)
expect(page).to have_no_css("#feed_token") expect(page).not_to have_field('Feed token')
end end
end end
end end
......
import { mountExtended } from 'helpers/vue_test_utils_helper';
import Token from '~/access_tokens/components/token.vue';
import InputCopyToggleVisibility from '~/vue_shared/components/form/input_copy_toggle_visibility.vue';
describe('Token', () => {
let wrapper;
const defaultPropsData = {
token: 'az4a2l5f8ssa0zvdfbhidbzlx',
inputId: 'feed_token',
inputLabel: 'Feed token',
copyButtonTitle: 'Copy feed token',
};
const defaultSlots = {
title: 'Feed token title',
description: 'Feed token description',
'input-description': 'Feed token input description',
};
const createComponent = () => {
wrapper = mountExtended(Token, { propsData: defaultPropsData, slots: defaultSlots });
};
afterEach(() => {
wrapper.destroy();
});
it('renders title slot', () => {
createComponent();
expect(wrapper.findByText(defaultSlots.title, { selector: 'h4' }).exists()).toBe(true);
});
it('renders description slot', () => {
createComponent();
expect(wrapper.findByText(defaultSlots.description).exists()).toBe(true);
});
it('renders input description slot', () => {
createComponent();
expect(wrapper.findByText(defaultSlots['input-description']).exists()).toBe(true);
});
it('correctly passes props to `InputCopyToggleVisibility` component', () => {
createComponent();
const inputCopyToggleVisibilityComponent = wrapper.findComponent(InputCopyToggleVisibility);
expect(inputCopyToggleVisibilityComponent.props()).toMatchObject({
formInputGroupProps: {
id: defaultPropsData.inputId,
},
value: defaultPropsData.token,
copyButtonTitle: defaultPropsData.copyButtonTitle,
});
expect(inputCopyToggleVisibilityComponent.attributes()).toMatchObject({
label: defaultPropsData.inputLabel,
'label-for': defaultPropsData.inputId,
});
});
});
import { merge } from 'lodash';
import { mountExtended, extendedWrapper } from 'helpers/vue_test_utils_helper';
import TokensApp from '~/access_tokens/components/tokens_app.vue';
import { FEED_TOKEN, INCOMING_EMAIL_TOKEN, STATIC_OBJECT_TOKEN } from '~/access_tokens/constants';
describe('TokensApp', () => {
let wrapper;
const defaultProvide = {
tokenTypes: {
[FEED_TOKEN]: {
enabled: true,
token: 'DUKu345VD73Py7zz3z89',
resetPath: '/-/profile/reset_feed_token',
},
[INCOMING_EMAIL_TOKEN]: {
enabled: true,
token: 'az4a2l5f8ssa0zvdfbhidbzlx',
resetPath: '/-/profile/reset_incoming_email_token',
},
[STATIC_OBJECT_TOKEN]: {
enabled: true,
token: 'QHXwGHYioHTgxQnAcyZ-',
resetPath: '/-/profile/reset_static_object_token',
},
},
};
const createComponent = (options = {}) => {
wrapper = mountExtended(TokensApp, merge({}, { provide: defaultProvide }, options));
};
const expectTokenRendered = ({
testId,
expectedLabel,
expectedDescription,
expectedInputDescription,
expectedResetPath,
expectedResetConfirmMessage,
expectedProps,
}) => {
const container = extendedWrapper(wrapper.findByTestId(testId));
expect(container.findByText(expectedLabel, { selector: 'h4' }).exists()).toBe(true);
expect(container.findByText(expectedDescription).exists()).toBe(true);
expect(container.findByText(expectedInputDescription, { exact: false }).exists()).toBe(true);
expect(container.findByText('reset this token').attributes()).toMatchObject({
'data-confirm': expectedResetConfirmMessage,
'data-method': 'put',
href: expectedResetPath,
});
expect(container.props()).toMatchObject(expectedProps);
};
afterEach(() => {
wrapper.destroy();
});
it('renders all enabled tokens', () => {
createComponent();
expectTokenRendered({
testId: TokensApp.htmlAttributes[FEED_TOKEN].containerTestId,
expectedLabel: TokensApp.i18n[FEED_TOKEN].label,
expectedDescription: TokensApp.i18n[FEED_TOKEN].description,
expectedInputDescription:
'Keep this token secret. Anyone who has it can read activity and issue RSS feeds or your calendar feed as if they were you.',
expectedResetPath: defaultProvide.tokenTypes[FEED_TOKEN].resetPath,
expectedResetConfirmMessage: TokensApp.i18n[FEED_TOKEN].resetConfirmMessage,
expectedProps: {
token: defaultProvide.tokenTypes[FEED_TOKEN].token,
inputId: TokensApp.htmlAttributes[FEED_TOKEN].inputId,
inputLabel: TokensApp.i18n[FEED_TOKEN].label,
copyButtonTitle: TokensApp.i18n[FEED_TOKEN].copyButtonTitle,
},
});
expectTokenRendered({
testId: TokensApp.htmlAttributes[INCOMING_EMAIL_TOKEN].containerTestId,
expectedLabel: TokensApp.i18n[INCOMING_EMAIL_TOKEN].label,
expectedDescription: TokensApp.i18n[INCOMING_EMAIL_TOKEN].description,
expectedInputDescription:
'Keep this token secret. Anyone who has it can create issues as if they were you.',
expectedResetPath: defaultProvide.tokenTypes[INCOMING_EMAIL_TOKEN].resetPath,
expectedResetConfirmMessage: TokensApp.i18n[INCOMING_EMAIL_TOKEN].resetConfirmMessage,
expectedProps: {
token: defaultProvide.tokenTypes[INCOMING_EMAIL_TOKEN].token,
inputId: TokensApp.htmlAttributes[INCOMING_EMAIL_TOKEN].inputId,
inputLabel: TokensApp.i18n[INCOMING_EMAIL_TOKEN].label,
copyButtonTitle: TokensApp.i18n[INCOMING_EMAIL_TOKEN].copyButtonTitle,
},
});
expectTokenRendered({
testId: TokensApp.htmlAttributes[STATIC_OBJECT_TOKEN].containerTestId,
expectedLabel: TokensApp.i18n[STATIC_OBJECT_TOKEN].label,
expectedDescription: TokensApp.i18n[STATIC_OBJECT_TOKEN].description,
expectedInputDescription:
'Keep this token secret. Anyone who has it can access repository static objects as if they were you.',
expectedResetPath: defaultProvide.tokenTypes[STATIC_OBJECT_TOKEN].resetPath,
expectedResetConfirmMessage: TokensApp.i18n[STATIC_OBJECT_TOKEN].resetConfirmMessage,
expectedProps: {
token: defaultProvide.tokenTypes[STATIC_OBJECT_TOKEN].token,
inputId: TokensApp.htmlAttributes[STATIC_OBJECT_TOKEN].inputId,
inputLabel: TokensApp.i18n[STATIC_OBJECT_TOKEN].label,
copyButtonTitle: TokensApp.i18n[STATIC_OBJECT_TOKEN].copyButtonTitle,
},
});
});
it("doesn't render disabled tokens", () => {
createComponent({
provide: {
tokenTypes: {
[FEED_TOKEN]: {
enabled: false,
},
},
},
});
expect(
wrapper.findByTestId(TokensApp.htmlAttributes[FEED_TOKEN].containerTestId).exists(),
).toBe(false);
});
describe('when there are tokens missing an `i18n` definition', () => {
it('renders without errors', () => {
createComponent({
provide: {
tokenTypes: {
fooBar: {
enabled: true,
token: 'rewjoa58dfm54jfkdlsdf',
resetPath: '/-/profile/foo_bar',
},
},
},
});
expect(
wrapper.findByTestId(TokensApp.htmlAttributes[FEED_TOKEN].containerTestId).exists(),
).toBe(true);
});
});
});
...@@ -15,4 +15,53 @@ RSpec.describe AccessTokensHelper do ...@@ -15,4 +15,53 @@ RSpec.describe AccessTokensHelper do
it { expect(helper.scope_description(prefix)).to eq(description_location) } it { expect(helper.scope_description(prefix)).to eq(description_location) }
end end
end end
describe '#tokens_app_data' do
let_it_be(:feed_token) { 'DUKu345VD73Py7zz3z89' }
let_it_be(:incoming_email_token) { 'az4a2l5f8ssa0zvdfbhidbzlx' }
let_it_be(:static_object_token) { 'QHXwGHYioHTgxQnAcyZ-' }
let_it_be(:feed_token_reset_path) { '/-/profile/reset_feed_token' }
let_it_be(:incoming_email_token_reset_path) { '/-/profile/reset_incoming_email_token' }
let_it_be(:static_object_token_reset_path) { '/-/profile/reset_static_object_token' }
let_it_be(:user) do
build(
:user,
feed_token: feed_token,
incoming_email_token: incoming_email_token,
static_object_token: static_object_token
)
end
it 'returns expected json' do
allow(Gitlab::CurrentSettings).to receive_messages(
disable_feed_token: false,
static_objects_external_storage_enabled?: true
)
allow(Gitlab::IncomingEmail).to receive(:supports_issue_creation?).and_return(true)
allow(helper).to receive_messages(
current_user: user,
reset_feed_token_profile_path: feed_token_reset_path,
reset_incoming_email_token_profile_path: incoming_email_token_reset_path,
reset_static_object_token_profile_path: static_object_token_reset_path
)
expect(helper.tokens_app_data).to eq({
feed_token: {
enabled: true,
token: feed_token,
reset_path: feed_token_reset_path
},
incoming_email_token: {
enabled: true,
token: incoming_email_token,
reset_path: incoming_email_token_reset_path
},
static_object_token: {
enabled: true,
token: static_object_token,
reset_path: static_object_token_reset_path
}
}.to_json)
end
end
end end
...@@ -1616,6 +1616,46 @@ RSpec.describe User do ...@@ -1616,6 +1616,46 @@ RSpec.describe User do
end end
end end
describe 'enabled_static_object_token' do
let_it_be(:static_object_token) { 'ilqx6jm1u945macft4eff0nw' }
it 'returns incoming email token when supported' do
allow(Gitlab::CurrentSettings).to receive(:static_objects_external_storage_enabled?).and_return(true)
user = create(:user, static_object_token: static_object_token)
expect(user.enabled_static_object_token).to eq(static_object_token)
end
it 'returns `nil` when not supported' do
allow(Gitlab::CurrentSettings).to receive(:static_objects_external_storage_enabled?).and_return(false)
user = create(:user, static_object_token: static_object_token)
expect(user.enabled_static_object_token).to be_nil
end
end
describe 'enabled_incoming_email_token' do
let_it_be(:incoming_email_token) { 'ilqx6jm1u945macft4eff0nw' }
it 'returns incoming email token when supported' do
allow(Gitlab::IncomingEmail).to receive(:supports_issue_creation?).and_return(true)
user = create(:user, incoming_email_token: incoming_email_token)
expect(user.enabled_incoming_email_token).to eq(incoming_email_token)
end
it 'returns `nil` when not supported' do
allow(Gitlab::IncomingEmail).to receive(:supports_issue_creation?).and_return(false)
user = create(:user, incoming_email_token: incoming_email_token)
expect(user.enabled_incoming_email_token).to be_nil
end
end
describe '#recently_sent_password_reset?' do describe '#recently_sent_password_reset?' do
it 'is false when reset_password_sent_at is nil' do it 'is false when reset_password_sent_at is nil' do
user = build_stubbed(:user, reset_password_sent_at: nil) user = build_stubbed(:user, reset_password_sent_at: nil)
......
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