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: 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 ```html
<#if hcaptchaRequired??> <#if mosparoRequired??>
<div class="form-group"> <div class="form-group">
<div class="${properties.kcInputWrapperClass!}"> <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>
</div> </div>
</#if> </#if>

View file

@ -17,6 +17,9 @@
package at.schuerz.keycloak.authenticator; 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.NameValuePair;
import org.apache.http.client.entity.UrlEncodedFormEntity; import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpPost; import org.apache.http.client.methods.HttpPost;
@ -47,6 +50,9 @@ import org.keycloak.util.JsonSerialization;
import java.io.InputStream; import java.io.InputStream;
import jakarta.ws.rs.core.MultivaluedMap; import jakarta.ws.rs.core.MultivaluedMap;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
@ -65,9 +71,13 @@ import org.apache.http.util.EntityUtils;
public class RegistrationMosparo implements FormAction, FormActionFactory { public class RegistrationMosparo implements FormAction, FormActionFactory {
public static final String MOSPARO_RESPONSE = "mosparo-response"; public static final String MOSPARO_RESPONSE = "mosparo-response";
public static final String MOSPARO_REFERENCE_CATEGORY = "mosparo"; 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_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); private static final Logger logger = Logger.getLogger(RegistrationMosparo.class);
public static final String PROVIDER_ID = "registration-mosparo-action"; public static final String PROVIDER_ID = "registration-mosparo-action";
@ -91,31 +101,32 @@ public class RegistrationMosparo implements FormAction, FormActionFactory {
AuthenticationExecutionModel.Requirement.REQUIRED, AuthenticationExecutionModel.Requirement.REQUIRED,
AuthenticationExecutionModel.Requirement.DISABLED AuthenticationExecutionModel.Requirement.DISABLED
}; };
@Override @Override
public AuthenticationExecutionModel.Requirement[] getRequirementChoices() { public AuthenticationExecutionModel.Requirement[] getRequirementChoices() {
return REQUIREMENT_CHOICES; return REQUIREMENT_CHOICES;
} }
@Override @Override
public void buildPage(FormContext context, LoginFormsProvider form) { public void buildPage(FormContext context, LoginFormsProvider form) {
AuthenticatorConfigModel captchaConfig = context.getAuthenticatorConfig(); AuthenticatorConfigModel captchaConfig = context.getAuthenticatorConfig();
String userLanguageTag = context.getSession().getContext().resolveLocale(context.getUser()).toLanguageTag();
if (captchaConfig == null || captchaConfig.getConfig() == null 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_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; return;
} }
String siteUUID = captchaConfig.getConfig().get(SITE_UUID);
form.addScript("https://" + getMosparoHostname(captchaConfig) + "/build/mosparo-frontend.js"); form.setAttribute("mosparoRequired", true);
form.addScript( "var m; " + form.setAttribute("mosparoHost", captchaConfig.getConfig().get(MOSPARO_HOST));
"window.onload = function(){ " + form.setAttribute("mosparoUuid", captchaConfig.getConfig().get(MOSPARO_UUID));
"m = new mosparo( " + form.setAttribute("mosparoPublicKey", captchaConfig.getConfig().get(MOSPARO_PUBLIC_KEY));
"'mosparo-box'," +
" captchaConfig.getConfig().get(MOSPARO_HOST)," + // The hostname should always start with https://
" captchaConfig.getConfig().get(SITE_UUID)," + form.addScript(getMosparoHostname(captchaConfig) + "/build/mosparo-frontend.js");
" captchaConfig.getConfig().get(SITE_PUBKEY)); };");
} }
@Override @Override
@ -125,13 +136,8 @@ public class RegistrationMosparo implements FormAction, FormActionFactory {
boolean success = false; boolean success = false;
context.getEvent().detail(Details.REGISTER_METHOD, "form"); context.getEvent().detail(Details.REGISTER_METHOD, "form");
String captcha = formData.getFirst(MOSPARO_RESPONSE); success = verifyFormData(context, formData);
if (!Validation.isBlank(captcha)) {
AuthenticatorConfigModel captchaConfig = context.getAuthenticatorConfig();
String pubkey = captchaConfig.getConfig().get(SITE_PUBKEY);
success = validateMosparo(context, success, captcha, pubkey);
}
if (success) { if (success) {
context.success(); context.success();
} else { } else {
@ -141,20 +147,54 @@ public class RegistrationMosparo implements FormAction, FormActionFactory {
context.validationError(formData, errors); context.validationError(formData, errors);
context.excludeOtherErrors(); context.excludeOtherErrors();
return; return;
} }
} }
private String getMosparoHostname(AuthenticatorConfigModel config) { private String getMosparoHostname(AuthenticatorConfigModel config) {
System.out.println(MOSPARO_HOST);
return config.getConfig().get(MOSPARO_HOST); return config.getConfig().get(MOSPARO_HOST);
} }
protected boolean validateMosparo(ValidationContext context, boolean success, String captcha, String pubkey) { protected boolean validateMosparo(ValidationContext context, MultivaluedMap<String, String> formData) {
CloseableHttpClient httpClient = context.getSession().getProvider(HttpClientProvider.class).getHttpClient(); // 1. Remove the ignored fields from the form data
HttpPost post = new HttpPost("https://" + getMosparoHostname(context.getAuthenticatorConfig()) + "/api/v1/frontend/verification/verify"); 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<>(); List<NameValuePair> formparams = new LinkedList<>();
formparams.add(new BasicNameValuePair("pubkey", pubkey)); formparams.add(new BasicNameValuePair("pubkey", pubkey));
formparams.add(new BasicNameValuePair("response", captcha)); formparams.add(new BasicNameValuePair("response", captcha));
@ -174,10 +214,25 @@ public class RegistrationMosparo implements FormAction, FormActionFactory {
} }
} catch (Exception e) { } catch (Exception e) {
ServicesLogger.LOGGER.recaptchaFailed(e); ServicesLogger.LOGGER.recaptchaFailed(e);
} }*/
return success; 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 @Override
public void success(FormContext context) { public void success(FormContext context) {
@ -238,18 +293,6 @@ public class RegistrationMosparo implements FormAction, FormActionFactory {
static { static {
ProviderConfigProperty property; 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 = new ProviderConfigProperty();
property.setName(MOSPARO_HOST); property.setName(MOSPARO_HOST);
@ -257,6 +300,34 @@ public class RegistrationMosparo implements FormAction, FormActionFactory {
property.setType(ProviderConfigProperty.STRING_TYPE); property.setType(ProviderConfigProperty.STRING_TYPE);
property.setHelpText("Hostname mosparo-host"); property.setHelpText("Hostname mosparo-host");
CONFIG_PROPERTIES.add(property); 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);
} }