diff --git a/internal/scan-manager/core/pom.xml b/internal/scan-manager/core/pom.xml index bbed9328..2a969b9a 100644 --- a/internal/scan-manager/core/pom.xml +++ b/internal/scan-manager/core/pom.xml @@ -170,6 +170,11 @@ powermock-api-mockito test + + com.sun.mail + javax.mail + ${com.sun.mail.javax.mail} + @@ -235,6 +240,7 @@ 5.4.14.Final 8081 + 1.6.2 diff --git a/internal/scan-manager/core/src/main/java/org/wso2/security/tools/scanmanager/core/config/ScanManagerConfiguration.java b/internal/scan-manager/core/src/main/java/org/wso2/security/tools/scanmanager/core/config/ScanManagerConfiguration.java index cf55f97a..64095b40 100644 --- a/internal/scan-manager/core/src/main/java/org/wso2/security/tools/scanmanager/core/config/ScanManagerConfiguration.java +++ b/internal/scan-manager/core/src/main/java/org/wso2/security/tools/scanmanager/core/config/ScanManagerConfiguration.java @@ -19,6 +19,8 @@ import org.wso2.security.tools.scanmanager.core.exception.ScanManagerException; +import java.util.Arrays; +import java.util.List; import java.util.Map; import static org.wso2.security.tools.scanmanager.core.util.Constants.DEFAULT_LOG_PAGE_SIZE; @@ -36,12 +38,28 @@ public class ScanManagerConfiguration { private Integer scanPageSize; private Integer logPageSize; + // Email notification related configurations + private Boolean isNotificationEnabled; + private String notificationSubject; + private String smtpServerHost; + private Integer smtpServerPort; + private String smtpUserName; + private String emailFromaddress; + private List emailCCaddress; + private static final String SCAN_MANAGER_HOST_KEY = "scanManagerHost"; private static final String SCAN_MANAGER_PORT_KEY = "scanManagerPort"; private static final String SCANNER_SERVICE_HOST_KEY = "scannerServiceHost"; private static final String SCANNER_SERVICE_PORT_KEY = "scannerServicePort"; private static final String SCAN_PAGE_SIZE = "scanPageSize"; private static final String LOG_PAGE_SIZE = "logPageSize"; + private static final String SMTP_SERVERHOST_KEY = "smtpServerHost"; + private static final String SMTP_SERVERPORT_KEY = "smtpServerPort"; + private static final String SMTP_USERNAME = "smtpUsername"; + private static final String SMTP_EMAIL_FROM_ADDRESS = "emailFromaddress"; + private static final String SMTP_EMAIL_CC_ADDRESS = "emailCCaddress"; + private static final String IS_NOTIFICATION_ENABLED = "isNotificationEnabled"; + private static final String NOTIFICATION_SUBJECT = "notificationSubject"; private static final ScanManagerConfiguration scanManagerConfiguration = new ScanManagerConfiguration(); @@ -63,6 +81,13 @@ public void initScanConfiguration(Map configObjectMap) throws Sc Integer scanManagerPort = (Integer) configObjectMap.get(SCAN_MANAGER_PORT_KEY); String scannerServiceHost = (String) configObjectMap.get(SCANNER_SERVICE_HOST_KEY); Integer scannerServicePort = (Integer) configObjectMap.get(SCANNER_SERVICE_PORT_KEY); + String smtpServerHost = (String) configObjectMap.get(SMTP_SERVERHOST_KEY); + Integer smtpServerPort = (Integer) configObjectMap.get(SMTP_SERVERPORT_KEY); + String smtpUserName = (String) configObjectMap.get(SMTP_USERNAME); + String smtpEmailFromAddress = (String) configObjectMap.get(SMTP_EMAIL_FROM_ADDRESS); + String smtpEmailCCAddress = (String) configObjectMap.get(SMTP_EMAIL_CC_ADDRESS); + Boolean isNotificationEnabled = (Boolean) configObjectMap.get(IS_NOTIFICATION_ENABLED); + String notificiationSubject = (String) configObjectMap.get(NOTIFICATION_SUBJECT); if (scanManagerHost != null) { this.scanManagerHost = scanManagerHost; @@ -77,17 +102,36 @@ public void initScanConfiguration(Map configObjectMap) throws Sc if (scannerServiceHost != null) { this.scannerServiceHost = scannerServiceHost; } else { - throw new ScanManagerException("Unable to find scaner service host configuration"); + throw new ScanManagerException("Unable to find scanner service host configuration"); } if (scannerServicePort != null) { this.scannerServicePort = scannerServicePort; } else { throw new ScanManagerException("Unable to find scaner service port configuration"); } + if (smtpServerHost != null || smtpServerPort != null || smtpUserName != null) { + this.smtpServerHost = smtpServerHost; + this.smtpServerPort = smtpServerPort; + this.smtpUserName = smtpUserName; + } else { + throw new ScanManagerException("Unable to find email notification related configuration"); + } + if (smtpEmailFromAddress != null) { + this.emailFromaddress = smtpEmailFromAddress; + } else { + throw new ScanManagerException("Unable to find valid email address of sender"); + } + + // CC email addresses are not mandatory. + if (smtpEmailCCAddress != null) { + this.emailCCaddress = Arrays.asList(smtpEmailCCAddress.trim().split(",")); + } // Not mandatory as there are default values. this.scanPageSize = (Integer) configObjectMap.get(SCAN_PAGE_SIZE); this.logPageSize = (Integer) configObjectMap.get(LOG_PAGE_SIZE); + this.isNotificationEnabled = isNotificationEnabled; + this.notificationSubject = notificiationSubject; } public String getScanManagerHost() { @@ -119,4 +163,32 @@ public Integer getLogPageSize() { } return logPageSize; } + + public Boolean getNotificationEnabled() { + return isNotificationEnabled; + } + + public String getNotificationSubject() { + return notificationSubject; + } + + public String getSmtpServerHost() { + return smtpServerHost; + } + + public Integer getSmtpServerPort() { + return smtpServerPort; + } + + public String getSmtpUserName() { + return smtpUserName; + } + + public String getEmailFromaddress() { + return emailFromaddress; + } + + public List getEmailCCaddress() { + return emailCCaddress; + } } diff --git a/internal/scan-manager/core/src/main/java/org/wso2/security/tools/scanmanager/core/controller/ScanController.java b/internal/scan-manager/core/src/main/java/org/wso2/security/tools/scanmanager/core/controller/ScanController.java index 63299614..0e8f0ed6 100644 --- a/internal/scan-manager/core/src/main/java/org/wso2/security/tools/scanmanager/core/controller/ScanController.java +++ b/internal/scan-manager/core/src/main/java/org/wso2/security/tools/scanmanager/core/controller/ScanController.java @@ -166,27 +166,26 @@ private Scanner validateScanRequest(ScanManagerScanRequest scanRequest) throws I /** * Get the list of available scans by page. * - * @param page required page number + * @param product product name + * @param page required page number * @return the requested scans page */ - @GetMapping(path = "scans") - @ResponseBody - public ResponseEntity getScans(@RequestParam(name = "page", required = false) - Integer page) { + @GetMapping(path = "scans") @ResponseBody + public ResponseEntity getScans(@RequestParam("product") String product, + @RequestParam(name = "page", required = false) Integer page) { Integer scanPageSize = ScanManagerConfiguration.getInstance().getScanPageSize(); if (page == null) { page = 1; // Initialize to first page if no page number is defined. } // Internal page indexing starts at 0 - Page scansPage = scanService.getAll(page - 1, scanPageSize); - List scanExternalList = - scansPage.getContent().parallelStream() - .map(ScanExternal::new) - .collect(Collectors.toList()); - return new ResponseEntity<>(new ScanManagerScansResponse(scanExternalList, scansPage.getTotalPages(), - page, scansPage.getSize(), scansPage.hasNext(), scansPage.hasPrevious(), scansPage.isFirst(), - scansPage.isLast()), HttpStatus.OK); + Page scansPage = scanService.getScanByProduct(page - 1, scanPageSize, product); + List scanExternalList = scansPage.getContent().parallelStream().map(ScanExternal::new) + .collect(Collectors.toList()); + return new ResponseEntity<>( + new ScanManagerScansResponse(scanExternalList, scansPage.getTotalPages(), page, scansPage.getSize(), + scansPage.hasNext(), scansPage.hasPrevious(), scansPage.isFirst(), scansPage.isLast()), + HttpStatus.OK); } /** @@ -258,9 +257,8 @@ public ResponseEntity> getScansByState(@PathVariable("status" * @throws ScanManagerException when an error occurs while updating the priority of the scan * @throws ResourceNotFoundException when unable to find a scan for the given job id */ - @PostMapping(value = "scans/{jobId}") - public ResponseEntity updateScanPriority(@PathVariable("jobId") String jobId, - @RequestBody ScanPriorityUpdateRequest scanPriorityUpdateRequest) + @PostMapping(value = "scans/{jobId}") public ResponseEntity updateScanPriority(@PathVariable("jobId") String jobId, + @RequestBody ScanPriorityUpdateRequest scanPriorityUpdateRequest) throws ResourceNotFoundException, ScanManagerException { Scan scan = scanService.getByJobId(jobId); if (scan != null) { diff --git a/internal/scan-manager/core/src/main/java/org/wso2/security/tools/scanmanager/core/dao/ScanDAO.java b/internal/scan-manager/core/src/main/java/org/wso2/security/tools/scanmanager/core/dao/ScanDAO.java index 856a4b62..58e73760 100644 --- a/internal/scan-manager/core/src/main/java/org/wso2/security/tools/scanmanager/core/dao/ScanDAO.java +++ b/internal/scan-manager/core/src/main/java/org/wso2/security/tools/scanmanager/core/dao/ScanDAO.java @@ -39,10 +39,13 @@ public interface ScanDAO extends PagingAndSortingRepository { /** * Get all scans. * + * @param product product name * @param pageable page request object * @return page containing the list of requested scans */ - public Page getAllByOrderBySubmittedTimestampDesc(Pageable pageable); + @Query("select o from Scan o where o.product = :product") + public Page getScanByProductByOrderBySubmittedTimestampDesc(@Param("product") String product, + Pageable pageable); /** * Get scan by job id. @@ -112,4 +115,16 @@ public interface ScanDAO extends PagingAndSortingRepository { @Query("select o from Scan o where o.status in :statuses and o.scanner = :scanner and o.product = :product") public List getByStatusInAndScannerAndProduct(@Param("statuses") List statuses, @Param( "scanner") Scanner scanner, @Param("product") String product); + +// /** +// * Get logs by scan. +// * +// * @param product scan details +// * @param pageable page information +// * @return list of logs for a given scan +// */ +// public Page getByScanOrderByTimeStampDesc(String product, Pageable pageable); + + @Query("select o from Scan o where o.product = :product") + public List getByProduct(@Param("product") String product); } diff --git a/internal/scan-manager/core/src/main/java/org/wso2/security/tools/scanmanager/core/dao/UserDAO.java b/internal/scan-manager/core/src/main/java/org/wso2/security/tools/scanmanager/core/dao/UserDAO.java index 8f27c313..715dcd81 100644 --- a/internal/scan-manager/core/src/main/java/org/wso2/security/tools/scanmanager/core/dao/UserDAO.java +++ b/internal/scan-manager/core/src/main/java/org/wso2/security/tools/scanmanager/core/dao/UserDAO.java @@ -33,7 +33,7 @@ public interface UserDAO extends PagingAndSortingRepository { * @param id user id assigned for a user * @return user object for the given user id */ - public User getById(String id); + public User getById(int id); /** * Get User by username. diff --git a/internal/scan-manager/core/src/main/java/org/wso2/security/tools/scanmanager/core/handler/EmailNotificationHandler.java b/internal/scan-manager/core/src/main/java/org/wso2/security/tools/scanmanager/core/handler/EmailNotificationHandler.java new file mode 100644 index 00000000..5d7deedc --- /dev/null +++ b/internal/scan-manager/core/src/main/java/org/wso2/security/tools/scanmanager/core/handler/EmailNotificationHandler.java @@ -0,0 +1,141 @@ +/* + * + * Copyright (c) 2020, WSO2 Inc., WSO2 Inc. (http://www.wso2.org) All Rights Reserved. + * + * WSO2 Inc. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * / + */ + +package org.wso2.security.tools.scanmanager.core.handler; + +import org.wso2.security.tools.scanmanager.common.external.model.Scan; +import org.wso2.security.tools.scanmanager.core.config.ScanManagerConfiguration; +import org.wso2.security.tools.scanmanager.core.config.ScanMangerConfigurationBuilder; +import org.wso2.security.tools.scanmanager.core.exception.ResourceNotFoundException; +import org.wso2.security.tools.scanmanager.core.exception.ScanManagerException; +import org.wso2.security.tools.scanmanager.core.model.Email; +import org.wso2.security.tools.scanmanager.core.util.Util; + +import java.util.Arrays; +import java.util.Properties; + +import javax.mail.Authenticator; +import javax.mail.Message; +import javax.mail.MessagingException; +import javax.mail.PasswordAuthentication; +import javax.mail.Session; +import javax.mail.Transport; +import javax.mail.internet.InternetAddress; +import javax.mail.internet.MimeMessage; + +import static org.wso2.security.tools.scanmanager.core.util.Constants.SMTP_PASSWORD; + +/** + * This class provides the implementation for the email notification operations. + */ +public class EmailNotificationHandler implements NotificationHandler { + + private static final String EMAIL_TEMPLATE = "emailTemplate.html"; + + // Place holders used for email template. + private static final String EMAIL_TEMPLATE_SCAN_TITLE_PLACEHOLDER = "{scanTitle}"; + private static final String EMAIL_TEMPLATE_JOBID_PLACEHOLDER = "{jobId}"; + private static final String EMAIL_TEMPLATE_PRODUCT_NAME_PLACEHOLDER = "{productName}"; + private static final String EMAIL_TEMPLATE_USER_NAME_PLACEHOLDER = "{launchedBy}"; + private static final String EMAIL_TEMPLATE_SCAN_TYPE_PLACEHOLDER = "{ScannerId}"; + private static final String EMAIL_TEMPLATE_SCAN_STATUS_PLACEHOLDER = "{scanStatus}"; + + @Override public void sendNotification(Scan scan, String toAddress) throws ScanManagerException { + + // Set smtp configurations. + Properties props = new Properties(); + props.setProperty("mail.smtp.host", ScanManagerConfiguration.getInstance().getSmtpServerHost()); + props.setProperty("mail.smtp.port", String.valueOf(ScanManagerConfiguration.getInstance().getSmtpServerPort())); + props.setProperty("mail.smtp.auth", "true"); + props.setProperty("mail.smtp.starttls.enable", "true"); + props.setProperty("mail.smtp.ssl.trust", ScanManagerConfiguration.getInstance().getSmtpServerHost()); + props.setProperty("mail.debug", "true"); + + char[] password = ((String) ScanMangerConfigurationBuilder.getConfiguration().get(SMTP_PASSWORD)).toCharArray(); + Session session = getSession(props, password); + + // Clear credential. + Arrays.fill(password, '0'); + try { + Email email = assembleEmail(scan, toAddress); + MimeMessage message = new MimeMessage(session); + message.setFrom(new InternetAddress(ScanManagerConfiguration.getInstance().getEmailFromaddress())); + message.addRecipient(Message.RecipientType.TO, new InternetAddress(toAddress)); + + if (email.getCcList() != null && !email.getCcList().isEmpty()) { + for (String ccAddress : email.getCcList()) { + message.addRecipient(Message.RecipientType.CC, new InternetAddress((String) ccAddress)); + } + } + message.setSubject(email.getSubject()); + message.setContent(email.getBody(), "text/html"); + Transport.send(message); + } catch (MessagingException | ResourceNotFoundException e) { + throw new ScanManagerException("Mail sending failed", e); + } + } + + /** + * This method use to form complete email. + * + * @param scan object which represents scan + * @param toAddress email address of scan launcher + * @return email object + * @throws ResourceNotFoundException error occurred if email template in not found + */ + private Email assembleEmail(Scan scan, String toAddress) throws ResourceNotFoundException { + Email email = new Email(); + email.setFromAddress(ScanManagerConfiguration.getInstance().getEmailFromaddress()); + email.setCcList(ScanManagerConfiguration.getInstance().getEmailCCaddress()); + email.setSubject(ScanManagerConfiguration.getInstance().getNotificationSubject().concat(" " + scan.getJobId())); + email.setBody(buildEmailBody(scan, scan.getStatus().name(), toAddress)); + return email; + } + + /** + * This method is used to build email body. This method reads the email template file and replace the relevant + * information from scan object. + * + * @param scan object which represents scan + * @param status status of scan + * @param toAddress email address of scan launcher + * @return email body + * @throws ResourceNotFoundException error occurred if email template file is not found + */ + private String buildEmailBody(Scan scan, String status, String toAddress) throws ResourceNotFoundException { + String htmlTemplate = Util.readHTMLEmailTemplate(EMAIL_TEMPLATE); + String htmlwithContent = htmlTemplate.replace(EMAIL_TEMPLATE_SCAN_TITLE_PLACEHOLDER, scan.getName()) + .replace(EMAIL_TEMPLATE_JOBID_PLACEHOLDER, scan.getJobId()) + .replace(EMAIL_TEMPLATE_PRODUCT_NAME_PLACEHOLDER, scan.getProduct()) + .replace(EMAIL_TEMPLATE_USER_NAME_PLACEHOLDER, toAddress) + .replace(EMAIL_TEMPLATE_SCAN_TYPE_PLACEHOLDER, scan.getScanner().getName()) + .replace(EMAIL_TEMPLATE_SCAN_STATUS_PLACEHOLDER, status); + return htmlwithContent; + } + + private static Session getSession(Properties props, char[] password) { + return Session.getInstance(props, new Authenticator() { + @Override protected PasswordAuthentication getPasswordAuthentication() { + return new PasswordAuthentication(ScanManagerConfiguration.getInstance().getSmtpUserName(), + String.valueOf(password)); + } + }); + } +} diff --git a/internal/scan-manager/core/src/main/java/org/wso2/security/tools/scanmanager/core/handler/NotificationHandler.java b/internal/scan-manager/core/src/main/java/org/wso2/security/tools/scanmanager/core/handler/NotificationHandler.java new file mode 100644 index 00000000..db822eb8 --- /dev/null +++ b/internal/scan-manager/core/src/main/java/org/wso2/security/tools/scanmanager/core/handler/NotificationHandler.java @@ -0,0 +1,32 @@ +/* + * + * Copyright (c) 2020, WSO2 Inc., WSO2 Inc. (http://www.wso2.org) All Rights Reserved. + * + * WSO2 Inc. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * / + */ + +package org.wso2.security.tools.scanmanager.core.handler; + +import org.wso2.security.tools.scanmanager.common.external.model.Scan; +import org.wso2.security.tools.scanmanager.core.exception.ScanManagerException; + +/** + * This class provides an interface to handle notification upon scan job completion related operations. + */ +public interface NotificationHandler { + + public void sendNotification(Scan scan, String toAddress) throws ScanManagerException; +} diff --git a/internal/scan-manager/core/src/main/java/org/wso2/security/tools/scanmanager/core/model/Email.java b/internal/scan-manager/core/src/main/java/org/wso2/security/tools/scanmanager/core/model/Email.java new file mode 100644 index 00000000..f33e99dd --- /dev/null +++ b/internal/scan-manager/core/src/main/java/org/wso2/security/tools/scanmanager/core/model/Email.java @@ -0,0 +1,75 @@ +/* + * + * Copyright (c) 2020, WSO2 Inc., WSO2 Inc. (http://www.wso2.org) All Rights Reserved. + * + * WSO2 Inc. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * / + */ + +package org.wso2.security.tools.scanmanager.core.model; + +import java.util.List; + +/** + * Represents an Email object + */ +public class Email { + + String fromAddress; + String subject; + String body; + List toList; + List ccList; + + public String getFromAddress() { + return fromAddress; + } + + public void setFromAddress(String fromAddress) { + this.fromAddress = fromAddress; + } + + public String getSubject() { + return subject; + } + + public void setSubject(String subject) { + this.subject = subject; + } + + public String getBody() { + return body; + } + + public void setBody(String body) { + this.body = body; + } + + public List getToList() { + return toList; + } + + public void setToList(List toList) { + this.toList = toList; + } + + public List getCcList() { + return ccList; + } + + public void setCcList(List ccList) { + this.ccList = ccList; + } +} diff --git a/internal/scan-manager/core/src/main/java/org/wso2/security/tools/scanmanager/core/service/CallbackServiceImpl.java b/internal/scan-manager/core/src/main/java/org/wso2/security/tools/scanmanager/core/service/CallbackServiceImpl.java index 6010d7d2..a17b8a2d 100644 --- a/internal/scan-manager/core/src/main/java/org/wso2/security/tools/scanmanager/core/service/CallbackServiceImpl.java +++ b/internal/scan-manager/core/src/main/java/org/wso2/security/tools/scanmanager/core/service/CallbackServiceImpl.java @@ -35,11 +35,14 @@ public class CallbackServiceImpl implements CallbackService { private ScanEngineService scanEngineService; private ScanService scanService; + private UserService userService; @Autowired - public CallbackServiceImpl(ScanEngineService scanEngineService, ScanService scanService) { + public CallbackServiceImpl(ScanEngineService scanEngineService, ScanService scanService, + UserService userService) { this.scanEngineService = scanEngineService; this.scanService = scanService; + this.userService = userService; } @Override @@ -51,36 +54,42 @@ public void updateScan(Scan scan, ScanStatus scanStatus, String scannerScanId, S // synchronized block. Scan newScanObject = scanService.getByJobId(scan.getJobId()); switch (scanStatus) { - case RUNNING: - if (newScanObject.getStatus() == ScanStatus.SUBMITTED) { - Timestamp timestamp = new Timestamp(System.currentTimeMillis()); - newScanObject.setStartTimestamp(timestamp); - if (StringUtils.isNotBlank(scannerScanId)) { - newScanObject.setScannerScanId(scannerScanId); - } else { - throw new InvalidRequestException("Scanner scan id cannot be found"); - } - newScanObject.setStatus(scanStatus); - } - break; - case COMPLETED: - if (StringUtils.isNotBlank(scanReportPath)) { - newScanObject.setReportPath(scanReportPath); + case RUNNING: + if (newScanObject.getStatus() == ScanStatus.SUBMITTED) { + Timestamp timestamp = new Timestamp(System.currentTimeMillis()); + newScanObject.setStartTimestamp(timestamp); + if (StringUtils.isNotBlank(scannerScanId)) { + newScanObject.setScannerScanId(scannerScanId); } else { - throw new InvalidRequestException("Scan report path cannot be found"); + throw new InvalidRequestException("Scanner scan id cannot be found"); } newScanObject.setStatus(scanStatus); - scanEngineService.removeContainer(newScanObject); - new Thread(() -> scanEngineService.beginPendingScans(), "BeginPendingScansFromCallback").start(); - break; - case ERROR: - case CANCELED: - newScanObject.setStatus(scanStatus); - scanEngineService.removeContainer(newScanObject); - new Thread(() -> scanEngineService.beginPendingScans(), "BeginPendingScansFromCallback").start(); - break; - default: - throw new InvalidRequestException("Unsupported scan status: " + scanStatus); + } + break; + case COMPLETED: + if (StringUtils.isNotBlank(scanReportPath)) { + newScanObject.setReportPath(scanReportPath); + } else { + throw new InvalidRequestException("Scan report path cannot be found"); + } + newScanObject.setStatus(scanStatus); + + // Once a scan is completed, notification will be send to launcher of the scan. + scanEngineService.sendNotification(scan, userService.getById(scan.getUserId()).getEmail()); + scanEngineService.removeContainer(newScanObject); + new Thread(() -> scanEngineService.beginPendingScans(), "BeginPendingScansFromCallback").start(); + break; + case ERROR: + case CANCELED: + newScanObject.setStatus(scanStatus); + + // Once a scan is completed with error or cancelled, notification will be send to launcher of the scan. + scanEngineService.sendNotification(scan, userService.getById(scan.getUserId()).getEmail()); + scanEngineService.removeContainer(newScanObject); + new Thread(() -> scanEngineService.beginPendingScans(), "BeginPendingScansFromCallback").start(); + break; + default: + throw new InvalidRequestException("Unsupported scan status: " + scanStatus); } scanService.update(newScanObject); } diff --git a/internal/scan-manager/core/src/main/java/org/wso2/security/tools/scanmanager/core/service/ScanEngineService.java b/internal/scan-manager/core/src/main/java/org/wso2/security/tools/scanmanager/core/service/ScanEngineService.java index bd08d363..249dfb41 100644 --- a/internal/scan-manager/core/src/main/java/org/wso2/security/tools/scanmanager/core/service/ScanEngineService.java +++ b/internal/scan-manager/core/src/main/java/org/wso2/security/tools/scanmanager/core/service/ScanEngineService.java @@ -46,4 +46,12 @@ public interface ScanEngineService { * @return scan manager container model representing the removed scanner */ public Container removeContainer(Scan scan); + + /** + * Send notification upon scan completion. + * + * @param scan scan object + * @param toAddress recipient email address. This will be an email address of scan launcher + */ + public void sendNotification(Scan scan, String toAddress); } diff --git a/internal/scan-manager/core/src/main/java/org/wso2/security/tools/scanmanager/core/service/ScanEngineServiceImpl.java b/internal/scan-manager/core/src/main/java/org/wso2/security/tools/scanmanager/core/service/ScanEngineServiceImpl.java index bcb7ff42..3061f53e 100644 --- a/internal/scan-manager/core/src/main/java/org/wso2/security/tools/scanmanager/core/service/ScanEngineServiceImpl.java +++ b/internal/scan-manager/core/src/main/java/org/wso2/security/tools/scanmanager/core/service/ScanEngineServiceImpl.java @@ -36,6 +36,7 @@ import org.wso2.security.tools.scanmanager.core.config.ScanManagerConfiguration; import org.wso2.security.tools.scanmanager.core.exception.ScanManagerException; import org.wso2.security.tools.scanmanager.core.handler.ContainerHandler; +import org.wso2.security.tools.scanmanager.core.handler.NotificationHandler; import org.wso2.security.tools.scanmanager.core.model.Container; import org.wso2.security.tools.scanmanager.core.util.Constants; @@ -66,6 +67,7 @@ public class ScanEngineServiceImpl implements ScanEngineService { private ScannerService scannerService; private LogService logService; private ContainerHandler dockerHandler; + private NotificationHandler emailNotificationHandler; private static final String PROPERTY_MAP_PARAMETER_NAME = "propertyMap"; private static final String FILE_MAP_PARAMETER_NAME = "fileMap"; @@ -75,12 +77,13 @@ public class ScanEngineServiceImpl implements ScanEngineService { private static final Integer SCANNER_SERVICE_WAIT_TIME = 10000; @Autowired - public ScanEngineServiceImpl(ScanService scanService, ScannerService scannerService, LogService logService, - ContainerHandler dockerHandler) { + public ScanEngineServiceImpl(ScanService scanService, ScannerService scannerService, + LogService logService, ContainerHandler dockerHandler, NotificationHandler emailNotificationHandler) { this.scanService = scanService; this.scannerService = scannerService; this.logService = logService; this.dockerHandler = dockerHandler; + this.emailNotificationHandler = emailNotificationHandler; } @Override @@ -98,11 +101,9 @@ private void beginScan(Scan scan) { // There can be multiple scanner apps for a given product in a particular scanner. We need to // identify the currently occupied apps and check for any available free app to start the scan. - List occupiedApps = getOccupiedApps(newScanObject.getScanner(), - newScanObject.getProduct()); - List scannerApps = - scannerService.getAppsByScannerAndAssignedProduct(newScanObject.getScanner(), - newScanObject.getProduct()); + List occupiedApps = getOccupiedApps(newScanObject.getScanner(), newScanObject.getProduct()); + List scannerApps = scannerService + .getAppsByScannerAndAssignedProduct(newScanObject.getScanner(), newScanObject.getProduct()); logService.insert(newScanObject, LogType.INFO, "Checking for a free scanner application for the scan: " + scan.getJobId()); @@ -112,8 +113,8 @@ private void beginScan(Scan scan) { freeAppFound = true; logService.insert(newScanObject, LogType.INFO, - "Free scanner app found. Initiating the scan with the scanner app id: " + - scannerApp.getAppId()); + "Free scanner app found. Initiating the scan with the scanner app id: " + scannerApp + .getAppId()); // Initiating the scan request to create a scanner container and send the start scan request // to the container micro service. @@ -143,10 +144,9 @@ private void beginScan(Scan scan) { } private List getOccupiedApps(Scanner scanner, String product) { - return scanService.getByStatusesAndScannerAndProduct(new ArrayList<>(Arrays.asList(ScanStatus.SUBMITTED, - ScanStatus.RUNNING, ScanStatus.CANCEL_PENDING)), scanner, product).stream() - .map(Scan::getScannerAppId) - .collect(Collectors.toList()); + return scanService.getByStatusesAndScannerAndProduct( + new ArrayList<>(Arrays.asList(ScanStatus.SUBMITTED, ScanStatus.RUNNING, ScanStatus.CANCEL_PENDING)), + scanner, product).stream().map(Scan::getScannerAppId).collect(Collectors.toList()); } @Override @@ -158,16 +158,16 @@ public void cancelScan(Scan scan) throws ScanManagerException { Scan newScanObject = scanService.getByJobId(scan.getJobId()); // A scan can be cancelled only if the scan is any of the following status. - if (newScanObject.getStatus() == ScanStatus.SUBMIT_PENDING || - newScanObject.getStatus() == ScanStatus.SUBMITTED || - newScanObject.getStatus() == ScanStatus.RUNNING) { + if (newScanObject.getStatus() == ScanStatus.SUBMIT_PENDING + || newScanObject.getStatus() == ScanStatus.SUBMITTED + || newScanObject.getStatus() == ScanStatus.RUNNING) { try { List containerInfos = dockerHandler.list(); boolean isContainerFound = false; for (Container containerInfo : containerInfos) { Map labels = containerInfo.getLabels(); - if (labels != null && labels.containsKey(CONTAINER_SCAN_JOB_ID_LABEL_NAME) && - labels.get(CONTAINER_SCAN_JOB_ID_LABEL_NAME).equals(scan.getJobId())) { + if (labels != null && labels.containsKey(CONTAINER_SCAN_JOB_ID_LABEL_NAME) && labels + .get(CONTAINER_SCAN_JOB_ID_LABEL_NAME).equals(scan.getJobId())) { isContainerFound = true; // A container is running for this particular scan. Hence we need to send a cancel scan @@ -208,12 +208,9 @@ public void cancelScan(Scan scan) throws ScanManagerException { private URI buildScannerScanURI(Container containerInfo) throws ScanManagerException { try { - return (new URIBuilder()) - .setHost(ScanManagerConfiguration.getInstance().getScannerServiceHost()) - .setPort(containerInfo.getPortMappings().get(ScanManagerConfiguration.getInstance() - .getScannerServicePort())) - .setScheme(SCHEME).setPath(SCANNER_SCAN_ENDPOINT) - .build(); + return (new URIBuilder()).setHost(ScanManagerConfiguration.getInstance().getScannerServiceHost()).setPort( + containerInfo.getPortMappings().get(ScanManagerConfiguration.getInstance().getScannerServicePort())) + .setScheme(SCHEME).setPath(SCANNER_SCAN_ENDPOINT).build(); } catch (URISyntaxException e) { throw new ScanManagerException("Error occurred while building the scan URI", e); } @@ -241,16 +238,28 @@ public Container removeContainer(Scan scan) { return removedContainerInfo; } + @Override + public void sendNotification(Scan scan, String toAddress) { + logService.insert(scan, LogType.INFO, "Scan completion notification starts..."); + if (ScanManagerConfiguration.getInstance().getNotificationEnabled()) { + try { + emailNotificationHandler.sendNotification(scan, toAddress); + logService.insert(scan, LogType.INFO, "Scan completion notification has successfully done..."); + } catch (ScanManagerException e) { + logService.insertError(scan, e); + } + } + } + private void initiateScanRequest(Scan scan, ScannerApp scannerApp) throws ScanManagerException { Container containerInfo = null; try { logService.insert(scan, LogType.INFO, "Creating a container for the scan"); - containerInfo = createContainer(scan, scannerApp, ScanManagerConfiguration - .getInstance().getScannerServiceHost(), ScanManagerConfiguration.getInstance() - .getScannerServicePort()); + containerInfo = createContainer(scan, scannerApp, + ScanManagerConfiguration.getInstance().getScannerServiceHost(), + ScanManagerConfiguration.getInstance().getScannerServicePort()); dockerHandler.start(containerInfo.getId()); - logService.insert(scan, LogType.INFO, - "Scanner container started. Container id: " + containerInfo.getId()); + logService.insert(scan, LogType.INFO, "Scanner container started. Container id: " + containerInfo.getId()); // Sleep till the scanner service is started. Thread.sleep(SCANNER_SERVICE_WAIT_TIME); @@ -274,12 +283,10 @@ private void sendStartScanRequest(Container containerInfo, ScannerApp scannerApp requestParams.put(JOB_ID_PARAMETER_NAME, scan.getJobId()); requestParams.put(SCANNER_APP_ID_PARAMETER_NAME, scannerApp.getAppId()); - Map> fileMap = scan.getFileList().stream() - .collect(Collectors.toMap(ScanFile::getName, - scanFile -> Collections.singletonList(scanFile.getLocation()))); - Map> propertyMap = scan.getPropertyList().stream() - .collect(Collectors.toMap(ScanProperty::getName, - scanProperty -> Collections.singletonList(scanProperty.getValue()))); + Map> fileMap = scan.getFileList().stream().collect( + Collectors.toMap(ScanFile::getName, scanFile -> Collections.singletonList(scanFile.getLocation()))); + Map> propertyMap = scan.getPropertyList().stream().collect(Collectors + .toMap(ScanProperty::getName, scanProperty -> Collections.singletonList(scanProperty.getValue()))); requestParams.put(FILE_MAP_PARAMETER_NAME, fileMap); requestParams.put(PROPERTY_MAP_PARAMETER_NAME, propertyMap); MultiValueMap requestHeaders = new LinkedMultiValueMap<>(); @@ -287,26 +294,28 @@ private void sendStartScanRequest(Container containerInfo, ScannerApp scannerApp ResponseEntity response = HTTPUtil.sendPOST(startScanRequest); if (response.getStatusCode().isError()) { - throw new ScanManagerException("Error occurred while sending start scan request to the scanner " + - "service"); + throw new ScanManagerException( + "Error occurred while sending start scan request to the scanner " + "service"); } } catch (RestClientException e) { throw new ScanManagerException("Error occurred while connecting to the scanner service endpoint"); } } - private Container createContainer(Scan scan, ScannerApp scannerApp, String containerHost, - Integer containerPort) throws ScanManagerException { + private Container createContainer(Scan scan, ScannerApp scannerApp, String containerHost, Integer containerPort) + throws ScanManagerException { Map labels = new HashMap<>(); labels.put(CONTAINER_SCAN_JOB_ID_LABEL_NAME, scan.getJobId()); labels.put(CONTAINER_APP_LABEL_NAME, scannerApp.getAppId()); labels.put(CONTAINER_SCANNER_LABEL_NAME, scannerApp.getScanner().getName()); - String[] envVariables = - new String[]{CONTAINER_ENV_NAME_SCAN_MANAGER_HOST + "=" + ScanManagerConfiguration.getInstance() - .getScanManagerHost(), CONTAINER_ENV_NAME_SCAN_MANAGER_PORT + "=" + ScanManagerConfiguration - .getInstance().getScanManagerPort()}; - return dockerHandler.create(scannerApp.getScanner().getImage(), containerHost, containerPort, labels, - new ArrayList<>(), envVariables); + String[] envVariables = new String[] { + CONTAINER_ENV_NAME_SCAN_MANAGER_HOST + "=" + ScanManagerConfiguration.getInstance() + .getScanManagerHost(), + CONTAINER_ENV_NAME_SCAN_MANAGER_PORT + "=" + ScanManagerConfiguration.getInstance() + .getScanManagerPort() }; + return dockerHandler + .create(scannerApp.getScanner().getImage(), containerHost, containerPort, labels, new ArrayList<>(), + envVariables); } } diff --git a/internal/scan-manager/core/src/main/java/org/wso2/security/tools/scanmanager/core/service/ScanService.java b/internal/scan-manager/core/src/main/java/org/wso2/security/tools/scanmanager/core/service/ScanService.java index b0495e8a..7d7ea029 100644 --- a/internal/scan-manager/core/src/main/java/org/wso2/security/tools/scanmanager/core/service/ScanService.java +++ b/internal/scan-manager/core/src/main/java/org/wso2/security/tools/scanmanager/core/service/ScanService.java @@ -47,13 +47,14 @@ public interface ScanService { public Scan insert(Scan scan); /** - * Get all scans by page. + * Get all scans of given product by page. * * @param pageNumber page number * @param pageSize size of the page - * @return a page containing the requested scans + * @param product product + * @return a page containing the requested scans of given product */ - public Page getAll(Integer pageNumber, Integer pageSize); + public Page getScanByProduct(Integer pageNumber, Integer pageSize, String product); /** * Get scan by job id. diff --git a/internal/scan-manager/core/src/main/java/org/wso2/security/tools/scanmanager/core/service/ScanServiceImpl.java b/internal/scan-manager/core/src/main/java/org/wso2/security/tools/scanmanager/core/service/ScanServiceImpl.java index 1bb0c952..9a20c36d 100644 --- a/internal/scan-manager/core/src/main/java/org/wso2/security/tools/scanmanager/core/service/ScanServiceImpl.java +++ b/internal/scan-manager/core/src/main/java/org/wso2/security/tools/scanmanager/core/service/ScanServiceImpl.java @@ -58,9 +58,9 @@ public Scan insert(Scan scan) { } @Override - public Page getAll(Integer pageNumber, Integer pageSize) { + public Page getScanByProduct(Integer pageNumber, Integer pageSize, String product) { Pageable pageable = PageRequest.of(pageNumber, pageSize); - return scanDAO.getAllByOrderBySubmittedTimestampDesc(pageable); + return scanDAO.getScanByProductByOrderBySubmittedTimestampDesc(product, pageable); } @Override diff --git a/internal/scan-manager/core/src/main/java/org/wso2/security/tools/scanmanager/core/service/UserService.java b/internal/scan-manager/core/src/main/java/org/wso2/security/tools/scanmanager/core/service/UserService.java index 876949a5..11f2d82e 100644 --- a/internal/scan-manager/core/src/main/java/org/wso2/security/tools/scanmanager/core/service/UserService.java +++ b/internal/scan-manager/core/src/main/java/org/wso2/security/tools/scanmanager/core/service/UserService.java @@ -41,10 +41,10 @@ public interface UserService { public User getByUsername(String username); /** - * Get user for a given username. + * Get user for a given userId. * * @param id user id of the user - * @return the user for the given username + * @return the user for the given userId */ - public User getById(String id); + public User getById(int id); } diff --git a/internal/scan-manager/core/src/main/java/org/wso2/security/tools/scanmanager/core/service/UserServiceImpl.java b/internal/scan-manager/core/src/main/java/org/wso2/security/tools/scanmanager/core/service/UserServiceImpl.java index 66250f27..d298270d 100644 --- a/internal/scan-manager/core/src/main/java/org/wso2/security/tools/scanmanager/core/service/UserServiceImpl.java +++ b/internal/scan-manager/core/src/main/java/org/wso2/security/tools/scanmanager/core/service/UserServiceImpl.java @@ -50,7 +50,7 @@ public User getByUsername(String userName) { } @Override - public User getById(String id) { + public User getById(int id) { return userDAO.getById(id); } } diff --git a/internal/scan-manager/core/src/main/java/org/wso2/security/tools/scanmanager/core/util/Constants.java b/internal/scan-manager/core/src/main/java/org/wso2/security/tools/scanmanager/core/util/Constants.java index 325263fd..34a5abf6 100644 --- a/internal/scan-manager/core/src/main/java/org/wso2/security/tools/scanmanager/core/util/Constants.java +++ b/internal/scan-manager/core/src/main/java/org/wso2/security/tools/scanmanager/core/util/Constants.java @@ -37,4 +37,12 @@ public class Constants { public static final Integer DEFAULT_SCAN_PAGE_SIZE = 10; public static final Integer DEFAULT_LOG_PAGE_SIZE = 10; + + // Constants related to Email + public static final String SMTP_SERVER_HOST = "smtpServerHost"; + public static final String SMTP_SERVER_PORT = "smtpServerPort"; + public static final String SMTP_USERNAME = "smtpUsername"; + public static final String SMTP_PASSWORD = "smtpPassword"; + public static final String SMTP_ENABLE_DEBUG = "smtpEnableDebug"; + public static final String EMAIL_FROM_ADDRESS = "emailFromaddress"; } diff --git a/internal/scan-manager/core/src/main/java/org/wso2/security/tools/scanmanager/core/util/Util.java b/internal/scan-manager/core/src/main/java/org/wso2/security/tools/scanmanager/core/util/Util.java new file mode 100644 index 00000000..82231899 --- /dev/null +++ b/internal/scan-manager/core/src/main/java/org/wso2/security/tools/scanmanager/core/util/Util.java @@ -0,0 +1,55 @@ +/* + * + * Copyright (c) 2020, WSO2 Inc., WSO2 Inc. (http://www.wso2.org) All Rights Reserved. + * + * WSO2 Inc. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * / + */ + +package org.wso2.security.tools.scanmanager.core.util; + +import org.wso2.security.tools.scanmanager.core.exception.ResourceNotFoundException; + +import java.io.InputStream; +import java.util.Scanner; + +/** + * This class represents the implementation of util methods. + */ +public class Util { + + /** + * Read HTML based email template from the filesystem. + * + * @param fileName file name of template + * @return html Template + * @throws ResourceNotFoundException Error occurred while reading HTML Template + */ + public static String readHTMLEmailTemplate(String fileName) throws ResourceNotFoundException { + InputStream input = Util.class.getClassLoader().getResourceAsStream(fileName); + if (input == null) { + throw new ResourceNotFoundException("Unable to find email template: " + fileName); + } + + Scanner scanner = new Scanner(input, "UTF-8"); + scanner.useDelimiter("\\A"); + String htmlTemplate = scanner.next(); + scanner.close(); + return htmlTemplate; + } + + + +} diff --git a/internal/scan-manager/core/src/main/resources/emailTemplate.html b/internal/scan-manager/core/src/main/resources/emailTemplate.html new file mode 100644 index 00000000..3e8f62e1 --- /dev/null +++ b/internal/scan-manager/core/src/main/resources/emailTemplate.html @@ -0,0 +1,102 @@ + + + + + + + + + +This automated email notification is sent from Scan Manager upon scan completion. + + + + + + + + Scan Summary Details + + + Scan Title + {scanTitle} + + + Scan Job ID + {jobId} + + + Product Name + {productName} + + + Launched By + {launchedBy} + + + Scan Type + {ScannerId} + + + Scan Status + {scanStatus} + + + + + + + +To download the scan report and view further information , click here. + + +If you have any issues in conducting a new scan or downloading scan report using WSO2 Scan Manager, please contact WSO2 Security & Compliance Team. + + + +WSO2 Scan Manager. + + + diff --git a/internal/scan-manager/core/src/main/resources/scan-manager-config.yaml b/internal/scan-manager/core/src/main/resources/scan-manager-config.yaml index df95f1c9..e0eee64a 100644 --- a/internal/scan-manager/core/src/main/resources/scan-manager-config.yaml +++ b/internal/scan-manager/core/src/main/resources/scan-manager-config.yaml @@ -16,11 +16,24 @@ # Scan Manager Configuration -scanManagerHost: -scanManagerPort: +scanManagerHost: [IP address of docker] +scanManagerPort: [Port number] -scannerServiceHost: -scannerServicePort: +scannerServiceHost: [IP address of scanner service] +scannerServicePort: [Port number of scanner service] -scanPageSize: -logPageSize: +scanPageSize: [Optional; page size for scan pagination; default value is 10] +logPageSize: [Optional; page size for log pagination; default value is 10] + +# Notification Configuration +isNotificationEnabled: true +notificationSubject: SCAN MANAGER - Scan completion of Job + +# EMAIL Notification Configurations +smtpServerHost: [SMTP host name] +smtpServerPort: [SMTP port number] +smtpUsername: [SMTP username] +smtpPassword: [SMTP password] +smtpEnableDebug: [True if debug is enabled; False if debug is disabled] +emailFromaddress: [Email address of sender] +emailCCaddress: [Optional; Email addresses of CC list. Values should be comma seperated] diff --git a/internal/scan-manager/core/src/main/webapp/WEB-INF/dispatcher-servlet.xml b/internal/scan-manager/core/src/main/webapp/WEB-INF/dispatcher-servlet.xml index fc3ffa7f..58526193 100644 --- a/internal/scan-manager/core/src/main/webapp/WEB-INF/dispatcher-servlet.xml +++ b/internal/scan-manager/core/src/main/webapp/WEB-INF/dispatcher-servlet.xml @@ -55,6 +55,9 @@ + + diff --git a/internal/scan-manager/core/src/test/java/org/wso2/security/tools/scanmanager/core/service/ScanServiceImplTest.java b/internal/scan-manager/core/src/test/java/org/wso2/security/tools/scanmanager/core/service/ScanServiceImplTest.java index 12213ee3..2b24cc88 100644 --- a/internal/scan-manager/core/src/test/java/org/wso2/security/tools/scanmanager/core/service/ScanServiceImplTest.java +++ b/internal/scan-manager/core/src/test/java/org/wso2/security/tools/scanmanager/core/service/ScanServiceImplTest.java @@ -36,6 +36,7 @@ import java.util.List; import static org.wso2.security.tools.scanmanager.core.util.ScanManagerTestConstants.SCANNER_APP_ID; +import static org.wso2.security.tools.scanmanager.core.util.ScanManagerTestConstants.TEST_PRODUCT_ID; /** * Test class for scan service methods. @@ -69,10 +70,11 @@ public void testInsert(Scan scan) { @Test(dataProvider = "getScanData", dataProviderClass = ServiceTestDataProvider.class) public void testGetAll(Scan scan) { - Mockito.when(mockScanDAO.getAllByOrderBySubmittedTimestampDesc(PageRequest.of(1, 10))) + Mockito.when(mockScanDAO.getScanByProductByOrderBySubmittedTimestampDesc(TEST_PRODUCT_ID, + PageRequest.of(1, 10))) .thenReturn(new PageImpl<>(Collections.singletonList(scan))); - Page retrievedScansPage = scanService.getAll(1, 10); + Page retrievedScansPage = scanService.getScanByProduct(1, 10, TEST_PRODUCT_ID); Assert.assertNotNull(retrievedScansPage); List retrievedScans = retrievedScansPage.getContent(); diff --git a/internal/scan-manager/scanners/common/src/main/java/org/wso2/security/tools/scanmanager/scanners/common/ScannerConstants.java b/internal/scan-manager/scanners/common/src/main/java/org/wso2/security/tools/scanmanager/scanners/common/ScannerConstants.java index e0cc6399..1b6eb495 100644 --- a/internal/scan-manager/scanners/common/src/main/java/org/wso2/security/tools/scanmanager/scanners/common/ScannerConstants.java +++ b/internal/scan-manager/scanners/common/src/main/java/org/wso2/security/tools/scanmanager/scanners/common/ScannerConstants.java @@ -53,6 +53,14 @@ public final class ScannerConstants { public static final String CONTENT_DISPOSITION_PATTERN = "attachment;\\s*filename\\s*=\\s*\"([^\"]*)\""; public static final String CONTENT_DISPOSITION = "content-disposition"; + // Email alerter related constants. + public static final String SMTP_SERVER_HOST = "smtp_server_host"; + public static final String SMTP_SERVER_PORT = "smtp.server.port"; + public static final String SMTP_USERNAME = "smtp.username"; + public static final String SMTP_PASSWORD = "smtp.password"; + public static final String SMTP_ENABLE_DEBUG = "smtp.enable.debug"; + public static final String EMAIL_FROM_ADDRESS = "email.fromaddress"; + private ScannerConstants() { } } diff --git a/internal/scan-manager/webapp/src/main/java/org/wso2/security/tools/scanmanager/webapp/controller/ScanController.java b/internal/scan-manager/webapp/src/main/java/org/wso2/security/tools/scanmanager/webapp/controller/ScanController.java index 7ca24f8f..17a79d1e 100644 --- a/internal/scan-manager/webapp/src/main/java/org/wso2/security/tools/scanmanager/webapp/controller/ScanController.java +++ b/internal/scan-manager/webapp/src/main/java/org/wso2/security/tools/scanmanager/webapp/controller/ScanController.java @@ -51,6 +51,7 @@ import java.io.IOException; import java.io.InputStream; import java.net.URLConnection; +import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -63,6 +64,7 @@ import static org.wso2.security.tools.scanmanager.webapp.util.Constants.MAX_FILE_SIZE_PROPERTY_KEY; import static org.wso2.security.tools.scanmanager.webapp.util.Constants.SCANS_VIEW; import static org.wso2.security.tools.scanmanager.webapp.util.Constants.SCAN_CONFIGURATION_VIEW; +import static org.wso2.security.tools.scanmanager.webapp.util.Constants.SCAN_MANAGER_INDEX; import static org.wso2.security.tools.scanmanager.webapp.util.Constants.SCAN_MANAGER_VIEW; import static org.wso2.security.tools.scanmanager.webapp.util.Constants.SCAN_REPORT_DATA_DIRECTORY_NAME; import static org.wso2.security.tools.scanmanager.webapp.util.Constants.URL_SEPARATOR; @@ -99,8 +101,27 @@ public ScanController(ScanService scanService, ScannerService scannerService, Lo } @GetMapping(value = "/") - public String scanManager() { - return "scan-manager/index"; + public ModelAndView scanManager() throws ScanManagerWebappException { + List scanners = null; + ModelAndView scannerConfigModel = new ModelAndView(SCAN_MANAGER_VIEW + File.separator + SCAN_MANAGER_INDEX); + List productList = new ArrayList<>(); + + // Get the product list. + scanners = scannerService.getScanners(); + for (Scanner scanner : scanners) { + List scannerSpecificProductLst = scanner.getApps().stream().map(ScannerApp::getAssignedProduct) + .collect(Collectors.toList()); + Set set = new HashSet<>(scannerSpecificProductLst); + scannerSpecificProductLst.clear(); + scannerSpecificProductLst.addAll(set); + for (String product : scannerSpecificProductLst) { + if (!productList.contains(product)) { + productList.add(product); + } + } + } + scannerConfigModel.addObject(PRODUCT_DATA_ATTRIBUTE_NAME, productList); + return scannerConfigModel; } /** @@ -148,8 +169,8 @@ public String startScan(MultipartHttpServletRequest multipartHttpServletRequest) * @throws ScanManagerWebappException when an error occurs while getting the list of scans */ @GetMapping(value = "scans") - public ModelAndView getScans(@RequestParam(name = "page", required = false) Integer page) - throws ScanManagerWebappException { + public ModelAndView getScans( + @RequestParam(name = "page", required = false) Integer page) throws ScanManagerWebappException { ModelAndView scansView = new ModelAndView(SCAN_MANAGER_VIEW + URL_SEPARATOR + SCANS_VIEW); @@ -161,6 +182,35 @@ public ModelAndView getScans(@RequestParam(name = "page", required = false) Inte return scansView; } + /** + * Get the list of scans by given product. + * + * @param page required page number + * @param product product + * @return scans view + * @throws ScanManagerWebappException when an error occurs while getting the list of scans + */ + @GetMapping(value = "scansByProduct") + public ModelAndView getScansByProducts( + @RequestParam(name = "page", required = false) Integer page, @RequestParam("product") String product) + throws ScanManagerWebappException { + + ModelAndView scansView = new ModelAndView(SCAN_MANAGER_VIEW + URL_SEPARATOR + SCANS_VIEW); + + // List of scans submitted to scan manager API. + scansView.addObject(SCAN_LIST_RESPONSE_ATTRIBUTE_NAME, scanService.getScansByProduct(product, page)); + + // list of scan preparing to be submitted to scan manager API. + List preparingScansByProduct = new ArrayList<>(); + for (ScanExternal scanExternal : scanService.getPreparingScans()) { + if (!scanExternal.getProduct().equals(product)) { + preparingScansByProduct.add(scanExternal); + } + } + scansView.addObject(PREPARING_SCAN_LIST_ATTRIBUTE_NAME, preparingScansByProduct); + return scansView; + } + /** * Get the logs for a scan. * @@ -171,7 +221,7 @@ public ModelAndView getScans(@RequestParam(name = "page", required = false) Inte */ @GetMapping(value = "logs") public ModelAndView getLogs(@RequestParam(name = "page", required = false) Integer page, - @RequestParam("jobId") String jobId) throws ScanManagerWebappException { + @RequestParam("jobId") String jobId) throws ScanManagerWebappException { ModelAndView logsView = new ModelAndView(SCAN_MANAGER_VIEW + URL_SEPARATOR + LOGS_VIEW); ScanManagerLogResponse scanManagerLogResponse = logService.getLogs(jobId, page); logsView.addObject(LOG_RESPONSE_ATTRIBUTE_NAME, scanManagerLogResponse); @@ -187,7 +237,8 @@ public ModelAndView getLogs(@RequestParam(name = "page", required = false) Integ * @throws ScanManagerWebappException when an error occurs while cancelling the scan */ @PostMapping(value = "stop") - public String stopScan(@RequestParam("jobId") String jobId) throws ScanManagerWebappException { + public String stopScan(@RequestParam("jobId") String jobId) + throws ScanManagerWebappException { ResponseEntity responseEntity = scanService.stopScan(jobId); if (responseEntity != null && responseEntity.getStatusCode().is2xxSuccessful()) { return "redirect:scans"; @@ -204,7 +255,8 @@ public String stopScan(@RequestParam("jobId") String jobId) throws ScanManagerWe * @throws ScanManagerWebappException when an error occurs while clearing the scan */ @PostMapping(value = "clear") - public String clearScan(@RequestParam("jobId") String jobId) throws ScanManagerWebappException { + public String clearScan(@RequestParam("jobId") String jobId) + throws ScanManagerWebappException { if (scanService.clearScan(jobId)) { return "redirect:scans"; } else { @@ -220,8 +272,8 @@ public String clearScan(@RequestParam("jobId") String jobId) throws ScanManagerW * @throws ScanManagerWebappException when an error occurs while downloading the scan report */ @GetMapping(value = "report") - public void getReport(HttpServletResponse response, @RequestParam("jobId") String jobId) - throws ScanManagerWebappException { + public void getReport(HttpServletResponse response, + @RequestParam("jobId") String jobId) throws ScanManagerWebappException { ScanExternal scan = scanService.getScan(jobId); if (scan != null) { if (StringUtils.isBlank(scan.getScanReportPath())) { @@ -230,30 +282,29 @@ public void getReport(HttpServletResponse response, @RequestParam("jobId") Strin // Download the scan report temporally into the local machine. File file = new File(scan.getScanReportPath()); - File reportDirectory = - new File(SCAN_REPORT_DATA_DIRECTORY_NAME + File.separator + scan.getJobId()); + File reportDirectory = new File(SCAN_REPORT_DATA_DIRECTORY_NAME + File.separator + scan.getJobId()); if (!reportDirectory.exists() && !reportDirectory.mkdirs()) { throw new ScanManagerWebappException("Error occurred while creating the report output directory"); } - File outputFile = new File(SCAN_REPORT_DATA_DIRECTORY_NAME + File.separator + scan.getJobId() - + File.separator + file.getName()); - scanService.getScanReport(scan.getScanReportPath(), - outputFile.getPath()); + File outputFile = new File( + SCAN_REPORT_DATA_DIRECTORY_NAME + File.separator + scan.getJobId() + File.separator + file + .getName()); + scanService.getScanReport(scan.getScanReportPath(), outputFile.getPath()); String mimeType = URLConnection.guessContentTypeFromName(file.getName()); if (mimeType == null) { mimeType = MediaType.APPLICATION_OCTET_STREAM_VALUE; } response.setContentType(mimeType); - response.setHeader(CONTENT_DISPOSITION_HEADER_NAME, String.format("inline; filename=\"" - + outputFile.getName() + "\"")); + response.setHeader(CONTENT_DISPOSITION_HEADER_NAME, + String.format("inline; filename=\"" + outputFile.getName() + "\"")); response.setContentLength((int) outputFile.length()); try (InputStream inputStream = new BufferedInputStream(new FileInputStream(outputFile))) { FileCopyUtils.copy(inputStream, response.getOutputStream()); } catch (IOException e) { - throw new ScanManagerWebappException("Error occurred while downloading scan report for " + - "the scan: " + jobId, e); + throw new ScanManagerWebappException( + "Error occurred while downloading scan report for " + "the scan: " + jobId, e); } } else { throw new ScanManagerWebappException("Unable to find a scan for the given id: " + jobId); @@ -268,18 +319,18 @@ public void getReport(HttpServletResponse response, @RequestParam("jobId") Strin * @throws ScanManagerWebappException when an error occurs while getting scanner configuration */ @GetMapping(value = "configuration") - public ModelAndView getScannerConfig(@RequestParam("scannerId") String scannerId) - throws ScanManagerWebappException { + public ModelAndView getScannerConfig( + @RequestParam("scannerId") String scannerId) throws ScanManagerWebappException { Scanner scanner = null; - ModelAndView scannerConfigModel = new ModelAndView(SCAN_MANAGER_VIEW + - File.separator + SCAN_CONFIGURATION_VIEW); + ModelAndView scannerConfigModel = new ModelAndView( + SCAN_MANAGER_VIEW + File.separator + SCAN_CONFIGURATION_VIEW); scanner = scannerService.getScanner(scannerId); scannerConfigModel.addObject(SCANNER_DATA_ATTRIBUTE_NAME, scanner); // List the applicable products for the given scanner. - List productLst = - scanner.getApps().stream().map(ScannerApp::getAssignedProduct).collect(Collectors.toList()); + List productLst = scanner.getApps().stream().map(ScannerApp::getAssignedProduct) + .collect(Collectors.toList()); Set set = new HashSet<>(productLst); productLst.clear(); productLst.addAll(set); diff --git a/internal/scan-manager/webapp/src/main/java/org/wso2/security/tools/scanmanager/webapp/service/ScanService.java b/internal/scan-manager/webapp/src/main/java/org/wso2/security/tools/scanmanager/webapp/service/ScanService.java index abf7fb91..eebd6941 100644 --- a/internal/scan-manager/webapp/src/main/java/org/wso2/security/tools/scanmanager/webapp/service/ScanService.java +++ b/internal/scan-manager/webapp/src/main/java/org/wso2/security/tools/scanmanager/webapp/service/ScanService.java @@ -52,6 +52,17 @@ public interface ScanService { */ public ScanManagerScansResponse getScans(Integer pageNumber) throws ScanManagerWebappException; + /** + * Get the list of scans for a given product and page. + * + * @param product product name + * @param pageNumber page number + * @return scan manager response containing a list of scans for the given page number and product + * @throws ScanManagerWebappException when an error occurs when getting the list of scans + */ + public ScanManagerScansResponse getScansByProduct(String product, Integer pageNumber) + throws ScanManagerWebappException; + /** * Get the list of scans under preparation to be submitted to scan manager API. * diff --git a/internal/scan-manager/webapp/src/main/java/org/wso2/security/tools/scanmanager/webapp/service/ScanServiceImpl.java b/internal/scan-manager/webapp/src/main/java/org/wso2/security/tools/scanmanager/webapp/service/ScanServiceImpl.java index c03fddb4..50ab3401 100644 --- a/internal/scan-manager/webapp/src/main/java/org/wso2/security/tools/scanmanager/webapp/service/ScanServiceImpl.java +++ b/internal/scan-manager/webapp/src/main/java/org/wso2/security/tools/scanmanager/webapp/service/ScanServiceImpl.java @@ -150,8 +150,9 @@ public Scan submitScan(Map fileMap, Map p } // Begin the pre scans tasks and initiate the scan submission. - new Thread(() -> beginScanSubmit(storedFileMap, parameterMap, filesToBeDownloadedFromURL, - preparingScan, user), "BeginScanSubmitToScanManagerAPI").start(); + new Thread( + () -> beginScanSubmit(storedFileMap, parameterMap, filesToBeDownloadedFromURL, preparingScan, user), + "BeginScanSubmitToScanManagerAPI").start(); } catch (ScanManagerWebappException e) { // Update the status if the scan under preparation to ERROR. @@ -170,6 +171,33 @@ public Scan submitScan(Map fileMap, Map p return preparingScan; } + @Override + public ScanManagerScansResponse getScansByProduct(String product, Integer pageNumber) + throws ScanManagerWebappException { + ScanManagerScansResponse scansResponse = null; + List nameValuePairs = new ArrayList<>(); + try { + if (pageNumber != null) { + nameValuePairs.add(new BasicNameValuePair(PAGE_PARAM_NAME, pageNumber.toString())); + } + nameValuePairs.add(new BasicNameValuePair("product", product)); + HTTPRequest getScansRequest = new HTTPRequest( + ScanManagerWebappConfiguration.getInstance().getScanURL("", nameValuePairs).toString(), null, null); + ResponseEntity responseEntity = HTTPUtil.sendGET(getScansRequest); + if (responseEntity != null && responseEntity.getStatusCode().is2xxSuccessful()) { + ObjectMapper mapper = new ObjectMapper(); + scansResponse = mapper.readValue(responseEntity.getBody(), ScanManagerScansResponse.class); + + } else { + throw new ScanManagerWebappException("Unable to get the scans from scan manager for given product"); + } + } catch (RestClientException | IOException e) { + throw new ScanManagerWebappException("Unable to get the scans for given product", e); + } + + return scansResponse; + } + @Override public ScanManagerScansResponse getScans(Integer pageNumber) throws ScanManagerWebappException { ScanManagerScansResponse scansResponse = null; @@ -180,8 +208,8 @@ public ScanManagerScansResponse getScans(Integer pageNumber) throws ScanManagerW nameValuePairs.add(new BasicNameValuePair(PAGE_PARAM_NAME, pageNumber.toString())); } - HTTPRequest getScansRequest = new HTTPRequest(ScanManagerWebappConfiguration.getInstance() - .getScanURL("", nameValuePairs).toString(), null, null); + HTTPRequest getScansRequest = new HTTPRequest( + ScanManagerWebappConfiguration.getInstance().getScanURL("", nameValuePairs).toString(), null, null); ResponseEntity responseEntity = HTTPUtil.sendGET(getScansRequest); if (responseEntity != null && responseEntity.getStatusCode().is2xxSuccessful()) { ObjectMapper mapper = new ObjectMapper(); @@ -195,6 +223,7 @@ public ScanManagerScansResponse getScans(Integer pageNumber) throws ScanManagerW return scansResponse; } + @Override public List getPreparingScans() { // Convert the scan object to scan external object. @@ -214,8 +243,9 @@ public ScanExternal getScan(String jobId) throws ScanManagerWebappException { return new ScanExternal(preparingScans.get(jobId)); } } else { - HTTPRequest getScanRequest = new HTTPRequest(ScanManagerWebappConfiguration.getInstance() - .getScanURL("/" + jobId, nameValuePairs).toString(), null, null); + HTTPRequest getScanRequest = new HTTPRequest( + ScanManagerWebappConfiguration.getInstance().getScanURL("/" + jobId, nameValuePairs).toString(), + null, null); ResponseEntity responseEntity = HTTPUtil.sendGET(getScanRequest); if (responseEntity != null && responseEntity.getStatusCode().is2xxSuccessful()) { ObjectMapper mapper = new ObjectMapper(); @@ -247,8 +277,7 @@ public ResponseEntity stopScan(String id) throws ScanManagerWebappException { } } - @Override - public boolean clearScan(String id) { + @Override public boolean clearScan(String id) { // Clear scan from preparing scans list if the scan status is ERROR. if (preparingScans.containsKey(id) && preparingScans.get(id).getStatus() == ScanStatus.ERROR) { preparingScans.remove(id); @@ -354,7 +383,7 @@ private void beginScanSubmit(Map storedFileMap, Map uploadedFileMap, Map parameterMap, - User user) throws ScanManagerWebappException { + User user) throws ScanManagerWebappException { List nameValuePairs = new ArrayList<>(); Map requestParams = new HashMap<>(); diff --git a/internal/scan-manager/webapp/src/main/java/org/wso2/security/tools/scanmanager/webapp/util/Constants.java b/internal/scan-manager/webapp/src/main/java/org/wso2/security/tools/scanmanager/webapp/util/Constants.java index b6b20fa4..a653e2db 100644 --- a/internal/scan-manager/webapp/src/main/java/org/wso2/security/tools/scanmanager/webapp/util/Constants.java +++ b/internal/scan-manager/webapp/src/main/java/org/wso2/security/tools/scanmanager/webapp/util/Constants.java @@ -27,6 +27,7 @@ private Constants() { public static final String SCHEME = "http"; public static final String SCAN_MANAGER_ENDPOINT = "/scan-manager"; + public static final String SCAN_MANAGER_INDEX = "index"; public static final String SCAN_MANAGER_VIEW = "scan-manager"; public static final String SCANNERS_VIEW_NAME = "scanners"; public static final String SCAN_CONFIGURATION_VIEW = "scan_configuration"; diff --git a/internal/scan-manager/webapp/src/main/resources/application.properties b/internal/scan-manager/webapp/src/main/resources/application.properties index 7665d3d5..d449b5be 100644 --- a/internal/scan-manager/webapp/src/main/resources/application.properties +++ b/internal/scan-manager/webapp/src/main/resources/application.properties @@ -14,10 +14,10 @@ # limitations under the License. # -spring.http.multipart.maxFileSize=2000MB -spring.http.multipart.max-request-size=20000MB -spring.http.multipart.enabled=true -spring.http.multipart.location=${user.dir} +spring.servlet.multipart.max-file-size=3000MB +spring.servlet.multipart.max-request-size=20000MB +spring.servlet.multipart.enabled=true +spring.servlet.multipart.location=${user.dir} server.tomcat.additional-tld-skip-patterns= jaxb-*.jar logging.level.org.springframework.web=INFO diff --git a/internal/scan-manager/webapp/src/main/webapp/WEB-INF/views/scan-manager/index.jsp b/internal/scan-manager/webapp/src/main/webapp/WEB-INF/views/scan-manager/index.jsp index 7ac3a10a..4d6b01ba 100644 --- a/internal/scan-manager/webapp/src/main/webapp/WEB-INF/views/scan-manager/index.jsp +++ b/internal/scan-manager/webapp/src/main/webapp/WEB-INF/views/scan-manager/index.jsp @@ -38,8 +38,19 @@ Start a New Scan - View Scans + <%-- + <%--role="button">View Scans--%> + + + + Choose a product + + ${product} + + + + + Help diff --git a/internal/scan-manager/webapp/src/main/webapp/WEB-INF/views/scan-manager/scans.jsp b/internal/scan-manager/webapp/src/main/webapp/WEB-INF/views/scan-manager/scans.jsp index e16f7fa6..b34e16a0 100644 --- a/internal/scan-manager/webapp/src/main/webapp/WEB-INF/views/scan-manager/scans.jsp +++ b/internal/scan-manager/webapp/src/main/webapp/WEB-INF/views/scan-manager/scans.jsp @@ -20,6 +20,7 @@ <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <%@taglib prefix="e" uri="https://www.owasp.org/index.php/OWASP_Java_Encoder_Project" %> <%@ page contentType="text/html;charset=UTF-8" language="java" %> + @@ -44,6 +45,7 @@ + Name: ${e:forHtml(scanListResponse.scanList.get(index).name)} Job ID: ${scanListResponse.scanList.get(index).jobId} @@ -129,8 +131,9 @@ - + + First @@ -140,8 +143,9 @@ - + + Previous Page @@ -151,8 +155,9 @@ - + + Next Page @@ -162,8 +167,9 @@ - + + Last diff --git a/internal/scan-manager/webapp/src/main/webapp/scan-manager/resources/custom/css/button.css b/internal/scan-manager/webapp/src/main/webapp/scan-manager/resources/custom/css/button.css index f6fc6744..e3c98404 100644 --- a/internal/scan-manager/webapp/src/main/webapp/scan-manager/resources/custom/css/button.css +++ b/internal/scan-manager/webapp/src/main/webapp/scan-manager/resources/custom/css/button.css @@ -47,4 +47,21 @@ width: 400px; font-size: 25px; margin-bottom: 15px; +} + +.scan-manager-index-page-dropdown { + height: 100px; + width: 300px; + font-size: 35px; + margin-bottom: 15px; + margin-top: 15px; +} + +.scan-manager-index-page-dropdown-button { + height: 100px; + width: 300px; + font-size: 35px; + margin-bottom: 15px; + margin-top: 15px; + } \ No newline at end of file