Skip to content

Commit

Permalink
OA-40: Add property to control starting of the module (#24)
Browse files Browse the repository at this point in the history
  • Loading branch information
corneliouzbett authored Dec 3, 2024
1 parent 62ade3c commit 548f1d9
Show file tree
Hide file tree
Showing 8 changed files with 232 additions and 62 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,20 @@
package org.openmrs.module.oauth2login;

import static org.openmrs.module.oauth2login.OAuth2LoginConstants.AUTH_SCHEME_COMPONENT;
import static org.openmrs.module.oauth2login.OAuth2LoginConstants.MODULE_ARTIFACT_ID;
import static org.openmrs.module.oauth2login.OAuth2LoginConstants.OAUTH2_ENABLED_PROPERTY;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.openmrs.api.context.Context;
import org.openmrs.module.BaseModuleActivator;
import org.openmrs.module.DaemonToken;
import org.openmrs.module.DaemonTokenAware;
import org.openmrs.module.ModuleException;
import org.openmrs.module.ModuleFactory;

import java.io.IOException;
import java.util.Properties;

/**
* This class contains the logic that is run every time this module is either started or shutdown
Expand All @@ -27,6 +34,31 @@ public class OAuth2LoginActivator extends BaseModuleActivator implements DaemonT

private DaemonToken daemonToken;

/**
* @see #willStart()
*/
@Override
public void willStart() {
// Checks if OAuth2 is enabled, if not, stops the module and unloads it
try {
Properties oauth2Props = PropertyUtils.getOAuth2Properties();
boolean moduleEnabled = Boolean.parseBoolean(oauth2Props.getProperty(OAUTH2_ENABLED_PROPERTY, "true"));
if (!moduleEnabled) {
log.info("OAuth2 is disabled. Skipping module start.");
// Stop the module if it is already started
if (ModuleFactory.isModuleStarted(MODULE_ARTIFACT_ID)) {
ModuleFactory.stopModule(ModuleFactory.getModuleById(MODULE_ARTIFACT_ID));
}
ModuleFactory.unloadModule(ModuleFactory.getModuleById(MODULE_ARTIFACT_ID));
} else {
log.info("OAuth2 is enabled. " + OAuth2LoginConstants.MODULE_NAME + " module will start.");
}
}
catch (IOException e) {
throw new ModuleException("Failed to load OAuth2 properties file", e);
}
}

/**
* @see #started()
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,6 @@ public class OAuth2LoginConstants {

public static final String USER_PROP_ID_TOKEN = "oauth2IdToken";

public static final String OAUTH2_ENABLED_PROPERTY = "oauth2.enabled";

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
/*
* 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.
*
* Copyright (C) OpenMRS Inc. OpenMRS is a registered trademark and the OpenMRS
* graphic logo is a trademark of OpenMRS Inc.
*/
package org.openmrs.module.oauth2login;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.openmrs.util.OpenmrsUtil;

import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Properties;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class PropertyUtils {

protected static final Log LOG = LogFactory.getLog(PropertyUtils.class);

private static final Pattern ENV_PATTERN = Pattern.compile("\\$\\{([^}]+)}");

public static Properties getProperties(Path path) throws IOException {
Properties props = new Properties();
try (InputStream inputStream = Files.newInputStream(path)) {
props.load(new InputStreamReader(inputStream, StandardCharsets.UTF_8));
for (String key : props.stringPropertyNames()) {
String value = props.getProperty(key);
props.setProperty(key, resolveEnvVariables(value));
}
}
return props;
}

/**
* Resolves environment variables in the given string.
* <p>
* This method searches for placeholders in the format ${ENV_VAR} within the input string and
* replaces them with the corresponding environment variable values. It first checks system
* properties and then environment variables for the value of each placeholder.
*
* @param value the input string containing placeholders for environment variables
* @return the input string with all environment variables resolved
*/
public static String resolveEnvVariables(String value) {
Matcher matcher = ENV_PATTERN.matcher(value);
StringBuffer buffer = new StringBuffer();
while (matcher.find()) {
String envVar = matcher.group(1);
String envValue = System.getProperty(envVar, System.getenv(envVar));
matcher.appendReplacement(buffer, Matcher.quoteReplacement(envValue != null ? envValue : matcher.group(0)));
}
matcher.appendTail(buffer);
return buffer.toString();
}

public static Path getOAuth2PropertiesPath() {
Path path = Paths.get(OpenmrsUtil.getApplicationDataDirectory(), "oauth2.properties");
if (!path.toFile().exists()) {
LOG.error("the property file doesn't exist " + path.toAbsolutePath());
}
return path;
}

public static Properties getOAuth2Properties() throws IOException {
return getProperties(getOAuth2PropertiesPath());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
/*
* 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.
*
* Copyright (C) OpenMRS Inc. OpenMRS is a registered trademark and the OpenMRS
* graphic logo is a trademark of OpenMRS Inc.
*/
package org.openmrs.module.oauth2login;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.openmrs.module.ModuleException;
import org.openmrs.module.ModuleFactory;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;

import java.io.IOException;
import java.util.Properties;

import static org.mockito.Mockito.times;
import static org.powermock.api.mockito.PowerMockito.mock;
import static org.powermock.api.mockito.PowerMockito.verifyStatic;
import static org.powermock.api.mockito.PowerMockito.when;

@RunWith(PowerMockRunner.class)
@PrepareForTest({ PropertyUtils.class, ModuleFactory.class })
public class OAuth2LoginActivatorTest {

private OAuth2LoginActivator activator;

@Before
public void setup() {
activator = new OAuth2LoginActivator();
PowerMockito.mockStatic(PropertyUtils.class);
PowerMockito.mockStatic(ModuleFactory.class);
}

@Test
public void willStart_shouldStopAndUnloadModuleIfOAuth2IsDisabled() throws IOException {
// Setup
Properties mockProperties = mock(Properties.class);
when(mockProperties.getProperty(OAuth2LoginConstants.OAUTH2_ENABLED_PROPERTY, "true")).thenReturn("false");
when(PropertyUtils.getOAuth2Properties()).thenReturn(mockProperties);

// Replay
activator.willStart();

// Verify
verifyStatic();
ModuleFactory.stopModule(ModuleFactory.getModuleById(OAuth2LoginConstants.MODULE_ARTIFACT_ID));

verifyStatic();
ModuleFactory.unloadModule(ModuleFactory.getModuleById(OAuth2LoginConstants.MODULE_ARTIFACT_ID));
}

@Test
public void willStart_shouldStartModuleIfOAuth2IsEnabled() throws IOException {
// Setup
Properties mockProperties = mock(Properties.class);
when(mockProperties.getProperty(OAuth2LoginConstants.OAUTH2_ENABLED_PROPERTY, "true")).thenReturn("true");
when(PropertyUtils.getOAuth2Properties()).thenReturn(mockProperties);

// Replay
activator.willStart();

// Verify
verifyStatic(times(0));
ModuleFactory.stopModule(ModuleFactory.getModuleById(OAuth2LoginConstants.MODULE_ARTIFACT_ID));

verifyStatic(times(0));
ModuleFactory.unloadModule(ModuleFactory.getModuleById(OAuth2LoginConstants.MODULE_ARTIFACT_ID));
}

@Test
public void willStart_shouldStopAndUnloadModuleIfModuleIsAlreadyStarted() throws IOException {
// Setup
Properties mockProperties = mock(Properties.class);
when(mockProperties.getProperty(OAuth2LoginConstants.OAUTH2_ENABLED_PROPERTY, "true")).thenReturn("false");
when(PropertyUtils.getOAuth2Properties()).thenReturn(mockProperties);
when(ModuleFactory.isModuleStarted(OAuth2LoginConstants.MODULE_ARTIFACT_ID)).thenReturn(true);

// Replay
activator.willStart();

// Verify
verifyStatic();
ModuleFactory.isModuleStarted(OAuth2LoginConstants.MODULE_ARTIFACT_ID);

verifyStatic(times(2));
ModuleFactory.stopModule(ModuleFactory.getModuleById(OAuth2LoginConstants.MODULE_ARTIFACT_ID));

verifyStatic(times(2));
ModuleFactory.unloadModule(ModuleFactory.getModuleById(OAuth2LoginConstants.MODULE_ARTIFACT_ID));
}

@Test(expected = ModuleException.class)
public void willStart_shouldThrowModuleExceptionIfPropertiesFileCannotBeLoaded() throws IOException {
OAuth2LoginActivator activator = new OAuth2LoginActivator();
PropertyUtils propertyUtils = mock(PropertyUtils.class);
when(PropertyUtils.getOAuth2Properties()).thenThrow(new IOException());

activator.willStart();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,21 @@
* Copyright (C) OpenMRS Inc. OpenMRS is a registered trademark and the OpenMRS
* graphic logo is a trademark of OpenMRS Inc.
*/
package org.openmrs.module.oauth2login.web.controller;
package org.openmrs.module.oauth2login;

import org.junit.Test;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;

public class OAuth2BeanFactoryTest {
public class PropertyUtilsTest {

@Test
public void resolveEnvVariables_shouldReturnResolvedValue() {
String value = "Authorization URL: ${OAUTH_URL}";
System.setProperty("OAUTH_URL", "https://localhost:8080/auth");

String resolvedValue = OAuth2BeanFactory.resolveEnvVariables(value);
String resolvedValue = PropertyUtils.resolveEnvVariables(value);

assertNotNull(resolvedValue);
assertEquals("Authorization URL: https://localhost:8080/auth", resolvedValue);
Expand All @@ -33,7 +33,7 @@ public void resolveEnvVariables_shouldReturnResolvedValueWithMultipleEnvVariable
System.setProperty("OAUTH_URL", "https://localhost:8080/auth");
System.setProperty("CLIENT_SECRET", "secret");

String resolvedValue = OAuth2BeanFactory.resolveEnvVariables(value);
String resolvedValue = PropertyUtils.resolveEnvVariables(value);

assertNotNull(resolvedValue);
assertEquals("Authorization URL: https://localhost:8080/auth, Client Secret: secret", resolvedValue);
Expand All @@ -44,7 +44,7 @@ public void resolveEnvVariables_shouldReturnResolvedValueWithMultipleOccurrences
String value = "Authorization URL: ${OAUTH_URL}, Authorization URL: ${OAUTH_URL}";
System.setProperty("OAUTH_URL", "https://localhost:8080/auth");

String resolvedValue = OAuth2BeanFactory.resolveEnvVariables(value);
String resolvedValue = PropertyUtils.resolveEnvVariables(value);

assertNotNull(resolvedValue);
assertEquals("Authorization URL: https://localhost:8080/auth, Authorization URL: https://localhost:8080/auth",
Expand All @@ -55,7 +55,7 @@ public void resolveEnvVariables_shouldReturnResolvedValueWithMultipleOccurrences
public void resolveEnvVariables_shouldReturnOriginalValueIfNoEnvVariable() {
String value = "Authorization URL: ${OAUTH_URL}";

String resolvedValue = OAuth2BeanFactory.resolveEnvVariables(value);
String resolvedValue = PropertyUtils.resolveEnvVariables(value);

assertNotNull(resolvedValue);
assertEquals("Authorization URL: ${OAUTH_URL}", resolvedValue);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@

import org.apache.commons.lang3.StringUtils;
import org.openmrs.api.context.Context;
import org.openmrs.module.oauth2login.PropertyUtils;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
import org.springframework.security.web.authentication.logout.SimpleUrlLogoutSuccessHandler;
Expand All @@ -30,7 +31,7 @@ public class CustomLogoutSuccessHandler extends SimpleUrlLogoutSuccessHandler im
@Override
public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication)
throws IOException, ServletException {
Properties properties = OAuth2BeanFactory.getProperties(OAuth2BeanFactory.getOAuth2PropertiesPath());
Properties properties = PropertyUtils.getProperties(PropertyUtils.getOAuth2PropertiesPath());
String redirectPath = properties.getProperty("logoutUri");
//the redirect path can contain a [token] that should be replaced by the aut token
if (StringUtils.isNoneBlank(redirectPath) && redirectPath.contains("[token]")) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,20 +10,12 @@
package org.openmrs.module.oauth2login.web.controller;

import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.Properties;
import java.util.regex.Pattern;
import java.util.regex.Matcher;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.openmrs.util.OpenmrsUtil;
import org.openmrs.module.oauth2login.PropertyUtils;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
Expand All @@ -42,59 +34,15 @@
@Configuration
public class OAuth2BeanFactory {

private static final Pattern ENV_PATTERN = Pattern.compile("\\$\\{([^}]+)}");

protected static final Log LOG = LogFactory.getLog(OAuth2BeanFactory.class);

public static Properties getProperties(Path path) throws IOException {
Properties props = new Properties();
try (InputStream inputStream = Files.newInputStream(path)) {
props.load(new InputStreamReader(inputStream, StandardCharsets.UTF_8));
for (String key : props.stringPropertyNames()) {
String value = props.getProperty(key);
props.setProperty(key, resolveEnvVariables(value));
}
}
return props;
}

/**
* Resolves environment variables in the given string.
* <p>
* This method searches for placeholders in the format ${ENV_VAR} within the input string and
* replaces them with the corresponding environment variable values. It first checks system
* properties and then environment variables for the value of each placeholder.
*
* @param value the input string containing placeholders for environment variables
* @return the input string with all environment variables resolved
*/
public static String resolveEnvVariables(String value) {
Matcher matcher = ENV_PATTERN.matcher(value);
StringBuffer buffer = new StringBuffer();
while (matcher.find()) {
String envVar = matcher.group(1);
String envValue = System.getProperty(envVar, System.getenv(envVar));
matcher.appendReplacement(buffer, Matcher.quoteReplacement(envValue != null ? envValue : matcher.group(0)));
}
matcher.appendTail(buffer);
return buffer.toString();
}

public static Path getOAuth2PropertiesPath() {
Path path = Paths.get(OpenmrsUtil.getApplicationDataDirectory(), "oauth2.properties");
if (!path.toFile().exists()) {
LOG.error("the property file doesn't exist " + path.toAbsolutePath());
}
return path;
}

/**
* Accessor to the properties files that contains the OAuth 2 client configuration: - client ID
* - client secret - user authorization URI - access token URI and - user info URI
*/
@Bean(name = "oauth2.properties")
public Properties getOAuth2Properties() throws IOException {
return getProperties(getOAuth2PropertiesPath());
return PropertyUtils.getOAuth2Properties();
}

@Bean(name = "oauth2.userInfoUri")
Expand Down
Loading

0 comments on commit 548f1d9

Please sign in to comment.