Skip to content

Commit

Permalink
BAH-2962 | Created a script to run on sms-service startup
Browse files Browse the repository at this point in the history
* Added token generation script to run on docker up of sms-service

* Refactored test cases

* Update TokenValidator.java

* Update TokenValidator.java

* BAH-2962-token | added spring-security filter to validate token.

* Update SMSController.java

* BAH-2962-token | added spring-security filter to validate token.

* BAH-2962-token | added spring-security filter to validate token.

---------

Co-authored-by: atish160384 <atish@beehyv.com>
  • Loading branch information
anubhavBeehyv and atishbeehyv123 authored Nov 21, 2023
1 parent 3f6c17b commit 2a86a8f
Show file tree
Hide file tree
Showing 9 changed files with 239 additions and 29 deletions.
2 changes: 2 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ task bootRunLocal {
bootRunLocal.finalizedBy bootRun

dependencies {
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'io.jsonwebtoken:jjwt-api:0.11.2'
runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.2'
runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.11.2'
Expand Down
8 changes: 6 additions & 2 deletions package/docker/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
FROM amazoncorretto:11.0.18
VOLUME /tmp
RUN yum install openssl -y
COPY package/docker/generate_token.sh /home/
COPY package/docker/sms-startup.sh /
RUN chmod +x /home/generate_token.sh /sms-startup.sh
COPY build/libs/* app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "/app.jar"]
VOLUME /tmp
CMD ["./sms-startup.sh"]
34 changes: 34 additions & 0 deletions package/docker/generate_token.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
#!/bin/bash

header='{"alg": "RS256", "typ": "JWT"}'
token_dir="/opt/tokens"
private_key_path="/opt/private_key.pkcs8"
public_key_path="/opt/public_key.crt"

if [ ! -f "$private_key_path" ]; then
private_key_tempfile=$(mktemp)
openssl genrsa -out $private_key_tempfile 2048
openssl pkcs8 -topk8 -inform PEM -outform PEM -nocrypt -in $private_key_tempfile -out $private_key_path
rm $private_key_tempfile
fi

if [ ! -f "$public_key_path" ]; then
openssl rsa -pubout -in $private_key_path -out $public_key_path
fi

mkdir -p $token_dir

token_file="$token_dir/sms-communications-token.txt"
claims='{"user": "bahmni-emr", "token_id": "'$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 10 | head -n 1)'"}'

encoded_header=$(echo -n $header | base64 | tr '+/' '-_' | tr -d '=')
encoded_payload=$(echo -n $claims | base64 | tr '+/' '-_' | tr -d '=')

data_to_sign="${encoded_header}.${encoded_payload}"

signature=$(echo -n "$data_to_sign" | openssl dgst -sha256 -sign $private_key_path | base64 | tr '+/' '-_' | tr -d '=')

jwt_token="${data_to_sign}.${signature}"
jwt_token=$(echo -n "$jwt_token" | tr -d '\n')

echo "$jwt_token" > $token_file
3 changes: 3 additions & 0 deletions package/docker/sms-startup.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#!/bin/bash
./home/generate_token.sh &
java -jar app.jar
26 changes: 4 additions & 22 deletions src/main/java/org/bahmni/sms/web/SMSController.java
Original file line number Diff line number Diff line change
@@ -1,12 +1,8 @@
package org.bahmni.sms.web;

import lombok.AllArgsConstructor;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.bahmni.sms.SMSSender;
import org.bahmni.sms.model.SMSContract;
import org.bahmni.sms.web.security.OpenMRSAuthenticator;
import org.bahmni.sms.web.security.TokenValidator;
import org.springframework.http.HttpHeaders;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
Expand All @@ -15,27 +11,13 @@
@RequestMapping(value = "/notification/sms")
@AllArgsConstructor
public class SMSController {


private final SMSSender smsSender;
private OpenMRSAuthenticator authenticator;

@RequestMapping(method = RequestMethod.POST)
@ResponseBody
public ResponseEntity sendSMS(@RequestBody SMSContract smsContract) throws Exception {
boolean isValidToken = false;
// if (authorizationHeader != null && authorizationHeader.startsWith("Bearer ")) {
// String token = authorizationHeader.substring(7);
// isValidToken = TokenValidator.validateToken(token);
// }
//
// if (isValidToken) {
// return smsSender.send(smsContract.getPhoneNumber(), smsContract.getMessage());
// } else {
// ResponseEntity authenticationResponse = authenticator.authenticate(cookie);
// if (authenticationResponse.getStatusCode().is2xxSuccessful()) {
return smsSender.send(smsContract.getPhoneNumber(), smsContract.getMessage());
// } else {
// return authenticationResponse;
// }
// }
public ResponseEntity sendSMS(@RequestBody SMSContract smsContract, @RequestHeader(value = HttpHeaders.AUTHORIZATION, required = false) String authorizationHeader) throws Exception {
return smsSender.send(smsContract.getPhoneNumber(), smsContract.getMessage());
}
}
33 changes: 33 additions & 0 deletions src/main/java/org/bahmni/sms/web/config/SecurityConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package org.bahmni.sms.web.config;

import org.bahmni.sms.web.security.TokenValidator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;


@Configuration
@EnableWebSecurity
public class SecurityConfig {

@Autowired
TokenValidator tokenValidator;
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception {

httpSecurity
.csrf()
.disable()
.authorizeHttpRequests((authorize) -> authorize
.antMatchers("/*")
.permitAll())
.addFilterBefore(new TokenValidatorFilter(tokenValidator), BasicAuthenticationFilter.class);

return httpSecurity.build();
}
}

40 changes: 40 additions & 0 deletions src/main/java/org/bahmni/sms/web/config/TokenValidatorFilter.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package org.bahmni.sms.web.config;

import org.bahmni.sms.web.security.TokenValidator;
import org.springframework.web.filter.OncePerRequestFilter;
import java.io.IOException;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;


public class TokenValidatorFilter extends OncePerRequestFilter {

private TokenValidator tokenValidator;


public TokenValidatorFilter(TokenValidator tokenValidator) {
this.tokenValidator = tokenValidator;
}




@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
boolean isValidToken = false;

String AuthHeader = request.getHeader("Authorization");
if (AuthHeader != null && AuthHeader.startsWith("Bearer ")) {
String token = AuthHeader.substring(7);
isValidToken = tokenValidator.validateToken(token);
}
if (!isValidToken) {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Invalid token");
return;
}

filterChain.doFilter(request,response);
}
}
12 changes: 7 additions & 5 deletions src/main/java/org/bahmni/sms/web/security/TokenValidator.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package org.bahmni.sms.web.security;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwt;
import io.jsonwebtoken.Jwts;
import org.springframework.stereotype.Service;

Expand All @@ -16,7 +15,10 @@
import java.util.Base64;
@Service
public class TokenValidator {
private static final String PUBLIC_KEY_FILE_PATH = "/../tmp/public_key.crt";


private static final String PUBLIC_KEY_FILE_PATH = "/../opt/public_key.crt";


private static Key loadPublicKey() throws IOException, NoSuchAlgorithmException, InvalidKeySpecException {
String publicKeyString = Files.readString(Paths.get(PUBLIC_KEY_FILE_PATH));
Expand All @@ -34,7 +36,7 @@ private static Key loadPublicKey() throws IOException, NoSuchAlgorithmException,



public static boolean validateToken(String token) {
public boolean validateToken(String token) {
try {
Key publicKey = loadPublicKey();

Expand All @@ -44,10 +46,10 @@ public static boolean validateToken(String token) {
.parseClaimsJws(token)
.getBody();

return ((String) claims.get("user")).equals("Communications");
return ((String) claims.get("user")).equals("bahmni-emr");
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
}
}
110 changes: 110 additions & 0 deletions src/test/java/org/bahmni/sms/web/SMSControllerTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
package org.bahmni.sms.web;

import org.apache.http.HttpResponse;
import org.apache.http.HttpVersion;
import org.apache.http.impl.DefaultHttpResponseFactory;
import org.apache.http.message.BasicStatusLine;
import org.bahmni.sms.SMSSender;
import org.bahmni.sms.web.security.OpenMRSAuthenticator;
import org.bahmni.sms.web.security.TokenValidator;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mockito;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.reactive.AutoConfigureWebTestClient;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.web.reactive.server.WebTestClient;

import static org.mockito.Mockito.times;
import static org.mockito.Mockito.when;

@ExtendWith(SpringExtension.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@AutoConfigureWebTestClient
class SMSControllerTest {
@MockBean
private SMSSender smsSender;

@Autowired
private WebTestClient webClient;

@MockBean
private OpenMRSAuthenticator authenticator;

@MockBean
private TokenValidator tokenValidator;


@Test
public void shouldAcceptTheSMSRequest() {
Object requestBody = "{" +
"\"phoneNumber\":\"+919999999999\"," +
"\"message\":\"hello\"" +
"}";
when(tokenValidator.validateToken("dummy")).thenReturn(Boolean.valueOf("true"));
webClient.post()
.uri("/notification/sms")
.contentType(MediaType.APPLICATION_JSON)
.bodyValue(requestBody)
.header("Authorization","Bearer dummy")
.exchange()
.expectStatus()
.is2xxSuccessful();
}

@Test
public void shouldThrowBadRequest() {
Object requestBody = "{" +
"'phoneNumber':'+919999999999'," +
"'message':'hello'" +
"}";
when(tokenValidator.validateToken("dummy")).thenReturn(Boolean.valueOf("true"));
webClient.post()
.uri("/notification/sms")
.contentType(MediaType.APPLICATION_JSON)
.bodyValue(requestBody)
.header("Authorization","Bearer dummy")
.exchange()
.expectStatus()
.isBadRequest();
}

@Test
public void shouldCallSend() {
Object requestBody = "{" +
"\"message\":\"hello\"," +
"\"phoneNumber\":\"919999999999\"" +
"}";
when(tokenValidator.validateToken("dummy")).thenReturn(Boolean.valueOf("true"));
webClient.post()
.uri("/notification/sms")
.contentType(MediaType.APPLICATION_JSON)
.bodyValue(requestBody)
.header("Authorization","Bearer dummy")
.exchange();

Mockito.verify(smsSender, times(1)).send("919999999999", "hello");
}

@Test
public void shouldThrowUnAuthorizedWhenAuthenticationFailed() {
Object requestBody = "{" +
"\"message\":\"hello\"," +
"\"phoneNumber\":\"919999999999\"" +
"}";
when(tokenValidator.validateToken("dummy")).thenReturn(Boolean.valueOf("false"));
webClient.post()
.uri("/notification/sms")
.contentType(MediaType.APPLICATION_JSON)
.bodyValue(requestBody)
.header("Authorization","Bearer dummy")
.exchange()
.expectStatus()
.isUnauthorized();
}
}

0 comments on commit 2a86a8f

Please sign in to comment.