Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SAML SLO support #11182

Merged
merged 20 commits into from
Aug 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
d5820ac
wip
aalves08 Jun 5, 2024
9744818
add logout action according to logic (SAML - logoutAll) + add modal t…
aalves08 Jun 5, 2024
ee331f7
update authProvidersInfo method to allow for a getter usage in order …
aalves08 Jun 6, 2024
23e55b2
wip - missing final plumbing for the finalRedirectUrl
aalves08 Jun 7, 2024
68cc9cb
bug fix for non-reactive header logout when auth provider is enabled
aalves08 Jun 7, 2024
338ba30
adjust slo UI elements to match expected UI/UX
aalves08 Jun 10, 2024
4147a4c
minor adjustments + pr cleanup
aalves08 Jun 11, 2024
d5e5b45
cleanup
aalves08 Jun 11, 2024
adf26e8
handle promise rejection for standard users (apparently they cannot g…
aalves08 Jun 11, 2024
a4ac971
Updates
richard-cox Jun 17, 2024
fa0f26a
Add error handling (WIP with Andreas, does not currently reach final …
richard-cox Jun 20, 2024
190271e
Fix check for IS_SLO
richard-cox Jun 20, 2024
3e54637
Only allow optional auth provider log out if logged in auth provider
richard-cox Jun 20, 2024
22eef85
Fix error message (SLO failures still result in rancher log out)
richard-cox Jun 20, 2024
6ae39e1
check complete slo flow + add special copy when doing a slo logout
aalves08 Aug 21, 2024
5d89a3f
Update shell/dialog/SloDialog.vue
aalves08 Aug 21, 2024
4c27215
Update shell/edit/auth/saml.vue
aalves08 Aug 21, 2024
c87a8c3
update syntax from phil pr review
aalves08 Aug 21, 2024
fc4e47a
Fix logout modal on diangostics, about, etc pages that use plain temp…
richard-cox Aug 22, 2024
45c61c4
Remove PromptRemove from diagnostic page
richard-cox Aug 22, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions shell/assets/translations/en-us.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -507,6 +507,11 @@ authConfig:
UID: UID Field
adfs: Configure an AD FS account
api: '{vendor} API Host'
sloTitle: Log Out behavior
sloOptions:
onlyRancher: Log out of Rancher and not {name}
logoutAll: Log out of Rancher and {name} (includes all other applications registered with {name})
choose: Allow the user to choose one of the above in an additional log out step
cert:
label: Certificate
placeholder: Paste in the certificate, starting with -----BEGIN CERTIFICATE-----
Expand Down Expand Up @@ -3265,6 +3270,7 @@ login:
welcome: Welcome to {vendor}
loggedOut: You have been logged out.
loggedOutFromSso: You've been logged out of Rancher, however you may still be logged in to your single sign-on identity provider.
loggedOutFromSlo: You've been logged out of Rancher and from your single sign-on identity provider.
loginAgain: Log in again to continue.
serverError:
authFailedCreds: "Logging in failed: Check credentials, or your account may not be authorized to log in."
Expand All @@ -3289,6 +3295,10 @@ login:

logout:
message: Logging Out...
error: 'An error occurred logging out: {msg}'
specificError:
unknown: Unknown.\n\nPlease contact your system administrator.
500: "Server-side error.\n\nPlease contact your system administrator."

managementNode:
customName: Custom Name
Expand Down Expand Up @@ -4648,6 +4658,15 @@ promptScaleMachineDown:
retainedMachine1: At least one Machine must exist for roles Control Plane and Etcd.
retainedMachine2: <b>{ name }</b> will remain

promptSlo:
title: "Log out"
text: "Log out of only Rancher, or all {name} applications."
rancher:
active: Only log out of Rancher
all:
active: Log out of {name}
waiting: Logging Out

promptRemove:
title: Are you sure?
andOthers: |-
Expand Down
61 changes: 57 additions & 4 deletions shell/components/nav/Header.vue
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<script>
import { mapGetters } from 'vuex';
import debounce from 'lodash/debounce';
import { NORMAN, STEVE } from '@shell/config/types';
import { NORMAN, STEVE, MANAGEMENT } from '@shell/config/types';
import { ucFirst } from '@shell/utils/string';
import { isAlternate, isMac } from '@shell/utils/platform';
import Import from '@shell/components/Import';
Expand All @@ -20,6 +20,7 @@ import { ActionLocation, ExtensionPoint } from '@shell/core/types';
import { getApplicableExtensionEnhancements } from '@shell/core/plugin-helpers';
import IconOrSvg from '@shell/components/IconOrSvg';
import { wait } from '@shell/utils/async';
import { authProvidersInfo, parseAuthProvidersInfo } from '@shell/utils/auth';

export default {

Expand All @@ -43,11 +44,17 @@ export default {
}
},

fetch() {
// fetch needed data to check if any auth provider is enabled
authProvidersInfo(this.$store);
},

data() {
const searchShortcut = isMac ? '(\u2318-K)' : '(Ctrl+K)';
const shellShortcut = '(Ctrl+`)';

return {
authInfo: {},
show: false,
showTooltip: false,
kubeConfigCopying: false,
Expand All @@ -66,6 +73,26 @@ export default {
...mapGetters(['clusterReady', 'isExplorer', 'isRancher', 'currentCluster',
'currentProduct', 'rootProduct', 'backToRancherLink', 'backToRancherGlobalLink', 'pageActions', 'isSingleProduct', 'isRancherInHarvester', 'showTopLevelMenu']),

authProviderEnabled() {
const authProviders = this.$store.getters['management/all'](MANAGEMENT.AUTH_CONFIG);
const authInfo = parseAuthProvidersInfo(authProviders);

return authInfo.enabled?.[0] || {};
},

shouldShowSloLogoutModal() {
if (this.isAuthLocalProvider) {
// If the user logged in as a local user... they cannot log out as if they were an auth config user
return false;
}

const {
logoutAllSupported, logoutAllEnabled, logoutAllForced, configType
} = this.authProviderEnabled;

return configType === 'saml' && logoutAllSupported && logoutAllEnabled && !logoutAllForced;
},

appName() {
return getProduct();
},
Expand Down Expand Up @@ -210,6 +237,13 @@ export default {
},

methods: {
showSloModal() {
this.$store.dispatch('management/promptModal', {
component: 'SloDialog',
componentProps: { authProvider: this.authProviderEnabled },
modalWidth: '500px'
});
},
// Sizes the product area of the header such that it shrinks to ensure the whole header bar can be shown
// where possible - we use a minimum width of 32px which is enough to just show the product icon
layoutHeader() {
Expand Down Expand Up @@ -678,6 +712,7 @@ export default {
data-testid="user-menu-dropdown"
@click.stop="showMenu(false)"
>
<!-- username and icon -->
<li
v-if="authEnabled"
class="user-info"
Expand All @@ -691,6 +726,7 @@ export default {
</template>
</div>
</li>
<!-- preferences -->
<router-link
v-if="showPreferencesLink"
v-slot="{ href, navigate }"
Expand All @@ -705,6 +741,7 @@ export default {
<a :href="href">{{ t('nav.userMenu.preferences') }}</a>
</li>
</router-link>
<!-- account & api keys -->
<router-link
v-if="showAccountAndApiKeyLink"
v-slot="{ href, navigate }"
Expand All @@ -719,8 +756,18 @@ export default {
<a :href="href">{{ t('nav.userMenu.accountAndKeys', {}, true) }}</a>
</li>
</router-link>
<!-- SLO modal -->
<li
v-if="authEnabled && shouldShowSloLogoutModal"
class="user-menu-item no-link"
@click="showSloModal"
@keypress.enter="showSloModal"
>
<span>{{ t('nav.userMenu.logOut') }}</span>
</li>
<!-- logout -->
<router-link
v-if="authEnabled"
v-else-if="authEnabled"
v-slot="{ href, navigate }"
custom
:to="generateLogoutRoute"
Expand Down Expand Up @@ -1100,8 +1147,8 @@ export default {
}

.user-menu-item {
a {
cursor: hand;
a, &.no-link > span {
cursor: pointer;
padding: 0px 10px;

&:hover {
Expand All @@ -1118,6 +1165,12 @@ export default {
}
}

&.no-link > span {
display: flex;
justify-content: space-between;
padding: 10px;
}

div.menu-separator {
cursor: default;
padding: 4px 0;
Expand Down
6 changes: 4 additions & 2 deletions shell/components/templates/home.vue
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import AzureWarning from '@shell/components/auth/AzureWarning';
import BrowserTabVisibility from '@shell/mixins/browser-tab-visibility';
import Inactivity from '@shell/components/Inactivity';
import { mapState, mapGetters } from 'vuex';
import PromptModal from '@shell/components/PromptModal';

export default {

Expand All @@ -18,7 +19,8 @@ export default {
GrowlManager,
AzureWarning,
AwsComplianceBanner,
Inactivity
Inactivity,
PromptModal
},

mixins: [Brand, BrowserTabVisibility],
Expand Down Expand Up @@ -55,7 +57,7 @@ export default {
<Inactivity />
<AwsComplianceBanner />
<AzureWarning />

<PromptModal />
<div
class="dashboard-content"
:class="{'dashboard-padding-left': showTopLevelMenu}"
Expand Down
3 changes: 3 additions & 0 deletions shell/components/templates/plain.vue
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import AzureWarning from '@shell/components/auth/AzureWarning';
import BrowserTabVisibility from '@shell/mixins/browser-tab-visibility';
import Inactivity from '@shell/components/Inactivity';
import { mapGetters } from 'vuex';
import PromptModal from '@shell/components/PromptModal';

export default {

Expand All @@ -22,6 +23,7 @@ export default {
Header,
IndentedPanel,
PromptRemove,
PromptModal,
FixedBanner,
GrowlManager,
AwsComplianceBanner,
Expand Down Expand Up @@ -75,6 +77,7 @@ export default {
</IndentedPanel>
<ActionMenu />
<PromptRemove />
<PromptModal />
<AssignTo />
<button
v-if="themeShortcut"
Expand Down
1 change: 1 addition & 0 deletions shell/config/query-params.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export const SETUP = 'setup';
export const STEP = 'step';
export const LOGGED_OUT = 'logged-out';
export const IS_SSO = 'is-sso';
export const IS_SLO = 'is-slo';
export const UPGRADED = 'upgraded';
export const TIMED_OUT = 'timed-out';
export const AUTH_TEST = 'test';
Expand Down
95 changes: 95 additions & 0 deletions shell/dialog/SloDialog.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
<script>
import { Card } from '@components/Card';
import AsyncButton from '@shell/components/AsyncButton';
import { IS_SSO, LOGGED_OUT } from '@shell/config/query-params';

export default {
components: { Card, AsyncButton },

props: {
authProvider: {
type: Object,
required: true
}
},

computed: {
name() {
return this.authProvider?.nameDisplay;
}
},

methods: {
async doLogout(logoutAll = false) {
const options = { force: true, provider: this.authProvider };

if (logoutAll) {
// SLO - Single Log Out
options.slo = true;
options.provider = this.authProvider;

await this.$store.dispatch('auth/logout', options, { root: true });

this.$emit('close');
} else {
// Rancher Log Out (ensure we show the logging out page as per standard log out)
this.$router.replace({ name: 'auth-logout', query: { [LOGGED_OUT]: true, [IS_SSO]: true } });
}
},

cancel() {
this.$emit('close');
}
}
};
</script>

<template>
<Card
class="prompt-remove"
:show-highlight-border="false"
>
<template #title>
<h4 class="text-default-text">
{{ t('promptSlo.title', { name }) }}
</h4>
</template>
<template #body>
<div class="pl-10 pr-10 mt-20 mb-20">
{{ t('promptSlo.text', { name }) }}
</div>
</template>
<template #actions>
<div class="btn-block">
<button
class="btn role-secondary"
@click="cancel()"
>
{{ t('generic.cancel') }}
</button>
<div class="spacer" />
<button
class="btn role-secondary"
@click="doLogout()"
>
{{ t('promptSlo.rancher.active') }}
</button>
<AsyncButton
class="ml-10"
:action-label="t('promptSlo.all.active', { name })"
:waiting-label="t('promptSlo.all.waiting')"
@click="doLogout(true)"
/>
</div>
</template>
</Card>
</template>

<style lang='scss' scoped>
.btn-block {
width: 100%;
display: flex;
justify-content: space-between;
padding: 0;
}
</style>
Loading
Loading