Fix access for IDP links tab. (#29428)

* Fix access for IDP links tab.

Signed-off-by: Stan Silvert <ssilvert@redhat.com>

* Fix tests.

Signed-off-by: Stan Silvert <ssilvert@redhat.com>

---------

Signed-off-by: Stan Silvert <ssilvert@redhat.com>
This commit is contained in:
Stan Silvert 2024-05-14 08:49:47 -04:00 committed by GitHub
parent b4d231fd40
commit f14f4805d6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 116 additions and 93 deletions

View file

@ -60,7 +60,7 @@ export default class IdentityProviderLinksTab {
public assertNoIdentityProvidersLinkedMessageExist(exist: boolean) {
cy.get(this.#linkedProvidersSection).should(
(exist ? "" : "not.") + "contain.text",
"No identity providers linked. Choose one from the list below.",
"No identity providers linked.",
);
return this;

View file

@ -2105,7 +2105,7 @@ duplicateEmailsHelpText=Allow multiple users to have the same email address. Cha
importOverwritten_zero=No records overwritten.
usermodel.realmRoleMapping.rolePrefix.label=Realm Role prefix
eventTypes.GRANT_CONSENT.name=Grant consent
noProvidersLinked=No identity providers linked. Choose one from the list below.
noProvidersLinked=No identity providers linked.
testConnectionSuccess=Success\! SMTP connection successful. E-mail was sent\!
samlSettings=SAML settings
userFedDisableConfirm=If you disable this user federation provider, it will not be considered for queries and imported users will be disabled and read-only until the provider is enabled again.
@ -2852,7 +2852,7 @@ syncModeOverrideHelp=Overrides the default sync mode of the IDP for this mapper.
eventTypes.TOKEN_EXCHANGE_ERROR.description=Token exchange error
strictTransportSecurityHelp=The Strict-Transport-Security HTTP header tells browsers to always use HTTPS. Once a browser sees this header, it will only visit the site over HTTPS for the time specified (1 year) at max-age, including the subdomains. <1>Learn more</1>
authenticationExplain=Authentication is the area where you can configure and manage different credential types.
passwordPoliciesHelp.hashIterations=The number of times a password is hashed before storage or verification. Default\: -1 in case argon2 is used as the hashing algorithm; 210,000 in case pbkdf2-sha512 is used as the hashing algorithm; 600,000 if the pbkdf2-sha256 algorithm is used as the hashing algorithm; 1,300,000 if the pbkdf2 algorithm is used as the hashing algorithm.
passwordPoliciesHelp.hashIterations=The number of times a password is hashed before storage or verification. Default\: -1 in case argon2 is used as the hashing algorithm; 210,000 in case pbkdf2-sha512 is used as the hashing algorithm; 600,000 if the pbkdf2-sha256 algorithm is used as the hashing algorithm; 1,300,000 if the pbkdf2 algorithm is used as the hashing algorithm.
dropNonexistingGroupsDuringSync=Drop non-existing groups during sync
clientAssertionSigningAlgHelp=Signature algorithm to create JWT assertion as client authentication. In the case of JWT signed with private key or JWT signed with client secret, it is required. If no algorithm is specified, the following algorithm is adapted. RS256 is adapted in the case of JWT signed with private key. HS256 is adapted in the case of JWT signed with client secret.
jwtX509HeadersEnabledHelp=If enabled, the x5t (X.509 Certificate SHA-1 Thumbprint) header will be added to the JWT to reference the certificate used to sign it. Otherwise, the kid (Key ID) header will be used instead.

View file

@ -368,17 +368,15 @@ export default function EditUser() {
>
<UserConsents />
</Tab>
{hasAccess("view-identity-providers") && (
<Tab
data-testid="identity-provider-links-tab"
title={
<TabTitleText>{t("identityProviderLinks")}</TabTitleText>
}
{...identityProviderLinksTab}
>
<UserIdentityProviderLinks userId={user.id!} />
</Tab>
)}
<Tab
data-testid="identity-provider-links-tab"
title={
<TabTitleText>{t("identityProviderLinks")}</TabTitleText>
}
{...identityProviderLinksTab}
>
<UserIdentityProviderLinks userId={user.id!} />
</Tab>
<Tab
data-testid="user-sessions-tab"
title={<TabTitleText>{t("sessions")}</TabTitleText>}

View file

@ -24,6 +24,7 @@ import { useServerInfo } from "../context/server-info/ServerInfoProvider";
import { toIdentityProvider } from "../identity-providers/routes/IdentityProvider";
import { emptyFormatter, upperCaseFormatter } from "../util";
import { UserIdpModal } from "./UserIdPModal";
import { useAccess } from "../context/access/Access";
type UserIdentityProviderLinksProps = {
userId: string;
@ -41,6 +42,12 @@ export const UserIdentityProviderLinks = ({
const { realm } = useRealm();
const { addAlert, addError } = useAlerts();
const { t } = useTranslation();
const { hasAccess, hasSomeAccess } = useAccess();
const canQueryIDPDetails = hasSomeAccess(
"manage-identity-providers",
"view-identity-providers",
);
const refresh = () => setKey(new Date().getTime());
@ -51,15 +58,17 @@ export const UserIdentityProviderLinks = ({
const identityProviders = useServerInfo().identityProviders;
const getFederatedIdentities = async () => {
const allProviders = await adminClient.identityProviders.find();
const allFedIds = (await adminClient.users.listFederatedIdentities({
id: userId,
})) as WithProviderId[];
for (const element of allFedIds) {
element.providerId = allProviders.find(
(item) => item.alias === element.identityProvider,
)?.providerId!;
if (canQueryIDPDetails) {
const allProviders = await adminClient.identityProviders.find();
for (const element of allFedIds) {
element.providerId = allProviders.find(
(item) => item.alias === element.identityProvider,
)?.providerId!;
}
}
return allFedIds;
@ -107,6 +116,9 @@ export const UserIdentityProviderLinks = ({
});
const idpLinkRenderer = (idp: WithProviderId) => {
if (!canQueryIDPDetails)
return <span>{capitalize(idp.identityProvider)}</span>;
return (
<Link
to={toIdentityProvider({
@ -148,6 +160,8 @@ export const UserIdentityProviderLinks = ({
};
const unlinkRenderer = (fedIdentity: FederatedIdentityRepresentation) => {
if (!hasAccess("manage-users")) return <span />;
return (
<Button
variant="link"
@ -175,6 +189,48 @@ export const UserIdentityProviderLinks = ({
);
};
const linkedIdpColumns = () => {
const columns = [
{
name: "identityProvider",
displayKey: "name",
cellFormatters: [emptyFormatter()],
cellRenderer: idpLinkRenderer,
transforms: [cellWidth(20)],
},
{
name: "userId",
displayKey: "userID",
cellFormatters: [emptyFormatter()],
transforms: [cellWidth(30)],
},
{
name: "userName",
displayKey: "username",
cellFormatters: [emptyFormatter()],
transforms: [cellWidth(20)],
},
{
name: "",
cellFormatters: [emptyFormatter()],
cellRenderer: unlinkRenderer,
transforms: [cellWidth(20)],
},
];
if (canQueryIDPDetails)
columns.splice(1, 0, {
name: "type",
displayKey: "type",
cellFormatters: [emptyFormatter()],
cellRenderer: badgeRenderer1,
transforms: [cellWidth(10)],
});
return columns;
};
return (
<>
{isLinkIdPModalOpen && (
@ -199,40 +255,7 @@ export const UserIdentityProviderLinks = ({
isPaginated={false}
ariaLabelKey="LinkedIdPs"
className="kc-linked-IdPs-table"
columns={[
{
name: "identityProvider",
displayKey: "name",
cellFormatters: [emptyFormatter()],
cellRenderer: idpLinkRenderer,
transforms: [cellWidth(20)],
},
{
name: "type",
displayKey: "type",
cellFormatters: [emptyFormatter()],
cellRenderer: badgeRenderer1,
transforms: [cellWidth(10)],
},
{
name: "userId",
displayKey: "userID",
cellFormatters: [emptyFormatter()],
transforms: [cellWidth(30)],
},
{
name: "userName",
displayKey: "username",
cellFormatters: [emptyFormatter()],
transforms: [cellWidth(20)],
},
{
name: "",
cellFormatters: [emptyFormatter()],
cellRenderer: unlinkRenderer,
transforms: [cellWidth(20)],
},
]}
columns={linkedIdpColumns()}
emptyState={
<TextContent className="kc-no-providers-text">
<Text>{t("noProvidersLinked")}</Text>
@ -240,45 +263,47 @@ export const UserIdentityProviderLinks = ({
}
/>
</FormPanel>
<FormPanel className="kc-available-idps" title={t("availableIdPs")}>
<TextContent>
<Text className="kc-available-idps-text">
{t("availableIdPsText")}
</Text>
</TextContent>
<KeycloakDataTable
loader={availableIdPsLoader}
key={key}
isPaginated={false}
ariaLabelKey="LinkedIdPs"
className="kc-linked-IdPs-table"
columns={[
{
name: "alias",
displayKey: "name",
cellFormatters: [emptyFormatter(), upperCaseFormatter()],
transforms: [cellWidth(20)],
},
{
name: "type",
displayKey: "type",
cellFormatters: [emptyFormatter()],
cellRenderer: badgeRenderer2,
transforms: [cellWidth(60)],
},
{
name: "",
cellFormatters: [emptyFormatter()],
cellRenderer: linkRenderer,
},
]}
emptyState={
<TextContent className="kc-no-providers-text">
<Text>{t("noAvailableIdentityProviders")}</Text>
</TextContent>
}
/>
</FormPanel>
{hasAccess("manage-users") && canQueryIDPDetails && (
<FormPanel className="kc-available-idps" title={t("availableIdPs")}>
<TextContent>
<Text className="kc-available-idps-text">
{t("availableIdPsText")}
</Text>
</TextContent>
<KeycloakDataTable
loader={availableIdPsLoader}
key={key}
isPaginated={false}
ariaLabelKey="LinkedIdPs"
className="kc-linked-IdPs-table"
columns={[
{
name: "alias",
displayKey: "name",
cellFormatters: [emptyFormatter(), upperCaseFormatter()],
transforms: [cellWidth(20)],
},
{
name: "type",
displayKey: "type",
cellFormatters: [emptyFormatter()],
cellRenderer: badgeRenderer2,
transforms: [cellWidth(60)],
},
{
name: "",
cellFormatters: [emptyFormatter()],
cellRenderer: linkRenderer,
},
]}
emptyState={
<TextContent className="kc-no-providers-text">
<Text>{t("noAvailableIdentityProviders")}</Text>
</TextContent>
}
/>
</FormPanel>
)}
</PageSection>
</>
);