Skip to content

Commit c427e65

Browse files
committed
Secondary factor bypass in step-up authentication
closes #34 Signed-off-by: mposolda <[email protected]> (cherry picked from commit e632c03ec4dbfbb7c74c65b0627027390b2e605d)
1 parent 897c44b commit c427e65

File tree

44 files changed

+1545
-140
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+1545
-140
lines changed

js/apps/account-ui/src/account-security/SigningIn.tsx

Lines changed: 17 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,10 @@ import {
1919
} from "@patternfly/react-core/deprecated";
2020
import { CSSProperties, useState } from "react";
2121
import { Trans, useTranslation } from "react-i18next";
22-
import { ContinueCancelModal, useAlerts } from "ui-shared";
23-
import { deleteCredentials, getCredentials } from "../api/methods";
22+
import { getCredentials } from "../api/methods";
2423
import {
2524
CredentialContainer,
2625
CredentialMetadataRepresentation,
27-
CredentialRepresentation,
2826
} from "../api/representations";
2927
import { EmptyRow } from "../components/datalist/EmptyRow";
3028
import { Page } from "../components/page/Page";
@@ -70,16 +68,15 @@ const MobileLink = ({ title, onClick, testid }: MobileLinkProps) => {
7068
export const SigningIn = () => {
7169
const { t } = useTranslation();
7270
const context = useEnvironment();
73-
const { addAlert, addError } = useAlerts();
7471
const { login } = context.keycloak;
7572

7673
const [credentials, setCredentials] = useState<CredentialContainer[]>();
77-
const [key, setKey] = useState(1);
78-
const refresh = () => setKey(key + 1);
7974

80-
usePromise((signal) => getCredentials({ signal, context }), setCredentials, [
81-
key,
82-
]);
75+
usePromise(
76+
(signal) => getCredentials({ signal, context }),
77+
setCredentials,
78+
[],
79+
);
8380

8481
const credentialRowCells = (
8582
credMetadata: CredentialMetadataRepresentation,
@@ -115,9 +112,6 @@ export const SigningIn = () => {
115112
return items;
116113
};
117114

118-
const label = (credential: CredentialRepresentation) =>
119-
credential.userLabel || t(credential.type as TFuncKey);
120-
121115
if (!credentials) {
122116
return <Spinner />;
123117
}
@@ -205,41 +199,19 @@ export const SigningIn = () => {
205199
aria-labelledby={`cred-${meta.credential.id}`}
206200
>
207201
{container.removeable ? (
208-
<ContinueCancelModal
209-
buttonTitle={t("delete")}
210-
buttonTestRole="remove"
211-
modalTitle={t("removeCred", {
212-
name: label(meta.credential),
213-
})}
214-
continueLabel={t("confirm")}
215-
cancelLabel={t("cancel")}
216-
buttonVariant="danger"
217-
onContinue={async () => {
218-
try {
219-
await deleteCredentials(
220-
context,
221-
meta.credential,
222-
);
223-
addAlert(
224-
t("successRemovedMessage", {
225-
userLabel: label(meta.credential),
226-
}),
227-
);
228-
refresh();
229-
} catch (error) {
230-
addError(
231-
t("errorRemovedMessage", {
232-
userLabel: label(meta.credential),
233-
error,
234-
}).toString(),
235-
);
236-
}
202+
<Button
203+
variant="danger"
204+
data-testrole="remove"
205+
onClick={() => {
206+
login({
207+
action:
208+
"delete_credential:" +
209+
meta.credential.id,
210+
});
237211
}}
238212
>
239-
{t("stopUsingCred", {
240-
name: label(meta.credential),
241-
})}
242-
</ContinueCancelModal>
213+
{t("delete")}
214+
</Button>
243215
) : (
244216
<Button
245217
variant="secondary"

js/apps/account-ui/src/api/methods.ts

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import { parseResponse } from "./parse-response";
44
import {
55
ClientRepresentation,
66
CredentialContainer,
7-
CredentialRepresentation,
87
DeviceRepresentation,
98
Group,
109
LinkedAccountRepresentation,
@@ -99,15 +98,6 @@ export async function getCredentials({ signal, context }: CallOptions) {
9998
return parseResponse<CredentialContainer[]>(response);
10099
}
101100

102-
export async function deleteCredentials(
103-
context: KeycloakContext,
104-
credential: CredentialRepresentation,
105-
) {
106-
return request("/credentials/" + credential.id, context, {
107-
method: "DELETE",
108-
});
109-
}
110-
111101
export async function getLinkedAccounts({ signal, context }: CallOptions) {
112102
const response = await request("/linked-accounts", context, { signal });
113103
return parseResponse<LinkedAccountRepresentation[]>(response);

js/apps/account-ui/src/index.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,6 @@ export { SharedWith } from "./resources/SharedWith";
4242
export { ShareTheResource } from "./resources/ShareTheResource";
4343
export {
4444
deleteConsent,
45-
deleteCredentials,
4645
deleteSession,
4746
getApplications,
4847
getCredentials,
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
/*
2+
* Copyright 2024 Red Hat, Inc. and/or its affiliates
3+
* and other contributors as indicated by the @author tags.
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
*
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*
18+
*/
19+
20+
package org.keycloak.migration.migrators;
21+
22+
import org.jboss.logging.Logger;
23+
import org.keycloak.migration.ModelVersion;
24+
import org.keycloak.models.KeycloakSession;
25+
import org.keycloak.models.RealmModel;
26+
import org.keycloak.models.utils.DefaultRequiredActions;
27+
import org.keycloak.representations.idm.RealmRepresentation;
28+
29+
/**
30+
* @author <a href="mailto:[email protected]">Marek Posolda</a>
31+
*/
32+
public class MigrateTo24_0_3 implements Migration {
33+
34+
private static final Logger LOG = Logger.getLogger(MigrateTo24_0_3.class);
35+
36+
public static final ModelVersion VERSION = new ModelVersion("24.0.3");
37+
38+
@Override
39+
public void migrate(KeycloakSession session) {
40+
session.realms().getRealmsStream().forEach(realm -> migrateRealm(session, realm));
41+
}
42+
43+
@Override
44+
public void migrateImport(KeycloakSession session, RealmModel realm, RealmRepresentation rep, boolean skipUserDependent) {
45+
migrateRealm(session, realm);
46+
}
47+
48+
@Override
49+
public ModelVersion getVersion() {
50+
return VERSION;
51+
}
52+
53+
private void migrateRealm(KeycloakSession session, RealmModel realm) {
54+
DefaultRequiredActions.addDeleteCredentialAction(realm);
55+
}
56+
}

model/storage-private/src/main/java/org/keycloak/storage/datastore/DefaultMigrationManager.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
import org.keycloak.migration.migrators.MigrateTo22_0_0;
3939
import org.keycloak.migration.migrators.MigrateTo23_0_0;
4040
import org.keycloak.migration.migrators.MigrateTo24_0_0;
41+
import org.keycloak.migration.migrators.MigrateTo24_0_3;
4142
import org.keycloak.migration.migrators.MigrateTo25_0_0;
4243
import org.keycloak.migration.migrators.MigrateTo2_0_0;
4344
import org.keycloak.migration.migrators.MigrateTo2_1_0;
@@ -115,6 +116,7 @@ public class DefaultMigrationManager implements MigrationManager {
115116
new MigrateTo22_0_0(),
116117
new MigrateTo23_0_0(),
117118
new MigrateTo24_0_0(),
119+
new MigrateTo24_0_3(),
118120
new MigrateTo25_0_0()
119121
};
120122

server-spi-private/src/main/java/org/keycloak/authentication/AbstractAuthenticationFlowContext.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import org.keycloak.common.ClientConnection;
2222
import org.keycloak.events.EventBuilder;
2323
import org.keycloak.models.AuthenticationExecutionModel;
24+
import org.keycloak.models.AuthenticationFlowModel;
2425
import org.keycloak.models.AuthenticatorConfigModel;
2526
import org.keycloak.models.KeycloakSession;
2627
import org.keycloak.models.RealmModel;
@@ -57,6 +58,11 @@ public interface AbstractAuthenticationFlowContext {
5758
*/
5859
AuthenticationExecutionModel getExecution();
5960

61+
/**
62+
* @return the top level flow (root flow) of this authentication
63+
*/
64+
AuthenticationFlowModel getTopLevelFlow();
65+
6066
/**
6167
* Current realm
6268
*

server-spi-private/src/main/java/org/keycloak/authentication/AuthenticationFlowCallback.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818

1919
package org.keycloak.authentication;
2020

21+
import org.keycloak.models.AuthenticationFlowModel;
22+
2123
/**
2224
* Callback to be triggered during various lifecycle events of authentication flow.
2325
*
@@ -40,7 +42,9 @@ public interface AuthenticationFlowCallback extends Authenticator {
4042
/**
4143
* Triggered after the top authentication flow is successfully finished.
4244
* It is really suitable for last verification of successful authentication
45+
*
46+
* @param topFlow which was successfully finished
4347
*/
44-
default void onTopFlowSuccess() {
48+
default void onTopFlowSuccess(AuthenticationFlowModel topFlow) {
4549
}
4650
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/*
2+
* Copyright 2024 Red Hat, Inc. and/or its affiliates
3+
* and other contributors as indicated by the @author tags.
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
*
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*
18+
*/
19+
20+
package org.keycloak.authentication;
21+
22+
import org.keycloak.models.KeycloakSession;
23+
import org.keycloak.sessions.AuthenticationSessionModel;
24+
25+
/**
26+
* Marking any required action implementation, that is supposed to work with user credentials
27+
*
28+
* @author <a href="mailto:[email protected]">Marek Posolda</a>
29+
*/
30+
public interface CredentialAction {
31+
32+
/**
33+
* @return credential type, which this action is able to register. This should refer to the same value as returned by {@link org.keycloak.credential.CredentialProvider#getType} of the
34+
* corresponding credential provider and {@link AuthenticatorFactory#getReferenceCategory()} of the corresponding authenticator
35+
*/
36+
String getCredentialType(KeycloakSession session, AuthenticationSessionModel authenticationSession);
37+
}
Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
package org.keycloak.authentication;
22

3-
public interface CredentialRegistrator {
3+
/**
4+
* Marking implementation of the action, which is able to register credential of the particular type
5+
*/
6+
public interface CredentialRegistrator extends CredentialAction {
47
}

server-spi-private/src/main/java/org/keycloak/authentication/RequiredActionProvider.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,14 +38,14 @@ public interface RequiredActionProvider extends Provider {
3838
default InitiatedActionSupport initiatedActionSupport() {
3939
return InitiatedActionSupport.NOT_SUPPORTED;
4040
}
41-
41+
4242
/**
4343
* Callback to let the action know that an application-initiated action
4444
* was canceled.
45-
*
45+
*
4646
* @param session The Keycloak session.
4747
* @param authSession The authentication session.
48-
*
48+
*
4949
*/
5050
default void initiatedActionCanceled(KeycloakSession session, AuthenticationSessionModel authSession) {
5151
return;

0 commit comments

Comments
 (0)