@rancher/shell 3.0.5-rc.9 → 3.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/assets/translations/en-us.yaml +11 -5
- package/components/PodSecurityAdmission.vue +2 -0
- package/components/form/ProjectMemberEditor.vue +2 -0
- package/components/nav/Header.vue +6 -5
- package/config/store.js +2 -0
- package/dialog/SloDialog.vue +1 -1
- package/edit/auth/oidc.vue +106 -6
- package/edit/auth/saml.vue +5 -5
- package/edit/provisioning.cattle.io.cluster/tabs/AddOnAdditionalManifest.vue +1 -0
- package/edit/provisioning.cattle.io.cluster/tabs/AddOnConfig.vue +1 -0
- package/edit/provisioning.cattle.io.cluster/tabs/Basics.vue +1 -0
- package/edit/provisioning.cattle.io.cluster/tabs/etcd/S3Config.vue +1 -0
- package/edit/provisioning.cattle.io.cluster/tabs/registries/index.vue +2 -0
- package/edit/provisioning.cattle.io.cluster/tabs/upgrade/DrainOptions.vue +6 -0
- package/initialize/install-plugins.js +1 -3
- package/mixins/__tests__/auth-config.test.ts +4 -6
- package/mixins/__tests__/chart.test.ts +1 -1
- package/mixins/auth-config.js +33 -10
- package/mixins/chart.js +1 -1
- package/models/management.cattle.io.cluster.js +1 -1
- package/models/workload.js +1 -1
- package/package.json +1 -1
- package/pages/auth/login.vue +8 -3
- package/pages/auth/logout.vue +6 -5
- package/pages/c/_cluster/explorer/tools/__tests__/index.test.ts +1 -1
- package/pages/c/_cluster/explorer/tools/index.vue +1 -1
- package/plugins/axios.js +3 -2
- package/plugins/ember-cookie.js +7 -3
- package/plugins/steve/subscribe.js +4 -2
- package/scripts/test-plugins-build.sh +0 -1
- package/store/__tests__/cookies.test.ts +72 -0
- package/store/auth.js +33 -10
- package/store/cookies.ts +30 -0
- package/store/prefs.js +10 -5
- package/types/shell/index.d.ts +2 -11
- package/utils/auth.js +1 -1
- package/utils/cookie-universal.js +0 -10
|
@@ -478,6 +478,12 @@ addProjectMemberDialog:
|
|
|
478
478
|
title: Add Project Member
|
|
479
479
|
|
|
480
480
|
authConfig:
|
|
481
|
+
slo:
|
|
482
|
+
sloTitle: Log Out behavior
|
|
483
|
+
sloOptions:
|
|
484
|
+
onlyRancher: Log out of Rancher and not {name}
|
|
485
|
+
logoutAll: Log out of Rancher and {name} (includes all other applications registered with {name})
|
|
486
|
+
choose: Allow the user to choose one of the above in an additional log out step
|
|
481
487
|
accessMode:
|
|
482
488
|
label: 'Configure who should be able to login and use {vendor}'
|
|
483
489
|
required: Restrict access to only the authorized users & groups
|
|
@@ -627,11 +633,6 @@ authConfig:
|
|
|
627
633
|
UID: UID Field
|
|
628
634
|
adfs: Configure an AD FS account
|
|
629
635
|
api: '{vendor} API Host'
|
|
630
|
-
sloTitle: Log Out behavior
|
|
631
|
-
sloOptions:
|
|
632
|
-
onlyRancher: Log out of Rancher and not {name}
|
|
633
|
-
logoutAll: Log out of Rancher and {name} (includes all other applications registered with {name})
|
|
634
|
-
choose: Allow the user to choose one of the above in an additional log out step
|
|
635
636
|
cert:
|
|
636
637
|
label: Certificate
|
|
637
638
|
placeholder: Paste in the certificate, starting with -----BEGIN CERTIFICATE-----
|
|
@@ -698,6 +699,9 @@ authConfig:
|
|
|
698
699
|
title: Are you sure? This update is irreversible.
|
|
699
700
|
body: '<p><b>You may need to make some additional changes</b>. Please ensure the Azure AD app has the Directory.Read.All <b>Application</b> permission added to Microsoft Graph.<br> If any endpoints were customized while configuring Azure AD authentication in Rancher, they will not be automatically updated. </p>'
|
|
700
701
|
oidc:
|
|
702
|
+
endSessionEndpoint:
|
|
703
|
+
title: End Session Endpoint
|
|
704
|
+
tooltip: Provider specific URL used for logging a user out of their session
|
|
701
705
|
genericoidc: Configure an OIDC account
|
|
702
706
|
keycloakoidc: Configure a Keycloak OIDC account
|
|
703
707
|
cognito: Configure an Amazon Cognito account
|
|
@@ -7566,6 +7570,8 @@ model:
|
|
|
7566
7570
|
saml: SAML
|
|
7567
7571
|
oauth: OAuth
|
|
7568
7572
|
oidc: OIDC
|
|
7573
|
+
keycloakoidc: Keycloak
|
|
7574
|
+
genericoidc: OIDC provider
|
|
7569
7575
|
cognito: Amazon Cognito
|
|
7570
7576
|
name:
|
|
7571
7577
|
keycloak: Keycloak (SAML)
|
|
@@ -222,6 +222,7 @@ export default defineComponent({
|
|
|
222
222
|
<Checkbox
|
|
223
223
|
v-if="!labelsAlwaysActive"
|
|
224
224
|
v-model:value="psaControl.active"
|
|
225
|
+
:mode="mode"
|
|
225
226
|
:data-testid="componentTestid + '--psaControl-' + i + '-active'"
|
|
226
227
|
:label="level"
|
|
227
228
|
:label-key="`podSecurityAdmission.labels.${ level }`"
|
|
@@ -281,6 +282,7 @@ export default defineComponent({
|
|
|
281
282
|
<span class="col span-2">
|
|
282
283
|
<Checkbox
|
|
283
284
|
v-model:value="psaExemptionsControl.active"
|
|
285
|
+
:mode="mode"
|
|
284
286
|
:data-testid="componentTestid + '--psaExemptionsControl-' + i + '-active'"
|
|
285
287
|
:label="dimension"
|
|
286
288
|
:label-key="`podSecurityAdmission.labels.${ dimension }`"
|
|
@@ -286,6 +286,7 @@ export default {
|
|
|
286
286
|
<template v-slot:body>
|
|
287
287
|
<RadioGroup
|
|
288
288
|
v-model:value="value.permissionGroup"
|
|
289
|
+
:mode="mode"
|
|
289
290
|
data-testid="permission-options"
|
|
290
291
|
:options="options"
|
|
291
292
|
name="permission-group"
|
|
@@ -301,6 +302,7 @@ export default {
|
|
|
301
302
|
>
|
|
302
303
|
<Checkbox
|
|
303
304
|
v-model:value="permission.value"
|
|
305
|
+
:mode="mode"
|
|
304
306
|
:data-testid="`custom-permission-${i}`"
|
|
305
307
|
:disabled="permission.locked"
|
|
306
308
|
class="mb-5"
|
|
@@ -29,6 +29,7 @@ import {
|
|
|
29
29
|
RcDropdownSeparator,
|
|
30
30
|
RcDropdownTrigger
|
|
31
31
|
} from '@components/RcDropdown';
|
|
32
|
+
import { SLO_AUTH_PROVIDERS } from '@shell/store/auth';
|
|
32
33
|
|
|
33
34
|
export default {
|
|
34
35
|
|
|
@@ -99,10 +100,10 @@ export default {
|
|
|
99
100
|
'showWorkspaceSwitcher'
|
|
100
101
|
]),
|
|
101
102
|
|
|
102
|
-
|
|
103
|
+
sloAuthProviderEnabled() {
|
|
103
104
|
const publicAuthProviders = this.$store.getters['rancher/all']('authProvider');
|
|
104
105
|
|
|
105
|
-
return publicAuthProviders.find((authProvider) => configType[authProvider
|
|
106
|
+
return publicAuthProviders.find((authProvider) => SLO_AUTH_PROVIDERS.includes(configType[authProvider?.id])) || {};
|
|
106
107
|
},
|
|
107
108
|
|
|
108
109
|
shouldShowSloLogoutModal() {
|
|
@@ -111,7 +112,7 @@ export default {
|
|
|
111
112
|
return false;
|
|
112
113
|
}
|
|
113
114
|
|
|
114
|
-
const { logoutAllSupported, logoutAllEnabled, logoutAllForced } = this.
|
|
115
|
+
const { logoutAllSupported, logoutAllEnabled, logoutAllForced } = this.sloAuthProviderEnabled;
|
|
115
116
|
|
|
116
117
|
return logoutAllSupported && logoutAllEnabled && !logoutAllForced;
|
|
117
118
|
},
|
|
@@ -276,8 +277,8 @@ export default {
|
|
|
276
277
|
showSloModal() {
|
|
277
278
|
this.$store.dispatch('management/promptModal', {
|
|
278
279
|
component: 'SloDialog',
|
|
279
|
-
componentProps: { authProvider: this.
|
|
280
|
-
modalWidth: '
|
|
280
|
+
componentProps: { authProvider: this.sloAuthProviderEnabled },
|
|
281
|
+
modalWidth: '600px'
|
|
281
282
|
});
|
|
282
283
|
},
|
|
283
284
|
// Sizes the product area of the header such that it shrinks to ensure the whole header bar can be shown
|
package/config/store.js
CHANGED
|
@@ -39,6 +39,7 @@ let store = {};
|
|
|
39
39
|
resolveStoreModules(require('../store/customisation.js'), 'customisation.js');
|
|
40
40
|
resolveStoreModules(require('../store/cru-resource.ts'), 'cru-resource.ts');
|
|
41
41
|
resolveStoreModules(require('../store/notifications.ts'), 'notifications.ts');
|
|
42
|
+
resolveStoreModules(require('../store/cookies.ts'), 'cookies.ts');
|
|
42
43
|
|
|
43
44
|
// If the environment supports hot reloading...
|
|
44
45
|
|
|
@@ -69,6 +70,7 @@ let store = {};
|
|
|
69
70
|
'../store/customisation.js',
|
|
70
71
|
'../store/cru-resource.ts',
|
|
71
72
|
'../store/notifications.ts',
|
|
73
|
+
'../store/cookies.ts',
|
|
72
74
|
], () => {
|
|
73
75
|
// Update `root.modules` with the latest definitions.
|
|
74
76
|
updateModules();
|
package/dialog/SloDialog.vue
CHANGED
package/edit/auth/oidc.vue
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<script>
|
|
2
2
|
import Loading from '@shell/components/Loading';
|
|
3
3
|
import CreateEditView from '@shell/mixins/create-edit-view';
|
|
4
|
-
import AuthConfig from '@shell/mixins/auth-config';
|
|
4
|
+
import AuthConfig, { SLO_OPTION_VALUES } from '@shell/mixins/auth-config';
|
|
5
5
|
import CruResource from '@shell/components/CruResource';
|
|
6
6
|
import AllowedPrincipals from '@shell/components/auth/AllowedPrincipals';
|
|
7
7
|
import FileSelector from '@shell/components/form/FileSelector';
|
|
@@ -15,6 +15,7 @@ import { RadioGroup } from '@components/Form/Radio';
|
|
|
15
15
|
import { Checkbox } from '@components/Form/Checkbox';
|
|
16
16
|
import { BASE_SCOPES } from '@shell/store/auth';
|
|
17
17
|
import CopyToClipboardText from '@shell/components/CopyToClipboardText.vue';
|
|
18
|
+
import isUrl from 'is-url';
|
|
18
19
|
|
|
19
20
|
export default {
|
|
20
21
|
components: {
|
|
@@ -33,6 +34,8 @@ export default {
|
|
|
33
34
|
CopyToClipboardText,
|
|
34
35
|
},
|
|
35
36
|
|
|
37
|
+
emits: ['validationChanged'],
|
|
38
|
+
|
|
36
39
|
mixins: [CreateEditView, AuthConfig],
|
|
37
40
|
|
|
38
41
|
data() {
|
|
@@ -56,7 +59,8 @@ export default {
|
|
|
56
59
|
userInfoEndpoint: null,
|
|
57
60
|
},
|
|
58
61
|
// TODO #13457: this is duplicated due wrong format
|
|
59
|
-
oidcScope: []
|
|
62
|
+
oidcScope: [],
|
|
63
|
+
SLO_OPTION_VALUES
|
|
60
64
|
};
|
|
61
65
|
},
|
|
62
66
|
|
|
@@ -89,6 +93,11 @@ export default {
|
|
|
89
93
|
return false;
|
|
90
94
|
}
|
|
91
95
|
|
|
96
|
+
// make sure that if SLO options are enabled on radio group, field "endSessionEndpoint" is required
|
|
97
|
+
if (this.isLogoutAllSupported && this.sloEndSessionEndpointUiEnabled && (!this.model.endSessionEndpoint || !isUrl(this.model.endSessionEndpoint))) {
|
|
98
|
+
return false;
|
|
99
|
+
}
|
|
100
|
+
|
|
92
101
|
if (this.isAmazonCognito) {
|
|
93
102
|
const { issuer } = this.model;
|
|
94
103
|
|
|
@@ -129,10 +138,36 @@ export default {
|
|
|
129
138
|
|
|
130
139
|
isAmazonCognito() {
|
|
131
140
|
return this.model?.id === 'cognito';
|
|
141
|
+
},
|
|
142
|
+
|
|
143
|
+
isLogoutAllSupported() {
|
|
144
|
+
return this.model?.logoutAllSupported;
|
|
145
|
+
},
|
|
146
|
+
|
|
147
|
+
sloOptions() {
|
|
148
|
+
return [
|
|
149
|
+
{ value: SLO_OPTION_VALUES.rancher, label: this.t('authConfig.slo.sloOptions.onlyRancher', { name: this.model?.nameDisplay }) },
|
|
150
|
+
{ value: SLO_OPTION_VALUES.all, label: this.t('authConfig.slo.sloOptions.logoutAll', { name: this.model?.nameDisplay }) },
|
|
151
|
+
{ value: SLO_OPTION_VALUES.both, label: this.t('authConfig.slo.sloOptions.choose') },
|
|
152
|
+
];
|
|
153
|
+
},
|
|
154
|
+
|
|
155
|
+
sloTypeText() {
|
|
156
|
+
const sloOptionSelected = this.sloOptions.find((item) => item.value === this.sloType);
|
|
157
|
+
|
|
158
|
+
return sloOptionSelected?.label || '';
|
|
159
|
+
},
|
|
160
|
+
|
|
161
|
+
sloEndSessionEndpointUiEnabled() {
|
|
162
|
+
return this.sloType === SLO_OPTION_VALUES.all || this.sloType === SLO_OPTION_VALUES.both;
|
|
132
163
|
}
|
|
133
164
|
},
|
|
134
165
|
|
|
135
166
|
watch: {
|
|
167
|
+
fvFormIsValid(newValue) {
|
|
168
|
+
this.$emit('validationChanged', !!newValue);
|
|
169
|
+
},
|
|
170
|
+
|
|
136
171
|
'oidcUrls.url'() {
|
|
137
172
|
this.updateEndpoints();
|
|
138
173
|
},
|
|
@@ -166,6 +201,25 @@ export default {
|
|
|
166
201
|
if (!old && neu) {
|
|
167
202
|
this.customEndpoint.value = !this.oidcUrls.url && !!this.model.issuer;
|
|
168
203
|
}
|
|
204
|
+
},
|
|
205
|
+
|
|
206
|
+
// sloType is defined on shell/mixins/auth-config.js
|
|
207
|
+
sloType(neu) {
|
|
208
|
+
switch (neu) {
|
|
209
|
+
case SLO_OPTION_VALUES.rancher:
|
|
210
|
+
this.model.logoutAllEnabled = false;
|
|
211
|
+
this.model.logoutAllForced = false;
|
|
212
|
+
this.model.endSessionEndpoint = '';
|
|
213
|
+
break;
|
|
214
|
+
case SLO_OPTION_VALUES.all:
|
|
215
|
+
this.model.logoutAllEnabled = true;
|
|
216
|
+
this.model.logoutAllForced = true;
|
|
217
|
+
break;
|
|
218
|
+
case SLO_OPTION_VALUES.both:
|
|
219
|
+
this.model.logoutAllEnabled = true;
|
|
220
|
+
this.model.logoutAllForced = false;
|
|
221
|
+
break;
|
|
222
|
+
}
|
|
169
223
|
}
|
|
170
224
|
},
|
|
171
225
|
|
|
@@ -224,11 +278,19 @@ export default {
|
|
|
224
278
|
:edit="goToEdit"
|
|
225
279
|
>
|
|
226
280
|
<template #rows>
|
|
227
|
-
<tr><td>{{ t(
|
|
228
|
-
<tr><td>{{ t(
|
|
229
|
-
<tr><td>{{ t(
|
|
281
|
+
<tr><td>{{ t('authConfig.oidc.rancherUrl') }}: </td><td>{{ model.rancherUrl }}</td></tr>
|
|
282
|
+
<tr><td>{{ t('authConfig.oidc.clientId') }}: </td><td>{{ model.clientId }}</td></tr>
|
|
283
|
+
<tr><td>{{ t('authConfig.oidc.issuer') }}: </td><td>{{ model.issuer }}</td></tr>
|
|
230
284
|
<tr v-if="model.authEndpoint">
|
|
231
|
-
<td>{{ t(
|
|
285
|
+
<td>{{ t('authConfig.oidc.authEndpoint') }}: </td><td>{{ model.authEndpoint }}</td>
|
|
286
|
+
</tr>
|
|
287
|
+
<tr v-if="isLogoutAllSupported">
|
|
288
|
+
<td>{{ t('authConfig.slo.sloTitle') }}: </td><td>{{ sloTypeText }}</td>
|
|
289
|
+
</tr>
|
|
290
|
+
<tr v-if="isLogoutAllSupported && sloEndSessionEndpointUiEnabled">
|
|
291
|
+
<td>
|
|
292
|
+
{{ t('authConfig.oidc.endSessionEndpoint.title') }}:
|
|
293
|
+
</td><td>{{ model.endSessionEndpoint }}</td>
|
|
232
294
|
</tr>
|
|
233
295
|
</template>
|
|
234
296
|
</AuthBanner>
|
|
@@ -494,6 +556,44 @@ export default {
|
|
|
494
556
|
</div>
|
|
495
557
|
</div>
|
|
496
558
|
</template>
|
|
559
|
+
|
|
560
|
+
<!-- SLO logout -->
|
|
561
|
+
<div
|
|
562
|
+
v-if="isLogoutAllSupported"
|
|
563
|
+
class="mt-40 mb-20"
|
|
564
|
+
>
|
|
565
|
+
<div class="row">
|
|
566
|
+
<div class="col span-12">
|
|
567
|
+
<h3>{{ t('authConfig.slo.sloTitle') }}</h3>
|
|
568
|
+
</div>
|
|
569
|
+
</div>
|
|
570
|
+
<div class="row">
|
|
571
|
+
<div class="col span-4">
|
|
572
|
+
<RadioGroup
|
|
573
|
+
v-model:value="sloType"
|
|
574
|
+
:mode="mode"
|
|
575
|
+
:options="sloOptions"
|
|
576
|
+
:disabled="!model.logoutAllSupported"
|
|
577
|
+
name="sloTypeRadio"
|
|
578
|
+
/>
|
|
579
|
+
</div>
|
|
580
|
+
</div>
|
|
581
|
+
<div
|
|
582
|
+
v-if="sloEndSessionEndpointUiEnabled"
|
|
583
|
+
class="row mt-20"
|
|
584
|
+
>
|
|
585
|
+
<div class="col span-6">
|
|
586
|
+
<LabeledInput
|
|
587
|
+
v-model:value="model.endSessionEndpoint"
|
|
588
|
+
:tooltip="t('authConfig.oidc.endSessionEndpoint.tooltip')"
|
|
589
|
+
:label="t('authConfig.oidc.endSessionEndpoint.title')"
|
|
590
|
+
:mode="mode"
|
|
591
|
+
required
|
|
592
|
+
data-testid="oidc-endSessionEndpoint"
|
|
593
|
+
/>
|
|
594
|
+
</div>
|
|
595
|
+
</div>
|
|
596
|
+
</div>
|
|
497
597
|
</template>
|
|
498
598
|
</CruResource>
|
|
499
599
|
</div>
|
package/edit/auth/saml.vue
CHANGED
|
@@ -73,9 +73,9 @@ export default {
|
|
|
73
73
|
|
|
74
74
|
sloOptions() {
|
|
75
75
|
return [
|
|
76
|
-
{ value: SLO_OPTION_VALUES.rancher, label: this.t('authConfig.
|
|
77
|
-
{ value: SLO_OPTION_VALUES.all, label: this.t('authConfig.
|
|
78
|
-
{ value: SLO_OPTION_VALUES.both, label: this.t('authConfig.
|
|
76
|
+
{ value: SLO_OPTION_VALUES.rancher, label: this.t('authConfig.slo.sloOptions.onlyRancher', { name: this.model?.nameDisplay }) },
|
|
77
|
+
{ value: SLO_OPTION_VALUES.all, label: this.t('authConfig.slo.sloOptions.logoutAll', { name: this.model?.nameDisplay }) },
|
|
78
|
+
{ value: SLO_OPTION_VALUES.both, label: this.t('authConfig.slo.sloOptions.choose') },
|
|
79
79
|
];
|
|
80
80
|
},
|
|
81
81
|
|
|
@@ -175,7 +175,7 @@ export default {
|
|
|
175
175
|
<tr><td>{{ t(`authConfig.saml.api`) }}: </td><td>{{ model.rancherApiHost }}</td></tr>
|
|
176
176
|
<tr><td>{{ t(`authConfig.saml.groups`) }}: </td><td>{{ model.groupsField }}</td></tr>
|
|
177
177
|
<tr v-if="isLogoutAllSupported">
|
|
178
|
-
<td>{{ t(`authConfig.
|
|
178
|
+
<td>{{ t(`authConfig.slo.sloTitle`) }}: </td><td>{{ sloTypeText }}</td>
|
|
179
179
|
</tr>
|
|
180
180
|
</template>
|
|
181
181
|
|
|
@@ -357,7 +357,7 @@ export default {
|
|
|
357
357
|
>
|
|
358
358
|
<div class="row">
|
|
359
359
|
<div class="col span-12">
|
|
360
|
-
<h3>{{ t('authConfig.
|
|
360
|
+
<h3>{{ t('authConfig.slo.sloTitle') }}</h3>
|
|
361
361
|
</div>
|
|
362
362
|
</div>
|
|
363
363
|
<div class="row">
|
|
@@ -78,6 +78,7 @@ export default {
|
|
|
78
78
|
<div class="row">
|
|
79
79
|
<Checkbox
|
|
80
80
|
:value="showCustomRegistryInput"
|
|
81
|
+
:mode="mode"
|
|
81
82
|
:label="t('cluster.privateRegistry.label')"
|
|
82
83
|
data-testid="registries-enable-checkbox"
|
|
83
84
|
@update:value="$emit('custom-registry-changed', $event)"
|
|
@@ -90,6 +91,7 @@ export default {
|
|
|
90
91
|
<div class="col span-6">
|
|
91
92
|
<LabeledInput
|
|
92
93
|
:value="registryHost"
|
|
94
|
+
:mode="mode"
|
|
93
95
|
label-key="catalog.chart.registry.custom.inputLabel"
|
|
94
96
|
placeholder-key="catalog.chart.registry.custom.placeholder"
|
|
95
97
|
:min-height="30"
|
|
@@ -102,6 +102,7 @@ export default {
|
|
|
102
102
|
<div class="mt-20">
|
|
103
103
|
<Checkbox
|
|
104
104
|
v-model:value="deleteEmptyDirData"
|
|
105
|
+
:mode="mode"
|
|
105
106
|
label-key="cluster.rke2.drain.deleteEmptyDir.label"
|
|
106
107
|
tooltip-key="cluster.rke2.drain.deleteEmptyDir.tooltip"
|
|
107
108
|
@update:value="update"
|
|
@@ -110,6 +111,7 @@ export default {
|
|
|
110
111
|
<div>
|
|
111
112
|
<Checkbox
|
|
112
113
|
v-model:value="force"
|
|
114
|
+
:mode="mode"
|
|
113
115
|
label="Delete standalone pods"
|
|
114
116
|
label-key="cluster.rke2.drain.force.label"
|
|
115
117
|
tooltip="Delete standalone pods which are not managed by a Workload controller (Deployment, Job, etc). Draining will fail if this is not set and there are standalone pods."
|
|
@@ -120,12 +122,14 @@ export default {
|
|
|
120
122
|
<div>
|
|
121
123
|
<Checkbox
|
|
122
124
|
v-model:value="customGracePeriod"
|
|
125
|
+
:mode="mode"
|
|
123
126
|
label-key="cluster.rke2.drain.gracePeriod.checkboxLabel"
|
|
124
127
|
@update:value="update"
|
|
125
128
|
/>
|
|
126
129
|
<UnitInput
|
|
127
130
|
v-if="customGracePeriod"
|
|
128
131
|
v-model:value="gracePeriod"
|
|
132
|
+
:mode="mode"
|
|
129
133
|
label-key="cluster.rke2.drain.gracePeriod.inputLabel"
|
|
130
134
|
:suffix="t('suffix.seconds', {count: timeout})"
|
|
131
135
|
class="mb-10"
|
|
@@ -135,12 +139,14 @@ export default {
|
|
|
135
139
|
<div>
|
|
136
140
|
<Checkbox
|
|
137
141
|
v-model:value="customTimeout"
|
|
142
|
+
:mode="mode"
|
|
138
143
|
label-key="cluster.rke2.drain.timeout.checkboxLabel"
|
|
139
144
|
@update:value="update"
|
|
140
145
|
/>
|
|
141
146
|
<UnitInput
|
|
142
147
|
v-if="customTimeout"
|
|
143
148
|
v-model:value="timeout"
|
|
149
|
+
:mode="mode"
|
|
144
150
|
label-key="cluster.rke2.drain.timeout.inputLabel"
|
|
145
151
|
:suffix="t('suffix.seconds', {count: timeout})"
|
|
146
152
|
class="drain-timeout"
|
|
@@ -12,7 +12,6 @@ import i18n from '@shell/plugins/i18n';
|
|
|
12
12
|
import globalFormatters from '@shell/plugins/global-formatters';
|
|
13
13
|
|
|
14
14
|
import axios from '@shell/utils/axios';
|
|
15
|
-
import cookieUniversal from '@shell/utils/cookie-universal';
|
|
16
15
|
import config from '@shell/utils/config';
|
|
17
16
|
import axiosShell from '@shell/plugins/axios';
|
|
18
17
|
import codeMirror from '@shell/plugins/codemirror-loader';
|
|
@@ -47,7 +46,7 @@ export async function installPlugins(vueApp) {
|
|
|
47
46
|
}
|
|
48
47
|
|
|
49
48
|
export async function installInjectedPlugins(app, vueApp) {
|
|
50
|
-
const pluginDefinitions = [config,
|
|
49
|
+
const pluginDefinitions = [config, axios, plugins, pluginsLoader, axiosShell, intNumber, codeMirror, nuxtClientInit, replaceAll, plugin, steveCreateWorker, emberCookie, internalApiPlugin];
|
|
51
50
|
|
|
52
51
|
const installations = pluginDefinitions.map(async(pluginDefinition) => {
|
|
53
52
|
if (typeof pluginDefinition === 'function') {
|
|
@@ -64,7 +63,6 @@ export async function installInjectedPlugins(app, vueApp) {
|
|
|
64
63
|
// If there's any performance reasons this can be done concurrently with all of the installation promises above but I felt it was organizationally better to keep both i18n items together.
|
|
65
64
|
await app.store.dispatch('i18n/init');
|
|
66
65
|
|
|
67
|
-
// Order matters here. This is coming after the other plugins specifically so $cookies can be installed. i18n/init relies on prefs/get which relies on $cookies.
|
|
68
66
|
vueApp.use(i18n, { store: app.store });
|
|
69
67
|
}
|
|
70
68
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { mount } from '@vue/test-utils';
|
|
2
2
|
import authConfigMixin from '@shell/mixins/auth-config';
|
|
3
|
-
|
|
3
|
+
import childHook from '@shell/mixins/child-hook';
|
|
4
|
+
//
|
|
4
5
|
describe('mixin: authConfigMixin', () => {
|
|
5
6
|
describe('method: save', () => {
|
|
6
7
|
const componentMock = (model: any) => ({
|
|
@@ -11,10 +12,7 @@ describe('mixin: authConfigMixin', () => {
|
|
|
11
12
|
computed: { principal: () => ({ me: {} }) },
|
|
12
13
|
global: {
|
|
13
14
|
mocks: {
|
|
14
|
-
$store: {
|
|
15
|
-
dispatch: () => model,
|
|
16
|
-
commit: () => ({ 'auth/loggedInAs': jest.fn() }),
|
|
17
|
-
},
|
|
15
|
+
$store: { dispatch: () => model },
|
|
18
16
|
$route: {
|
|
19
17
|
params: { id: '123' },
|
|
20
18
|
query: { mode: 'edit' },
|
|
@@ -24,7 +22,7 @@ describe('mixin: authConfigMixin', () => {
|
|
|
24
22
|
});
|
|
25
23
|
const FakeComponent = {
|
|
26
24
|
render() {},
|
|
27
|
-
mixins: [authConfigMixin],
|
|
25
|
+
mixins: [authConfigMixin, childHook],
|
|
28
26
|
methods: { applyHooks: jest.fn() },
|
|
29
27
|
};
|
|
30
28
|
|
package/mixins/auth-config.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { _EDIT } from '@shell/config/query-params';
|
|
2
2
|
import { NORMAN, MANAGEMENT } from '@shell/config/types';
|
|
3
3
|
import { AFTER_SAVE_HOOKS, BEFORE_SAVE_HOOKS } from '@shell/mixins/child-hook';
|
|
4
|
-
import { BASE_SCOPES } from '@shell/store/auth';
|
|
4
|
+
import { BASE_SCOPES, SLO_AUTH_PROVIDERS } from '@shell/store/auth';
|
|
5
5
|
import { addObject, findBy } from '@shell/utils/array';
|
|
6
6
|
import { exceptionToErrorsArray } from '@shell/utils/error';
|
|
7
7
|
import difference from 'lodash/difference';
|
|
@@ -30,6 +30,10 @@ export default {
|
|
|
30
30
|
}
|
|
31
31
|
},
|
|
32
32
|
|
|
33
|
+
created() {
|
|
34
|
+
this.registerAfterHook(this.updateAuthProviders, 'force-update-auth-providers');
|
|
35
|
+
},
|
|
36
|
+
|
|
33
37
|
async fetch() {
|
|
34
38
|
await this.mixinFetch();
|
|
35
39
|
},
|
|
@@ -88,6 +92,23 @@ export default {
|
|
|
88
92
|
},
|
|
89
93
|
|
|
90
94
|
methods: {
|
|
95
|
+
updateAuthProviders() {
|
|
96
|
+
// we need to forcefully re-fetch the authProviders list so that we can update the logout method
|
|
97
|
+
// this is to satisfy the SLO usecase where after setting an auth provider the logout method
|
|
98
|
+
// wasn't being updated because the resource is not watchable
|
|
99
|
+
this.$store.dispatch('auth/getAuthProviders', { force: true });
|
|
100
|
+
},
|
|
101
|
+
|
|
102
|
+
setSloType(selectedModel) {
|
|
103
|
+
if (!selectedModel.logoutAllEnabled && !selectedModel.logoutAllForced) {
|
|
104
|
+
this.sloType = SLO_OPTION_VALUES.rancher;
|
|
105
|
+
} else if (selectedModel.logoutAllEnabled && selectedModel.logoutAllForced) {
|
|
106
|
+
this.sloType = SLO_OPTION_VALUES.all;
|
|
107
|
+
} else if (selectedModel.logoutAllEnabled && !selectedModel.logoutAllForced) {
|
|
108
|
+
this.sloType = SLO_OPTION_VALUES.both;
|
|
109
|
+
}
|
|
110
|
+
},
|
|
111
|
+
|
|
91
112
|
async mixinFetch() {
|
|
92
113
|
this.authConfigName = this.$route.params.id;
|
|
93
114
|
|
|
@@ -115,20 +136,16 @@ export default {
|
|
|
115
136
|
if (this.model.openLdapConfig) {
|
|
116
137
|
this.showLdap = true;
|
|
117
138
|
}
|
|
118
|
-
|
|
139
|
+
|
|
140
|
+
// Logic for Single Logout/SLO for auth providers
|
|
141
|
+
if (this.value?.configType && SLO_AUTH_PROVIDERS.includes(this.value?.configType)) {
|
|
119
142
|
if (!this.model.rancherApiHost || !this.model.rancherApiHost.length) {
|
|
120
143
|
this.model['rancherApiHost'] = this.serverUrl;
|
|
121
144
|
}
|
|
122
145
|
|
|
123
146
|
// setting data for SLO
|
|
124
147
|
if (this.model && Object.keys(this.model).includes('logoutAllSupported')) {
|
|
125
|
-
|
|
126
|
-
this.sloType = SLO_OPTION_VALUES.rancher;
|
|
127
|
-
} else if (this.model.logoutAllEnabled && this.model.logoutAllForced) {
|
|
128
|
-
this.sloType = SLO_OPTION_VALUES.all;
|
|
129
|
-
} else if (this.model.logoutAllEnabled && !this.model.logoutAllForced) {
|
|
130
|
-
this.sloType = SLO_OPTION_VALUES.both;
|
|
131
|
-
}
|
|
148
|
+
this.setSloType(this.model);
|
|
132
149
|
}
|
|
133
150
|
}
|
|
134
151
|
|
|
@@ -220,7 +237,7 @@ export default {
|
|
|
220
237
|
addObject(this.model.allowedPrincipalIds, this.principal.id);
|
|
221
238
|
}
|
|
222
239
|
// Session has switched to new 'me', ensure we react
|
|
223
|
-
this.$store.
|
|
240
|
+
this.$store.dispatch('auth/loggedInAs', this.principal.id);
|
|
224
241
|
} else {
|
|
225
242
|
console.warn(`Unable to find principal marked as 'me'`); // eslint-disable-line no-console
|
|
226
243
|
}
|
|
@@ -292,6 +309,12 @@ export default {
|
|
|
292
309
|
// must be cancelling edit of an enabled config; reset any changes and return to add users/groups view for that config
|
|
293
310
|
this.$store.dispatch(`rancher/clone`, { resource: this.originalModel }).then((cloned) => {
|
|
294
311
|
this.model = cloned;
|
|
312
|
+
|
|
313
|
+
// reset SLO type (radio option)
|
|
314
|
+
if (cloned && Object.keys(cloned).includes('logoutAllSupported')) {
|
|
315
|
+
this.setSloType(cloned);
|
|
316
|
+
}
|
|
317
|
+
|
|
295
318
|
this.editConfig = false;
|
|
296
319
|
});
|
|
297
320
|
}
|
package/mixins/chart.js
CHANGED
|
@@ -278,7 +278,7 @@ export default class MgmtCluster extends SteveModel {
|
|
|
278
278
|
|
|
279
279
|
// Color to use as the underline for the icon in the app bar
|
|
280
280
|
get iconColor() {
|
|
281
|
-
return this.metadata?.annotations[CLUSTER_BADGE.COLOR];
|
|
281
|
+
return this.metadata?.annotations?.[CLUSTER_BADGE.COLOR];
|
|
282
282
|
}
|
|
283
283
|
|
|
284
284
|
// Custom badge to show for the Cluster (if the appropriate annotations are set)
|
package/models/workload.js
CHANGED
package/package.json
CHANGED
package/pages/auth/login.vue
CHANGED
|
@@ -134,7 +134,8 @@ export default {
|
|
|
134
134
|
},
|
|
135
135
|
|
|
136
136
|
async fetch() {
|
|
137
|
-
const
|
|
137
|
+
const cookie = this.$store.getters['cookies/get']({ key: USERNAME, options: { parseJSON: false } });
|
|
138
|
+
const username = cookie || '';
|
|
138
139
|
|
|
139
140
|
this.username = username;
|
|
140
141
|
this.remember = !!username;
|
|
@@ -272,15 +273,19 @@ export default {
|
|
|
272
273
|
}
|
|
273
274
|
|
|
274
275
|
if ( this.remember ) {
|
|
275
|
-
|
|
276
|
+
const options = {
|
|
276
277
|
encode: (x) => x,
|
|
277
278
|
maxAge: 86400 * 365,
|
|
278
279
|
path: '/',
|
|
279
280
|
sameSite: true,
|
|
280
281
|
secure: true,
|
|
282
|
+
};
|
|
283
|
+
|
|
284
|
+
this.$store.commit('cookies/set', {
|
|
285
|
+
key: USERNAME, value: this.username, options
|
|
281
286
|
});
|
|
282
287
|
} else {
|
|
283
|
-
this.$cookies
|
|
288
|
+
this.$store.commit('cookies/remove', { key: USERNAME });
|
|
284
289
|
}
|
|
285
290
|
|
|
286
291
|
// User logged with local login - we don't do any redirect/reload, so the boot-time plugin will not run again to laod the plugins
|
package/pages/auth/logout.vue
CHANGED
|
@@ -1,19 +1,20 @@
|
|
|
1
1
|
<script>
|
|
2
2
|
import { configType } from '@shell/models/management.cattle.io.authconfig';
|
|
3
|
+
import { SLO_AUTH_PROVIDERS } from '@shell/store/auth';
|
|
3
4
|
|
|
4
5
|
export default {
|
|
5
6
|
async fetch() {
|
|
6
7
|
const publicAuthProviders = await this.$store.dispatch('auth/getAuthProviders');
|
|
7
8
|
|
|
8
|
-
const
|
|
9
|
+
const sloAuthProvider = publicAuthProviders.find((authProvider) => SLO_AUTH_PROVIDERS.includes(configType[authProvider?.id]));
|
|
9
10
|
|
|
10
|
-
if (!!
|
|
11
|
-
const { logoutAllSupported, logoutAllEnabled, logoutAllForced } =
|
|
11
|
+
if (!!sloAuthProvider) {
|
|
12
|
+
const { logoutAllSupported, logoutAllEnabled, logoutAllForced } = sloAuthProvider;
|
|
12
13
|
|
|
13
14
|
if (logoutAllSupported && logoutAllEnabled && logoutAllForced) {
|
|
14
|
-
//
|
|
15
|
+
// force SLO (logout from all apps)
|
|
15
16
|
await this.$store.dispatch('auth/logout', {
|
|
16
|
-
force: true, slo: true, provider:
|
|
17
|
+
force: true, slo: true, provider: sloAuthProvider
|
|
17
18
|
}, { root: true });
|
|
18
19
|
} else {
|
|
19
20
|
// simple logout
|
|
@@ -117,7 +117,7 @@ describe('page: cluster tools', () => {
|
|
|
117
117
|
});
|
|
118
118
|
expect(actions[2]).toStrictEqual({
|
|
119
119
|
label: 'catalog.tools.action.downgrade',
|
|
120
|
-
icon: 'icon-
|
|
120
|
+
icon: 'icon-downgrade-alt',
|
|
121
121
|
action: 'downgrade'
|
|
122
122
|
});
|
|
123
123
|
expect(actions[3]).toStrictEqual({ divider: true });
|
|
@@ -158,7 +158,7 @@ export default {
|
|
|
158
158
|
if (currentIndex !== -1 && currentIndex < versions.length - 1) {
|
|
159
159
|
actions.push({
|
|
160
160
|
label: this.t('catalog.tools.action.downgrade'),
|
|
161
|
-
icon: 'icon-
|
|
161
|
+
icon: 'icon-downgrade-alt',
|
|
162
162
|
action: 'downgrade',
|
|
163
163
|
});
|
|
164
164
|
}
|
package/plugins/axios.js
CHANGED
|
@@ -2,13 +2,14 @@ import https from 'https';
|
|
|
2
2
|
import { CSRF } from '@shell/config/cookies';
|
|
3
3
|
|
|
4
4
|
export default function({
|
|
5
|
-
$axios,
|
|
5
|
+
$axios, store, isDev, req
|
|
6
6
|
}) {
|
|
7
7
|
$axios.defaults.headers.common['Accept'] = 'application/json';
|
|
8
8
|
$axios.defaults.withCredentials = true;
|
|
9
9
|
|
|
10
10
|
$axios.onRequest((config) => {
|
|
11
|
-
const
|
|
11
|
+
const options = { parseJSON: false };
|
|
12
|
+
const csrf = store.getters['cookies/get']({ key: CSRF, options });
|
|
12
13
|
|
|
13
14
|
if ( csrf ) {
|
|
14
15
|
config.headers['x-api-csrf'] = csrf;
|
package/plugins/ember-cookie.js
CHANGED
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
import { REDIRECTED } from '@shell/config/cookies';
|
|
2
2
|
|
|
3
|
-
export default function({
|
|
3
|
+
export default function({ store }) {
|
|
4
4
|
// This tells Ember not to redirect back to us once you've already been to dashboard once.
|
|
5
5
|
// TODO: Remove this once the ember portion of the app is no longer needed
|
|
6
|
-
if (
|
|
7
|
-
|
|
6
|
+
if ( !store.getters['cookies/get']({ key: REDIRECTED })) {
|
|
7
|
+
const options = {
|
|
8
8
|
path: '/',
|
|
9
9
|
sameSite: true,
|
|
10
10
|
secure: true,
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
store.commit('cookies/set', {
|
|
14
|
+
key: REDIRECTED, value: 'true', options
|
|
11
15
|
});
|
|
12
16
|
}
|
|
13
17
|
}
|
|
@@ -369,13 +369,15 @@ const sharedActions = {
|
|
|
369
369
|
if (!this.$workers[getters.storeName]) {
|
|
370
370
|
await createWorker(this, ctx);
|
|
371
371
|
}
|
|
372
|
+
const options = { parseJSON: false };
|
|
373
|
+
const csrf = rootGetters['cookies/get']({ key: CSRF, options });
|
|
372
374
|
|
|
373
375
|
// if the worker is in advanced mode then it'll contain it's own socket which it calls a 'watcher'
|
|
374
376
|
this.$workers[getters.storeName].postMessage({
|
|
375
377
|
createWatcher: {
|
|
376
378
|
metadata,
|
|
377
|
-
url:
|
|
378
|
-
csrf
|
|
379
|
+
url: `${ state.config.baseUrl }/subscribe`,
|
|
380
|
+
csrf,
|
|
379
381
|
maxTries
|
|
380
382
|
}
|
|
381
383
|
});
|
|
@@ -239,7 +239,6 @@ function clone_repo_test_extension_build() {
|
|
|
239
239
|
clone_repo_test_extension_build "rancher" "kubewarden-ui" "kubewarden"
|
|
240
240
|
clone_repo_test_extension_build "rancher" "elemental-ui" "elemental"
|
|
241
241
|
clone_repo_test_extension_build "neuvector" "manager-ext" "neuvector-ui-ext"
|
|
242
|
-
clone_repo_test_extension_build "rancher" "capi-ui-extension" "capi"
|
|
243
242
|
clone_repo_test_extension_build "StackVista" "rancher-extension-stackstate" "observability"
|
|
244
243
|
clone_repo_test_extension_build "harvester" "harvester-ui-extension" "harvester"
|
|
245
244
|
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import cookieStore from '../cookies';
|
|
2
|
+
|
|
3
|
+
// Mock cookie-universal
|
|
4
|
+
const mockCookie = {
|
|
5
|
+
get: jest.fn(),
|
|
6
|
+
set: jest.fn(),
|
|
7
|
+
remove: jest.fn(),
|
|
8
|
+
getAll: jest.fn(),
|
|
9
|
+
addChangeListener: jest.fn(),
|
|
10
|
+
removeChangeListener: jest.fn()
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
jest.mock('cookie-universal', () => {
|
|
14
|
+
return jest.fn(() => mockCookie);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
describe('store: cookies', () => {
|
|
18
|
+
let state: any;
|
|
19
|
+
|
|
20
|
+
beforeEach(() => {
|
|
21
|
+
state = cookieStore.state();
|
|
22
|
+
// Reset mocks before each test
|
|
23
|
+
mockCookie.get.mockClear();
|
|
24
|
+
mockCookie.set.mockClear();
|
|
25
|
+
mockCookie.remove.mockClear();
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
describe('getters', () => {
|
|
29
|
+
it('get should call cookies.get with the correct parameters', () => {
|
|
30
|
+
const { getters } = cookieStore;
|
|
31
|
+
const key = 'test-key';
|
|
32
|
+
const options = { from: 'server' };
|
|
33
|
+
|
|
34
|
+
getters.get(state, {}, {}, {})({ key, options });
|
|
35
|
+
expect(mockCookie.get).toHaveBeenCalledWith(key, options);
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
describe('mutations', () => {
|
|
40
|
+
it('set should call cookies.set with the correct parameters', () => {
|
|
41
|
+
const { mutations } = cookieStore;
|
|
42
|
+
const key = 'test-key';
|
|
43
|
+
const value = { data: 'test-value' };
|
|
44
|
+
const options = { path: '/' };
|
|
45
|
+
|
|
46
|
+
mutations.set(state, {
|
|
47
|
+
key, value, options
|
|
48
|
+
});
|
|
49
|
+
expect(mockCookie.set).toHaveBeenCalledWith(key, value, options);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('remove should call cookies.remove with the correct key', () => {
|
|
53
|
+
const { mutations } = cookieStore;
|
|
54
|
+
const key = 'test-key';
|
|
55
|
+
|
|
56
|
+
mutations.remove(state, { key });
|
|
57
|
+
expect(mockCookie.remove).toHaveBeenCalledWith(key);
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
describe('state', () => {
|
|
62
|
+
it('should return a cookies object', () => {
|
|
63
|
+
expect(state.cookies).toBeDefined();
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
describe('namespaced', () => {
|
|
68
|
+
it('should be namespaced', () => {
|
|
69
|
+
expect(cookieStore.namespaced).toBe(true);
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
});
|
package/store/auth.js
CHANGED
|
@@ -8,6 +8,13 @@ import { removeEmberPage } from '@shell/utils/ember-page';
|
|
|
8
8
|
import { randomStr } from '@shell/utils/string';
|
|
9
9
|
import { addParams, parse as parseUrl, removeParam } from '@shell/utils/url';
|
|
10
10
|
|
|
11
|
+
// configuration for Single Logout/SLO
|
|
12
|
+
// admissable auth providers compatible with SLO, based on shell/models/management.cattle.io.authconfig "configType"
|
|
13
|
+
export const SLO_AUTH_PROVIDERS = ['oidc', 'saml'];
|
|
14
|
+
|
|
15
|
+
// this is connected to the redirect url, for which the logic can be found in "shell/store/auth"
|
|
16
|
+
const SLO_TOKENS_ENDPOINT_LOGOUT_RES_BASETYPE = ['authConfigLogoutOutput'];
|
|
17
|
+
|
|
11
18
|
export const BASE_SCOPES = {
|
|
12
19
|
github: ['read:org'],
|
|
13
20
|
googleoauth: ['openid profile email'],
|
|
@@ -85,8 +92,6 @@ export const mutations = {
|
|
|
85
92
|
loggedInAs(state, principalId) {
|
|
86
93
|
state.loggedIn = true;
|
|
87
94
|
state.principalId = principalId;
|
|
88
|
-
|
|
89
|
-
this.$cookies.remove(KEY);
|
|
90
95
|
},
|
|
91
96
|
|
|
92
97
|
loggedOut(state) {
|
|
@@ -136,10 +141,18 @@ export const actions = {
|
|
|
136
141
|
commit('initialPass', pass);
|
|
137
142
|
},
|
|
138
143
|
|
|
139
|
-
getAuthProviders({ dispatch }) {
|
|
144
|
+
getAuthProviders({ dispatch }, opt) {
|
|
145
|
+
let force = false;
|
|
146
|
+
|
|
147
|
+
if (opt?.force) {
|
|
148
|
+
force = true;
|
|
149
|
+
}
|
|
150
|
+
|
|
140
151
|
return dispatch('rancher/findAll', {
|
|
141
152
|
type: 'authProvider',
|
|
142
|
-
opt: {
|
|
153
|
+
opt: {
|
|
154
|
+
url: `/v3-public/authProviders`, watch: false, force
|
|
155
|
+
}
|
|
143
156
|
}, { root: true });
|
|
144
157
|
},
|
|
145
158
|
|
|
@@ -183,13 +196,17 @@ export const actions = {
|
|
|
183
196
|
* Save nonce details. Information it contains will be used to validate auth requests/responses
|
|
184
197
|
* Note - this may be structurally different than the nonce we encode and send
|
|
185
198
|
*/
|
|
186
|
-
saveNonce(
|
|
199
|
+
saveNonce({ commit }, opt) {
|
|
187
200
|
const strung = JSON.stringify(opt);
|
|
188
201
|
|
|
189
|
-
|
|
202
|
+
const options = {
|
|
190
203
|
path: '/',
|
|
191
204
|
sameSite: true,
|
|
192
205
|
secure: true,
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
commit('cookies/set', {
|
|
209
|
+
key: KEY, value: strung, options
|
|
193
210
|
});
|
|
194
211
|
|
|
195
212
|
return strung;
|
|
@@ -265,8 +282,8 @@ export const actions = {
|
|
|
265
282
|
}
|
|
266
283
|
},
|
|
267
284
|
|
|
268
|
-
verifyOAuth({ dispatch }, { nonce, code, provider }) {
|
|
269
|
-
const expectJSON =
|
|
285
|
+
verifyOAuth({ dispatch, rootGetters }, { nonce, code, provider }) {
|
|
286
|
+
const expectJSON = rootGetters['cookies/get']({ key: KEY, options: { parseJSON: false } });
|
|
270
287
|
let parsed;
|
|
271
288
|
|
|
272
289
|
try {
|
|
@@ -351,6 +368,12 @@ export const actions = {
|
|
|
351
368
|
}
|
|
352
369
|
},
|
|
353
370
|
|
|
371
|
+
loggedInAs({ commit }, principalId) {
|
|
372
|
+
commit('loggedInAs', principalId);
|
|
373
|
+
|
|
374
|
+
commit('cookies/remove', { key: KEY });
|
|
375
|
+
},
|
|
376
|
+
|
|
354
377
|
uiLogout({ commit, dispatch }) {
|
|
355
378
|
removeEmberPage();
|
|
356
379
|
|
|
@@ -398,8 +421,8 @@ export const actions = {
|
|
|
398
421
|
redirectUnauthorized: false,
|
|
399
422
|
}, { root: true });
|
|
400
423
|
|
|
401
|
-
// Single-sign logout for
|
|
402
|
-
if (res.baseType
|
|
424
|
+
// Single-sign logout redirect for SLO compatible auth providers
|
|
425
|
+
if (SLO_TOKENS_ENDPOINT_LOGOUT_RES_BASETYPE.includes(res.baseType) && res.idpRedirectUrl) {
|
|
403
426
|
window.location.href = res.idpRedirectUrl;
|
|
404
427
|
|
|
405
428
|
return;
|
package/store/cookies.ts
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { MutationTree, GetterTree, ActionTree } from 'vuex';
|
|
2
|
+
import Cookie, { ICookie, ICookieGetOpts } from 'cookie-universal';
|
|
3
|
+
|
|
4
|
+
type State = { cookies: ICookie };
|
|
5
|
+
const options = { parseJSON: true };
|
|
6
|
+
const state = (): State => ({ cookies: Cookie(undefined, undefined, options.parseJSON) });
|
|
7
|
+
|
|
8
|
+
const getters: GetterTree<State, any> = {
|
|
9
|
+
get(state) {
|
|
10
|
+
return ({ key, options }: {key: string, options?: ICookieGetOpts}) => state.cookies.get(key, options);
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
const mutations: MutationTree<State> = {
|
|
14
|
+
set(state, { key, value, options }) {
|
|
15
|
+
return state.cookies.set(key, value, options);
|
|
16
|
+
},
|
|
17
|
+
|
|
18
|
+
remove(state, { key }) {
|
|
19
|
+
return state.cookies.remove(key);
|
|
20
|
+
},
|
|
21
|
+
};
|
|
22
|
+
const actions: ActionTree<State, any> = {};
|
|
23
|
+
|
|
24
|
+
export default {
|
|
25
|
+
namespaced: true,
|
|
26
|
+
state,
|
|
27
|
+
getters,
|
|
28
|
+
mutations,
|
|
29
|
+
actions
|
|
30
|
+
};
|
package/store/prefs.js
CHANGED
|
@@ -289,12 +289,16 @@ export const actions = {
|
|
|
289
289
|
commit('load', { key, value });
|
|
290
290
|
|
|
291
291
|
if ( definition.asCookie ) {
|
|
292
|
-
const
|
|
292
|
+
const options = {
|
|
293
293
|
...cookieOptions,
|
|
294
294
|
parseJSON: definition.parseJSON === true
|
|
295
295
|
};
|
|
296
296
|
|
|
297
|
-
|
|
297
|
+
const computedKey = `${ cookiePrefix }${ key }`.toUpperCase();
|
|
298
|
+
|
|
299
|
+
commit('cookies/set', {
|
|
300
|
+
key: computedKey, value, options
|
|
301
|
+
}, { root: true });
|
|
298
302
|
}
|
|
299
303
|
|
|
300
304
|
if ( definition.asUserPreference ) {
|
|
@@ -336,7 +340,7 @@ export const actions = {
|
|
|
336
340
|
await dispatch('set', { key: THEME, value: val });
|
|
337
341
|
},
|
|
338
342
|
|
|
339
|
-
loadCookies({ state, commit }) {
|
|
343
|
+
loadCookies({ state, commit, rootGetters }) {
|
|
340
344
|
if ( state.cookiesLoaded ) {
|
|
341
345
|
return;
|
|
342
346
|
}
|
|
@@ -348,8 +352,9 @@ export const actions = {
|
|
|
348
352
|
continue;
|
|
349
353
|
}
|
|
350
354
|
|
|
351
|
-
const
|
|
352
|
-
const
|
|
355
|
+
const options = { parseJSON: definition.parseJSON === true };
|
|
356
|
+
const computedKey = `${ cookiePrefix }${ key }`.toUpperCase();
|
|
357
|
+
const value = rootGetters['cookies/get']({ key: computedKey, options });
|
|
353
358
|
|
|
354
359
|
if (value !== undefined) {
|
|
355
360
|
commit('load', { key, value });
|
package/types/shell/index.d.ts
CHANGED
|
@@ -3601,9 +3601,10 @@ export namespace actions {
|
|
|
3601
3601
|
function setTheme({ dispatch }: {
|
|
3602
3602
|
dispatch: any;
|
|
3603
3603
|
}, val: any): Promise<void>;
|
|
3604
|
-
function loadCookies({ state, commit }: {
|
|
3604
|
+
function loadCookies({ state, commit, rootGetters }: {
|
|
3605
3605
|
state: any;
|
|
3606
3606
|
commit: any;
|
|
3607
|
+
rootGetters: any;
|
|
3607
3608
|
}): void;
|
|
3608
3609
|
function loadTheme({ dispatch }: {
|
|
3609
3610
|
dispatch: any;
|
|
@@ -3850,16 +3851,6 @@ declare function _default(context: any, inject: any): void;
|
|
|
3850
3851
|
export default _default;
|
|
3851
3852
|
}
|
|
3852
3853
|
|
|
3853
|
-
// @shell/utils/cookie-universal
|
|
3854
|
-
|
|
3855
|
-
declare module '@shell/utils/cookie-universal' {
|
|
3856
|
-
declare function _default({ req, res }: {
|
|
3857
|
-
req: any;
|
|
3858
|
-
res: any;
|
|
3859
|
-
}, inject: any): void;
|
|
3860
|
-
export default _default;
|
|
3861
|
-
}
|
|
3862
|
-
|
|
3863
3854
|
// @shell/utils/create-yaml
|
|
3864
3855
|
|
|
3865
3856
|
declare module '@shell/utils/create-yaml' {
|
package/utils/auth.js
CHANGED
|
@@ -263,7 +263,7 @@ export async function tryInitialSetup(store, password = 'admin') {
|
|
|
263
263
|
*/
|
|
264
264
|
export async function isLoggedIn(store, userData) {
|
|
265
265
|
store.commit('auth/hasAuth', true);
|
|
266
|
-
store.
|
|
266
|
+
store.dispatch('auth/loggedInAs', userData.id);
|
|
267
267
|
|
|
268
268
|
// Init the notification center now that we know who the user is
|
|
269
269
|
await store.dispatch('notifications/init', userData);
|