diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2023-02-09 00:11:09 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2023-02-09 00:11:09 +0300 |
commit | 3d42e098d9658853984534c9dfc2bf76284a8ac0 (patch) | |
tree | 269e9d9e017124f5f6d841311661832ea7e4eff8 /app/assets/javascripts/token_access | |
parent | a3487798aeff89daff78749e6d6f392d3ca23687 (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app/assets/javascripts/token_access')
10 files changed, 367 insertions, 29 deletions
diff --git a/app/assets/javascripts/token_access/components/inbound_token_access.vue b/app/assets/javascripts/token_access/components/inbound_token_access.vue new file mode 100644 index 00000000000..1a225550c95 --- /dev/null +++ b/app/assets/javascripts/token_access/components/inbound_token_access.vue @@ -0,0 +1,258 @@ +<script> +import { + GlAlert, + GlButton, + GlCard, + GlFormInput, + GlLink, + GlLoadingIcon, + GlSprintf, + GlToggle, +} from '@gitlab/ui'; +import { createAlert } from '~/flash'; +import { __, s__ } from '~/locale'; +import { helpPagePath } from '~/helpers/help_page_helper'; +import inboundAddProjectCIJobTokenScopeMutation from '../graphql/mutations/inbound_add_project_ci_job_token_scope.mutation.graphql'; +import inboundRemoveProjectCIJobTokenScopeMutation from '../graphql/mutations/inbound_remove_project_ci_job_token_scope.mutation.graphql'; +import inboundUpdateCIJobTokenScopeMutation from '../graphql/mutations/inbound_update_ci_job_token_scope.mutation.graphql'; +import inboundGetCIJobTokenScopeQuery from '../graphql/queries/inbound_get_ci_job_token_scope.query.graphql'; +import inboundGetProjectsWithCIJobTokenScopeQuery from '../graphql/queries/inbound_get_projects_with_ci_job_token_scope.query.graphql'; +import TokenProjectsTable from './token_projects_table.vue'; + +export default { + i18n: { + toggleLabelTitle: s__('CICD|Allow access to this project with a CI_JOB_TOKEN'), + toggleHelpText: s__( + `CICD|Manage which projects can use their CI_JOB_TOKEN to access this project. It is a security risk to disable this feature, because unauthorized projects might attempt to retrieve an active token and access the API. %{linkStart}Learn more.%{linkEnd}`, + ), + cardHeaderTitle: s__( + 'CICD|Allow CI job tokens from the following projects to access this project', + ), + settingDisabledMessage: s__( + 'CICD|Enable feature to allow job token access by the following projects.', + ), + addProject: __('Add project'), + cancel: __('Cancel'), + addProjectPlaceholder: __('Paste project path (i.e. gitlab-org/gitlab)'), + projectsFetchError: __('There was a problem fetching the projects'), + scopeFetchError: __('There was a problem fetching the job token scope value'), + }, + fields: [ + { + key: 'project', + label: __('Project with access'), + thClass: 'gl-border-t-none!', + columnClass: 'gl-w-40p', + }, + { + key: 'namespace', + label: __('Namespace'), + thClass: 'gl-border-t-none!', + columnClass: 'gl-w-40p', + }, + { + key: 'actions', + label: '', + tdClass: 'gl-text-right', + thClass: 'gl-border-t-none!', + columnClass: 'gl-w-10p', + }, + ], + components: { + GlAlert, + GlButton, + GlCard, + GlFormInput, + GlLink, + GlLoadingIcon, + GlSprintf, + GlToggle, + TokenProjectsTable, + }, + inject: { + fullPath: { + default: '', + }, + }, + apollo: { + inboundJobTokenScopeEnabled: { + query: inboundGetCIJobTokenScopeQuery, + variables() { + return { + fullPath: this.fullPath, + }; + }, + update({ project }) { + return project.ciCdSettings.inboundJobTokenScopeEnabled; + }, + error() { + createAlert({ message: this.$options.i18n.scopeFetchError }); + }, + }, + projects: { + query: inboundGetProjectsWithCIJobTokenScopeQuery, + variables() { + return { + fullPath: this.fullPath, + }; + }, + update({ project }) { + return project?.ciJobTokenScope?.inboundAllowlist?.nodes ?? []; + }, + error() { + createAlert({ message: this.$options.i18n.projectsFetchError }); + }, + }, + }, + data() { + return { + inboundJobTokenScopeEnabled: null, + targetProjectPath: '', + projects: [], + }; + }, + computed: { + isProjectPathEmpty() { + return this.targetProjectPath === ''; + }, + ciJobTokenHelpPage() { + return helpPagePath('ci/jobs/ci_job_token'); + }, + }, + methods: { + async updateCIJobTokenScope() { + try { + const { + data: { + ciCdSettingsUpdate: { errors }, + }, + } = await this.$apollo.mutate({ + mutation: inboundUpdateCIJobTokenScopeMutation, + variables: { + input: { + fullPath: this.fullPath, + inboundJobTokenScopeEnabled: this.inboundJobTokenScopeEnabled, + }, + }, + }); + + if (errors.length) { + throw new Error(errors[0]); + } + } catch (error) { + this.inboundJobTokenScopeEnabled = !this.inboundJobTokenScopeEnabled; + createAlert({ message: error.message }); + } + }, + async addProject() { + try { + const { + data: { + ciJobTokenScopeAddProject: { errors }, + }, + } = await this.$apollo.mutate({ + mutation: inboundAddProjectCIJobTokenScopeMutation, + variables: { + projectPath: this.fullPath, + targetProjectPath: this.targetProjectPath, + }, + }); + + if (errors.length) { + throw new Error(errors[0]); + } + } catch (error) { + createAlert({ message: error.message }); + } finally { + this.clearTargetProjectPath(); + this.getProjects(); + } + }, + async removeProject(removeTargetPath) { + try { + const { + data: { + ciJobTokenScopeRemoveProject: { errors }, + }, + } = await this.$apollo.mutate({ + mutation: inboundRemoveProjectCIJobTokenScopeMutation, + variables: { + projectPath: this.fullPath, + targetProjectPath: removeTargetPath, + }, + }); + + if (errors.length) { + throw new Error(errors[0]); + } + } catch (error) { + createAlert({ message: error.message }); + } finally { + this.getProjects(); + } + }, + clearTargetProjectPath() { + this.targetProjectPath = ''; + }, + getProjects() { + this.$apollo.queries.projects.refetch(); + }, + }, +}; +</script> +<template> + <div> + <gl-loading-icon v-if="$apollo.loading" size="lg" class="gl-mt-5" /> + <template v-else> + <gl-toggle + v-model="inboundJobTokenScopeEnabled" + :label="$options.i18n.toggleLabelTitle" + @change="updateCIJobTokenScope" + > + <template #help> + <gl-sprintf :message="$options.i18n.toggleHelpText"> + <template #link="{ content }"> + <gl-link :href="ciJobTokenHelpPage" class="inline-link" target="_blank"> + {{ content }} + </gl-link> + </template> + </gl-sprintf> + </template> + </gl-toggle> + + <div> + <gl-card class="gl-mt-5 gl-mb-3"> + <template #header> + <h5 class="gl-my-0">{{ $options.i18n.cardHeaderTitle }}</h5> + </template> + <template #default> + <gl-form-input + v-model="targetProjectPath" + :placeholder="$options.i18n.addProjectPlaceholder" + /> + </template> + <template #footer> + <gl-button variant="confirm" :disabled="isProjectPathEmpty" @click="addProject"> + {{ $options.i18n.addProject }} + </gl-button> + <gl-button @click="clearTargetProjectPath">{{ $options.i18n.cancel }}</gl-button> + </template> + </gl-card> + <gl-alert + v-if="!inboundJobTokenScopeEnabled" + class="gl-mb-3" + variant="warning" + :dismissible="false" + :show-icon="false" + > + {{ $options.i18n.settingDisabledMessage }} + </gl-alert> + <token-projects-table + :projects="projects" + :table-fields="$options.fields" + @removeProject="removeProject" + /> + </div> + </template> + </div> +</template> diff --git a/app/assets/javascripts/token_access/components/token_access.vue b/app/assets/javascripts/token_access/components/outbound_token_access.vue index 527f01f0a6f..47ec0910a7b 100644 --- a/app/assets/javascripts/token_access/components/token_access.vue +++ b/app/assets/javascripts/token_access/components/outbound_token_access.vue @@ -17,7 +17,6 @@ import removeProjectCIJobTokenScopeMutation from '../graphql/mutations/remove_pr import updateCIJobTokenScopeMutation from '../graphql/mutations/update_ci_job_token_scope.mutation.graphql'; import getCIJobTokenScopeQuery from '../graphql/queries/get_ci_job_token_scope.query.graphql'; import getProjectsWithCIJobTokenScopeQuery from '../graphql/queries/get_projects_with_ci_job_token_scope.query.graphql'; -import OptInJwt from './opt_in_jwt.vue'; import TokenProjectsTable from './token_projects_table.vue'; export default { @@ -36,6 +35,27 @@ export default { projectsFetchError: __('There was a problem fetching the projects'), scopeFetchError: __('There was a problem fetching the job token scope value'), }, + fields: [ + { + key: 'project', + label: __('Project that can be accessed'), + thClass: 'gl-border-t-none!', + columnClass: 'gl-w-40p', + }, + { + key: 'namespace', + label: __('Namespace'), + thClass: 'gl-border-t-none!', + columnClass: 'gl-w-40p', + }, + { + key: 'actions', + label: '', + tdClass: 'gl-text-right', + thClass: 'gl-border-t-none!', + columnClass: 'gl-w-10p', + }, + ], components: { GlAlert, GlButton, @@ -45,7 +65,6 @@ export default { GlLoadingIcon, GlSprintf, GlToggle, - OptInJwt, TokenProjectsTable, }, inject: { @@ -230,9 +249,12 @@ export default { > {{ $options.i18n.settingDisabledMessage }} </gl-alert> - <token-projects-table :projects="projects" @removeProject="removeProject" /> + <token-projects-table + :projects="projects" + :table-fields="$options.fields" + @removeProject="removeProject" + /> </div> - <opt-in-jwt /> </template> </div> </template> diff --git a/app/assets/javascripts/token_access/components/token_access_app.vue b/app/assets/javascripts/token_access/components/token_access_app.vue new file mode 100644 index 00000000000..410a6267dc8 --- /dev/null +++ b/app/assets/javascripts/token_access/components/token_access_app.vue @@ -0,0 +1,27 @@ +<script> +import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; +import OutboundTokenAccess from './outbound_token_access.vue'; +import InboundTokenAccess from './inbound_token_access.vue'; +import OptInJwt from './opt_in_jwt.vue'; + +export default { + components: { + OutboundTokenAccess, + InboundTokenAccess, + OptInJwt, + }, + mixins: [glFeatureFlagMixin()], + computed: { + inboundTokenAccessEnabled() { + return this.glFeatures.ciInboundJobTokenScope; + }, + }, +}; +</script> +<template> + <div> + <outbound-token-access /> + <inbound-token-access v-if="inboundTokenAccessEnabled" class="gl-pt-5" /> + <opt-in-jwt /> + </div> +</template> diff --git a/app/assets/javascripts/token_access/components/token_projects_table.vue b/app/assets/javascripts/token_access/components/token_projects_table.vue index ce33478cbee..c00dd882895 100644 --- a/app/assets/javascripts/token_access/components/token_projects_table.vue +++ b/app/assets/javascripts/token_access/components/token_projects_table.vue @@ -1,32 +1,11 @@ <script> import { GlButton, GlTable } from '@gitlab/ui'; -import { __, s__ } from '~/locale'; +import { s__ } from '~/locale'; export default { i18n: { emptyText: s__('CI/CD|No projects have been added to the scope'), }, - fields: [ - { - key: 'project', - label: __('Projects that can be accessed'), - thClass: 'gl-border-t-none!', - columnClass: 'gl-w-40p', - }, - { - key: 'namespace', - label: __('Namespace'), - thClass: 'gl-border-t-none!', - columnClass: 'gl-w-40p', - }, - { - key: 'actions', - label: '', - tdClass: 'gl-text-right', - thClass: 'gl-border-t-none!', - columnClass: 'gl-w-10p', - }, - ], components: { GlButton, GlTable, @@ -41,6 +20,10 @@ export default { type: Array, required: true, }, + tableFields: { + type: Array, + required: true, + }, }, methods: { removeProject(project) { @@ -52,7 +35,7 @@ export default { <template> <gl-table :items="projects" - :fields="$options.fields" + :fields="tableFields" :tbody-tr-attr="{ 'data-testid': 'projects-token-table-row' }" :empty-text="$options.i18n.emptyText" show-empty diff --git a/app/assets/javascripts/token_access/graphql/mutations/inbound_add_project_ci_job_token_scope.mutation.graphql b/app/assets/javascripts/token_access/graphql/mutations/inbound_add_project_ci_job_token_scope.mutation.graphql new file mode 100644 index 00000000000..f030a892af2 --- /dev/null +++ b/app/assets/javascripts/token_access/graphql/mutations/inbound_add_project_ci_job_token_scope.mutation.graphql @@ -0,0 +1,7 @@ +mutation inboundAddProjectCIJobTokenScope($projectPath: ID!, $targetProjectPath: ID!) { + ciJobTokenScopeAddProject( + input: { projectPath: $projectPath, targetProjectPath: $targetProjectPath, direction: INBOUND } + ) { + errors + } +} diff --git a/app/assets/javascripts/token_access/graphql/mutations/inbound_remove_project_ci_job_token_scope.mutation.graphql b/app/assets/javascripts/token_access/graphql/mutations/inbound_remove_project_ci_job_token_scope.mutation.graphql new file mode 100644 index 00000000000..cc6736bb80d --- /dev/null +++ b/app/assets/javascripts/token_access/graphql/mutations/inbound_remove_project_ci_job_token_scope.mutation.graphql @@ -0,0 +1,7 @@ +mutation inboundRemoveProjectCIJobTokenScope($projectPath: ID!, $targetProjectPath: ID!) { + ciJobTokenScopeRemoveProject( + input: { projectPath: $projectPath, targetProjectPath: $targetProjectPath, direction: INBOUND } + ) { + errors + } +} diff --git a/app/assets/javascripts/token_access/graphql/mutations/inbound_update_ci_job_token_scope.mutation.graphql b/app/assets/javascripts/token_access/graphql/mutations/inbound_update_ci_job_token_scope.mutation.graphql new file mode 100644 index 00000000000..aac9feab237 --- /dev/null +++ b/app/assets/javascripts/token_access/graphql/mutations/inbound_update_ci_job_token_scope.mutation.graphql @@ -0,0 +1,8 @@ +mutation inboundUpdateCIJobTokenScope($input: CiCdSettingsUpdateInput!) { + ciCdSettingsUpdate(input: $input) { + ciCdSettings { + inboundJobTokenScopeEnabled + } + errors + } +} diff --git a/app/assets/javascripts/token_access/graphql/queries/inbound_get_ci_job_token_scope.query.graphql b/app/assets/javascripts/token_access/graphql/queries/inbound_get_ci_job_token_scope.query.graphql new file mode 100644 index 00000000000..68d506a6c41 --- /dev/null +++ b/app/assets/javascripts/token_access/graphql/queries/inbound_get_ci_job_token_scope.query.graphql @@ -0,0 +1,8 @@ +query inboundGetCIJobTokenScope($fullPath: ID!) { + project(fullPath: $fullPath) { + id + ciCdSettings { + inboundJobTokenScopeEnabled + } + } +} diff --git a/app/assets/javascripts/token_access/graphql/queries/inbound_get_projects_with_ci_job_token_scope.query.graphql b/app/assets/javascripts/token_access/graphql/queries/inbound_get_projects_with_ci_job_token_scope.query.graphql new file mode 100644 index 00000000000..c51bdcbf7d2 --- /dev/null +++ b/app/assets/javascripts/token_access/graphql/queries/inbound_get_projects_with_ci_job_token_scope.query.graphql @@ -0,0 +1,18 @@ +query inboundGetProjectsWithCIJobTokenScope($fullPath: ID!) { + project(fullPath: $fullPath) { + id + ciJobTokenScope { + inboundAllowlist { + nodes { + id + name + namespace { + id + fullPath + } + fullPath + } + } + } + } +} diff --git a/app/assets/javascripts/token_access/index.js b/app/assets/javascripts/token_access/index.js index 6a29883290a..0253abe393e 100644 --- a/app/assets/javascripts/token_access/index.js +++ b/app/assets/javascripts/token_access/index.js @@ -1,7 +1,7 @@ import Vue from 'vue'; import VueApollo from 'vue-apollo'; import createDefaultClient from '~/lib/graphql'; -import TokenAccess from './components/token_access.vue'; +import TokenAccessApp from './components/token_access_app.vue'; Vue.use(VueApollo); @@ -25,7 +25,7 @@ export const initTokenAccess = (containerId = 'js-ci-token-access-app') => { fullPath, }, render(createElement) { - return createElement(TokenAccess); + return createElement(TokenAccessApp); }, }); }; |