Skip to content

Commit

Permalink
Merge pull request #449 from bcgov/feature/DPS-232
Browse files Browse the repository at this point in the history
Feature/dps 232
  • Loading branch information
KFloodBCGov authored Oct 30, 2024
2 parents 3bb0b21 + 23a2858 commit 45f3fd5
Show file tree
Hide file tree
Showing 43 changed files with 1,987 additions and 683 deletions.
632 changes: 325 additions & 307 deletions src/dps-email-poller/pom.xml

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package ca.bc.gov.open.pssg.rsbc.dps.dpsemailpoller.email;

public class DpsMSGraphException extends RuntimeException {

public DpsMSGraphException(String message) {
super(message);
}

public DpsMSGraphException(String message, Throwable cause) {
super(message, cause);
}
}


Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package ca.bc.gov.open.pssg.rsbc.dps.dpsemailpoller.email.api;

import ca.bc.gov.open.pssg.rsbc.dps.dpsemailpoller.email.DpsEmailException;
import ca.bc.gov.open.pssg.rsbc.dps.dpsemailpoller.email.configuration.EmailProperties;
import ca.bc.gov.open.pssg.rsbc.dps.dpsemailpoller.email.models.DpsEmailResponse;
import ca.bc.gov.open.pssg.rsbc.dps.dpsemailpoller.email.services.EmailService;
import ca.bc.gov.open.pssg.rsbc.dps.dpsemailpoller.email.services.MSGraphService;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
Expand All @@ -22,11 +24,14 @@
public class DpsEmailController {

private final Logger logger = LoggerFactory.getLogger(this.getClass());

private final MSGraphService graphService;
private final EmailService emailService;
private final EmailProperties emailProperties;

public DpsEmailController(EmailService emailService) {
public DpsEmailController(EmailService emailService, MSGraphService graphService, EmailProperties emailProperties) {
this.emailService = emailService;
this.graphService = graphService;
this.emailProperties = emailProperties;
}

@PutMapping(value = "/email/{id}/processed", produces = MediaType.APPLICATION_JSON_VALUE)
Expand All @@ -37,10 +42,16 @@ public DpsEmailController(EmailService emailService) {
@ApiOperation(value = "Mark email as processed", tags = {"DpsEmailProcessing"})
public ResponseEntity<DpsEmailResponse> Processed(@PathVariable String id, @RequestBody DpsEmailProcessedRequest dpsEmailProcessedRequest) {
try {
emailService.moveToProcessedFolder(new String(Base64.getDecoder().decode(id)));
logger.info("message successfully moved to processed folder, id: [{}]", dpsEmailProcessedRequest.getCorrelationId());
if (emailProperties.isMSGraph()) {
graphService.moveToFolder(new String(Base64.getDecoder().decode(id)), emailProperties.getProcessedFolder(), true);
}
else {
emailService.moveToProcessedFolder(new String(Base64.getDecoder().decode(id)));
}
logger.info("Message {} successfully moved to processed folder.", dpsEmailProcessedRequest.getCorrelationId());
return new ResponseEntity<>(DpsEmailResponse.Success(), HttpStatus.OK);
} catch (DpsEmailException ex) {
}
catch (DpsEmailException ex) {
return new ResponseEntity<>(DpsEmailResponse.Error(ex.getMessage()), HttpStatus.BAD_REQUEST);
}
}
Expand All @@ -53,10 +64,16 @@ public ResponseEntity<DpsEmailResponse> Processed(@PathVariable String id, @Requ
@ApiOperation(value = "Mark email as having an error in processing", tags = {"DpsEmailProcessing"})
public ResponseEntity<DpsEmailResponse> ProcessFailed(@PathVariable String id, @RequestBody DpsEmailProcessedRequest dpsEmailProcessedRequest) {
try {
emailService.moveToErrorFolder(new String(Base64.getDecoder().decode(id)));
logger.info("message successfully moved to error folder, id: [{}]", dpsEmailProcessedRequest.getCorrelationId());
if (emailProperties.isMSGraph()) {
graphService.moveToFolder(new String(Base64.getDecoder().decode(id)), emailProperties.getErrorFolder(), true);
}
else {
emailService.moveToErrorFolder(new String(Base64.getDecoder().decode(id)));
}
logger.info("Message {} successfully moved to error folder.", dpsEmailProcessedRequest.getCorrelationId());
return new ResponseEntity<>(DpsEmailResponse.Success(), HttpStatus.OK);
} catch (DpsEmailException ex) {
}
catch (DpsEmailException ex) {
return new ResponseEntity<>(DpsEmailResponse.Error(ex.getMessage()), HttpStatus.BAD_REQUEST);
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package ca.bc.gov.open.pssg.rsbc.dps.dpsemailpoller.email.component;

import ca.bc.gov.open.pssg.rsbc.dps.dpsemailpoller.email.configuration.MSGraphServiceProperties;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import com.azure.identity.ClientSecretCredential;
import com.azure.identity.ClientSecretCredentialBuilder;
import com.microsoft.graph.serviceclient.GraphServiceClient;
import jakarta.annotation.PostConstruct;

/**
*
* Provides a GraphServiceClient
*
* Reference: https://learn.microsoft.com/en-us/graph/sdks/choose-authentication-providers?tabs=java#client-credentials-provider
*
* If the expiry date of the Secret key is coming up due, send a notification.
*
*/
@Component
public class GraphServiceClientComp {

private static final Logger logger = LoggerFactory.getLogger(GraphServiceClientComp.class);

private String clientId;
private String tenantId;
private String clientSecret;

private GraphServiceClient graphClient = null;

private MSGraphServiceProperties props;

public GraphServiceClientComp(MSGraphServiceProperties props) {
this.props = props;
}

@PostConstruct
private void postConstruct() {

try {

this.clientId = props.getMsgClientId();
this.tenantId = props.getMsgAuthority();
this.clientSecret = props.getMsgSecretKey();

// The client credentials flow requires that you request the
// /.default scope, and pre-configure your permissions on the
// app registration in Azure. An administrator must grant consent
// to those permissions beforehand.
final String[] scopes = new String[] { props.getMsgEndpointHost() };

final ClientSecretCredential credential = new ClientSecretCredentialBuilder().clientId(clientId)
.tenantId(tenantId).clientSecret(clientSecret).build();

if (null == scopes || null == credential) {
throw new Exception("Unexpected error");
}

this.graphClient = new GraphServiceClient(credential, scopes);

logger.info("MS Graph Service Client created.");

} catch (Exception ex) {

logger.error("Unable to create MS Graph Service client. Check CLientId, Authority and/or Secret. Err: " + ex.getMessage());
}

}

public GraphServiceClient getGraphClient() {
return graphClient;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,9 @@ public DpsMetadataMapper dpsMetadataMapper(DpsEmailParser dpsEmailParser) {
return new DpsMetadataMapperImpl(dpsEmailParser);
}

@Bean
public DpsMSGraphMetadataMapper dpsMSGraphMetadataMapper(DpsEmailParser dpsEmailParser) {
return new DpsMSGraphMetadataMapperImpl(dpsEmailParser);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,23 @@ public class EmailProperties {
private Integer emailsPerBatch;
private String errorFolder;
private String processingFolder;

private String processedFolder;
private String graphExchangeSwitch;
public boolean isMSGraph() {
return getGraphExchangeSwitch().equalsIgnoreCase("Graph") ;
}
public boolean isMSExchange() {
return !getGraphExchangeSwitch().equalsIgnoreCase("Graph") ;
}


public String getGraphExchangeSwitch() {
return graphExchangeSwitch;
}

public void setGraphExchangeSwitch(String graphExchangeSwitch) {
this.graphExchangeSwitch = graphExchangeSwitch;
}
public String getCron() {
return cron;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package ca.bc.gov.open.pssg.rsbc.dps.dpsemailpoller.email.configuration;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;

@Configuration
@ConfigurationProperties()
public class MSGraphServiceProperties {

@Value("${msg.clientId}")
private String msgClientId;

@Value("${msg.authority}")
private String msgAuthority;

@Value("${msg.secretKey}")
private String msgSecretKey;

@Value("${msg.email.account}")
private String msgEmailAccount;

@Value("${msg.endpointHost}")
private String msgEndpointHost;

@Value("${msg.email.per.batch}")
private int msgEmailPerBatch;

// Number of days before MS Graph API Secret Key Expiry Date to start sending notifications.
@Value("${msg.secretKey.expiry.threshold}")
private String msgSecretKeyExpiryThreshold;

public String getMsgClientId() {
return msgClientId;
}

public String getMsgAuthority() {
return msgAuthority;
}

public String getMsgSecretKey() {
return msgSecretKey;
}

public String getMsgEndpointHost() {
return msgEndpointHost;
}

public String getMsgEmailAccount() {
return msgEmailAccount;
}

public int getMsgEmailPerBatch() {
return msgEmailPerBatch;
}

public String getMsgSecretKeyExpiryThreshold() {
return msgSecretKeyExpiryThreshold;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package ca.bc.gov.open.pssg.rsbc.dps.dpsemailpoller.email.services;

import ca.bc.gov.open.pssg.rsbc.models.DpsFileInfo;
import ca.bc.gov.open.pssg.rsbc.models.DpsMetadata;
import com.microsoft.graph.models.Message;

public interface DpsMSGraphMetadataMapper {
DpsMetadata map(Message emailMessage, DpsFileInfo dpsFileInfo, String tenant);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package ca.bc.gov.open.pssg.rsbc.dps.dpsemailpoller.email.services;

import ca.bc.gov.open.pssg.rsbc.dps.dpsemailpoller.email.DpsMSGraphException;
import ca.bc.gov.open.pssg.rsbc.dps.dpsemailpoller.email.models.DpsEmailContent;
import ca.bc.gov.open.pssg.rsbc.models.DpsFileInfo;
import ca.bc.gov.open.pssg.rsbc.models.DpsMetadata;
import com.microsoft.graph.models.EmailAddress;
import com.microsoft.graph.models.Message;

import java.util.Date;

public class DpsMSGraphMetadataMapperImpl implements DpsMSGraphMetadataMapper {


public static final String INBOUND = "inbound";
public static final String FAX = "FAX";
private final DpsEmailParser dpsEmailParser;

public DpsMSGraphMetadataMapperImpl(DpsEmailParser dpsEmailParser) {
this.dpsEmailParser = dpsEmailParser;
}

@Override
public DpsMetadata map(Message emailMessage, DpsFileInfo dpsFileInfo, String tenant) {

try {

String body = getEmailBodyText(emailMessage);

DpsEmailContent dpsEmailContent = dpsEmailParser.parseEmail(body);

return new DpsMetadata.Builder()
.withEmailId(emailMessage.getId())
.withApplicationID(tenant)
.withDirection(INBOUND)
.withInboundChannelType(FAX)
.withInboundChannelID(getFirstRecipientEmailLocalPart(emailMessage))
.withTo(getFirstRecipientEmailAddress(emailMessage))
.withFrom(getFromEmailAddress(emailMessage))
.withSubject(emailMessage.getSubject())
.withRecvdate(Date.from(emailMessage.getReceivedDateTime().toInstant()))
.withSentdate(Date.from(emailMessage.getSentDateTime().toInstant()))
.withBody(emailMessage.getBody().getContent())
.withFaxJobID(dpsEmailContent.getJobId())
.withOriginatingNumber(dpsEmailContent.getPhoneNumer())
.withNumberOfPages(dpsEmailContent.getPageCount())
.withFileInfo(dpsFileInfo)
.build();

} catch (DpsMSGraphException e) {
throw new DpsMSGraphException("exception");
}

}

private String getFirstRecipientEmailLocalPart(Message emailMessage) throws DpsMSGraphException {
String email = getFirstRecipientEmailAddress(emailMessage);
return email.substring(0, email.indexOf('@'));
}

private String getFirstRecipientEmailAddress(Message emailMessage) throws DpsMSGraphException {
return getFirstRecipients(emailMessage).getAddress();

}

private String getFromEmailAddress(Message emailMessage) throws DpsMSGraphException {
if (emailMessage.getFrom() == null) throw new DpsMSGraphException("From is null.");
return emailMessage.getFrom().getEmailAddress().getAddress();
}


private EmailAddress getFirstRecipients(Message emailMessage) throws DpsMSGraphException {
if (emailMessage.getToRecipients() == null) throw new DpsMSGraphException("Recipient is null.");
if (emailMessage.getToRecipients().isEmpty()) throw new DpsMSGraphException("Recipient is null.");
return emailMessage.getToRecipients().get(0).getEmailAddress();
}


private String getEmailBodyText(Message emailMessage) throws DpsMSGraphException {
return emailMessage.getBody().getContent();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package ca.bc.gov.open.pssg.rsbc.dps.dpsemailpoller.email.services;

import ca.bc.gov.open.pssg.rsbc.dps.dpsemailpoller.email.DpsMSGraphException;
import com.microsoft.graph.models.Attachment;
import com.microsoft.graph.models.Message;

import java.util.List;

public interface MSGraphService {

public List<Message> GetMessages(String filter) throws DpsMSGraphException;
public List<Attachment> getAttachments(Message message) throws DpsMSGraphException;
public byte[] getAttachmentContent(Attachment attachment) throws DpsMSGraphException;

public void sendMessage(Message message, boolean saveToSentItems) throws DpsMSGraphException;
public void deleteMessage(Message message) throws DpsMSGraphException;
public String getPasswordCredentialsExpiryDate() throws DpsMSGraphException;

public Message moveToFolder(String messageId, String folderName, boolean createIfNotExist) throws DpsMSGraphException;
}
Loading

0 comments on commit 45f3fd5

Please sign in to comment.