Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Disable username/password header fields for multimedia download endpoint #1183

Open
wants to merge 48 commits into
base: v2.1
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
5de2595
add redis test container
bennsimon Jul 6, 2022
a607ee9
Disable username/password header fields for multimedia download endpoint
ekigamba Jan 18, 2023
f05f49f
overide JdbcTokenStore storeAcessToken mthd
hilpitome Feb 14, 2023
111a29b
use reflection to access private fields
hilpitome Feb 14, 2023
0853933
refactor code
hilpitome Feb 14, 2023
bea4049
apply openMRS formatter
hilpitome Feb 14, 2023
0cf4fa4
use bean in OAuth2SecurityConfig
hilpitome Feb 14, 2023
a0a528b
update version and add logging
hilpitome Feb 15, 2023
5dc1f4e
Update pom.xml
hilpitome Feb 17, 2023
e6cde50
refactor storeaAccessToken method
hilpitome Feb 20, 2023
02feef0
Merge branch 'duplicate-auth-id-fix' of github.com:opensrp/opensrp-se…
hilpitome Feb 20, 2023
690124e
Update pom.xml
hilpitome Feb 21, 2023
953575a
add extra logging
hilpitome Feb 27, 2023
de19219
Merge branch 'duplicate-auth-id-fix' of github.com:opensrp/opensrp-se…
hilpitome Feb 27, 2023
3dcb9ca
update pom version
hilpitome Feb 27, 2023
dbdbf4f
apply formatter;
hilpitome Mar 2, 2023
d24820f
log when entering storAccessToken mthd
hilpitome Mar 2, 2023
fba0d5d
init unit test for jdbctokenstore
hilpitome Mar 3, 2023
a2bf76b
create OAuth2Request instance for testing
hilpitome Mar 6, 2023
bf86f64
mock connection;
hilpitome Mar 6, 2023
e7a5c42
add env with annotations
hilpitome Mar 7, 2023
8334adc
enable redis
hilpitome Mar 7, 2023
eeb57f7
add password to TestRedisConfig
hilpitome Mar 7, 2023
638662d
init use real objects instead of mocks
hilpitome Mar 10, 2023
920f743
delete OAth2SecurityTest and update server version
hilpitome Mar 13, 2023
ccd6ecd
retrigger checks
hilpitome Mar 13, 2023
84009bb
merge with branck add-redis-test-container
hilpitome Mar 13, 2023
4ed7f57
revert test postgres settings
hilpitome Mar 13, 2023
66066ac
Bump up version to 2.1.70.10-ALPHA1-SNAPSHOT
qiarie Mar 15, 2023
00a8316
Refactor profile image download
qiarie Mar 15, 2023
212ee51
Update unit tests
qiarie Mar 15, 2023
14f5127
Refactor file download by baseEntityId method signature
qiarie Mar 15, 2023
da13ff4
Remove credentials params from multimedia download
qiarie Mar 16, 2023
10df54e
Bump up version to 2.1.70.10-ALPHA3-SNAPSHOT
qiarie Mar 16, 2023
5a41a17
Remove unused downloadFileWithAuth method in MultimediaController
qiarie Mar 16, 2023
b387854
fix merge conflict
hilpitome Mar 16, 2023
204d8c1
test downloadFileByBaseEntityId in MultiMediaController
hilpitome Mar 16, 2023
cfaea93
add test to MultimediaController
hilpitome Mar 17, 2023
b970342
remove unused import
hilpitome Mar 17, 2023
c3b14b6
remove more unused imports
hilpitome Mar 17, 2023
4a94289
rest JdbcTokenStore
hilpitome Mar 22, 2023
842733b
subclass jdbcTokenStore for easier testing
hilpitome Mar 23, 2023
123ffd9
temp postgres port change
hilpitome Mar 23, 2023
dc37c14
suppress call to jdbctokenstore super during test
hilpitome Mar 23, 2023
85291d1
fix codacy issues
hilpitome Mar 23, 2023
0f5f680
fix codacy issue
hilpitome Mar 24, 2023
e0bc95f
remove unused connection mock
hilpitome Mar 24, 2023
86c675f
Update version to 2.1.70.10-ALPHA4-SNAPSHOT
hilpitome Mar 27, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 1 addition & 7 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,6 @@ jobs:
uses: actions/setup-java@v1
with:
java-version: 11
- name: Setup Redis
uses: supercharge/[email protected]

- name: Show running services
run: sudo netstat -tuplen # listing all the port for debug purpose.

- name: Run Unit tests with Maven
run: mvn -B clean test jacoco:report --file pom.xml --no-transfer-progress
- name: Set Branch name Environment variable
Expand All @@ -46,4 +40,4 @@ jobs:
-D repoToken="$COVERALLS_REPO_TOKEN" \
-D serviceName=Github \
-D branch="$BRANCH_NAME" \
-D pullRequest="$PR_NUMBER" \
-D pullRequest="$PR_NUMBER" \
8 changes: 7 additions & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

<artifactId>opensrp-server-web</artifactId>
<packaging>war</packaging>
<version>2.1.70.8-SNAPSHOT</version>
<version>2.1.70.10-ALPHA4-SNAPSHOT</version>
<name>opensrp-server-web</name>
<description>OpenSRP Server Web Application</description>
<url>https://github.com/OpenSRP/opensrp-server-web</url>
Expand Down Expand Up @@ -394,6 +394,12 @@
<version>3.29.2-GA</version>
</dependency>

<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>testcontainers</artifactId>
<version>1.17.2</version>
<scope>test</scope>
</dependency>
</dependencies>

<build>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,26 @@
/**
*
*
*/
package org.opensrp.web.config.security;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.opensrp.web.config.Role;
import org.opensrp.web.security.OauthAuthenticationProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.token.AuthenticationKeyGenerator;
import org.springframework.security.oauth2.provider.token.DefaultAuthenticationKeyGenerator;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
import org.springframework.security.oauth2.provider.token.store.JdbcTokenStore;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
Expand All @@ -26,15 +33,15 @@
@EnableWebSecurity
@Configuration
@Profile("oauth2")
public class OAuth2SecurityConfig extends BasicAuthSecurityConfig{
public class OAuth2SecurityConfig extends BasicAuthSecurityConfig {

@Autowired
private OauthAuthenticationProvider opensrpAuthenticationProvider;

@Autowired
private ClientDetailsService clientDetailsService;

@Qualifier( value = "openSRPDataSource")
@Qualifier(value = "openSRPDataSource")
@Autowired
private DataSource dataSource;

Expand Down Expand Up @@ -67,14 +74,13 @@ protected void configure(HttpSecurity http) throws Exception {
/* @formatter:on */
}


@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(opensrpAuthenticationProvider).eraseCredentials(false);
}
}

public DefaultTokenServices tokenServices() {
DefaultTokenServices tokenServices= new DefaultTokenServices();
DefaultTokenServices tokenServices = new DefaultTokenServices();
tokenServices.setTokenStore(tokenStore());
tokenServices.setSupportRefreshToken(true);
tokenServices.setClientDetailsService(clientDetailsService);
Expand All @@ -83,7 +89,40 @@ public DefaultTokenServices tokenServices() {

@Bean
public JdbcTokenStore tokenStore() {
return new JdbcTokenStore(dataSource);
return new CustomJdbcTokenStore(dataSource);
}

public class CustomJdbcTokenStore extends JdbcTokenStore {

JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);

AuthenticationKeyGenerator authenticationKeyGenerator = new DefaultAuthenticationKeyGenerator();

Logger logger = LogManager.getLogger(CustomJdbcTokenStore.class.toString());

public CustomJdbcTokenStore(DataSource dataSource) {
super(dataSource);
}

@Override
public void storeAccessToken(OAuth2AccessToken token, OAuth2Authentication authentication) {
logger.info("Invoking store access token method");
if (authentication != null) {
final String key = authenticationKeyGenerator.extractKey(authentication);
int rowsAffected = jdbcTemplate.update("delete from oauth_access_token where authentication_id = ?",
key);
String isSuccess = (rowsAffected > 0) ? "Success" : "Failure";
logger.info("Attempt to delete authentication_id {} from oauth_access_token table was a {}", key,
isSuccess);
}
storeAccessTokenInSuper(token, authentication);
}

// added to suppress call to super#storeAccesToken when testing
public void storeAccessTokenInSuper(OAuth2AccessToken token, OAuth2Authentication authentication){
super.storeAccessToken(token, authentication);
}

}

}
66 changes: 18 additions & 48 deletions src/main/java/org/opensrp/web/controller/MultimediaController.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,21 +20,14 @@
import org.springframework.stereotype.Controller;
import org.springframework.util.FileCopyUtils;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.*;
import java.net.URLConnection;
import java.nio.charset.Charset;
import java.util.Date;
Expand Down Expand Up @@ -81,31 +74,24 @@ public void setMultimediaService(MultimediaService multimediaService) {
*
* @param response
* @param fileName
* @param userName
* @param password
* @throws IOException
*/
@RequestMapping(value = "/download/{fileName:.+}", method = RequestMethod.GET)
public void downloadFileWithAuth(HttpServletResponse response, @PathVariable("fileName") String fileName,
@RequestHeader(value = "username") String userName,
@RequestHeader(value = "password") String password, HttpServletRequest request) {

public void downloadFileWithAuth(HttpServletResponse response, @PathVariable("fileName") String fileName, HttpServletRequest request) {
try {
if (hasSpecialCharacters(fileName)) {
specialCharactersError(response, FILE_NAME_ERROR_MESSAGE);
return;
}

if (authenticate(userName, password, request).isAuthenticated()) {
File file = multimediaService.retrieveFile(multiMediaDir + File.separator + "images" + File.separator + fileName.trim());
if (file != null) {
if (fileName.endsWith("mp4")) {
file = new File(multiMediaDir + File.separator + "videos" + File.separator + fileName.trim());
}
downloadFile(file, response);
} else {
writeFileNotFound(response);
File file = multimediaService.retrieveFile(multiMediaDir + File.separator + "images" + File.separator + fileName.trim());
if (file != null) {
if (fileName.endsWith("mp4")) {
file = new File(multiMediaDir + File.separator + "videos" + File.separator + fileName.trim());
}
downloadFile(file, response);
} else {
writeFileNotFound(response);
}
} catch (Exception e) {
logger.error("", e);
Expand All @@ -118,21 +104,17 @@ public void downloadFileWithAuth(HttpServletResponse response, @PathVariable("fi
*
* @param response
* @param baseEntityId
* @param userName
* @param password
* @throws Exception
*/
@RequestMapping(value = "/profileimage/{baseEntityId}", method = RequestMethod.GET)
public void downloadFileByClientId(HttpServletResponse response, @PathVariable("baseEntityId") String baseEntityId,
@RequestHeader(value = "username") String userName,
@RequestHeader(value = "password") String password, HttpServletRequest request) {

HttpServletRequest request) {
try {
if (hasSpecialCharacters(baseEntityId)) {
specialCharactersError(response, ENTITY_ID_ERROR_MESSAGE);
return;
}
downloadFileWithAuth(baseEntityId, userName, password, request, response);
downloadFileByBaseEntityId(baseEntityId, response);
} catch (Exception e) {
logger.error("Exception occurred in downloading file by client ID ", e);
}
Expand All @@ -148,20 +130,13 @@ public void downloadFileByClientId(HttpServletResponse response, @PathVariable("
* @param entityId
* @param contentType
* @param fileCategory
* @param userName
* @param password
*/
@RequestMapping(value = "/media/{entity-id}", method = RequestMethod.GET)
public void downloadFiles(HttpServletResponse response,
HttpServletRequest request,
@PathVariable("entity-id") String entityId,
@RequestParam(value = "content-type", required = false) String contentType,
@RequestParam(value = "file-category", required = false) String fileCategory,
@RequestHeader(value = "username") String userName,
@RequestHeader(value = "password") String password) {


if (!authenticate(userName, password, request).isAuthenticated()) { return; }
@RequestParam(value = "file-category", required = false) String fileCategory) {

if (!TextUtils.isBlank(fileCategory) && MULTI_VERSION.equals(fileCategory)) {
List<Multimedia> multimediaFiles = multimediaService
Expand All @@ -177,7 +152,7 @@ public void downloadFiles(HttpServletResponse response,
}
} else {
// default to single profile image retrieval logic
downloadFileWithAuth(entityId, userName, password, request, response);
downloadFileByBaseEntityId(entityId, response);
}
}

Expand Down Expand Up @@ -217,27 +192,23 @@ public ResponseEntity<String> uploadFiles(@RequestParam("anm-id") String provide
}

/**
* Downloads file on successful authentication
* Download file by baseEntityId
*
* @param baseEntityId
* @param userName
* @param password
* @param request
* @param response
*/
private void downloadFileWithAuth(String baseEntityId, String userName, String password, HttpServletRequest request,
HttpServletResponse response) {
private void downloadFileByBaseEntityId(String baseEntityId, HttpServletResponse response) {
try {
if (!authenticate(userName, password, request).isAuthenticated()) { return; }
MultimediaDTO multimediaDTO = new MultimediaDTO(baseEntityId, "", "image/jpeg", null, "");

File file = multimediaService.retrieveFile(multimediaService.getFileManager().getMultimediaFilePath(multimediaDTO, baseEntityId));
if (file != null) {
downloadFile(file, response);
} else {
writeFileNotFound(response);
}
} catch (Exception e) {
logger.error("", e);
logger.error("File download failed", e);
}
}

Expand All @@ -256,8 +227,7 @@ private Authentication authenticate(String userName, String password, HttpServle
* @throws Exception
*/
private void downloadFile(File file, HttpServletResponse response) throws Exception {

if(hasSpecialCharacters(file.getName())) {
if (hasSpecialCharacters(file.getName())) {
specialCharactersError(response, FILE_NAME_ERROR_MESSAGE);
return;
}
Expand Down
11 changes: 4 additions & 7 deletions src/test/java/org/opensrp/TestRedisConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,22 +22,19 @@
*/
@Configuration
@EnableCaching
public class TestRedisConfig {
public class TestRedisConfig extends TestRedisInstance {

@Value("#{opensrp['redis.host']}")
private String redisHost;

@Value("#{opensrp['redis.port']}")
private int redisPort;


private int redisDatabase = 0;

@Value("#{opensrp['redis.pool.max.connections']}")
private int redisMaxConnections = 0;


private RedisStandaloneConfiguration redisStandaloneConfiguration() {
RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration(redisHost, redisPort);
int port = TestRedisInstance.redisContainer.getMappedPort(TestRedisInstance.DOCKER_EXPOSE_PORT);
RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration(redisHost, port);
redisStandaloneConfiguration.setDatabase(redisDatabase);
return redisStandaloneConfiguration;
}
Expand Down
16 changes: 16 additions & 0 deletions src/test/java/org/opensrp/TestRedisInstance.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package org.opensrp;

import org.testcontainers.containers.GenericContainer;
import org.testcontainers.utility.DockerImageName;

public abstract class TestRedisInstance {
private static final String DOCKER_IMAGE_NAME = "redis:7-alpine";
protected static final int DOCKER_EXPOSE_PORT = 6379;

protected static final GenericContainer<?> redisContainer = new GenericContainer<>(DockerImageName.parse(DOCKER_IMAGE_NAME))
.withExposedPorts(DOCKER_EXPOSE_PORT);

static {
redisContainer.start();
}
}
Loading