Commit 54f0fd8c authored by Heinrich Lee Yu's avatar Heinrich Lee Yu

Merge branch '210181-project-level-access-tokens-project-bots' into 'master'

Add project level access tokens

See merge request gitlab-org/gitlab!30214
parents 3a5a6129 c27b2966
<script>
import { GlDatepicker } from '@gitlab/ui';
export default {
name: 'ExpiresAtField',
components: { GlDatepicker },
};
</script>
<template>
<gl-datepicker :target="null" :min-date="new Date()">
<slot></slot>
</gl-datepicker>
</template>
import Vue from 'vue';
import ExpiresAtField from './components/expires_at_field.vue';
const initExpiresAtField = () => {
// eslint-disable-next-line no-new
new Vue({
el: document.querySelector('.js-access-tokens-expires-at'),
components: { ExpiresAtField },
});
};
export default initExpiresAtField;
import DueDateSelectors from '~/due_date_select'; import initExpiresAtField from '~/access_tokens';
document.addEventListener('DOMContentLoaded', () => new DueDateSelectors()); document.addEventListener('DOMContentLoaded', initExpiresAtField);
import DueDateSelectors from '~/due_date_select'; import initExpiresAtField from '~/access_tokens';
document.addEventListener('DOMContentLoaded', () => new DueDateSelectors()); document.addEventListener('DOMContentLoaded', initExpiresAtField);
import initExpiresAtField from '~/access_tokens';
document.addEventListener('DOMContentLoaded', initExpiresAtField);
...@@ -674,6 +674,7 @@ module ProjectsHelper ...@@ -674,6 +674,7 @@ module ProjectsHelper
services#edit services#edit
hooks#index hooks#index
hooks#edit hooks#edit
access_tokens#index
hook_logs#show hook_logs#show
repository#show repository#show
ci_cd#show ci_cd#show
......
- add_to_breadcrumbs "Users", admin_users_path - add_to_breadcrumbs 'Users', admin_users_path
- breadcrumb_title @user.name - breadcrumb_title @user.name
- page_title "Impersonation Tokens", @user.name, "Users" - page_title _('Impersonation Tokens'), @user.name, _('Users')
- type = _('impersonation token')
- type_plural = _('impersonation tokens')
= render 'admin/users/head' = render 'admin/users/head'
.row.prepend-top-default .row.prepend-top-default
.col-lg-12 .col-lg-12
- if @new_impersonation_token - if @new_impersonation_token
= render "shared/personal_access_tokens_created_container", new_token_value: @new_impersonation_token, = render 'shared/access_tokens/created_container',
container_title: 'Your New Impersonation Token', type: type,
clipboard_button_title: _('Copy impersonation token') new_token_value: @new_impersonation_token
= render "shared/personal_access_tokens_form", path: admin_user_impersonation_tokens_path, impersonation: true, token: @impersonation_token, scopes: @scopes = render 'shared/access_tokens/form',
type: type,
title: _('Add an impersonation token'),
path: admin_user_impersonation_tokens_path,
impersonation: true,
token: @impersonation_token,
scopes: @scopes
= render "shared/personal_access_tokens_table", impersonation: true, active_tokens: @active_impersonation_tokens, inactive_tokens: @inactive_impersonation_tokens = render 'shared/access_tokens/table',
type: type,
type_plural: type_plural,
impersonation: true,
active_tokens: @active_impersonation_tokens,
revoke_route_helper: ->(token) { revoke_admin_user_impersonation_token_path(token.user, token) }
...@@ -363,6 +363,11 @@ ...@@ -363,6 +363,11 @@
= link_to project_hooks_path(@project), title: _('Webhooks'), data: { qa_selector: 'webhooks_settings_link' } do = link_to project_hooks_path(@project), title: _('Webhooks'), data: { qa_selector: 'webhooks_settings_link' } do
%span %span
= _('Webhooks') = _('Webhooks')
- if project_access_token_available?(@project)
= nav_link(controller: [:access_tokens]) do
= link_to project_settings_access_tokens_path(@project), title: _('Access Tokens'), data: { qa_selector: 'access_tokens_settings_link' } do
%span
= _('Access Tokens')
= nav_link(controller: :repository) do = nav_link(controller: :repository) do
= link_to project_settings_repository_path(@project), title: _('Repository') do = link_to project_settings_repository_path(@project), title: _('Repository') do
%span %span
......
- breadcrumb_title s_('AccessTokens|Access Tokens') - breadcrumb_title s_('AccessTokens|Access Tokens')
- page_title s_('AccessTokens|Personal Access Tokens') - page_title s_('AccessTokens|Personal Access Tokens')
- @content_class = "limit-container-width" unless fluid_layout - type = _('personal access token')
- type_plural = _('personal access tokens')
- @content_class = 'limit-container-width' unless fluid_layout
.row.prepend-top-default .row.prepend-top-default
.col-lg-4.profile-settings-sidebar .col-lg-4.profile-settings-sidebar
...@@ -14,11 +16,21 @@ ...@@ -14,11 +16,21 @@
.col-lg-8 .col-lg-8
- if @new_personal_access_token - if @new_personal_access_token
= render "shared/personal_access_tokens_created_container", new_token_value: @new_personal_access_token = render 'shared/access_tokens/created_container',
type: type,
new_token_value: @new_personal_access_token
= render "shared/personal_access_tokens_form", path: profile_personal_access_tokens_path, impersonation: false, token: @personal_access_token, scopes: @scopes = render 'shared/access_tokens/form',
type: type,
path: profile_personal_access_tokens_path,
token: @personal_access_token,
scopes: @scopes
= render "shared/personal_access_tokens_table", impersonation: false, active_tokens: @active_personal_access_tokens, inactive_tokens: @inactive_personal_access_tokens = render 'shared/access_tokens/table',
type: type,
type_plural: type_plural,
active_tokens: @active_personal_access_tokens,
revoke_route_helper: ->(token) { revoke_profile_personal_access_token_path(token) }
%hr %hr
.row.prepend-top-default .row.prepend-top-default
...@@ -30,7 +42,7 @@ ...@@ -30,7 +42,7 @@
%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.feed-token-reset
= label_tag :feed_token, s_('AccessTokens|Feed token'), class: "label-bold" = label_tag :feed_token, s_('AccessTokens|Feed token'), class: 'label-bold'
= text_field_tag :feed_token, current_user.feed_token, class: 'form-control js-select-on-focus', readonly: true = text_field_tag :feed_token, current_user.feed_token, class: 'form-control js-select-on-focus', readonly: true
%p.form-text.text-muted %p.form-text.text-muted
- reset_link = link_to s_('AccessTokens|reset it'), [:reset, :feed_token, :profile], method: :put, data: { confirm: s_('AccessTokens|Are you sure? Any RSS or calendar URLs currently in use will stop working.') } - reset_link = link_to s_('AccessTokens|reset it'), [:reset, :feed_token, :profile], method: :put, data: { confirm: s_('AccessTokens|Are you sure? Any RSS or calendar URLs currently in use will stop working.') }
...@@ -48,7 +60,7 @@ ...@@ -48,7 +60,7 @@
%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.incoming-email-token-reset
= label_tag :incoming_email_token, s_('AccessTokens|Incoming email token'), class: "label-bold" = label_tag :incoming_email_token, s_('AccessTokens|Incoming email token'), class: 'label-bold'
= text_field_tag :incoming_email_token, current_user.incoming_email_token, class: 'form-control js-select-on-focus', readonly: true = text_field_tag :incoming_email_token, current_user.incoming_email_token, class: 'form-control js-select-on-focus', readonly: true
%p.form-text.text-muted %p.form-text.text-muted
- reset_link = link_to s_('AccessTokens|reset it'), [:reset, :incoming_email_token, :profile], method: :put, data: { confirm: s_('AccessTokens|Are you sure? Any issue email addresses currently in use will stop working.') } - reset_link = link_to s_('AccessTokens|reset it'), [:reset, :incoming_email_token, :profile], method: :put, data: { confirm: s_('AccessTokens|Are you sure? Any issue email addresses currently in use will stop working.') }
......
- breadcrumb_title s_('AccessTokens|Access Tokens')
- page_title _('Project Access Tokens')
- type = _('project access token')
- type_plural = _('project access tokens')
- @content_class = 'limit-container-width' unless fluid_layout
.row.prepend-top-default
.col-lg-4.profile-settings-sidebar
%h4.prepend-top-0
= page_title
%p
= _('You can generate an access token scoped to this project for each application to use the GitLab API.')
%p
= _('You can also use project access tokens to authenticate against Git over HTTP.')
.col-lg-8
- if @new_project_access_token
= render 'shared/access_tokens/created_container',
type: type,
new_token_value: @new_project_access_token
= render 'shared/access_tokens/form',
type: type,
path: project_settings_access_tokens_path(@project),
token: @project_access_token,
scopes: @scopes,
prefix: :project_access_token
= render 'shared/access_tokens/table',
active_tokens: @active_project_access_tokens,
type: type,
type_plural: type_plural,
revoke_route_helper: ->(token) { revoke_namespace_project_settings_access_token_path(id: token) },
no_active_tokens_message: _('This project has no active access tokens.')
- container_title = local_assigns.fetch(:container_title, _('Your New Personal Access Token'))
- clipboard_button_title = local_assigns.fetch(:clipboard_button_title, _('Copy personal access token'))
.created-personal-access-token-container .created-personal-access-token-container
%h5.prepend-top-0 %h5.prepend-top-0
= container_title = _('Your new %{type}') % { type: type }
.form-group .form-group
.input-group .input-group
= text_field_tag 'created-personal-access-token', new_token_value, readonly: true, class: "qa-created-personal-access-token form-control js-select-on-focus", 'aria-describedby' => "created-token-help-block" = text_field_tag 'created-personal-access-token', new_token_value, readonly: true, class: 'qa-created-access-token form-control js-select-on-focus', 'aria-describedby' => 'created-token-help-block'
%span.input-group-append %span.input-group-append
= clipboard_button(text: new_token_value, title: clipboard_button_title, placement: "left", class: "input-group-text btn-default btn-clipboard") = clipboard_button(text: new_token_value, title: _('Copy %{type}') % { type: type }, placement: 'left', class: 'input-group-text btn-default btn-clipboard')
%span#created-token-help-block.form-text.text-muted.text-danger %span#created-token-help-block.form-text.text-muted.text-danger
= _("Make sure you save it - you won't be able to access it again.") = _("Make sure you save it - you won't be able to access it again.")
......
- type = impersonation ? s_('Profiles|impersonation') : s_('Profiles|personal access') - title = local_assigns.fetch(:title, _('Add a %{type}') % { type: type })
- prefix = local_assigns.fetch(:prefix, :personal_access_token)
%h5.prepend-top-0 %h5.prepend-top-0
= _('Add a %{type} token') % { type: type } = title
%p.profile-settings-content %p.profile-settings-content
= _("Pick a name for the application, and we'll give you a unique %{type} token.") % { type: type } = _("Enter the name of your application, and we'll return a unique %{type}.") % { type: type }
= form_for token, url: path, method: :post, html: { class: 'js-requires-input' } do |f| = form_for token, as: prefix, url: path, method: :post, html: { class: 'js-requires-input' } do |f|
= form_errors(token) = form_errors(token)
.row .row
.form-group.col-md-6 .form-group.col-md-6
= f.label :name, _('Name'), class: 'label-bold' = f.label :name, _('Name'), class: 'label-bold'
= f.text_field :name, class: "form-control", required: true, data: { qa_selector: 'personal_access_token_name_field' } = f.text_field :name, class: 'form-control', required: true, data: { qa_selector: 'access_token_name_field' }
.row .row
.form-group.col-md-6 .form-group.col-md-6
...@@ -21,11 +22,13 @@ ...@@ -21,11 +22,13 @@
= render_if_exists 'personal_access_tokens/callout_max_personal_access_token_lifetime' = render_if_exists 'personal_access_tokens/callout_max_personal_access_token_lifetime'
= f.text_field :expires_at, class: "datepicker form-control", placeholder: 'YYYY-MM-DD', data: { qa_selector: 'expiry_date_field' } .js-access-tokens-expires-at
%expires-at-field
= f.text_field :expires_at, class: 'datepicker form-control gl-datepicker-input', placeholder: 'YYYY-MM-DD', autocomplete: 'off', inputmode: 'none', data: { qa_selector: 'expiry_date_field' }
.form-group .form-group
= f.label :scopes, _('Scopes'), class: 'label-bold' = f.label :scopes, _('Scopes'), class: 'label-bold'
= render 'shared/tokens/scopes_form', prefix: 'personal_access_token', token: token, scopes: scopes = render 'shared/tokens/scopes_form', prefix: prefix, token: token, scopes: scopes
.prepend-top-default .prepend-top-default
= f.submit _('Create %{type} token') % { type: type }, class: "btn btn-success", data: { qa_selector: 'create_token_button' } = f.submit _('Create %{type}') % { type: type }, class: 'btn btn-success', data: { qa_selector: 'create_token_button' }
- type = impersonation ? s_('Profiles|Impersonation') : s_('Profiles|Personal Access') - no_active_tokens_message = local_assigns.fetch(:no_active_tokens_message, _('This user has no active %{type}.') % { type: type_plural })
- impersonation = local_assigns.fetch(:impersonation, false)
%hr %hr
%h5 %h5
= _('Active %{type} Tokens (%{token_length})') % { type: type, token_length: active_tokens.length } = _('Active %{type} (%{token_length})') % { type: type_plural, token_length: active_tokens.length }
- if impersonation - if impersonation
%p.profile-settings-content %p.profile-settings-content
= _("To see all the user's personal access tokens you must impersonate them first.") = _("To see all the user's personal access tokens you must impersonate them first.")
...@@ -28,9 +30,8 @@ ...@@ -28,9 +30,8 @@
In #{distance_of_time_in_words_to_now(token.expires_at)} In #{distance_of_time_in_words_to_now(token.expires_at)}
- else - else
%span.token-never-expires-label= _('Never') %span.token-never-expires-label= _('Never')
%td= token.scopes.present? ? token.scopes.join(", ") : _('<no scopes selected>') %td= token.scopes.present? ? token.scopes.join(', ') : _('<no scopes selected>')
- path = impersonation ? revoke_admin_user_impersonation_token_path(token.user, token) : revoke_profile_personal_access_token_path(token) %td= link_to _('Revoke'), revoke_route_helper.call(token), method: :put, class: 'btn btn-danger float-right qa-revoke-button', data: { confirm: _('Are you sure you want to revoke this %{type}? This action cannot be undone.') % { type: type } }
%td= link_to _('Revoke'), path, method: :put, class: "btn btn-danger float-right qa-revoke-button", data: { confirm: _('Are you sure you want to revoke this %{type} Token? This action cannot be undone.') % { type: type } }
- else - else
.settings-message.text-center .settings-message.text-center
= _('This user has no active %{type} Tokens.') % { type: type } = no_active_tokens_message
...@@ -67,7 +67,7 @@ ...@@ -67,7 +67,7 @@
class: 'btn btn-default align-self-center mr-sm-2', class: 'btn btn-default align-self-center mr-sm-2',
title: _('Resend invite') title: _('Resend invite')
- if user != current_user && member.can_update? - if user != current_user && member.can_update? && !user&.project_bot?
= form_for member, remote: true, html: { class: "js-edit-member-form form-group #{'d-sm-flex' unless force_mobile_view}" } do |f| = form_for member, remote: true, html: { class: "js-edit-member-form form-group #{'d-sm-flex' unless force_mobile_view}" } do |f|
= f.hidden_field :access_level = f.hidden_field :access_level
.member-form-control.dropdown{ class: [("mr-sm-2 d-sm-inline-block" unless force_mobile_view)] } .member-form-control.dropdown{ class: [("mr-sm-2 d-sm-inline-block" unless force_mobile_view)] }
...@@ -117,7 +117,7 @@ ...@@ -117,7 +117,7 @@
method: :delete, method: :delete,
data: { confirm: leave_confirmation_message(member.source) }, data: { confirm: leave_confirmation_message(member.source) },
class: "btn btn-remove align-self-center m-0 #{'ml-sm-2' unless force_mobile_view}" class: "btn btn-remove align-self-center m-0 #{'ml-sm-2' unless force_mobile_view}"
- else - elsif !user&.project_bot?
= link_to member, = link_to member,
method: :delete, method: :delete,
data: { confirm: remove_member_message(member), qa_selector: 'delete_member_button' }, data: { confirm: remove_member_message(member), qa_selector: 'delete_member_button' },
......
...@@ -1120,7 +1120,7 @@ msgstr "" ...@@ -1120,7 +1120,7 @@ msgstr ""
msgid "Active" msgid "Active"
msgstr "" msgstr ""
msgid "Active %{type} Tokens (%{token_length})" msgid "Active %{type} (%{token_length})"
msgstr "" msgstr ""
msgid "Active Sessions" msgid "Active Sessions"
...@@ -1173,7 +1173,7 @@ msgstr "" ...@@ -1173,7 +1173,7 @@ msgstr ""
msgid "Add Zoom meeting" msgid "Add Zoom meeting"
msgstr "" msgstr ""
msgid "Add a %{type} token" msgid "Add a %{type}"
msgstr "" msgstr ""
msgid "Add a GPG key" msgid "Add a GPG key"
...@@ -1224,6 +1224,9 @@ msgstr "" ...@@ -1224,6 +1224,9 @@ msgstr ""
msgid "Add an existing issue" msgid "Add an existing issue"
msgstr "" msgstr ""
msgid "Add an impersonation token"
msgstr ""
msgid "Add an issue" msgid "Add an issue"
msgstr "" msgstr ""
...@@ -2659,7 +2662,7 @@ msgstr "" ...@@ -2659,7 +2662,7 @@ msgstr ""
msgid "Are you sure you want to reset the health check token?" msgid "Are you sure you want to reset the health check token?"
msgstr "" msgstr ""
msgid "Are you sure you want to revoke this %{type} Token? This action cannot be undone." msgid "Are you sure you want to revoke this %{type}? This action cannot be undone."
msgstr "" msgstr ""
msgid "Are you sure you want to revoke this nickname?" msgid "Are you sure you want to revoke this nickname?"
...@@ -6045,6 +6048,9 @@ msgstr "" ...@@ -6045,6 +6048,9 @@ msgstr ""
msgid "Copy %{proxy_url}" msgid "Copy %{proxy_url}"
msgstr "" msgstr ""
msgid "Copy %{type}"
msgstr ""
msgid "Copy Account ID to clipboard" msgid "Copy Account ID to clipboard"
msgstr "" msgstr ""
...@@ -6090,9 +6096,6 @@ msgstr "" ...@@ -6090,9 +6096,6 @@ msgstr ""
msgid "Copy file path" msgid "Copy file path"
msgstr "" msgstr ""
msgid "Copy impersonation token"
msgstr ""
msgid "Copy key" msgid "Copy key"
msgstr "" msgstr ""
...@@ -6108,9 +6111,6 @@ msgstr "" ...@@ -6108,9 +6111,6 @@ msgstr ""
msgid "Copy link to chart" msgid "Copy link to chart"
msgstr "" msgstr ""
msgid "Copy personal access token"
msgstr ""
msgid "Copy reference" msgid "Copy reference"
msgstr "" msgstr ""
...@@ -6207,7 +6207,7 @@ msgstr "" ...@@ -6207,7 +6207,7 @@ msgstr ""
msgid "Create %{environment}" msgid "Create %{environment}"
msgstr "" msgstr ""
msgid "Create %{type} token" msgid "Create %{type}"
msgstr "" msgstr ""
msgid "Create New Directory" msgid "Create New Directory"
...@@ -8150,6 +8150,9 @@ msgstr "" ...@@ -8150,6 +8150,9 @@ msgstr ""
msgid "Enter the merge request title" msgid "Enter the merge request title"
msgstr "" msgstr ""
msgid "Enter the name of your application, and we'll return a unique %{type}."
msgstr ""
msgid "Enter your password to approve" msgid "Enter your password to approve"
msgstr "" msgstr ""
...@@ -11340,6 +11343,9 @@ msgstr "" ...@@ -11340,6 +11343,9 @@ msgstr ""
msgid "ImageViewerDimensions|W" msgid "ImageViewerDimensions|W"
msgstr "" msgstr ""
msgid "Impersonation Tokens"
msgstr ""
msgid "Impersonation has been disabled" msgid "Impersonation has been disabled"
msgstr "" msgstr ""
...@@ -15199,9 +15205,6 @@ msgstr "" ...@@ -15199,9 +15205,6 @@ msgstr ""
msgid "Pick a name" msgid "Pick a name"
msgstr "" msgstr ""
msgid "Pick a name for the application, and we'll give you a unique %{type} token."
msgstr ""
msgid "Pin code" msgid "Pin code"
msgstr "" msgstr ""
...@@ -16000,9 +16003,6 @@ msgstr "" ...@@ -16000,9 +16003,6 @@ msgstr ""
msgid "Profiles|Give your individual key a title" msgid "Profiles|Give your individual key a title"
msgstr "" msgstr ""
msgid "Profiles|Impersonation"
msgstr ""
msgid "Profiles|Include private contributions on my profile" msgid "Profiles|Include private contributions on my profile"
msgstr "" msgstr ""
...@@ -16048,9 +16048,6 @@ msgstr "" ...@@ -16048,9 +16048,6 @@ msgstr ""
msgid "Profiles|Path" msgid "Profiles|Path"
msgstr "" msgstr ""
msgid "Profiles|Personal Access"
msgstr ""
msgid "Profiles|Position and size your new avatar" msgid "Profiles|Position and size your new avatar"
msgstr "" msgstr ""
...@@ -16195,12 +16192,6 @@ msgstr "" ...@@ -16195,12 +16192,6 @@ msgstr ""
msgid "Profiles|e.g. My MacBook key" msgid "Profiles|e.g. My MacBook key"
msgstr "" msgstr ""
msgid "Profiles|impersonation"
msgstr ""
msgid "Profiles|personal access"
msgstr ""
msgid "Profiles|username" msgid "Profiles|username"
msgstr "" msgstr ""
...@@ -16252,6 +16243,9 @@ msgstr "" ...@@ -16252,6 +16243,9 @@ msgstr ""
msgid "Project '%{project_name}' will be deleted on %{date}" msgid "Project '%{project_name}' will be deleted on %{date}"
msgstr "" msgstr ""
msgid "Project Access Tokens"
msgstr ""
msgid "Project Audit Events" msgid "Project Audit Events"
msgstr "" msgstr ""
...@@ -21965,6 +21959,9 @@ msgstr "" ...@@ -21965,6 +21959,9 @@ msgstr ""
msgid "This project does not have billing enabled. To create a cluster, <a href=%{linkToBilling} target=\"_blank\" rel=\"noopener noreferrer\">enable billing <i class=\"fa fa-external-link\" aria-hidden=\"true\"></i></a> and try again." msgid "This project does not have billing enabled. To create a cluster, <a href=%{linkToBilling} target=\"_blank\" rel=\"noopener noreferrer\">enable billing <i class=\"fa fa-external-link\" aria-hidden=\"true\"></i></a> and try again."
msgstr "" msgstr ""
msgid "This project has no active access tokens."
msgstr ""
msgid "This project is archived and cannot be commented on." msgid "This project is archived and cannot be commented on."
msgstr "" msgstr ""
...@@ -22010,7 +22007,7 @@ msgstr "" ...@@ -22010,7 +22007,7 @@ msgstr ""
msgid "This user cannot be unlocked manually from GitLab" msgid "This user cannot be unlocked manually from GitLab"
msgstr "" msgstr ""
msgid "This user has no active %{type} Tokens." msgid "This user has no active %{type}."
msgstr "" msgstr ""
msgid "This user has no identities" msgid "This user has no identities"
...@@ -24477,6 +24474,9 @@ msgstr "" ...@@ -24477,6 +24474,9 @@ msgstr ""
msgid "You can also upload existing files from your computer using the instructions below." msgid "You can also upload existing files from your computer using the instructions below."
msgstr "" msgstr ""
msgid "You can also use project access tokens to authenticate against Git over HTTP."
msgstr ""
msgid "You can always edit this later" msgid "You can always edit this later"
msgstr "" msgstr ""
...@@ -24504,6 +24504,9 @@ msgstr "" ...@@ -24504,6 +24504,9 @@ msgstr ""
msgid "You can filter by 'days to merge' by clicking on the columns in the chart." msgid "You can filter by 'days to merge' by clicking on the columns in the chart."
msgstr "" msgstr ""
msgid "You can generate an access token scoped to this project for each application to use the GitLab API."
msgstr ""
msgid "You can get started by cloning the repository or start adding files to it with one of the following options." msgid "You can get started by cloning the repository or start adding files to it with one of the following options."
msgstr "" msgstr ""
...@@ -24879,9 +24882,6 @@ msgstr "" ...@@ -24879,9 +24882,6 @@ msgstr ""
msgid "Your Groups" msgid "Your Groups"
msgstr "" msgstr ""
msgid "Your New Personal Access Token"
msgstr ""
msgid "Your Personal Access Tokens will expire in %{days_to_expire} days or less" msgid "Your Personal Access Tokens will expire in %{days_to_expire} days or less"
msgstr "" msgstr ""
...@@ -24990,6 +24990,9 @@ msgstr "" ...@@ -24990,6 +24990,9 @@ msgstr ""
msgid "Your name" msgid "Your name"
msgstr "" msgstr ""
msgid "Your new %{type}"
msgstr ""
msgid "Your new SCIM token" msgid "Your new SCIM token"
msgstr "" msgstr ""
...@@ -25547,6 +25550,12 @@ msgstr "" ...@@ -25547,6 +25550,12 @@ msgstr ""
msgid "image diff" msgid "image diff"
msgstr "" msgstr ""
msgid "impersonation token"
msgstr ""
msgid "impersonation tokens"
msgstr ""
msgid "import flow" msgid "import flow"
msgstr "" msgstr ""
...@@ -26086,6 +26095,12 @@ msgstr "" ...@@ -26086,6 +26095,12 @@ msgstr ""
msgid "per day" msgid "per day"
msgstr "" msgstr ""
msgid "personal access token"
msgstr ""
msgid "personal access tokens"
msgstr ""
msgid "pipeline" msgid "pipeline"
msgstr "" msgstr ""
...@@ -26114,6 +26129,12 @@ msgid_plural "projects" ...@@ -26114,6 +26129,12 @@ msgid_plural "projects"
msgstr[0] "" msgstr[0] ""
msgstr[1] "" msgstr[1] ""
msgid "project access token"
msgstr ""
msgid "project access tokens"
msgstr ""
msgid "project avatar" msgid "project avatar"
msgstr "" msgstr ""
......
...@@ -6,9 +6,9 @@ module QA ...@@ -6,9 +6,9 @@ module QA
module Page module Page
module Profile module Profile
class PersonalAccessTokens < Page::Base class PersonalAccessTokens < Page::Base
view 'app/views/shared/_personal_access_tokens_form.html.haml' do view 'app/views/shared/access_tokens/_form.html.haml' do
element :expiry_date_field element :expiry_date_field
element :personal_access_token_name_field element :access_token_name_field
element :create_token_button element :create_token_button
end end
...@@ -16,15 +16,15 @@ module QA ...@@ -16,15 +16,15 @@ module QA
element :api_radio, 'qa-#{scope}-radio' # rubocop:disable QA/ElementWithPattern, Lint/InterpolationCheck element :api_radio, 'qa-#{scope}-radio' # rubocop:disable QA/ElementWithPattern, Lint/InterpolationCheck
end end
view 'app/views/shared/_personal_access_tokens_created_container.html.haml' do view 'app/views/shared/access_tokens/_created_container.html.haml' do
element :created_personal_access_token element :created_access_token
end end
view 'app/views/shared/_personal_access_tokens_table.html.haml' do view 'app/views/shared/access_tokens/_table.html.haml' do
element :revoke_button element :revoke_button
end end
def fill_token_name(name) def fill_token_name(name)
fill_element(:personal_access_token_name_field, name) fill_element(:access_token_name_field, name)
end end
def check_api def check_api
...@@ -36,7 +36,7 @@ module QA ...@@ -36,7 +36,7 @@ module QA
end end
def created_access_token def created_access_token
find_element(:created_personal_access_token, wait: 30).value find_element(:created_access_token, wait: 30).value
end end
def fill_expiry_date(date) def fill_expiry_date(date)
......
...@@ -70,7 +70,7 @@ describe 'Admin > Users > Impersonation Tokens', :js do ...@@ -70,7 +70,7 @@ describe 'Admin > Users > Impersonation Tokens', :js do
accept_confirm { click_on "Revoke" } accept_confirm { click_on "Revoke" }
expect(page).to have_selector(".settings-message") expect(page).to have_selector(".settings-message")
expect(no_personal_access_tokens_message).to have_text("This user has no active Impersonation Tokens.") expect(no_personal_access_tokens_message).to have_text("This user has no active impersonation tokens.")
end end
it "removes expired tokens from 'active' section" do it "removes expired tokens from 'active' section" do
...@@ -79,7 +79,7 @@ describe 'Admin > Users > Impersonation Tokens', :js do ...@@ -79,7 +79,7 @@ describe 'Admin > Users > Impersonation Tokens', :js do
visit admin_user_impersonation_tokens_path(user_id: user.username) visit admin_user_impersonation_tokens_path(user_id: user.username)
expect(page).to have_selector(".settings-message") expect(page).to have_selector(".settings-message")
expect(no_personal_access_tokens_message).to have_text("This user has no active Impersonation Tokens.") expect(no_personal_access_tokens_message).to have_text("This user has no active impersonation tokens.")
end end
end end
end end
...@@ -86,7 +86,7 @@ describe 'Profile > Personal Access Tokens', :js do ...@@ -86,7 +86,7 @@ describe 'Profile > Personal Access Tokens', :js do
accept_confirm { click_on "Revoke" } accept_confirm { click_on "Revoke" }
expect(page).to have_selector(".settings-message") expect(page).to have_selector(".settings-message")
expect(no_personal_access_tokens_message).to have_text("This user has no active Personal Access Tokens.") expect(no_personal_access_tokens_message).to have_text("This user has no active personal access tokens.")
end end
it "removes expired tokens from 'active' section" do it "removes expired tokens from 'active' section" do
...@@ -94,7 +94,7 @@ describe 'Profile > Personal Access Tokens', :js do ...@@ -94,7 +94,7 @@ describe 'Profile > Personal Access Tokens', :js do
visit profile_personal_access_tokens_path visit profile_personal_access_tokens_path
expect(page).to have_selector(".settings-message") expect(page).to have_selector(".settings-message")
expect(no_personal_access_tokens_message).to have_text("This user has no active Personal Access Tokens.") expect(no_personal_access_tokens_message).to have_text("This user has no active personal access tokens.")
end end
context "when revocation fails" do context "when revocation fails" do
......
...@@ -34,7 +34,7 @@ describe 'Project members list' do ...@@ -34,7 +34,7 @@ describe 'Project members list' do
expect(second_row).to be_blank expect(second_row).to be_blank
end end
it 'update user acess level', :js do it 'update user access level', :js do
project.add_developer(user2) project.add_developer(user2)
visit_members_page visit_members_page
...@@ -86,6 +86,23 @@ describe 'Project members list' do ...@@ -86,6 +86,23 @@ describe 'Project members list' do
end end
end end
context 'project bots' do
let(:project_bot) { create(:user, :project_bot, name: 'project_bot') }
before do
project.add_maintainer(project_bot)
end
it 'does not show form used to change roles and "Expiration date" or the remove user button' do
project_member = project.project_members.find_by(user_id: project_bot.id)
visit_members_page
expect(page).not_to have_selector("#edit_project_member_#{project_member.id}")
expect(page).not_to have_selector("#project_member_#{project_member.id} .btn-remove")
end
end
def add_user(id, role) def add_user(id, role)
page.within ".invite-users-form" do page.within ".invite-users-form" do
select2(id, from: "#user_ids", multiple: true) select2(id, from: "#user_ids", multiple: true)
......
# frozen_string_literal: true
require 'spec_helper'
describe 'Project > Settings > Access Tokens', :js do
let_it_be(:user) { create(:user) }
let_it_be(:bot_user) { create(:user, :project_bot) }
let_it_be(:project) { create(:project) }
before_all do
project.add_maintainer(user)
end
before do
sign_in(user)
end
def create_project_access_token
project.add_maintainer(bot_user)
create(:personal_access_token, user: bot_user)
end
def active_project_access_tokens
find('.table.active-tokens')
end
def no_project_access_tokens_message
find('.settings-message')
end
def created_project_access_token
find('#created-personal-access-token').value
end
describe 'token creation' do
it 'allows creation of a project access token' do
name = 'My project access token'
visit project_settings_access_tokens_path(project)
fill_in 'Name', with: name
# Set date to 1st of next month
find_field('Expires at').click
find('.pika-next').click
click_on '1'
# Scopes
check 'api'
check 'read_api'
click_on 'Create project access token'
expect(active_project_access_tokens).to have_text(name)
expect(active_project_access_tokens).to have_text('In')
expect(active_project_access_tokens).to have_text('api')
expect(active_project_access_tokens).to have_text('read_api')
expect(created_project_access_token).not_to be_empty
end
end
describe 'active tokens' do
let!(:project_access_token) { create_project_access_token }
it 'shows active project access tokens' do
visit project_settings_access_tokens_path(project)
expect(active_project_access_tokens).to have_text(project_access_token.name)
end
end
describe 'inactive tokens' do
let!(:project_access_token) { create_project_access_token }
no_active_tokens_text = 'This project has no active access tokens.'
it 'allows revocation of an active token' do
visit project_settings_access_tokens_path(project)
accept_confirm { click_on 'Revoke' }
expect(page).to have_selector('.settings-message')
expect(no_project_access_tokens_message).to have_text(no_active_tokens_text)
end
it 'removes expired tokens from active section' do
project_access_token.update(expires_at: 5.days.ago)
visit project_settings_access_tokens_path(project)
expect(page).to have_selector('.settings-message')
expect(no_project_access_tokens_message).to have_text(no_active_tokens_text)
end
end
end
...@@ -86,6 +86,7 @@ RSpec.shared_context 'project navbar structure' do ...@@ -86,6 +86,7 @@ RSpec.shared_context 'project navbar structure' do
_('Members'), _('Members'),
_('Integrations'), _('Integrations'),
_('Webhooks'), _('Webhooks'),
_('Access Tokens'),
_('Repository'), _('Repository'),
_('CI / CD'), _('CI / CD'),
_('Operations'), _('Operations'),
......
...@@ -196,4 +196,30 @@ describe 'layouts/nav/sidebar/_project' do ...@@ -196,4 +196,30 @@ describe 'layouts/nav/sidebar/_project' do
end end
end end
end end
describe 'project access tokens' do
context 'self-managed instance' do
before do
allow(Gitlab).to receive(:com?).and_return(false)
end
it 'displays "Access Tokens" nav item' do
render
expect(rendered).to have_link('Access Tokens', href: project_settings_access_tokens_path(project))
end
end
context 'gitlab.com' do
before do
allow(Gitlab).to receive(:com?).and_return(true)
end
it 'does not display "Access Tokens" nav item' do
render
expect(rendered).not_to have_link('Access Tokens', href: project_settings_access_tokens_path(project))
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