Skip to content
This repository has been archived by the owner on Sep 9, 2024. It is now read-only.

Commit

Permalink
Release 1.4.0
Browse files Browse the repository at this point in the history
  • Loading branch information
druchniewicz committed Jan 24, 2023
1 parent 8a0ebfe commit c10ae38
Show file tree
Hide file tree
Showing 46 changed files with 1,921 additions and 87 deletions.
10 changes: 9 additions & 1 deletion api/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<parent>
<groupId>org.openmrs.module</groupId>
<artifactId>cfl</artifactId>
<version>1.3.5</version>
<version>1.4.0</version>
</parent>

<artifactId>cfl-api</artifactId>
Expand Down Expand Up @@ -134,5 +134,13 @@
<groupId>org.openmrs.module</groupId>
<artifactId>metadatamapping-api</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</dependency>
</dependencies>
</project>
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ public final class CfldistributionConstants {
*/
public static final String ETL_MODULE_ID = "etllite";

public static final String PROJECT_LOCATION_ATTRIBUTE_TYPE_NAME = "Project";

private CfldistributionConstants() {

}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,22 @@ public final class CfldistributionGlobalParameterConstants {
"be restart after change this GP and in order to revert those changes you need to manually clean the " +
"appframework_component_state table";

public static final String GOOGLE_RECAPTCHA_SITE_KEY = "google.recaptcha.site.key";
public static final String GOOGLE_RECAPTCHA_SITE_VALUE = "";
public static final String GOOGLE_RECAPTCHA_SITE_DESCRIPTION = "Used to set Google Recaptcha Site Key";

public static final String GOOGLE_RECAPTCHA_SECRET_KEY = "google.recaptcha.secret.key";
public static final String GOOGLE_RECAPTCHA_SECRET_VALUE = "";
public static final String GOOGLE_RECAPTCHA_SECRET_DESCRIPTION = "Used to set Google Recaptcha Secret Key";

public static final String CAPTCHA_ENABLE_KEY = "cfl.captchaEnable";
public static final String CAPTCHA_ENABLED_DEFAULT_VALUE = "false";
public static final String CAPTCHA_ENABLED_DESCRIPTION = "Set to true to Enable Captcha and to false to disable";

public static final String GOOGLE_RECAPTCHA_MAX_FAILED_ATTEMPTS_KEY = "google.recaptcha.max.failed.attempts";
public static final String GOOGLE_RECAPTCHA_MAX_FAILED_ATTEMPTS_DEFAULT_VALUE = "4";
public static final String GOOGLE_RECAPTCHA_MAX_FAILED_ATTEMPTS_DESCRIPTION = "Maximum Failed Attempts Allowed Before Captcha Gets Blocked";

public static final String CFL_LOCATION_ATTRIBUTE_TYPE_UUID = "0a93cbc6-5d65-4886-8091-47a25d3df944";

public static final String CFL_TELEPHONE_NUMBER_PERSON_ATTRIBUTE_TYPE_UUID = "14d4f066-15f5-102d-96e4-000c29c2a5d7";
Expand All @@ -33,6 +49,12 @@ public final class CfldistributionGlobalParameterConstants {

public static final String REGISTRATIONCORE_IDENTIFIER_SOURCE_ID_KEY = "registrationcore.identifierSourceId";

public static final String CFL_SHOW_STACKTRACE_IN_ERROR_PAGE_KEY = "cfl.showStackTraceInErrorPage";
public static final String CFL_SHOW_STACKTRACE_IN_ERROR_PAGE_DESCRIPTION = "[true,false] whether the error pages " +
"should show exception stack traces. For security reasons, stack traces on error pages should be hidden in " +
"productive environments. Default: false";
public static final String CFL_SHOW_STACKTRACE_IN_ERROR_PAGE_DEFAULT_VALUE = "false";

private CfldistributionGlobalParameterConstants() {

}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,12 @@
package org.openmrs.module.cfl.api.activator.impl;

import org.apache.commons.logging.Log;
import org.openmrs.module.cflcore.CFLConstants;
import org.openmrs.module.cfl.CfldistributionGlobalParameterConstants;
import org.openmrs.module.cfl.api.activator.ModuleActivatorStep;
import org.openmrs.module.cflcore.CFLConstants;

import static org.openmrs.module.cflcore.api.util.GlobalPropertyUtils.createGlobalSettingIfNotExists;
import static org.openmrs.module.cfl.api.activator.impl.ModuleActivatorStepOrderEnum.CREATE_GLOBAL_PARAMETERS_ACTIVATOR_STEP;
import static org.openmrs.module.cflcore.api.util.GlobalPropertyUtils.createGlobalSettingIfNotExists;

/**
* The bean defined in moduleApplicationContext.xml because OpenMRS performance issues with
Expand All @@ -41,5 +41,25 @@ public void startup(Log log) {
createGlobalSettingIfNotExists(
CFLConstants.LOCATION_ATTRIBUTE_GLOBAL_PROPERTY_NAME,
CfldistributionGlobalParameterConstants.CFL_LOCATION_ATTRIBUTE_TYPE_UUID);
createGlobalSettingIfNotExists(
CfldistributionGlobalParameterConstants.CFL_SHOW_STACKTRACE_IN_ERROR_PAGE_KEY,
CfldistributionGlobalParameterConstants.CFL_SHOW_STACKTRACE_IN_ERROR_PAGE_DEFAULT_VALUE,
CfldistributionGlobalParameterConstants.CFL_SHOW_STACKTRACE_IN_ERROR_PAGE_DESCRIPTION);
createGlobalSettingIfNotExists(
CfldistributionGlobalParameterConstants.GOOGLE_RECAPTCHA_SECRET_KEY,
CfldistributionGlobalParameterConstants.GOOGLE_RECAPTCHA_SECRET_VALUE,
CfldistributionGlobalParameterConstants.GOOGLE_RECAPTCHA_SECRET_DESCRIPTION);
createGlobalSettingIfNotExists(
CfldistributionGlobalParameterConstants.GOOGLE_RECAPTCHA_SITE_KEY,
CfldistributionGlobalParameterConstants.GOOGLE_RECAPTCHA_SITE_VALUE,
CfldistributionGlobalParameterConstants.GOOGLE_RECAPTCHA_SITE_DESCRIPTION);
createGlobalSettingIfNotExists(
CfldistributionGlobalParameterConstants.CAPTCHA_ENABLE_KEY,
CfldistributionGlobalParameterConstants.CAPTCHA_ENABLED_DEFAULT_VALUE,
CfldistributionGlobalParameterConstants.CAPTCHA_ENABLED_DESCRIPTION);
createGlobalSettingIfNotExists(
CfldistributionGlobalParameterConstants.GOOGLE_RECAPTCHA_MAX_FAILED_ATTEMPTS_KEY,
CfldistributionGlobalParameterConstants.GOOGLE_RECAPTCHA_MAX_FAILED_ATTEMPTS_DEFAULT_VALUE,
CfldistributionGlobalParameterConstants.GOOGLE_RECAPTCHA_MAX_FAILED_ATTEMPTS_DESCRIPTION);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public License,
* v. 2.0. If a copy of the MPL was not distributed with this file, You can
* obtain one at http://mozilla.org/MPL/2.0/. OpenMRS is also distributed under
* the terms of the Healthcare Disclaimer located at http://openmrs.org/license.
* <p>
* Copyright (C) OpenMRS Inc. OpenMRS is a registered trademark and the OpenMRS
* graphic logo is a trademark of OpenMRS Inc.
*/

package org.openmrs.module.cfl.api.service;

import javax.servlet.http.HttpServletRequest;

/** This service is used to get user's response to the reCAPTCHA challenge and validate response. */
public interface CaptchaService {

/**
* Retrieves and validates response
*
* @param request
*/
void processResponse(HttpServletRequest request);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package org.openmrs.module.cfl.api.service;

import java.util.List;
import org.openmrs.Location;
import org.openmrs.module.appframework.domain.Extension;

public interface CustomUserAppService {

/**
* Sets specific app extensions based on user location. For example if registration form tile is
* displayed on home page, this method checks if there is a specific registration form for current
* user location (based on 'Project' location attribute) and replaces it.
*
* @param location user location
* @param extensions current user app extensions
*/
void setSpecificAppExtensionsByLocation(Location location, List<Extension> extensions);
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public License,
* v. 2.0. If a copy of the MPL was not distributed with this file, You can
* obtain one at http://mozilla.org/MPL/2.0/. OpenMRS is also distributed under
* the terms of the Healthcare Disclaimer located at http://openmrs.org/license.
* <p>
* Copyright (C) OpenMRS Inc. OpenMRS is a registered trademark and the OpenMRS
* graphic logo is a trademark of OpenMRS Inc.
*/

package org.openmrs.module.cfl.api.service;

import org.openmrs.User;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public License,
* v. 2.0. If a copy of the MPL was not distributed with this file, You can
* obtain one at http://mozilla.org/MPL/2.0/. OpenMRS is also distributed under
* the terms of the Healthcare Disclaimer located at http://openmrs.org/license.
* <p>
* Copyright (C) OpenMRS Inc. OpenMRS is a registered trademark and the OpenMRS
* graphic logo is a trademark of OpenMRS Inc.
*/

package org.openmrs.module.cfl.api.service.impl;

import org.openmrs.api.context.Context;
import org.openmrs.module.cfl.CfldistributionGlobalParameterConstants;
import org.openmrs.module.cfl.api.service.CaptchaService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.StringUtils;
import org.springframework.web.client.RestClientException;
import org.springframework.web.client.RestTemplate;

import javax.servlet.http.HttpServletRequest;
import java.net.URI;
import java.util.regex.Pattern;

public class CaptchaServiceImpl implements CaptchaService {

private static final Pattern RESPONSE_PATTERN = Pattern.compile("[A-Za-z0-9_-]+");

private static final Logger LOGGER = LoggerFactory.getLogger(CaptchaServiceImpl.class);

private static final String RECAPTCHA_URL_TEMPLATE =
"https://www.google.com/recaptcha/api/siteverify?secret=%s&response=%s&remoteip=%s";

private RestTemplate restTemplate = new RestTemplate();

private ReCaptchaAttemptService reCaptchaAttemptService;

public CaptchaServiceImpl() {
this(new RestTemplate());
}

CaptchaServiceImpl(RestTemplate restTemplate) {
this.restTemplate = restTemplate;
}

@Override
public void processResponse(HttpServletRequest request) {

String response = request.getParameter("g-recaptcha-response");

securityCheck(response, request);
final URI verifyUri =
URI.create(
String.format(
RECAPTCHA_URL_TEMPLATE, getReCaptchaSecret(), response, getClientIP(request)));
try {
final GoogleResponse googleResponse =
restTemplate.getForObject(verifyUri, GoogleResponse.class);
LOGGER.debug("Google's response: {} ", googleResponse);

if (!googleResponse.isSuccess()) {
if (googleResponse.hasClientError()) {
reCaptchaAttemptService.reCaptchaFailed(getClientIP(request));
}
throw new ReCaptchaInvalidException("reCaptcha was not successfully validated");
}
} catch (RestClientException rce) {
throw new ReCaptchaInvalidException(
"Login unavailable at this time. Please try again later.", rce);
}
reCaptchaAttemptService.reCaptchaSucceeded(getClientIP(request));
}

private String getClientIP(HttpServletRequest request) {
final String xfHeader = request.getHeader("X-Forwarded-For");
if (xfHeader == null) {
return request.getRemoteAddr();
}
return xfHeader.split(",")[0];
}

private String getReCaptchaSecret() {
return Context.getAdministrationService()
.getGlobalProperty(CfldistributionGlobalParameterConstants.GOOGLE_RECAPTCHA_SECRET_KEY);
}

private boolean responseSanityCheck(String response) {
return StringUtils.hasLength(response) && RESPONSE_PATTERN.matcher(response).matches();
}

public void setReCaptchaAttemptService(ReCaptchaAttemptService reCaptchaAttemptService) {
this.reCaptchaAttemptService = reCaptchaAttemptService;
}

private void securityCheck(String response, HttpServletRequest request) {
if (!responseSanityCheck(response)) {
throw new ReCaptchaInvalidException("Response contains invalid characters");
}
if (reCaptchaAttemptService.isBlocked(getClientIP(request))) {
throw new ReCaptchaInvalidException("Client exceeded maximum number of failed attempts");
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package org.openmrs.module.cfl.api.service.impl;

import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import org.apache.commons.lang.StringUtils;
import org.openmrs.Location;
import org.openmrs.LocationAttribute;
import org.openmrs.api.context.Context;
import org.openmrs.module.appframework.domain.AppDescriptor;
import org.openmrs.module.appframework.domain.Extension;
import org.openmrs.module.appframework.service.AppFrameworkService;
import org.openmrs.module.cfl.CfldistributionConstants;
import org.openmrs.module.cfl.api.service.CustomUserAppService;

public class CustomUserAppServiceImpl implements CustomUserAppService {

@Override
public void setSpecificAppExtensionsByLocation(Location location, List<Extension> extensions) {
if (location != null) {
String projectName = getProjectNameByLocation(location);
if (StringUtils.isNotBlank(projectName)) {
replaceExtensionsIfRequired(extensions, projectName);
}
}
}

private void replaceExtensionsIfRequired(List<Extension> extensions, String projectName) {
List<Extension> allAvailableProjectExtensions = getAllProjectSpecificExtensions(projectName);
for (int i = 0; i < extensions.size(); i++) {
String projectSpecificExtensionName = getProjectExtensionName(extensions.get(i), projectName);
Optional<Extension> projectSpecificExtension = findSpecificExtension(
allAvailableProjectExtensions, projectSpecificExtensionName);
if (projectSpecificExtension.isPresent()) {
extensions.set(i, projectSpecificExtension.get());
}
}
}

private String getProjectNameByLocation(Location location) {
String projectName = null;
Optional<LocationAttribute> locationProjectAttribute = location.getActiveAttributes().stream()
.filter(attribute -> StringUtils.equalsIgnoreCase(attribute.getAttributeType().getName(),
CfldistributionConstants.PROJECT_LOCATION_ATTRIBUTE_TYPE_NAME))
.findFirst();

if (locationProjectAttribute.isPresent()) {
projectName = locationProjectAttribute.get().getValueReference();
}

return projectName;
}

private List<Extension> getAllProjectSpecificExtensions(String projectName) {
return Context.getService(AppFrameworkService.class).getAllApps().stream()
.filter(app -> StringUtils.endsWith(app.getId(), projectName))
.flatMap(app -> app.getExtensions().stream())
.collect(Collectors.toList());
}

private String getProjectExtensionName(Extension extension, String projectName) {
String extensionName = null;
AppDescriptor extDescriptor = extension.getBelongsTo();
if (extDescriptor != null) {
extensionName = extDescriptor.getId().concat(".").concat(projectName);
}

return extensionName;
}

private Optional<Extension> findSpecificExtension(List<Extension> list, String appId) {
return list.stream()
.filter(ext -> ext.getBelongsTo() != null)
.filter(ext -> StringUtils.equals(ext.getBelongsTo().getId(), appId))
.findFirst();
}
}
Loading

0 comments on commit c10ae38

Please sign in to comment.