Work in progress
This commit is contained in:
parent
d2f46919b3
commit
36981a234e
2 changed files with 131 additions and 47 deletions
17
README.md
17
README.md
|
@ -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>
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue