Skip to content

Commit

Permalink
feat: add scope selection to application form (#463)
Browse files Browse the repository at this point in the history
  • Loading branch information
davidma415 authored Mar 14, 2024
1 parent b28530e commit 07ccd92
Show file tree
Hide file tree
Showing 11 changed files with 111 additions and 7 deletions.
9 changes: 8 additions & 1 deletion cypress/e2e/specs/application_registration.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -282,7 +282,8 @@ describe('Application Registration', () => {
cy.mockApplicationAuthStrategies([
{ name: 'foo', id: '1', credential_type: 'client_credentials', auth_methods: ['client_credentials', 'session'] },
{ name: 'bar', id: '2', credential_type: 'key_auth', key_names: ['key1', 'key2'] },
{ name: 'baz', id: '3', credential_type: 'self_managed_client_credentials', auth_methods: ['client_credentials', 'session', 'bearer'] }
{ name: 'baz', id: '3', credential_type: 'self_managed_client_credentials', auth_methods: ['client_credentials', 'session', 'bearer'] },
{ name: 'scopes', id: '4', credential_type: 'client_credentials', auth_methods: ['client_credentials', 'session'], available_scopes: ['scope1', 'scope2'] }
], 0)

cy.mockDcrPortal()
Expand All @@ -307,6 +308,12 @@ describe('Application Registration', () => {
cy.get('#redirectUri').should('exist')
cy.get('[data-testid="reference-id-input"]').should('exist')

cy.get('[data-testid="application-auth-strategy-select"]').click()
cy.get('[data-testid="k-select-item-4"] > .k-select-item-container > button').contains('scopes').click()
cy.get('#redirectUri').should('exist')
cy.get('#availableScopes').should('exist')
cy.get('[data-testid="reference-id-input"]').should('not.exist')

cy.get('[data-testid="application-auth-strategy-select"]').click()
cy.get('[data-testid="k-select-item-1"] > .k-select-item-container > button').contains('foo').click()
cy.get('#redirectUri').should('exist')
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
"@kong-ui-public/spec-renderer": "2.1.0",
"@kong/kong-auth-elements": "2.11.1",
"@kong/kongponents": "8.127.0",
"@kong/sdk-portal-js": "2.9.0",
"@kong/sdk-portal-js": "2.10.0",
"@xstate/vue": "2.0.0",
"axios": "1.6.0",
"date-fns": "3.3.0",
Expand Down
6 changes: 5 additions & 1 deletion src/components/ViewSpecRegistrationModal.vue
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,11 @@
:placeholder="fetchingScopes ? helpText.applicationRegistration.fetchingScopesLabel : helpText.applicationRegistration.filterScopes"
width="100%"
@change="handleChangedItem"
/>
>
<template #label-tooltip>
{{ helpText.applicationRegistration.updateScopesWarning }}
</template>
</KMultiselect>
</div>
</div>
</template>
Expand Down
3 changes: 3 additions & 0 deletions src/locales/ca_ES.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,8 @@ export const ca_ES: I18nType = {
authStrategy: translationNeeded(en.application.authStrategy),
authStrategyWarning: translationNeeded(en.application.authStrategyWarning),
grantedScopes: translationNeeded(en.application.grantedScopes),
availableScopes: translationNeeded(en.application.availableScopes),
filterScopesPlaceholder: translationNeeded(en.application.filterScopesPlaceholder),
clientID: 'ID de client: ',
clientSecret: 'Clau secreta de client: ',
reqField: ' indica un camp obligatori',
Expand Down Expand Up @@ -204,6 +206,7 @@ export const ca_ES: I18nType = {
filterScopes: translationNeeded(en.applicationRegistration.filterScopes),
availableScopesLabel: translationNeeded(en.applicationRegistration.availableScopesLabel),
fetchingScopesLabel: translationNeeded(en.applicationRegistration.fetchingScopesLabel),
updateScopesWarning: translationNeeded(en.applicationRegistration.updateScopesWarning),
noApplications: 'Sense aplicacions',
selectApplication: 'Seleccionar aplicació',
createNewApplication: 'Crear nova aplicació +',
Expand Down
3 changes: 3 additions & 0 deletions src/locales/de.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,8 @@ export const de: I18nType = {
authStrategy: translationNeeded(en.application.authStrategy),
authStrategyWarning: translationNeeded(en.application.authStrategyWarning),
grantedScopes: translationNeeded(en.application.grantedScopes),
availableScopes: translationNeeded(en.application.availableScopes),
filterScopesPlaceholder: translationNeeded(en.application.filterScopesPlaceholder),
clientID: translationNeeded(en.application.clientID),
clientSecret: translationNeeded(en.application.clientSecret),
reqField: ' Pflichtfeld',
Expand Down Expand Up @@ -204,6 +206,7 @@ export const de: I18nType = {
filterScopes: translationNeeded(en.applicationRegistration.filterScopes),
availableScopesLabel: translationNeeded(en.applicationRegistration.availableScopesLabel),
fetchingScopesLabel: translationNeeded(en.applicationRegistration.fetchingScopesLabel),
updateScopesWarning: translationNeeded(en.applicationRegistration.updateScopesWarning),
noApplications: 'Keine Applikationen',
selectApplication: 'Applikation auswählen',
createNewApplication: 'Neue Applikation anlegen +',
Expand Down
3 changes: 3 additions & 0 deletions src/locales/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,8 @@ export const en = {
authStrategy: 'Auth Strategy',
authStrategyWarning: 'You cannot create an application as this developer portal has no available application auth strategies. Please contact a developer portal admin.',
grantedScopes: 'Granted Scopes:',
availableScopes: 'Available Scopes',
filterScopesPlaceholder: 'Filter Scopes',
clientID: 'Client ID: ',
clientSecret: 'Client Secret: ',
reqField: ' indicates required field',
Expand Down Expand Up @@ -204,6 +206,7 @@ export const en = {
cancelButton: 'Cancel',
filterScopes: 'Filter...',
availableScopesLabel: 'Select scopes',
updateScopesWarning: 'Updating scopes will affect all application registrations related to this application',
fetchingScopesLabel: 'Fetching scopes...',
registeredApplicationsProduct: 'The following applications are already registered to this product:',
modalApplicationRegistrationDefault: {
Expand Down
3 changes: 3 additions & 0 deletions src/locales/es_ES.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,8 @@ export const es_ES: I18nType = {
authStrategy: translationNeeded(en.application.authStrategy),
authStrategyWarning: translationNeeded(en.application.authStrategyWarning),
grantedScopes: translationNeeded(en.application.grantedScopes),
availableScopes: translationNeeded(en.application.availableScopes),
filterScopesPlaceholder: translationNeeded(en.application.filterScopesPlaceholder),
clientID: 'ID de cliente: ',
clientSecret: 'Clave secreta de cliente: ',
reqField: ' indica campo obligatorio',
Expand Down Expand Up @@ -204,6 +206,7 @@ export const es_ES: I18nType = {
filterScopes: translationNeeded(en.applicationRegistration.filterScopes),
availableScopesLabel: translationNeeded(en.applicationRegistration.availableScopesLabel),
fetchingScopesLabel: translationNeeded(en.applicationRegistration.fetchingScopesLabel),
updateScopesWarning: translationNeeded(en.applicationRegistration.updateScopesWarning),
noApplications: 'No hay aplicaciones',
selectApplication: 'Seleccionar aplicación',
createNewApplication: 'Crear aplicación nueva +',
Expand Down
3 changes: 3 additions & 0 deletions src/locales/fr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,8 @@ export const fr: I18nType = {
authStrategy: translationNeeded(en.application.authStrategy),
authStrategyWarning: translationNeeded(en.application.authStrategyWarning),
grantedScopes: translationNeeded(en.application.grantedScopes),
availableScopes: translationNeeded(en.application.availableScopes),
filterScopesPlaceholder: translationNeeded(en.application.filterScopesPlaceholder),
clientID: 'Client ID : ',
clientSecret: 'Client Secret : ',
reqField: ' indique un champ obligatoire',
Expand Down Expand Up @@ -204,6 +206,7 @@ export const fr: I18nType = {
filterScopes: translationNeeded(en.applicationRegistration.filterScopes),
availableScopesLabel: translationNeeded(en.applicationRegistration.availableScopesLabel),
fetchingScopesLabel: translationNeeded(en.applicationRegistration.fetchingScopesLabel),
updateScopesWarning: translationNeeded(en.applicationRegistration.updateScopesWarning),
noApplications: 'Aucune application',
selectApplication: 'Sélectionner une application',
createNewApplication: 'Créer une nouvelle application +',
Expand Down
3 changes: 3 additions & 0 deletions src/locales/i18n-type.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,8 @@ export interface I18nType {
clientSecret: string;
reqField: string;
grantedScopes: string;
availableScopes: string;
filterScopesPlaceholder: string;
redirectUriLabel: string;
applicationCredentials: string;
applicationSecret: string;
Expand Down Expand Up @@ -205,6 +207,7 @@ export interface I18nType {
registeredApplicationsProduct: string;
searchPlaceholder: string;
availableScopesLabel: string;
updateScopesWarning: string;
filterScopes: string;
modalApplicationRegistrationDefault: {
title: (serviceName: string, productVersion: string) => string;
Expand Down
75 changes: 75 additions & 0 deletions src/views/Applications/ApplicationForm.vue
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,27 @@
@change="onChangeAuthStrategy"
/>
</div>
<div
v-if="selectedAuthStrategy?.availableScopes"
class="mb-5"
>
<KLabel
for="availableScopes"
>
{{ helpText.application.availableScopes }}
</KLabel>
<KMultiselect
id="availableScopes"
v-model="selectedScopes"
collapsed-context
data-testid="available-scopes-select"
class="available-scopes-select"
:items="mappedAvailableScopes"
:placeholder="helpText.application.filterScopesPlaceholder"
width="100%"
@change="handleChangedItem"
/>
</div>
<div
v-if="(!appRegV2Enabled && isDcr) || (appRegV2Enabled && appIsDcr) || (appRegV2Enabled && appIsSelfManaged)"
class="mb-5"
Expand Down Expand Up @@ -272,6 +293,9 @@ export default defineComponent({
const appRegV2Enabled = useLDFeatureFlag(FeatureFlags.AppRegV2, false)
const appAuthStrategies = ref([])
const appIsDcr = ref(false)
const selectedAuthStrategy = ref(null)
const selectedScopes = ref([])
const alreadyGrantedScopes = ref([])
const appIsSelfManaged = ref(false)
const hasAppAuthStrategies = ref(false)
const fetchingAuthStrategies = ref(true)
Expand Down Expand Up @@ -355,6 +379,7 @@ export default defineComponent({
value: strat.id,
isDcr: strat.credential_type === 'client_credentials',
isSelfManaged: strat.credential_type === 'self_managed_client_credentials',
availableScopes: strat.credential_type === 'client_credentials' ? strat.available_scopes ? strat.available_scopes : undefined : undefined,
selected: formData.value.auth_strategy_id ? strat.id === formData.value.auth_strategy_id : (strat.id === $route.query.auth_strategy_id || false)
}))
}
Expand All @@ -366,6 +391,7 @@ export default defineComponent({
if (selected) {
formData.value.auth_strategy_id = selected.value
appIsDcr.value = selected.isDcr
selectedAuthStrategy.value = selected
appIsSelfManaged.value = selected.isSelfManaged
}
Expand All @@ -382,6 +408,22 @@ export default defineComponent({
}
})
const mappedAvailableScopes = computed(() => {
if (!selectedAuthStrategy.value.availableScopes?.length) {
return []
}
return selectedAuthStrategy.value.availableScopes?.map((scope) => {
const alreadySelected = alreadyGrantedScopes.value?.includes(scope)
return {
label: scope,
value: scope,
selected: alreadySelected
}
})
})
const copyTokenToClipboard = (executeCopy, copyItem) => {
if (!executeCopy(copyItem)) {
notify({
Expand All @@ -395,8 +437,20 @@ export default defineComponent({
})
}
const handleChangedItem = (item) => {
if (!item) { return }
const itemAdded = selectedScopes.value.includes(item.value)
// If a new item selected, set its `selected` state to true
item.selected = !itemAdded
}
const onChangeAuthStrategy = (event) => {
const selected = appAuthStrategies.value.find((authStrat) => authStrat.value === event.value)
selectedAuthStrategy.value = selected
if (!selected) {
return
}
Expand All @@ -418,6 +472,12 @@ export default defineComponent({
}
}
if (selectedAuthStrategy.value?.availableScopes) {
formData.value.scopes = selectedScopes.value?.length ? selectedScopes.value : []
} else {
formData.value.scopes = undefined
}
portalApiV2.value.service.applicationsApi
.createApplication({
createApplicationPayload: cleanupEmptyFields(formData.value) as CreateApplicationPayload
Expand All @@ -440,6 +500,12 @@ export default defineComponent({
send('CLICKED_SUBMIT')
errorMessage.value = ''
if (selectedAuthStrategy.value?.availableScopes) {
formData.value.scopes = selectedScopes.value?.length ? selectedScopes.value : []
} else {
formData.value.scopes = undefined
}
delete formData.value.auth_strategy_id
portalApiV2.value.service.applicationsApi
.updateApplication({
Expand Down Expand Up @@ -474,6 +540,11 @@ export default defineComponent({
reference_id: res.data.reference_id,
auth_strategy_id: res.data.auth_strategy?.id
}
if (res.data.scopes?.length) {
alreadyGrantedScopes.value = res.data.scopes
}
if (!appRegV2Enabled && isDcr.value) {
delete newFormData.reference_id
} else {
Expand Down Expand Up @@ -566,6 +637,9 @@ export default defineComponent({
copyTokenToClipboard,
fetchingAuthStrategies,
secretModalIsVisible,
handleChangedItem,
mappedAvailableScopes,
selectedScopes,
handleAcknowledgeSecret,
hasAppAuthStrategies,
appRegV2Enabled,
Expand All @@ -578,6 +652,7 @@ export default defineComponent({
generateReferenceId,
helpText,
appAuthStrategies,
selectedAuthStrategy,
onChangeAuthStrategy,
appIsDcr,
appIsSelfManaged
Expand Down
8 changes: 4 additions & 4 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1225,10 +1225,10 @@
v-calendar "3.0.0-alpha.8"
vue-draggable-next "^2.2.1"

"@kong/sdk-portal-js@2.9.0":
version "2.9.0"
resolved "https://registry.yarnpkg.com/@kong/sdk-portal-js/-/sdk-portal-js-2.9.0.tgz#f35a728955f33b049861ea1c2bd5e5de515c82f3"
integrity sha512-NHuzKRyLEYebnN0nu+TUCHhewTENBEObOOUIF6GDDdEhgsAQMKOGcpbJoDf5wjKnp87sXWbBK02WEaVS8vt6Vw==
"@kong/sdk-portal-js@2.10.0":
version "2.10.0"
resolved "https://registry.yarnpkg.com/@kong/sdk-portal-js/-/sdk-portal-js-2.10.0.tgz#c5c0d32280b82455f837d1730e8ea9804f2e689e"
integrity sha512-hwVUK/Tln0C5mooQGJ6Ayf1Tu9LIK9/S+56QLF2AfozinACeuIkhTzfjW+6tBsUtefAYfWyOig+K6Bx1tQ9+4w==
dependencies:
axios "1.6.0"

Expand Down

0 comments on commit 07ccd92

Please sign in to comment.