Skip to content

Commit d05ada5

Browse files
committed
added some more proxies to be able to render pages
Signed-off-by: Erik Jan de Wit <erikjan.dewit@gmail.com>
1 parent 7a28ec8 commit d05ada5

6 files changed

Lines changed: 164 additions & 18 deletions

File tree

kc-ext/theme-preview/src/main/java/org/keycloak/ext/theme/DummyAuthenticationSessionModel.java

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,13 @@
1313
public class DummyAuthenticationSessionModel implements AuthenticationSessionModel {
1414

1515
private Map<String, String> authNotes = new HashMap<>();
16+
private final RealmModel realm;
17+
private final ClientModel client;
18+
19+
public DummyAuthenticationSessionModel(RealmModel realm) {
20+
this.realm = realm;
21+
this.client = DummyClientModel.create(realm);
22+
}
1623

1724
@Override
1825
public String getTabId() {
@@ -156,12 +163,12 @@ public void setRedirectUri(String s) {
156163

157164
@Override
158165
public RealmModel getRealm() {
159-
return null;
166+
return realm;
160167
}
161168

162169
@Override
163170
public ClientModel getClient() {
164-
return null;
171+
return client;
165172
}
166173

167174
@Override
@@ -176,7 +183,7 @@ public void setAction(String s) {
176183

177184
@Override
178185
public String getProtocol() {
179-
return null;
186+
return "openid-connect";
180187
}
181188

182189
@Override
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
package org.keycloak.ext.theme;
2+
3+
import org.keycloak.models.ClientModel;
4+
import org.keycloak.models.RealmModel;
5+
6+
import java.lang.reflect.InvocationHandler;
7+
import java.lang.reflect.Method;
8+
import java.lang.reflect.Proxy;
9+
import java.util.Collections;
10+
import java.util.Map;
11+
import java.util.Set;
12+
import java.util.stream.Stream;
13+
14+
public class DummyClientModel {
15+
16+
public static ClientModel create(RealmModel realm) {
17+
return (ClientModel) Proxy.newProxyInstance(
18+
DummyClientModel.class.getClassLoader(),
19+
new Class[]{ClientModel.class},
20+
new ClientInvocationHandler(realm)
21+
);
22+
}
23+
24+
private static class ClientInvocationHandler implements InvocationHandler {
25+
26+
private final RealmModel realm;
27+
28+
public ClientInvocationHandler(RealmModel realm) {
29+
this.realm = realm;
30+
}
31+
32+
@Override
33+
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
34+
String methodName = method.getName();
35+
Class<?> returnType = method.getReturnType();
36+
37+
// Handle specific methods
38+
switch (methodName) {
39+
case "getId":
40+
case "getClientId":
41+
return "dummy-client";
42+
case "getName":
43+
return "Dummy Client";
44+
case "getDescription":
45+
return "Dummy client for theme preview";
46+
case "getRealm":
47+
return realm;
48+
case "getProtocol":
49+
return "openid-connect";
50+
case "isEnabled":
51+
case "isPublicClient":
52+
case "isStandardFlowEnabled":
53+
return true;
54+
}
55+
56+
// Handle by return type
57+
if (returnType == boolean.class || returnType == Boolean.class) {
58+
return false;
59+
} else if (returnType == int.class || returnType == Integer.class) {
60+
return 0;
61+
} else if (returnType == String.class) {
62+
return null;
63+
} else if (returnType == Set.class) {
64+
return Collections.emptySet();
65+
} else if (returnType == Map.class) {
66+
return Collections.emptyMap();
67+
} else if (returnType == Stream.class) {
68+
return Stream.empty();
69+
} else if (returnType == void.class) {
70+
return null;
71+
}
72+
73+
return null;
74+
}
75+
}
76+
}

kc-ext/theme-preview/src/main/java/org/keycloak/ext/theme/DummySubjectCredentialManager.java

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import org.keycloak.credential.CredentialInput;
44
import org.keycloak.credential.CredentialModel;
55
import org.keycloak.models.SubjectCredentialManager;
6+
import org.keycloak.models.credential.OTPCredentialModel;
67
import org.keycloak.models.credential.RecoveryAuthnCodesCredentialModel;
78
import org.keycloak.models.utils.RecoveryAuthnCodesUtils;
89

@@ -46,14 +47,21 @@ public Stream<CredentialModel> getStoredCredentialsStream() {
4647
}
4748

4849
@Override
49-
public Stream<CredentialModel> getStoredCredentialsByTypeStream(String s) {
50-
List<String> generatedRecoveryAuthnCodes = RecoveryAuthnCodesUtils.generateRawCodes();
51-
CredentialModel recoveryAuthnCodesCred = RecoveryAuthnCodesCredentialModel.createFromValues(
52-
generatedRecoveryAuthnCodes,
53-
System.currentTimeMillis(),
54-
null);
55-
56-
return Stream.of(recoveryAuthnCodesCred);
50+
public Stream<CredentialModel> getStoredCredentialsByTypeStream(String type) {
51+
if (RecoveryAuthnCodesCredentialModel.TYPE.equals(type)) {
52+
List<String> generatedRecoveryAuthnCodes = RecoveryAuthnCodesUtils.generateRawCodes();
53+
CredentialModel recoveryAuthnCodesCred = RecoveryAuthnCodesCredentialModel.createFromValues(
54+
generatedRecoveryAuthnCodes,
55+
System.currentTimeMillis(),
56+
null);
57+
return Stream.of(recoveryAuthnCodesCred);
58+
} else if (OTPCredentialModel.TYPE.equals(type)) {
59+
OTPCredentialModel otpCred = OTPCredentialModel.createTOTP("dummy-secret", 6, 30, "HmacSHA1");
60+
otpCred.setId("dummy-otp-id");
61+
otpCred.setUserLabel("Authenticator App");
62+
return Stream.of(otpCred);
63+
}
64+
return Stream.empty();
5765
}
5866

5967
@Override

kc-ext/theme-preview/src/main/java/org/keycloak/ext/theme/DummyUserModel.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ public void removeRequiredAction(String s) {
9494

9595
@Override
9696
public String getFirstName() {
97-
return null;
97+
return "John";
9898
}
9999

100100
@Override
@@ -104,7 +104,7 @@ public void setFirstName(String s) {
104104

105105
@Override
106106
public String getLastName() {
107-
return null;
107+
return "Doe";
108108
}
109109

110110
@Override
@@ -114,7 +114,7 @@ public void setLastName(String s) {
114114

115115
@Override
116116
public String getEmail() {
117-
return null;
117+
return "john.doe@example.com";
118118
}
119119

120120
@Override
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package org.keycloak.ext.theme;
2+
3+
import org.keycloak.common.util.Time;
4+
import org.keycloak.models.utils.RecoveryAuthnCodesUtils;
5+
6+
import java.util.List;
7+
8+
/**
9+
* Custom bean for recovery codes that provides the property names
10+
* expected by the login-spa theme template.
11+
*/
12+
public class RecoveryCodesBean {
13+
14+
private final List<String> generatedRecoveryAuthnCodes;
15+
private final long generatedAt;
16+
17+
public RecoveryCodesBean() {
18+
this.generatedRecoveryAuthnCodes = RecoveryAuthnCodesUtils.generateRawCodes();
19+
this.generatedAt = Time.currentTimeMillis();
20+
}
21+
22+
// Property name expected by template: generatedRecoveryAuthnCodes
23+
public List<String> getGeneratedRecoveryAuthnCodes() {
24+
return this.generatedRecoveryAuthnCodes;
25+
}
26+
27+
// Also provide the List suffix version for compatibility
28+
public List<String> getGeneratedRecoveryAuthnCodesList() {
29+
return this.generatedRecoveryAuthnCodes;
30+
}
31+
32+
public String getGeneratedRecoveryAuthnCodesAsString() {
33+
return String.join(",", this.generatedRecoveryAuthnCodes);
34+
}
35+
36+
public long getGeneratedAt() {
37+
return generatedAt;
38+
}
39+
}

kc-ext/theme-preview/src/main/java/org/keycloak/ext/theme/ThemePreviewProvider.java

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,10 @@
22

33
import jakarta.ws.rs.*;
44
import jakarta.ws.rs.core.MediaType;
5+
import jakarta.ws.rs.core.MultivaluedHashMap;
56
import jakarta.ws.rs.core.Response;
7+
8+
import java.net.URI;
69
import org.keycloak.forms.login.LoginFormsProvider;
710
import org.keycloak.models.KeycloakSession;
811
import org.keycloak.models.RealmModel;
@@ -21,10 +24,17 @@ public class ThemePreviewProvider implements RealmResourceProvider {
2124
new Page("login-username", "Login - username", LoginFormsProvider::createLoginUsername),
2225
new Page("login-otp", "Login - OTP", LoginFormsProvider::createLoginTotp),
2326
new Page("login-recovery-codes", "Login - recovery codes", LoginFormsProvider::createLoginRecoveryAuthnCode),
24-
new Page("login-config-recover-codes", "Action - recovery codes", l -> l.createResponse(UserModel.RequiredAction.CONFIGURE_RECOVERY_AUTHN_CODES)),
27+
new Page("login-config-recover-codes", "Action - recovery codes", l -> {
28+
l.setAttribute("recoveryAuthnCodesConfigBean", new RecoveryCodesBean());
29+
return l.createResponse(UserModel.RequiredAction.CONFIGURE_RECOVERY_AUTHN_CODES);
30+
}),
2531
new Page("login-config-totp", "Action - OTP", l -> l.createResponse(UserModel.RequiredAction.CONFIGURE_TOTP)),
2632
new Page("login-update-password", "Action - Update password", l -> l.createResponse(UserModel.RequiredAction.UPDATE_PASSWORD)),
27-
new Page("login-register", "Action - Register", LoginFormsProvider::createRegistration)
33+
new Page("login-register", "Action - Register", l -> {
34+
l.setFormData(new MultivaluedHashMap<>());
35+
l.setAttribute("email", "john.doe@example.com");
36+
return l.createRegistration();
37+
})
2838
);
2939

3040
private KeycloakSession session;
@@ -67,8 +77,14 @@ public Response view(@PathParam("template") String page) {
6777

6878
LoginFormsProvider loginForms = session.getProvider(LoginFormsProvider.class);
6979

70-
loginForms.setActionUri(session.getContext().getUri().getRequestUri());
71-
loginForms.setAuthenticationSession(new DummyAuthenticationSessionModel());
80+
URI actionUri = session.getContext().getUri().getBaseUriBuilder()
81+
.path("realms")
82+
.path(session.getContext().getRealm().getName())
83+
.path("theme-preview")
84+
.path(page)
85+
.build();
86+
loginForms.setActionUri(actionUri);
87+
loginForms.setAuthenticationSession(new DummyAuthenticationSessionModel(proxy));
7288
loginForms.setUser(new DummyUserModel());
7389

7490
Optional<Page> pageHandler = pages.stream().filter(p -> p.template.equals(page)).findFirst();

0 commit comments

Comments
 (0)