Work in progress

This commit is contained in:
Matthias Zobrist 2024-02-15 21:05:12 +01:00
parent d2f46919b3
commit 36981a234e
2 changed files with 131 additions and 47 deletions

View file

@ -34,10 +34,23 @@ Authorizing Iframes
To show the hCaptcha you need to modify the registration template. You can find the files in your Keycloak installation under `themes/base/login/`. If you use the user profile preview (you start your Keycloak with the `-Dkeycloak.profile=preview` flag), you need to edit the `register-user-profile.ftl`, else the `register.ftl`. Add the following code beneith the reCaptcha code:
```html
<#if hcaptchaRequired??>
<#if mosparoRequired??>
<div class="form-group">
<div class="${properties.kcInputWrapperClass!}">
<div class="h-captcha" data-size="<#if hcaptchaCompact?? && hcaptchaCompact=="true">compact<#else>normal</#if>" data-sitekey="${hcaptchaSiteKey}"></div>
<div id="mosparo-box"></div>
<script type="module">
var m;
window.onload = function(){
m = new mosparo(
'mosparo-box',
'${mosparoHost}',
'${mosparoUuid}',
'${mosparoPublicKey}',
{ loadCssResource: true }
);
};
</script>
</div>
</div>
</#if>

View file

@ -17,6 +17,9 @@
package at.schuerz.keycloak.authenticator;
import jakarta.ws.rs.core.MultivaluedHashMap;
import org.apache.commons.collections4.MultiValuedMap;
import org.apache.commons.collections4.multimap.ArrayListValuedHashMap;
import org.apache.http.NameValuePair;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpPost;
@ -47,6 +50,9 @@ import org.keycloak.util.JsonSerialization;
import java.io.InputStream;
import jakarta.ws.rs.core.MultivaluedMap;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
@ -65,9 +71,13 @@ import org.apache.http.util.EntityUtils;
public class RegistrationMosparo implements FormAction, FormActionFactory {
public static final String MOSPARO_RESPONSE = "mosparo-response";
public static final String MOSPARO_REFERENCE_CATEGORY = "mosparo";
public static final String SITE_PUBKEY = "mosparo-public-key";
public static final String SITE_UUID = "uuid";
public static final String MOSPARO_HOST = "hostname";
public static final String MOSPARO_UUID = "uuid";
public static final String MOSPARO_PUBLIC_KEY = "mosparo-public-key";
public static final String MOSPARO_PRIVATE_KEY = "mosparo-private-key";
public static final String MOSPARO_VERIFY_SSL = "mosparo-verify-ssl";
private static final Logger logger = Logger.getLogger(RegistrationMosparo.class);
public static final String PROVIDER_ID = "registration-mosparo-action";
@ -91,31 +101,32 @@ public class RegistrationMosparo implements FormAction, FormActionFactory {
AuthenticationExecutionModel.Requirement.REQUIRED,
AuthenticationExecutionModel.Requirement.DISABLED
};
@Override
public AuthenticationExecutionModel.Requirement[] getRequirementChoices() {
return REQUIREMENT_CHOICES;
}
@Override
public void buildPage(FormContext context, LoginFormsProvider form) {
AuthenticatorConfigModel captchaConfig = context.getAuthenticatorConfig();
String userLanguageTag = context.getSession().getContext().resolveLocale(context.getUser()).toLanguageTag();
if (captchaConfig == null || captchaConfig.getConfig() == null
|| captchaConfig.getConfig().get(SITE_UUID) == null
|| captchaConfig.getConfig().get(SITE_PUBKEY) == null
|| captchaConfig.getConfig().get(MOSPARO_HOST) == null
|| captchaConfig.getConfig().get(MOSPARO_UUID) == null
|| captchaConfig.getConfig().get(MOSPARO_PUBLIC_KEY) == null
|| captchaConfig.getConfig().get(MOSPARO_PRIVATE_KEY) == null
) {
form.addError(new FormMessage(null, "Mosparo not configured properly."));
form.addError(new FormMessage(null, "mosparo not configured properly."));
return;
}
String siteUUID = captchaConfig.getConfig().get(SITE_UUID);
form.addScript("https://" + getMosparoHostname(captchaConfig) + "/build/mosparo-frontend.js");
form.addScript( "var m; " +
"window.onload = function(){ " +
"m = new mosparo( " +
"'mosparo-box'," +
" captchaConfig.getConfig().get(MOSPARO_HOST)," +
" captchaConfig.getConfig().get(SITE_UUID)," +
" captchaConfig.getConfig().get(SITE_PUBKEY)); };");
form.setAttribute("mosparoRequired", true);
form.setAttribute("mosparoHost", captchaConfig.getConfig().get(MOSPARO_HOST));
form.setAttribute("mosparoUuid", captchaConfig.getConfig().get(MOSPARO_UUID));
form.setAttribute("mosparoPublicKey", captchaConfig.getConfig().get(MOSPARO_PUBLIC_KEY));
// The hostname should always start with https://
form.addScript(getMosparoHostname(captchaConfig) + "/build/mosparo-frontend.js");
}
@Override
@ -125,13 +136,8 @@ public class RegistrationMosparo implements FormAction, FormActionFactory {
boolean success = false;
context.getEvent().detail(Details.REGISTER_METHOD, "form");
String captcha = formData.getFirst(MOSPARO_RESPONSE);
if (!Validation.isBlank(captcha)) {
AuthenticatorConfigModel captchaConfig = context.getAuthenticatorConfig();
String pubkey = captchaConfig.getConfig().get(SITE_PUBKEY);
success = verifyFormData(context, formData);
success = validateMosparo(context, success, captcha, pubkey);
}
if (success) {
context.success();
} else {
@ -141,20 +147,54 @@ public class RegistrationMosparo implements FormAction, FormActionFactory {
context.validationError(formData, errors);
context.excludeOtherErrors();
return;
}
}
private String getMosparoHostname(AuthenticatorConfigModel config) {
System.out.println(MOSPARO_HOST);
return config.getConfig().get(MOSPARO_HOST);
}
protected boolean validateMosparo(ValidationContext context, boolean success, String captcha, String pubkey) {
CloseableHttpClient httpClient = context.getSession().getProvider(HttpClientProvider.class).getHttpClient();
HttpPost post = new HttpPost("https://" + getMosparoHostname(context.getAuthenticatorConfig()) + "/api/v1/frontend/verification/verify");
protected boolean validateMosparo(ValidationContext context, MultivaluedMap<String, String> formData) {
// 1. Remove the ignored fields from the form data
formData.remove("password");
formData.remove("password-confirm");
// 2. Extract the submit and validation token from the form data
String mosparoSubmitToken = formData.getFirst("_mosparo_submitToken");
String mosparoValidationToken = formData.getFirst("_mosparo_validationToken");
// 3. Prepare the form data
MultivaluedMap<String, String> preparedFormData = new MultivaluedHashMap<>();
for (Map.Entry<String, List<String>> entry : formData.entrySet()) {
if (entry.getKey().startsWith("_mosparo_")) {
continue;
}
String value = entry.getValue().getFirst();
preparedFormData.add(entry.getKey(), value.replace("\r\n", "\n"));
}
// 4. Generate the hashes
MessageDigest digest = MessageDigest.getInstance("SHA-256");
MultivaluedMap<String, String> hashedFormData = new MultivaluedHashMap<>();
for (Map.Entry<String, List<String>> entry : preparedFormData.entrySet()) {
String value = entry.getValue().getFirst();
byte[] hashedValue = digest.digest(value.getBytes(StandardCharsets.UTF_8));
hashedFormData.add(entry.getKey(), convertBytesToHex(hashedValue));
}
// 5. Generate the form data signature
// 6. Generate the validation signature
// 7. Prepare the verification signature
// 8. Collect the request data
// 9. Generate the request signature
// 10. Send the API request
// 11. Check the response
boolean success = false;
/*CloseableHttpClient httpClient = context.getSession().getProvider(HttpClientProvider.class).getHttpClient();
HttpPost post = new HttpPost(getMosparoHostname(context.getAuthenticatorConfig()) + "/api/v1/verification/verify");
List<NameValuePair> formparams = new LinkedList<>();
formparams.add(new BasicNameValuePair("pubkey", pubkey));
formparams.add(new BasicNameValuePair("response", captcha));
@ -174,10 +214,25 @@ public class RegistrationMosparo implements FormAction, FormActionFactory {
}
} catch (Exception e) {
ServicesLogger.LOGGER.recaptchaFailed(e);
}
}*/
return success;
}
private static String convertBytesToHex(byte[] hash) {
StringBuilder hexString = new StringBuilder(2 * hash.length);
for (int i = 0; i < hash.length; i++) {
String hex = Integer.toHexString(0xff & hash[i]);
if (hex.length() == 1) {
hexString.append('0');
}
hexString.append(hex);
}
return hexString.toString();
}
@Override
public void success(FormContext context) {
@ -238,18 +293,6 @@ public class RegistrationMosparo implements FormAction, FormActionFactory {
static {
ProviderConfigProperty property;
property = new ProviderConfigProperty();
property.setName(SITE_UUID);
property.setLabel("UUID");
property.setType(ProviderConfigProperty.STRING_TYPE);
property.setHelpText("UUID");
CONFIG_PROPERTIES.add(property);
property = new ProviderConfigProperty();
property.setName(SITE_PUBKEY);
property.setLabel("Mosparo Public Key");
property.setType(ProviderConfigProperty.STRING_TYPE);
property.setHelpText("Mosparo Public Key (Site Key)");
CONFIG_PROPERTIES.add(property);
property = new ProviderConfigProperty();
property.setName(MOSPARO_HOST);
@ -257,6 +300,34 @@ public class RegistrationMosparo implements FormAction, FormActionFactory {
property.setType(ProviderConfigProperty.STRING_TYPE);
property.setHelpText("Hostname mosparo-host");
CONFIG_PROPERTIES.add(property);
property = new ProviderConfigProperty();
property.setName(MOSPARO_UUID);
property.setLabel("UUID");
property.setType(ProviderConfigProperty.STRING_TYPE);
property.setHelpText("UUID");
CONFIG_PROPERTIES.add(property);
property = new ProviderConfigProperty();
property.setName(MOSPARO_PUBLIC_KEY);
property.setLabel("mosparo Public Key");
property.setType(ProviderConfigProperty.STRING_TYPE);
property.setHelpText("Mosparo Public Key (Site Key)");
CONFIG_PROPERTIES.add(property);
property = new ProviderConfigProperty();
property.setName(MOSPARO_PRIVATE_KEY);
property.setLabel("mosparo Private Key");
property.setType(ProviderConfigProperty.STRING_TYPE);
property.setHelpText("mosparo Private Key");
CONFIG_PROPERTIES.add(property);
property = new ProviderConfigProperty();
property.setName(MOSPARO_VERIFY_SSL);
property.setLabel("mosparo Verify SSL");
property.setType(ProviderConfigProperty.BOOLEAN_TYPE);
property.setHelpText("mosparo Verify SSL");
CONFIG_PROPERTIES.add(property);
}