Compare commits
45 Commits
feature-gm
...
feature-ml
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
61e8282e00 | ||
|
|
22bf62473f | ||
|
|
04e1ca12c8 | ||
|
|
2876be0d52 | ||
|
|
deab9666da | ||
|
|
878bfce095 | ||
|
|
9f4771f16d | ||
|
|
b8ee29ce9a | ||
|
|
0716214a31 | ||
|
|
e8836f1b5e | ||
|
|
d24ece60fd | ||
|
|
0301b0e1fb | ||
|
|
180f53dcaf | ||
|
|
3e4f18849e | ||
|
|
69c8bbb450 | ||
|
|
213a6dfc70 | ||
|
|
474d785c5c | ||
|
|
624bfdc2f9 | ||
|
|
3dda5fa770 | ||
|
|
b176e5c54f | ||
|
|
9adb53e7ab | ||
|
|
b86e680673 | ||
|
|
334b3867ec | ||
|
|
e6899eafdb | ||
|
|
f5d6396b06 | ||
|
|
393737e0f7 | ||
|
|
25711506b4 | ||
|
|
0592f6bfd1 | ||
|
|
28a16ead4f | ||
|
|
bbeb85bb0c | ||
|
|
5f55b073f3 | ||
|
|
08a9b3b630 | ||
|
|
8665693642 | ||
|
|
63e2d299fd | ||
|
|
32b604b172 | ||
|
|
c8cfe610a6 | ||
|
|
53f9acd922 | ||
|
|
e2ca15f556 | ||
|
|
cd047b33af | ||
|
|
d2c2767578 | ||
|
|
0901e7f07f | ||
|
|
3fb5ad039a | ||
|
|
31476e4d75 | ||
|
|
9d5b39e89c | ||
|
|
dcd5058f70 |
62
README.md
Normal file
62
README.md
Normal file
@@ -0,0 +1,62 @@
|
||||
# SafeQR Spring Boot Project
|
||||
|
||||
This is a Spring Boot project built with Java 17. This guide will help you set up the project and install the necessary dependencies using Maven.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
Before you begin, ensure you have the following software installed on your system:
|
||||
|
||||
- **Java 17**: This project requires Java 17. You can download it from the [official Oracle website](https://www.oracle.com/java/technologies/javase/jdk17-archive-downloads.html) or install it via a package manager (e.g., `sdkman` or `brew` on macOS).
|
||||
- **Maven 3.6+**: Apache Maven is used to manage the project's dependencies. You can download Maven from the [official Apache website](https://maven.apache.org/download.cgi) or install it via a package manager.
|
||||
|
||||
## Installation
|
||||
|
||||
Follow these steps to set up and run the project locally:
|
||||
|
||||
### 1. Clone the Repository
|
||||
|
||||
Clone the project repository to your local machine using the following command:
|
||||
|
||||
```bash
|
||||
git clone https://github.com/safeqr/backend-springboot.git
|
||||
cd backend-springboot
|
||||
```
|
||||
|
||||
### 2. Verify Java and Maven Installation
|
||||
|
||||
Ensure that Java 17 and Maven are installed and available on your system by running the following commands:
|
||||
|
||||
```bash
|
||||
java -version
|
||||
mvn -version
|
||||
```
|
||||
|
||||
You should see output indicating that Java 17 and Maven 3.6+ are installed.
|
||||
|
||||
### 3. Install Project Dependencies
|
||||
|
||||
Navigate to the root directory of the project (if you haven't already) and run the following command to clean the project and install all necessary dependencies:
|
||||
|
||||
```bash
|
||||
mvn clean install
|
||||
```
|
||||
|
||||
This command will:
|
||||
|
||||
- **Clean**: Remove any previously compiled files.
|
||||
- **Install**: Download all required dependencies as defined in the `pom.xml` file and compile the project.
|
||||
|
||||
### 4. Run the Application
|
||||
|
||||
Once the dependencies are installed, you can run the application with the following command:
|
||||
|
||||
```bash
|
||||
mvn spring-boot:run
|
||||
```
|
||||
|
||||
This will start the Spring Boot application, and you should see the application logs in the terminal. By default, the application will be available at `http://localhost:8080`.
|
||||
|
||||
## Additional Information
|
||||
|
||||
- **Configuration**: Any necessary configurations can be adjusted in the `application.properties` or `application.yml` files located in the `src/main/resources` directory.
|
||||
- **Building the Project**: To build a standalone JAR file, you can use `mvn package`, which will generate a JAR file in the `target` directory.
|
||||
21
pom.xml
21
pom.xml
@@ -121,26 +121,7 @@
|
||||
<artifactId>jackson-annotations</artifactId>
|
||||
<version>2.17.2</version>
|
||||
</dependency>
|
||||
<!-- https://mvnrepository.com/artifact/org.apache.spark/spark-core -->
|
||||
<dependency>
|
||||
<groupId>org.apache.spark</groupId>
|
||||
<artifactId>spark-core_2.13</artifactId>
|
||||
<version>3.4.3</version>
|
||||
</dependency>
|
||||
<!-- https://mvnrepository.com/artifact/org.apache.spark/spark-sql -->
|
||||
<dependency>
|
||||
<groupId>org.apache.spark</groupId>
|
||||
<artifactId>spark-sql_2.13</artifactId>
|
||||
<version>3.4.3</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<!-- https://mvnrepository.com/artifact/org.apache.spark/spark-mllib -->
|
||||
<dependency>
|
||||
<groupId>org.apache.spark</groupId>
|
||||
<artifactId>spark-mllib_2.13</artifactId>
|
||||
<version>3.4.3</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
|
||||
@@ -13,6 +13,7 @@ public class APIConstants {
|
||||
public static final String API_URL_QRCODE_VIRUS_TOTAL_CHECK = "/qrcodetypes/virusTotalCheck";
|
||||
public static final String API_URL_QRCODE_REDIRECT_COUNT = "/qrcodetypes/checkRedirects";
|
||||
public static final String API_URL_QRCODE_GET_QR_DETAILS = "/qrcodetypes/getQRDetails";
|
||||
public static final String PREDICTION_API_URL = "http://localhost:8000/predict";
|
||||
|
||||
|
||||
public static final String API_URL_USER_GET = "/user/getUser";
|
||||
@@ -23,9 +24,12 @@ public class APIConstants {
|
||||
public static final String API_URL_USER_SET_BOOKMARK = "/user/setBookmark";
|
||||
public static final String API_URL_USER_DELETE_BOOKMARK = "/user/deleteBookmark";
|
||||
public static final String API_URL_USER_DELETE_ALL_BOOKMARK = "/user/deleteAllBookmark";
|
||||
|
||||
|
||||
public static final String API_URL_GMAIL_GET_EMAILS = "/gmail/getEmails";
|
||||
public static final String API_URL_GMAIL_GET_SCANNED_EMAILS = "/gmail/getScannedEmails";
|
||||
public static final String API_URL_GMAIL_DELETE_MESSAGE = "/gmail/deleteMessage";
|
||||
public static final String API_URL_GMAIL_DELETE_ALL_MESSAGES = "/gmail/deleteAllMessages";
|
||||
|
||||
|
||||
|
||||
public static final String API_URL_TIPS_GET = "/tips/getTips";
|
||||
}
|
||||
|
||||
@@ -22,4 +22,11 @@ public class CommonConstants {
|
||||
public static final String CLASSIFY_WARNING = "WARNING";
|
||||
public static final String CLASSIFY_UNSAFE = "UNSAFE";
|
||||
public static final String CLASSIFY_UNKNOWN = "UNKNOWN";
|
||||
|
||||
public static final String CAT_BENIGN = "Benign";
|
||||
public static final String CAT_DEFACEMENT = "Defacement";
|
||||
public static final String CAT_MALWARE = "Malware";
|
||||
public static final String CAT_PHISHING = "Phishing";
|
||||
|
||||
public static final Integer GMAIL_ACTIVE = 1;
|
||||
}
|
||||
|
||||
@@ -16,4 +16,8 @@ public class GlobalExceptionHandler {
|
||||
public ResponseEntity<ErrorResponse> handleResourceAlreadyExistsException(ResourceAlreadyExists e) {
|
||||
return new ResponseEntity<>(new ErrorResponse(e.getMessage(), HttpStatus.BAD_REQUEST.value()), HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
@ExceptionHandler(InvalidFormatExceptions.class)
|
||||
public ResponseEntity<ErrorResponse> handleInvalidFormatException(InvalidFormatExceptions e) {
|
||||
return new ResponseEntity<>(new ErrorResponse(e.getMessage(), HttpStatus.BAD_REQUEST.value()), HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
package com.safeqr.app.exceptions;
|
||||
|
||||
public class InvalidFormatExceptions extends RuntimeException {
|
||||
public InvalidFormatExceptions(String message){
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
package com.safeqr.app.gmail.controller;
|
||||
|
||||
import com.google.api.services.gmail.model.*;
|
||||
import com.safeqr.app.gmail.dto.MessageRequestDto;
|
||||
import com.safeqr.app.gmail.dto.ScannedGmailResponseDto;
|
||||
import org.apache.commons.codec.binary.Base64;
|
||||
import com.safeqr.app.gmail.dto.BaseResponse;
|
||||
import org.json.JSONObject;
|
||||
import com.google.api.client.auth.oauth2.AuthorizationCodeRequestUrl;
|
||||
import com.google.api.client.auth.oauth2.Credential;
|
||||
@@ -26,11 +26,11 @@ import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.servlet.view.RedirectView;
|
||||
import static com.safeqr.app.constants.APIConstants.*;
|
||||
import java.io.IOException;
|
||||
import java.lang.Thread;
|
||||
import static com.safeqr.app.constants.CommonConstants.HEADER_USER_ID;
|
||||
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.CompletionException;
|
||||
|
||||
|
||||
@RestController
|
||||
@@ -129,5 +129,17 @@ public class GmailController {
|
||||
|
||||
return new ResponseEntity<>("Scan Gmail Request is being processed", HttpStatus.ACCEPTED);
|
||||
}
|
||||
|
||||
@PutMapping(value = API_URL_GMAIL_DELETE_MESSAGE, produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
public ResponseEntity<BaseResponse> deleteMessage(@RequestHeader(name = HEADER_USER_ID) String userId, @RequestBody MessageRequestDto messageRequestDto) {
|
||||
logger.info("User Id Invoking PUT Delete Single Email endpoint: {}", userId);
|
||||
return ResponseEntity.ok(gmailService.deleteMessage(userId, messageRequestDto.getMessageId()));
|
||||
}
|
||||
|
||||
@PutMapping(value = API_URL_GMAIL_DELETE_ALL_MESSAGES, produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
public ResponseEntity<BaseResponse> deleteAllMessages(@RequestHeader(name = HEADER_USER_ID) String userId) {
|
||||
logger.info("User Id Invoking PUT Delete All Emails endpoint: {}", userId);
|
||||
return ResponseEntity.ok(gmailService.deleteAllMessages(userId));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
12
src/main/java/com/safeqr/app/gmail/dto/BaseResponse.java
Normal file
12
src/main/java/com/safeqr/app/gmail/dto/BaseResponse.java
Normal file
@@ -0,0 +1,12 @@
|
||||
package com.safeqr.app.gmail.dto;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
public class BaseResponse {
|
||||
private String message;
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package com.safeqr.app.gmail.dto;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
public class MessageRequestDto {
|
||||
private String messageId;
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
||||
@Builder
|
||||
|
||||
@@ -7,6 +7,7 @@ import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import org.hibernate.annotations.UuidGenerator;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.time.OffsetDateTime;
|
||||
import java.util.UUID;
|
||||
|
||||
@@ -33,7 +34,7 @@ public class GmailEmailEntity {
|
||||
private String threadId;
|
||||
|
||||
@Column(name = "history_id")
|
||||
private Long historyId;
|
||||
private BigInteger historyId;
|
||||
|
||||
@Column(name= "subject")
|
||||
private String subject;
|
||||
@@ -44,6 +45,9 @@ public class GmailEmailEntity {
|
||||
@Column(name = "date_created")
|
||||
private OffsetDateTime dateCreated;
|
||||
|
||||
@Column(name = "active")
|
||||
private int active = 1;
|
||||
|
||||
@PrePersist
|
||||
public void prePersist() {
|
||||
dateCreated = OffsetDateTime.now();
|
||||
|
||||
@@ -13,6 +13,7 @@ public class EmailMessage {
|
||||
private String subject;
|
||||
private String historyId;
|
||||
private String date;
|
||||
private int active;
|
||||
|
||||
@JsonInclude(JsonInclude.Include.NON_EMPTY)
|
||||
List<QRCodeByContentId> qrCodeByContentId;
|
||||
@@ -29,6 +30,7 @@ public class EmailMessage {
|
||||
this.subject = subject;
|
||||
this.historyId = historyId;
|
||||
this.date = date;
|
||||
this.active = 1;
|
||||
this.qrCodeByContentId = new ArrayList<>();
|
||||
this.qrCodeByURL = new ArrayList<>();
|
||||
this.decodedContentsDetails = new ArrayList<>();
|
||||
|
||||
@@ -3,10 +3,31 @@ package com.safeqr.app.gmail.repository;
|
||||
|
||||
import com.safeqr.app.gmail.entity.GmailEmailEntity;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.Modifying;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
public interface GmailEmailRespository extends JpaRepository<GmailEmailEntity, UUID> {
|
||||
List<GmailEmailEntity> findByUserId(String userId);
|
||||
// Method to find by userId and active status
|
||||
List<GmailEmailEntity> findByUserIdAndActive(String userId, Integer active);
|
||||
|
||||
// Method to update active status to 0 for a specific userId
|
||||
@Modifying
|
||||
@Transactional
|
||||
@Query("UPDATE GmailEmailEntity e SET e.active = (SELECT MIN(e2.active) FROM GmailEmailEntity e2 WHERE e2.userId = :userId) - 1 " +
|
||||
"WHERE e.userId = :userId " +
|
||||
"AND e.active = 1 ")
|
||||
int deactivateEmailsByUserId(String userId);
|
||||
|
||||
// Method to update active status to 0 for a specific userId and messageId
|
||||
@Modifying
|
||||
@Transactional
|
||||
@Query("UPDATE GmailEmailEntity e SET e.active = (SELECT MIN(e2.active) FROM GmailEmailEntity e2 WHERE e2.userId = :userId AND e2.messageId = :messageId) - 1 " +
|
||||
"WHERE e.userId = :userId AND e.messageId = :messageId " +
|
||||
"AND e.active = 1")
|
||||
|
||||
int deactivateEmailByUserIdAndMessageId(String userId, String messageId);
|
||||
}
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
package com.safeqr.app.gmail.service;
|
||||
|
||||
import com.google.api.client.auth.oauth2.BearerToken;
|
||||
import com.google.api.client.auth.oauth2.ClientParametersAuthentication;
|
||||
import com.google.api.client.auth.oauth2.Credential;
|
||||
import com.google.api.client.auth.oauth2.*;
|
||||
|
||||
import com.google.api.client.auth.oauth2.TokenResponse;
|
||||
import com.google.api.client.googleapis.auth.oauth2.GoogleRefreshTokenRequest;
|
||||
import com.google.api.client.googleapis.json.GoogleJsonResponseException;
|
||||
import com.google.api.client.http.GenericUrl;
|
||||
@@ -18,7 +15,9 @@ import com.google.zxing.*;
|
||||
import com.google.zxing.client.j2se.BufferedImageLuminanceSource;
|
||||
import com.google.zxing.common.HybridBinarizer;
|
||||
import com.google.zxing.multi.qrcode.QRCodeMultiReader;
|
||||
import com.google.zxing.qrcode.encoder.QRCode;
|
||||
|
||||
import com.safeqr.app.exceptions.ResourceNotFoundExceptions;
|
||||
import com.safeqr.app.gmail.dto.BaseResponse;
|
||||
import com.safeqr.app.gmail.dto.ScannedGmailResponseDto;
|
||||
import com.safeqr.app.gmail.entity.GmailCidEntity;
|
||||
import com.safeqr.app.gmail.entity.GmailEmailEntity;
|
||||
@@ -31,8 +30,10 @@ import com.safeqr.app.gmail.repository.GmailEmailRespository;
|
||||
import com.safeqr.app.gmail.repository.GmailUrlsRespository;
|
||||
import com.safeqr.app.qrcode.model.QRCodeModel;
|
||||
import com.safeqr.app.qrcode.service.QRCodeTypeService;
|
||||
import com.safeqr.app.user.entity.UserEntity;
|
||||
import com.safeqr.app.user.service.UserService;
|
||||
import com.safeqr.app.utils.DateParsingUtils;
|
||||
import io.hypersistence.utils.common.StringUtils;
|
||||
import org.apache.commons.codec.binary.Base64;
|
||||
import org.jsoup.Jsoup;
|
||||
import org.jsoup.nodes.Document;
|
||||
@@ -43,12 +44,14 @@ import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.dao.DataIntegrityViolationException;
|
||||
import org.springframework.scheduling.annotation.Async;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.lang.Thread;
|
||||
import java.math.BigInteger;
|
||||
import java.net.ConnectException;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
@@ -61,9 +64,11 @@ import java.util.*;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
import org.apache.http.auth.AuthenticationException;
|
||||
|
||||
import static com.google.api.client.googleapis.auth.oauth2.GoogleOAuthConstants.TOKEN_SERVER_URL;
|
||||
import static com.safeqr.app.constants.APIConstants.APPLICATION_NAME;
|
||||
import static com.safeqr.app.constants.CommonConstants.GMAIL_ACTIVE;
|
||||
|
||||
@Service
|
||||
public class GmailService {
|
||||
@@ -110,22 +115,67 @@ public class GmailService {
|
||||
.build();
|
||||
}
|
||||
// Renew the access token if it has expired using the refresh token.
|
||||
private String refreshAccessToken(String refreshToken) throws IOException {
|
||||
private String refreshAccessToken(String refreshToken) throws IOException, AuthenticationException {
|
||||
logger.info("Refresh token in refreshAccessToken: {}", refreshToken);
|
||||
if (StringUtils.isBlank(refreshToken)) {
|
||||
logger.error("Refresh token is null or empty");
|
||||
throw new AuthenticationException("Invalid refresh token");
|
||||
}
|
||||
|
||||
int maxRetries = 3;
|
||||
int retryCount = 0;
|
||||
while (retryCount < maxRetries) {
|
||||
try {
|
||||
logger.info("Attempting to refresh access token (attempt {})", retryCount + 1);
|
||||
TokenResponse response = new GoogleRefreshTokenRequest(
|
||||
httpTransport, JSON_FACTORY, refreshToken, clientId, clientSecret)
|
||||
.execute();
|
||||
return response.getAccessToken();
|
||||
String newAccessToken = response.getAccessToken();
|
||||
logger.info("Access token refreshed successfully");
|
||||
return newAccessToken;
|
||||
} catch (TokenResponseException e) {
|
||||
logger.error("Failed to refresh access token. Status code: {}", e.getStatusCode());
|
||||
logger.error("Error message: {}", e.getDetails().getError());
|
||||
logger.error("Error description: {}", e.getDetails().getErrorDescription());
|
||||
|
||||
if (e.getStatusCode() == 401) {
|
||||
logger.warn("Unauthorized error. The refresh token may be invalid or revoked.");
|
||||
throw new AuthenticationException("Refresh token is invalid. User needs to re-authenticate.");
|
||||
}
|
||||
|
||||
private Gmail refreshAndGetGmailService(String accessToken, String refreshToken) throws IOException {
|
||||
if (++retryCount >= maxRetries) {
|
||||
logger.error("Max retries reached. Unable to refresh access token.");
|
||||
throw e;
|
||||
}
|
||||
|
||||
// Implement exponential backoff
|
||||
try {
|
||||
return getGmailService(accessToken, refreshToken);
|
||||
Thread.sleep((long) (Math.pow(2, retryCount) * 1000));
|
||||
} catch (InterruptedException ie) {
|
||||
Thread.currentThread().interrupt();
|
||||
throw new IOException("Token refresh interrupted", ie);
|
||||
}
|
||||
}
|
||||
}
|
||||
throw new IOException("Failed to refresh access token after " + maxRetries + " attempts");
|
||||
}
|
||||
|
||||
private Gmail refreshAndGetGmailService(String accessToken, String refreshToken) throws IOException, AuthenticationException {
|
||||
try {
|
||||
Gmail service = getGmailService(accessToken, refreshToken);
|
||||
service.users().getProfile("me").execute();
|
||||
logger.info("Gmail service authenticated with provided access token.");
|
||||
return service;
|
||||
} catch (GoogleJsonResponseException e) {
|
||||
if (e.getStatusCode() == 401) {
|
||||
logger.info("Access token expired. Refreshing token...");
|
||||
logger.info("Access token expired. Refreshing...");
|
||||
String newAccessToken = refreshAccessToken(refreshToken);
|
||||
return getGmailService(newAccessToken, refreshToken);
|
||||
Gmail service = getGmailService(newAccessToken, refreshToken);
|
||||
service.users().getProfile("me").execute();
|
||||
logger.info("Gmail service authenticated with refreshed token.");
|
||||
return service;
|
||||
}
|
||||
logger.error("Failed to authenticate with Gmail API", e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
@@ -138,28 +188,44 @@ public class GmailService {
|
||||
CompletableFuture.completedFuture(result);
|
||||
} catch (IOException e) {
|
||||
logger.error("Error processing Gmail", e);
|
||||
} catch (AuthenticationException e) {
|
||||
logger.error("Error Authenticating", e);
|
||||
}
|
||||
}
|
||||
// Scan all emails in the user's inbox.
|
||||
public ScannedGmailResponseDto getEmail(String userId, String accessToken, String refreshToken) throws IOException {
|
||||
public ScannedGmailResponseDto getEmail(String userId, String accessToken, String refreshToken) throws IOException, AuthenticationException {
|
||||
Gmail service = refreshAndGetGmailService(accessToken, refreshToken);
|
||||
logger.info("Gmail service initialized: {}", service);
|
||||
|
||||
List<EmailMessage> emailMessagesList = new ArrayList<>();
|
||||
String meUserId = "me";
|
||||
String nextPageToken = null;
|
||||
UserEntity userEntity = userService.getUserByIdForGmail(userId);
|
||||
BigInteger historyId = userEntity.getGmailHistoryId();
|
||||
|
||||
// Fetch history if historyId is not 0 (Default db value)
|
||||
if (historyId.compareTo(BigInteger.ZERO) != 0) {
|
||||
logger.info("HistoryId from db: {}", historyId);
|
||||
|
||||
ListHistoryResponse historyResponse = service.users().history().list(meUserId)
|
||||
.setStartHistoryId(historyId)
|
||||
.execute();
|
||||
List<History> historyList = historyResponse.getHistory();
|
||||
if (historyList != null) {
|
||||
for (History history : historyList) {
|
||||
logger.info("History Response - History Id: {}, Message Id: {}", history.getId(), history.getMessages().get(0).getId());
|
||||
List<Message> messages = history.getMessages();
|
||||
for (Message message : messages) {
|
||||
EmailMessage emailMessage = processMessage(service, meUserId, message);
|
||||
if (emailMessage != null) {
|
||||
emailMessagesList.add(emailMessage);
|
||||
saveEmailMessageAndScanQRCode(userId, emailMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Fetching email messages with page token and setting max results, Default value is 100.
|
||||
do {
|
||||
|
||||
// ListHistoryResponse historyResponse = service.users().history().list(meUserId)
|
||||
// .setStartHistoryId(BigInteger.valueOf(689335))
|
||||
// .execute();
|
||||
//
|
||||
// List<History> historyList = historyResponse.getHistory();
|
||||
//
|
||||
// for (History history : historyList) {
|
||||
// logger.info("History Id: {}, Message Id: {}, Message Snippet: {}", history.getId(), history.getMessages().get(0).getId(), history.getMessages().get(0).getHistoryId());
|
||||
// }
|
||||
ListMessagesResponse listResponse = fetchMessages(service, meUserId, nextPageToken);
|
||||
List<Message> messages = listResponse.getMessages();
|
||||
nextPageToken = listResponse.getNextPageToken();
|
||||
@@ -174,12 +240,21 @@ public class GmailService {
|
||||
}
|
||||
}
|
||||
} while (nextPageToken != null);
|
||||
}
|
||||
|
||||
// Update user's history id.
|
||||
// TODO: Update user's history id.
|
||||
BigInteger latestHistoryId = getLatestHistoryId(service, meUserId);
|
||||
if (latestHistoryId != null) {
|
||||
userEntity.setGmailHistoryId(latestHistoryId);
|
||||
userService.updateUserEntity(userEntity);
|
||||
}
|
||||
|
||||
return new ScannedGmailResponseDto(emailMessagesList);
|
||||
}
|
||||
private BigInteger getLatestHistoryId(Gmail service, String userId) throws IOException {
|
||||
Profile profile = service.users().getProfile(userId).execute();
|
||||
return profile.getHistoryId();
|
||||
}
|
||||
// Save email message to database and scan QR code.
|
||||
private void saveEmailMessageAndScanQRCode(String userId, EmailMessage emailMessage) {
|
||||
GmailEmailEntity gmailEmailEntity = saveEmailMessage(userId, emailMessage);
|
||||
@@ -203,9 +278,10 @@ public class GmailService {
|
||||
.userId(userId)
|
||||
.messageId(emailMessage.getMessageId())
|
||||
.threadId(emailMessage.getThreadId())
|
||||
.historyId(Long.valueOf(emailMessage.getHistoryId()))
|
||||
.historyId(new BigInteger(emailMessage.getHistoryId()))
|
||||
.subject(emailMessage.getSubject())
|
||||
.dateReceived(dateReceived)
|
||||
.active(emailMessage.getActive())
|
||||
.build();
|
||||
return gmailEmailRespository.save(gmailEmailEntity);
|
||||
} catch (DataIntegrityViolationException e) {
|
||||
@@ -263,7 +339,7 @@ public class GmailService {
|
||||
// Fetching Scanned Gmail from database
|
||||
public ScannedGmailResponseDto fetchScannedGmail(String userId){
|
||||
// Fetching all emails from gmail_email table
|
||||
List<GmailEmailEntity> userEmailsList = gmailEmailRespository.findByUserId(userId);
|
||||
List<GmailEmailEntity> userEmailsList = gmailEmailRespository.findByUserIdAndActive(userId, GMAIL_ACTIVE);
|
||||
List<EmailMessage> emailMessageList = new ArrayList<>();
|
||||
|
||||
if (userEmailsList != null && !userEmailsList.isEmpty()) {
|
||||
@@ -335,6 +411,7 @@ public class GmailService {
|
||||
emailMessageList.add(emailMessage);
|
||||
|
||||
});
|
||||
emailMessageList.sort(Comparator.comparing(EmailMessage::getDate, Comparator.nullsLast(Comparator.reverseOrder())));
|
||||
}
|
||||
return ScannedGmailResponseDto.builder().messages(emailMessageList).build();
|
||||
}
|
||||
@@ -346,7 +423,8 @@ public class GmailService {
|
||||
.execute();
|
||||
}
|
||||
// Processing email message and returning EmailMessage object if it has a valid QR code.
|
||||
private EmailMessage processMessage(Gmail service, String userId, Message message) throws IOException {
|
||||
private EmailMessage processMessage(Gmail service, String userId, Message message) {
|
||||
try {
|
||||
message = service.users().messages().get(userId, message.getId()).setFormat("full").execute();
|
||||
List<MessagePart> parts = message.getPayload().getParts();
|
||||
Set<String> attachmentIds = new HashSet<>();
|
||||
@@ -369,6 +447,18 @@ public class GmailService {
|
||||
processImageUrls(imageUrls, emailMessage);
|
||||
|
||||
return emailMessage.hasQRCodes() ? emailMessage : null;
|
||||
} catch (GoogleJsonResponseException e) {
|
||||
if (e.getStatusCode() == 404) {
|
||||
logger.warn("Message with ID {} not found. It may have been deleted.", message.getId());
|
||||
return null;
|
||||
} else {
|
||||
logger.error("Error processing message with ID {}: {}", message.getId(), e.getMessage());
|
||||
throw new RuntimeException("Error processing Gmail message", e);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
logger.error("IO error processing message with ID {}: {}", message.getId(), e.getMessage());
|
||||
throw new RuntimeException("IO error processing Gmail message", e);
|
||||
}
|
||||
}
|
||||
// Process all the attachments.
|
||||
private void processAttachments(Gmail service, String messageId, List<MessagePart> parts, Set<String> attachmentIds, EmailMessage emailMessage) throws IOException {
|
||||
@@ -549,4 +639,21 @@ public class GmailService {
|
||||
String lowerUrl = url.toLowerCase();
|
||||
return lowerUrl.endsWith(".jpg") || lowerUrl.endsWith(".jpeg") || lowerUrl.endsWith(".png") || lowerUrl.endsWith(".gif") || lowerUrl.endsWith(".bmp");
|
||||
}
|
||||
@Transactional
|
||||
public BaseResponse deleteMessage(String userId, String messageId) {
|
||||
int updatedCount = gmailEmailRespository.deactivateEmailByUserIdAndMessageId(userId, messageId);
|
||||
// throw exception if email message not found
|
||||
if (updatedCount < 1)
|
||||
throw new ResourceNotFoundExceptions("Email message not found");
|
||||
|
||||
return BaseResponse.builder().message("Email deleted successfully").build();
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public BaseResponse deleteAllMessages(String userId) {
|
||||
int updatedCount = gmailEmailRespository.deactivateEmailsByUserId(userId);
|
||||
return (updatedCount < 1) ?
|
||||
BaseResponse.builder().message("No Email found").build() :
|
||||
BaseResponse.builder().message("All Emails deleted successfully").build();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,213 @@
|
||||
package com.safeqr.app.prediction.model;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.safeqr.app.qrcode.entity.QRCodeTypeEntity;
|
||||
import com.safeqr.app.qrcode.entity.URLEntity;
|
||||
import com.safeqr.app.qrcode.model.URLModel;
|
||||
import lombok.*;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Getter
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class URLFeaturesMapper {
|
||||
private static final Logger logger = LoggerFactory.getLogger(URLFeaturesMapper.class);
|
||||
|
||||
@JsonProperty("domain")
|
||||
private Integer domain;
|
||||
|
||||
@JsonProperty("subdomain")
|
||||
private Integer subdomain;
|
||||
|
||||
@JsonProperty("top_level_domain")
|
||||
private Integer topLevelDomain;
|
||||
|
||||
@JsonProperty("query")
|
||||
private Integer query;
|
||||
|
||||
@JsonProperty("fragment")
|
||||
private Integer fragment;
|
||||
|
||||
@JsonProperty("redirect")
|
||||
private Integer redirect;
|
||||
|
||||
@JsonProperty("path")
|
||||
private Integer path;
|
||||
|
||||
@JsonProperty("redirect_chain")
|
||||
private Integer redirectChain;
|
||||
|
||||
@JsonProperty("hsts_header")
|
||||
private Integer hstsHeader;
|
||||
|
||||
@JsonProperty("ssl_stripping")
|
||||
private Integer sslStripping;
|
||||
|
||||
@JsonProperty("hostname_embedding")
|
||||
private Integer hostnameEmbedding;
|
||||
|
||||
@JsonProperty("javascript_check")
|
||||
private Integer javascriptCheck;
|
||||
|
||||
@JsonProperty("shortening_service")
|
||||
private Integer shorteningService;
|
||||
|
||||
@JsonProperty("has_ip_address")
|
||||
private Integer hasIpAddress;
|
||||
|
||||
@JsonProperty("tracking_descriptions")
|
||||
private Integer trackingDescriptions;
|
||||
|
||||
@JsonProperty("url_encoding")
|
||||
private Integer urlEncoding;
|
||||
|
||||
@JsonProperty("has_executable")
|
||||
private Integer hasExecutable;
|
||||
|
||||
@JsonProperty("tls")
|
||||
private Integer tls;
|
||||
|
||||
@JsonProperty("contents")
|
||||
private Integer contents;
|
||||
|
||||
public static URLFeaturesMapper fromEntity(URLModel urlModel) {
|
||||
URLEntity details = urlModel.getDetails();
|
||||
QRCodeTypeEntity qrCodeTypeEntity = urlModel.getData().getInfo();
|
||||
URLFeaturesMapper features = URLFeaturesMapper.builder()
|
||||
.build();
|
||||
features.setDomain(details.getDomain());
|
||||
features.setSubdomain(details.getSubdomain());
|
||||
features.setTopLevelDomain(details.getTopLevelDomain());
|
||||
features.setQuery(details.getQuery());
|
||||
features.setFragment(details.getFragment());
|
||||
features.setPath(details.getPath());
|
||||
features.setRedirect(details.getRedirect());
|
||||
features.setRedirectChain(details.getRedirectChain());
|
||||
features.setHstsHeader(details.getHstsHeader());
|
||||
features.setSslStripping(details.getSslStripping());
|
||||
features.setHostnameEmbedding(details.getHostnameEmbedding());
|
||||
features.setJavascriptCheck(details.getJavascriptCheck());
|
||||
features.setShorteningService(details.getShorteningService());
|
||||
features.setHasIpAddress(details.getHasIpAddress());
|
||||
features.setTrackingDescriptions(details.getTrackingDescriptions());
|
||||
features.setUrlEncoding(details.getUrlEncoding());
|
||||
features.setHasExecutable(details.getHasExecutable());
|
||||
features.setTls(Math.toIntExact(qrCodeTypeEntity.getId()));
|
||||
features.setContents(urlModel.getData().getContents());
|
||||
|
||||
return features;
|
||||
}
|
||||
|
||||
private void setRedirect(int redirect) {
|
||||
this.redirect = redirect;
|
||||
}
|
||||
|
||||
// Custom setter for tls (qr_code_type_id)
|
||||
public void setTls(Integer tls) {
|
||||
if (tls != null) {
|
||||
this.tls = tls == 1 ? 0 : tls == 9 ? 1 : tls.intValue();
|
||||
} else {
|
||||
this.tls = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Custom setter for hostnameEmbedding and other similar columns
|
||||
public void setHostnameEmbedding(Integer hostnameEmbedding) {
|
||||
this.hostnameEmbedding = (hostnameEmbedding != null && hostnameEmbedding != 0) ? 1 : 0;
|
||||
}
|
||||
|
||||
public void setJavascriptCheck(String javascriptCheck) {
|
||||
this.javascriptCheck = (javascriptCheck != null && !javascriptCheck.isEmpty()) ? 1 : 0;
|
||||
}
|
||||
|
||||
public void setShorteningService(String shorteningService) {
|
||||
this.shorteningService = (shorteningService != null && !shorteningService.isEmpty()) ? 1 : 0;
|
||||
}
|
||||
|
||||
public void setHasIpAddress(String hasIpAddress) {
|
||||
this.hasIpAddress = (hasIpAddress != null && !hasIpAddress.isEmpty()) ? 1 : 0;
|
||||
}
|
||||
|
||||
public void setUrlEncoding(String urlEncoding) {
|
||||
this.urlEncoding = (urlEncoding != null && !urlEncoding.isEmpty()) ? 1 : 0;
|
||||
}
|
||||
|
||||
public void setHasExecutable(String hasExecutable) {
|
||||
this.hasExecutable = (hasExecutable != null && !hasExecutable.isEmpty()) ? 1 : 0;
|
||||
}
|
||||
|
||||
public void setTrackingDescriptions(List<String> trackingDescriptions) {
|
||||
this.trackingDescriptions = (trackingDescriptions != null && !trackingDescriptions.isEmpty()) ? 1 : 0;
|
||||
}
|
||||
|
||||
// Custom setter for sslStripping
|
||||
public void setSslStripping(List<Boolean> sslStripping) {
|
||||
if (sslStripping != null && !sslStripping.isEmpty() && sslStripping.get(0) != null) {
|
||||
this.sslStripping = sslStripping.get(0) ? 1 : 0;
|
||||
} else {
|
||||
this.sslStripping = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Custom setter for hstsHeader
|
||||
public void setHstsHeader(List<String> hstsHeader) {
|
||||
logger.info("HSTS header value: {}", hstsHeader);
|
||||
if (hstsHeader == null || hstsHeader.isEmpty()) {
|
||||
this.hstsHeader = 0;
|
||||
} else {
|
||||
logger.info("first hsts header value: {}", hstsHeader.get(0));
|
||||
if (hstsHeader.get(0).toLowerCase().contains("no")) {
|
||||
this.hstsHeader = 0;
|
||||
} else {
|
||||
this.hstsHeader = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Custom setters for calculating string lengths
|
||||
public void setDomain(String domain) {
|
||||
this.domain = (domain != null) ? domain.length() : 0;
|
||||
}
|
||||
|
||||
public void setSubdomain(String subdomain) {
|
||||
this.subdomain = (subdomain != null) ? subdomain.length() : 0;
|
||||
}
|
||||
|
||||
public void setTopLevelDomain(String topLevelDomain) {
|
||||
this.topLevelDomain = (topLevelDomain != null) ? topLevelDomain.length() : 0;
|
||||
}
|
||||
|
||||
public void setQuery(String query) {
|
||||
this.query = (query != null) ? query.length() : 0;
|
||||
}
|
||||
|
||||
public void setFragment(String fragment) {
|
||||
this.fragment = (fragment != null) ? fragment.length() : 0;
|
||||
}
|
||||
|
||||
public void setPath(String path) {
|
||||
this.path = (path != null) ? path.length() : 0;
|
||||
}
|
||||
|
||||
public void setRedirectChain(List<String> redirectChain) {
|
||||
logger.info("Redirect chain: {}", redirectChain);
|
||||
if (redirectChain != null) {
|
||||
// Calculate the total number of characters in the list of strings
|
||||
int totalChars;
|
||||
totalChars = redirectChain.stream()
|
||||
.mapToInt(String::length)
|
||||
.sum();
|
||||
this.redirectChain = totalChars;
|
||||
} else {
|
||||
this.redirectChain = 0;
|
||||
}
|
||||
}
|
||||
|
||||
public void setContents(String contents) {
|
||||
this.contents = (contents != null) ? contents.length() : 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
package com.safeqr.app.prediction.service;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.safeqr.app.prediction.model.URLFeaturesMapper;
|
||||
import com.safeqr.app.qrcode.model.URLModel;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.http.HttpEntity;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.MediaType;
|
||||
|
||||
import static com.safeqr.app.constants.APIConstants.PREDICTION_API_URL;
|
||||
|
||||
@Service
|
||||
public class PredictionService {
|
||||
private static final Logger logger = LoggerFactory.getLogger(PredictionService.class);
|
||||
|
||||
private final RestTemplate restTemplate;
|
||||
private final ObjectMapper objectMapper;
|
||||
|
||||
public PredictionService(RestTemplate restTemplate, ObjectMapper objectMapper) {
|
||||
this.restTemplate = restTemplate;
|
||||
this.objectMapper = objectMapper;
|
||||
}
|
||||
|
||||
public String predict(URLModel urlModel) {
|
||||
// Convert URLModel to URLFeatures
|
||||
URLFeaturesMapper features = URLFeaturesMapper.fromEntity(urlModel);
|
||||
logger.info("Prediction request: {}", features);
|
||||
logger.info("feature contents : {}", features.getContents());
|
||||
logger.info("feature domain : {}", features.getDomain());
|
||||
logger.info("feature sub-domain : {}", features.getSubdomain());
|
||||
logger.info("feature tld : {}", features.getTopLevelDomain());
|
||||
logger.info("feature path : {}", features.getPath());
|
||||
logger.info("feature query : {}", features.getQuery());
|
||||
logger.info("feature fragment : {}", features.getFragment());
|
||||
logger.info("feature redirect : {}", features.getRedirect());
|
||||
logger.info("feature redirect chain: {}", features.getRedirectChain());
|
||||
logger.info("feature shortening service: {}", features.getShorteningService());
|
||||
logger.info("feature hasExecutable: {}", features.getHasExecutable());
|
||||
logger.info("feature hasIP: {}", features.getHasIpAddress());
|
||||
logger.info("feature hostname embedding: {}", features.getHostnameEmbedding());
|
||||
logger.info("feature hsts header: {}", features.getHstsHeader());
|
||||
logger.info("feature javascript check: {}", features.getJavascriptCheck());
|
||||
logger.info("feature tracking: {}", features.getTrackingDescriptions());
|
||||
logger.info("feature urlencoding: {}", features.getUrlEncoding());
|
||||
|
||||
// Prepare the HTTP headers
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setContentType(MediaType.APPLICATION_JSON);
|
||||
|
||||
// Create the HTTP entity containing the features and headers
|
||||
HttpEntity<URLFeaturesMapper> requestEntity = new HttpEntity<>(features, headers);
|
||||
|
||||
// Make the HTTP POST request to the FastAPI prediction endpoint
|
||||
ResponseEntity<String> response = restTemplate.exchange(
|
||||
PREDICTION_API_URL,
|
||||
HttpMethod.POST,
|
||||
requestEntity,
|
||||
String.class
|
||||
);
|
||||
|
||||
// Use ObjectMapper to deserialize the response and automatically remove quotes
|
||||
String prediction = response.getBody();
|
||||
try {
|
||||
prediction = objectMapper.readValue(prediction, String.class);
|
||||
} catch (Exception e) {
|
||||
logger.error("Failed to parse prediction response", e);
|
||||
prediction = "Unknown";
|
||||
}
|
||||
logger.info("Prediction response: {}", prediction);
|
||||
|
||||
// Return the prediction
|
||||
return prediction;
|
||||
}
|
||||
}
|
||||
@@ -3,12 +3,9 @@ package com.safeqr.app.qrcode.controller;
|
||||
import static com.safeqr.app.constants.APIConstants.*;
|
||||
import static com.safeqr.app.constants.CommonConstants.*;
|
||||
import com.safeqr.app.qrcode.dto.request.QRCodePayload;
|
||||
import com.safeqr.app.qrcode.dto.RedirectCountResponse;
|
||||
import com.safeqr.app.qrcode.dto.URLVerificationResponse;
|
||||
import com.safeqr.app.qrcode.dto.response.BaseScanResponse;
|
||||
import com.safeqr.app.qrcode.entity.QRCodeTypeEntity;
|
||||
import com.safeqr.app.qrcode.service.QRCodeTypeService;
|
||||
import com.safeqr.app.qrcode.service.RedirectCountService;
|
||||
import com.safeqr.app.qrcode.service.URLVerificationService;
|
||||
import com.safeqr.app.qrcode.service.VirusTotalService;
|
||||
import org.slf4j.Logger;
|
||||
@@ -36,9 +33,6 @@ public class QRCodeTypeController {
|
||||
@Autowired
|
||||
private VirusTotalService virusTotalService;
|
||||
|
||||
@Autowired
|
||||
private RedirectCountService redirectCountService;
|
||||
|
||||
@GetMapping(value = API_URL_QRCODE_GET_ALL)
|
||||
public ResponseEntity<List<QRCodeTypeEntity>> getAllTypes() {
|
||||
return ResponseEntity.ok(qrCodeTypeService.getAllTypes());
|
||||
@@ -56,15 +50,12 @@ public class QRCodeTypeController {
|
||||
return ResponseEntity.ok(qrCodeTypeService.scanQRCode(userId, payload));
|
||||
}
|
||||
|
||||
@PostMapping(API_URL_QRCODE_DETECT)
|
||||
public ResponseEntity<String> detectType(@RequestBody QRCodePayload payload) {
|
||||
return ResponseEntity.ok(qrCodeTypeService.detectType(payload).block());
|
||||
}
|
||||
@PostMapping(value = API_URL_QRCODE_VERIFY_URL, produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
public ResponseEntity<BaseScanResponse> verifyURL(@RequestBody QRCodePayload payload,
|
||||
@RequestHeader(required = false, name = HEADER_USER_ID) String userId) {
|
||||
logger.info("User Id Invoking verify url endpoint: {}", userId);
|
||||
return ResponseEntity.ok(qrCodeTypeService.scanQRCode(userId, payload));
|
||||
|
||||
@PostMapping(API_URL_QRCODE_VERIFY_URL)
|
||||
public ResponseEntity<URLVerificationResponse> verifyURL(@RequestBody QRCodePayload payload) {
|
||||
URLVerificationResponse response = urlVerificationService.verifyURL(payload);
|
||||
return ResponseEntity.ok(response);
|
||||
}
|
||||
|
||||
@PostMapping(API_URL_QRCODE_VIRUS_TOTAL_CHECK)
|
||||
@@ -78,9 +69,5 @@ public class QRCodeTypeController {
|
||||
}
|
||||
}
|
||||
|
||||
@PostMapping(API_URL_QRCODE_REDIRECT_COUNT)
|
||||
public ResponseEntity<RedirectCountResponse> checkRedirects(@RequestBody QRCodePayload payload) {
|
||||
return ResponseEntity.ok(redirectCountService.countRedirects(payload).block());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
package com.safeqr.app.qrcode.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class RedirectCountResponse {
|
||||
private int redirectCount;
|
||||
private String message;
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package com.safeqr.app.qrcode.entity;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import jakarta.persistence.*;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
@@ -30,4 +31,8 @@ public class PhoneEntity {
|
||||
private UUID qrCodeId;
|
||||
|
||||
private String phone;
|
||||
|
||||
@JsonInclude(JsonInclude.Include.NON_EMPTY)
|
||||
@Column(name = "remarks")
|
||||
private String remarks;
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
package com.safeqr.app.qrcode.entity;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import jakarta.persistence.*;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
@@ -39,6 +40,7 @@ public class QRCodeEntity {
|
||||
@Column(name = "created_at", insertable = false, updatable = false)
|
||||
private LocalDateTime createdAt;
|
||||
|
||||
@JsonInclude(JsonInclude.Include.NON_EMPTY)
|
||||
@Column(name = "result_category")
|
||||
private String result = CLASSIFY_UNKNOWN;
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package com.safeqr.app.qrcode.entity;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import jakarta.persistence.*;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
@@ -31,4 +32,8 @@ public class SMSEntity {
|
||||
|
||||
private String phone;
|
||||
private String message;
|
||||
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
@Column(name = "keyword_detected")
|
||||
private String keywordDetected;
|
||||
}
|
||||
|
||||
@@ -52,4 +52,9 @@ public class ScanHistoryEntity {
|
||||
dateCreated = now;
|
||||
dateUpdated = now;
|
||||
}
|
||||
@PreUpdate
|
||||
public void preUpdate() {
|
||||
dateUpdated = OffsetDateTime.now();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -23,6 +23,10 @@ import java.util.UUID;
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class URLEntity {
|
||||
@JsonInclude(JsonInclude.Include.NON_EMPTY)
|
||||
@Column(name="classifications")
|
||||
private String classifications;
|
||||
|
||||
@Id
|
||||
@JsonIgnore
|
||||
@GeneratedValue(generator = "UUID")
|
||||
|
||||
@@ -27,6 +27,8 @@ public final class EmailModel extends QRCodeModel<EmailEntity> {
|
||||
@Override
|
||||
public void setDetails() {
|
||||
details = EmailEntity.builder().qrCodeId(data.getId()).build();
|
||||
|
||||
emailVerificationService.parseEmailString(details, data.getContents());
|
||||
// Insert into email table
|
||||
emailVerificationService.insertDB(details);
|
||||
}
|
||||
|
||||
@@ -27,6 +27,8 @@ public final class PhoneModel extends QRCodeModel<PhoneEntity> {
|
||||
@Override
|
||||
public void setDetails() {
|
||||
details = PhoneEntity.builder().qrCodeId(data.getId()).build();
|
||||
|
||||
phoneVerificationService.parsePhoneString(details, data.getContents());
|
||||
// Insert into phone table
|
||||
phoneVerificationService.insertDB(details);
|
||||
}
|
||||
@@ -36,6 +38,6 @@ public final class PhoneModel extends QRCodeModel<PhoneEntity> {
|
||||
}
|
||||
@Override
|
||||
public String retrieveClassification() {
|
||||
return "";
|
||||
return phoneVerificationService.checkPhoneNumber(details);
|
||||
}
|
||||
}
|
||||
@@ -27,6 +27,8 @@ public final class SMSModel extends QRCodeModel<SMSEntity> {
|
||||
@Override
|
||||
public void setDetails() {
|
||||
details = SMSEntity.builder().qrCodeId(data.getId()).build();
|
||||
|
||||
smsVerificationService.parseSMSString(details, data.getContents());
|
||||
// Insert into sms table
|
||||
smsVerificationService.insertDB(details);
|
||||
}
|
||||
@@ -36,6 +38,6 @@ public final class SMSModel extends QRCodeModel<SMSEntity> {
|
||||
}
|
||||
@Override
|
||||
public String retrieveClassification() {
|
||||
return "";
|
||||
return smsVerificationService.getClassification(details);
|
||||
}
|
||||
}
|
||||
@@ -47,6 +47,6 @@ public final class URLModel extends QRCodeModel<URLEntity> {
|
||||
|
||||
@Override
|
||||
public String retrieveClassification() {
|
||||
return "";
|
||||
return urlVerificationService.getClassification(this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,7 +25,8 @@ public class QRCodeFactoryProvider {
|
||||
case QR_CODE_TYPE_EMAIL -> applicationContext.getBean(EmailFactory.class).create(scannedQRCodeEntity);
|
||||
case QR_CODE_TYPE_WIFI -> applicationContext.getBean(WifiFactory.class).create(scannedQRCodeEntity);
|
||||
case DEFAULT_QR_CODE_TYPE -> applicationContext.getBean(TextFactory.class).create(scannedQRCodeEntity);
|
||||
default -> throw new IllegalArgumentException("Unsupported QR code type: " + scannedQRCodeEntity.getInfo().getType());
|
||||
//default -> throw new IllegalArgumentException("Unsupported QR code type: " + scannedQRCodeEntity.getInfo().getType());
|
||||
default -> applicationContext.getBean(TextFactory.class).create(scannedQRCodeEntity);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.safeqr.app.qrcode.service;
|
||||
|
||||
import com.safeqr.app.exceptions.InvalidFormatExceptions;
|
||||
import com.safeqr.app.exceptions.ResourceNotFoundExceptions;
|
||||
import com.safeqr.app.qrcode.entity.EmailEntity;
|
||||
import com.safeqr.app.qrcode.repository.EmailRepository;
|
||||
@@ -8,7 +9,11 @@ import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Service
|
||||
public class EmailVerificationService {
|
||||
@@ -28,4 +33,35 @@ public class EmailVerificationService {
|
||||
emailRepository.save(emailEntity);
|
||||
}
|
||||
|
||||
public void parseEmailString(EmailEntity emailEntity, String emailString) {
|
||||
Optional.ofNullable(emailString)
|
||||
.filter(s -> !s.isEmpty())
|
||||
.filter(s -> s.startsWith("MAILTO:"))
|
||||
.map(s -> s.substring(7))
|
||||
.map(s -> s.split("\\?", 2))
|
||||
.filter(parts -> parts.length > 0)
|
||||
.ifPresentOrElse(
|
||||
parts -> {
|
||||
String email = parts[0];
|
||||
Map<String, String> params = (parts.length == 2)
|
||||
? Arrays.stream(parts[1].split("&"))
|
||||
.map(param -> param.split("=", 2))
|
||||
.filter(keyValue -> keyValue.length == 2)
|
||||
.collect(Collectors.toMap(
|
||||
keyValue -> keyValue[0],
|
||||
keyValue -> keyValue[1],
|
||||
(v1, v2) -> v1
|
||||
))
|
||||
: Map.of();
|
||||
|
||||
emailEntity.setEmail(email);
|
||||
emailEntity.setTitle(params.getOrDefault("subject", ""));
|
||||
emailEntity.setMessage(params.getOrDefault("body", ""));
|
||||
},
|
||||
() -> {
|
||||
throw new InvalidFormatExceptions("Invalid email format. Expected format: MAILTO:<email>?subject=<title>&body=<message>");
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.safeqr.app.qrcode.service;
|
||||
|
||||
import com.safeqr.app.exceptions.InvalidFormatExceptions;
|
||||
import com.safeqr.app.exceptions.ResourceNotFoundExceptions;
|
||||
import com.safeqr.app.qrcode.entity.PhoneEntity;
|
||||
import com.safeqr.app.qrcode.repository.PhoneRepository;
|
||||
@@ -10,6 +11,8 @@ import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import static com.safeqr.app.constants.CommonConstants.*;
|
||||
|
||||
@Service
|
||||
public class PhoneVerificationService {
|
||||
private final PhoneRepository phoneRepository;
|
||||
@@ -28,4 +31,49 @@ public class PhoneVerificationService {
|
||||
phoneRepository.save(phoneEntity);
|
||||
}
|
||||
|
||||
public void parsePhoneString(PhoneEntity phoneEntity, String phoneString) {
|
||||
// Validate the string format
|
||||
if (phoneString == null || phoneString.isEmpty()) {
|
||||
throw new InvalidFormatExceptions("Phone string cannot be null or empty.");
|
||||
}
|
||||
|
||||
// Remove the "TEL:" prefix
|
||||
String phoneNumber = phoneString.substring(4);
|
||||
|
||||
// Further validation for phone number can be done here (optional)
|
||||
if (phoneNumber.matches("\\+?[0-9]*")) {
|
||||
// Populate the PhoneEntity object
|
||||
phoneEntity.setPhone(phoneNumber);
|
||||
} else {
|
||||
throw new InvalidFormatExceptions("Invalid phone number format.");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public String checkPhoneNumber(PhoneEntity phoneEntity) {
|
||||
// Remove any spaces, dashes, parentheses, and trim the ends
|
||||
String phoneNumber = phoneEntity.getPhone().replaceAll("[\\s\\-()]", "").trim();
|
||||
|
||||
// Check if the number starts with +65 or just 65
|
||||
if (phoneNumber.startsWith("+65")) {
|
||||
phoneNumber = phoneNumber.substring(3); // Remove the "+65"
|
||||
} else if (phoneNumber.startsWith("65")) {
|
||||
phoneNumber = phoneNumber.substring(2); // Remove the "65"
|
||||
}
|
||||
|
||||
// Check if it's a valid Singapore mobile or landline number
|
||||
if (phoneNumber.matches("^[689]\\d{7}$")) {
|
||||
if (phoneNumber.startsWith("8") || phoneNumber.startsWith("9")) {
|
||||
phoneEntity.setRemarks("Singapore mobile number - This number has not been scanned for scam. Please do not divulge your personal information.");
|
||||
} else if (phoneNumber.startsWith("6")) {
|
||||
phoneEntity.setRemarks("Singapore landline number - This phone number has not been scanned for scam. Please do not divulge your personal information.");
|
||||
}
|
||||
return CLASSIFY_UNKNOWN;
|
||||
}
|
||||
|
||||
// If it doesn't match mobile or landline pattern
|
||||
phoneEntity.setRemarks("Warning: This is either an overseas number or an invalid Singapore number. Please exercise caution.");
|
||||
return CLASSIFY_WARNING;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -88,41 +88,33 @@ public class QRCodeTypeService {
|
||||
String data = payload.getData();
|
||||
logger.info("scanQRCode: userId={}, data={}", userId, data);
|
||||
|
||||
// Get the QR Code Type
|
||||
QRCodeTypeEntity qrType = getQRCodeType(data);
|
||||
|
||||
// Insert the QR Code into main qrcode table
|
||||
QRCodeEntity scannedQR = qrCodeRepository.save(QRCodeEntity.builder()
|
||||
.userId(userId)
|
||||
.contents(data)
|
||||
.info(qrType)
|
||||
.createdAt(LocalDateTime.now())
|
||||
.build());
|
||||
QRCodeModel<?> qrCodeModel = scanAndClassify(userId, data);
|
||||
UUID qrId = qrCodeModel.getData().getId();
|
||||
|
||||
// Insert into Scan History table if userId is not null
|
||||
logger.info("scanQRCode: scannedQR new ID={}", scannedQR.getId());
|
||||
logger.info("scanQRCode: scannedQR new ID={}", qrId);
|
||||
if (userId != null) {
|
||||
scanHistoryRepository.save(ScanHistoryEntity.builder()
|
||||
.qrCodeId(scannedQR.getId())
|
||||
.qrCodeId(qrId)
|
||||
.userId(userId)
|
||||
.scanStatus(ScanHistoryEntity.ScanStatus.ACTIVE)
|
||||
.build());
|
||||
}
|
||||
// Create the QR Code Instance based on the QR Code Type & insert into the respective table
|
||||
QRCodeModel<?> qrCodeModel = qrCodeFactoryProvider.createQRCodeInstance(scannedQR);
|
||||
qrCodeModel.setDetails();
|
||||
// Get classifications based on verificationsv
|
||||
scannedQR.setResult(qrCodeModel.retrieveClassification());
|
||||
// update result category in qrcode table
|
||||
qrCodeRepository.save(scannedQR);
|
||||
|
||||
return BaseScanResponse.builder().qrcode(qrCodeModel).build();
|
||||
}
|
||||
|
||||
// Scan decoded contents from email message
|
||||
@Transactional
|
||||
public QRCodeModel<?> scanGmailDecodedContents(String userId, String data) {
|
||||
logger.info("Scan Gmail content: userId={}, data={}", userId, data);
|
||||
|
||||
return scanAndClassify(userId, data);
|
||||
}
|
||||
|
||||
// ScanAndClassify
|
||||
|
||||
private QRCodeModel<?> scanAndClassify(String userId, String data) {
|
||||
// Get the QR Code Type
|
||||
QRCodeTypeEntity qrType = getQRCodeType(data);
|
||||
|
||||
@@ -138,8 +130,12 @@ public class QRCodeTypeService {
|
||||
QRCodeModel<?> qrCodeModel = qrCodeFactoryProvider.createQRCodeInstance(scannedQR);
|
||||
qrCodeModel.setDetails();
|
||||
|
||||
// Get classifications based on verifications
|
||||
scannedQR.setResult(qrCodeModel.retrieveClassification());
|
||||
|
||||
return qrCodeModel;
|
||||
}
|
||||
|
||||
// Returns Default type as text if it does not fit into any of the category
|
||||
private QRCodeTypeEntity getQRCodeType(String data) {
|
||||
return configs.stream()
|
||||
@@ -147,27 +143,4 @@ public class QRCodeTypeService {
|
||||
.findFirst()
|
||||
.orElse(defaultQRCodeTypeEntity);
|
||||
}
|
||||
|
||||
public Mono<String> detectType(QRCodePayload payload) {
|
||||
String data = payload.getData();
|
||||
|
||||
for (QRCodeTypeEntity config : configs) {
|
||||
if (data.startsWith(config.getPrefix())) {
|
||||
if ("URL".equals(config.getType())) {
|
||||
try
|
||||
{
|
||||
return safeBrowsingService.isSafeUrl(data)
|
||||
.map(isSafe -> isSafe ? "Safe URL" : "Unsafe URL");
|
||||
} catch (NoSuchAlgorithmException e)
|
||||
{
|
||||
// TODO Auto-generated catch block
|
||||
return Mono.just("Error checking URL safety: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
return Mono.just(config.getType());
|
||||
}
|
||||
}
|
||||
|
||||
return Mono.just("Unknown");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
|
||||
package com.safeqr.app.qrcode.service;
|
||||
|
||||
import com.safeqr.app.qrcode.dto.request.QRCodePayload;
|
||||
import com.safeqr.app.qrcode.dto.RedirectCountResponse;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.web.reactive.function.client.WebClient;
|
||||
import reactor.core.publisher.Mono;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@Service
|
||||
public class RedirectCountService {
|
||||
private static final Logger logger = LoggerFactory.getLogger(RedirectCountService.class);
|
||||
|
||||
public Mono<RedirectCountResponse> countRedirects(QRCodePayload payload) {
|
||||
String url = payload.getData();
|
||||
logger.info("RedirectCountService: countRedirects: url={}", url);
|
||||
|
||||
return WebClient.create()
|
||||
.get()
|
||||
.uri("https://google.com")// replace with url when logic is complete
|
||||
.exchangeToMono(response -> {
|
||||
RedirectCountResponse redirectCountResponse = new RedirectCountResponse();
|
||||
redirectCountResponse.setRedirectCount(response.cookies().size());
|
||||
redirectCountResponse.setMessage("Redirect count calculated.");
|
||||
return Mono.just(redirectCountResponse);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,20 +1,51 @@
|
||||
package com.safeqr.app.qrcode.service;
|
||||
|
||||
import com.safeqr.app.exceptions.InvalidFormatExceptions;
|
||||
import com.safeqr.app.exceptions.ResourceNotFoundExceptions;
|
||||
import com.safeqr.app.qrcode.entity.PhoneEntity;
|
||||
import com.safeqr.app.qrcode.entity.SMSEntity;
|
||||
import com.safeqr.app.qrcode.repository.SMSRepository;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.UUID;
|
||||
import java.util.*;
|
||||
|
||||
import static com.safeqr.app.constants.CommonConstants.*;
|
||||
|
||||
@Service
|
||||
public class SMSVerificationService {
|
||||
private final SMSRepository smsRepository;
|
||||
private static final Logger logger = LoggerFactory.getLogger(SMSVerificationService.class);
|
||||
|
||||
// Define phishing keywords categories
|
||||
private static final Map<String, List<String>> PHISHING_KEYWORDS_MAP = new HashMap<>();
|
||||
|
||||
static {
|
||||
PHISHING_KEYWORDS_MAP.put("Generic", Arrays.asList("password", "verify", "urgent", "prize", "account update"));
|
||||
PHISHING_KEYWORDS_MAP.put("Tax Refund", Arrays.asList("tax refund", "claim your refund", "tax return"));
|
||||
PHISHING_KEYWORDS_MAP.put("Suspicious Activity", Arrays.asList("suspicious activity detected", "action required", "account compromised"));
|
||||
PHISHING_KEYWORDS_MAP.put("Social Media", Arrays.asList("social media account", "unauthorized login attempt", "verify your account"));
|
||||
PHISHING_KEYWORDS_MAP.put("Bogus Payment", Arrays.asList("payment confirmation", "transaction details", "payment receipt"));
|
||||
PHISHING_KEYWORDS_MAP.put("Incorrect Billing", Arrays.asList("incorrect billing information", "update billing details", "billing account"));
|
||||
PHISHING_KEYWORDS_MAP.put("iCloud", Arrays.asList("icloud account", "update your icloud", "icloud security alert"));
|
||||
PHISHING_KEYWORDS_MAP.put("HR Survey", Arrays.asList("human resources survey", "employee feedback", "survey participation"));
|
||||
PHISHING_KEYWORDS_MAP.put("Google Docs", Arrays.asList("google docs", "view shared document", "google drive"));
|
||||
PHISHING_KEYWORDS_MAP.put("USPS", Arrays.asList("usps delivery", "package tracking", "shipping details"));
|
||||
PHISHING_KEYWORDS_MAP.put("Voicemail", Arrays.asList("voicemail notification", "missed call", "listen to voicemail"));
|
||||
PHISHING_KEYWORDS_MAP.put("Bogus Invoice", Arrays.asList("invoice details", "view invoice", "payment invoice"));
|
||||
PHISHING_KEYWORDS_MAP.put("Email Upgrade", Arrays.asList("email account upgrade", "email settings update", "upgrade your email"));
|
||||
PHISHING_KEYWORDS_MAP.put("Dropbox", Arrays.asList("dropbox", "view shared file", "dropbox account"));
|
||||
PHISHING_KEYWORDS_MAP.put("CEO Phishing", Arrays.asList("ceo email", "urgent message from ceo", "ceo authorization"));
|
||||
PHISHING_KEYWORDS_MAP.put("Costco", Arrays.asList("costco", "costco membership", "costco rewards"));
|
||||
PHISHING_KEYWORDS_MAP.put("Bank", Arrays.asList("bank account", "unusual activity", "account login"));
|
||||
PHISHING_KEYWORDS_MAP.put("Fake App", Arrays.asList("app purchase", "app subscription", "confirm your purchase"));
|
||||
PHISHING_KEYWORDS_MAP.put("Advanced Fee", Arrays.asList("advance fee", "processing fee", "fee payment"));
|
||||
PHISHING_KEYWORDS_MAP.put("Account Suspension", Arrays.asList("account suspension", "suspend your account", "account deactivation"));
|
||||
}
|
||||
|
||||
@Autowired
|
||||
public SMSVerificationService(SMSRepository smsRepository) {
|
||||
this.smsRepository = smsRepository;
|
||||
@@ -29,4 +60,76 @@ public class SMSVerificationService {
|
||||
smsRepository.save(smsEntity);
|
||||
}
|
||||
|
||||
public void parseSMSString(SMSEntity smsEntity, String smsto) throws IllegalArgumentException{
|
||||
// Validate the string format
|
||||
if (smsto == null || smsto.isEmpty()) {
|
||||
throw new InvalidFormatExceptions("sms cannot be null or empty.");
|
||||
}
|
||||
// Remove the "SMSTO:" prefix
|
||||
String data = smsto.substring(6);
|
||||
|
||||
// Split the data into phone number and message
|
||||
String[] parts = data.split(":", 2);
|
||||
|
||||
// If both phone number and message are available
|
||||
if (parts.length == 2) {
|
||||
String phone = parts[0];
|
||||
String message = parts[1];
|
||||
|
||||
// Populate the SMSEntity object
|
||||
smsEntity.setPhone(phone);
|
||||
smsEntity.setMessage(message);
|
||||
} else {
|
||||
// Handle the case where the format is invalid
|
||||
throw new InvalidFormatExceptions("Invalid SMSTO format. Expected format: SMSTO:<phone>:<message>");
|
||||
}
|
||||
}
|
||||
public String getClassification (SMSEntity smsEntity) {
|
||||
|
||||
String lowerCaseSms = smsEntity.getMessage().toLowerCase();
|
||||
logger.info("Sms: {}", lowerCaseSms);
|
||||
|
||||
// Iterate over the map of phishing keywords
|
||||
for (Map.Entry<String, List<String>> entry : PHISHING_KEYWORDS_MAP.entrySet()) {
|
||||
String category = entry.getKey();
|
||||
List<String> keywords = entry.getValue();
|
||||
|
||||
// Check if the SMS contains any of the phishing keywords
|
||||
for (String keyword : keywords) {
|
||||
if (lowerCaseSms.contains(keyword)) {
|
||||
logger.info("Phishing keyword detected: {}", keyword);
|
||||
smsEntity.setKeywordDetected("Potential Phishing - " + category);
|
||||
return checkPhoneNumber(smsEntity.getPhone()).equals(CLASSIFY_WARNING) ?
|
||||
CLASSIFY_WARNING :
|
||||
CLASSIFY_UNSAFE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If no phishing keywords are found, sent for local phone number checks
|
||||
return checkPhoneNumber(smsEntity.getPhone()).equals(CLASSIFY_UNSAFE) ?
|
||||
CLASSIFY_WARNING :
|
||||
CLASSIFY_SAFE;
|
||||
}
|
||||
|
||||
private String checkPhoneNumber(String phoneNumber) {
|
||||
// Remove any spaces, dashes, parentheses, and trim the ends
|
||||
phoneNumber = phoneNumber.replaceAll("[\\s\\-()]", "").trim();
|
||||
|
||||
// Check if the number starts with +65 or just 65
|
||||
if (phoneNumber.startsWith("+65")) {
|
||||
phoneNumber = phoneNumber.substring(3); // Remove the "+65"
|
||||
} else if (phoneNumber.startsWith("65")) {
|
||||
phoneNumber = phoneNumber.substring(2); // Remove the "65"
|
||||
}
|
||||
|
||||
// Check if it's a valid Singapore mobile or landline number
|
||||
if (phoneNumber.matches("^[689]\\d{7}$") && (phoneNumber.startsWith("8") || phoneNumber.startsWith("9"))) {
|
||||
return CLASSIFY_WARNING;
|
||||
}
|
||||
|
||||
// If it doesn't match mobile
|
||||
return CLASSIFY_UNSAFE;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,11 +1,10 @@
|
||||
package com.safeqr.app.qrcode.service;
|
||||
|
||||
import static com.safeqr.app.constants.CommonConstants.*;
|
||||
|
||||
import com.safeqr.app.qrcode.dto.request.QRCodePayload;
|
||||
import com.safeqr.app.qrcode.dto.URLVerificationResponse;
|
||||
import com.safeqr.app.qrcode.entity.URLEntity;
|
||||
import com.safeqr.app.qrcode.model.URLModel;
|
||||
import com.safeqr.app.qrcode.repository.URLRepository;
|
||||
import com.safeqr.app.prediction.service.PredictionService;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
@@ -29,9 +28,11 @@ public class URLVerificationService {
|
||||
private static final int READ_TIMEOUT_MS = 10000;
|
||||
private static final Logger logger = LoggerFactory.getLogger(URLVerificationService.class);
|
||||
private final URLRepository urlRepository;
|
||||
private final PredictionService predictionService;
|
||||
@Autowired
|
||||
public URLVerificationService(URLRepository urlRepository) {
|
||||
public URLVerificationService(URLRepository urlRepository, PredictionService predictionService) {
|
||||
this.urlRepository = urlRepository;
|
||||
this.predictionService = predictionService;
|
||||
}
|
||||
|
||||
// Regular expression pattern for shortening services
|
||||
@@ -59,10 +60,11 @@ public class URLVerificationService {
|
||||
// Define a Set of suspicious file extensions
|
||||
private static final Set<String> SUSPICIOUS_EXTENSIONS = Stream.of(
|
||||
".exe", ".bat", ".sh", ".cmd", ".scr", ".pif", ".application", ".gadget",
|
||||
".vb", ".vbs", ".js", ".jse", ".ws", ".wsf", ".msc", ".com", ".cpl",
|
||||
".msi", ".ps1", ".py", ".pyc", ".pyo", ".rb", ".app", ".bin", ".run"
|
||||
".vb", ".vbs", ".js", ".jse", ".ws", ".wsf", ".msc", ".cpl",
|
||||
".msi", ".ps1", ".py", ".pyc", ".pyo", ".rb", ".bin", ".run", "apk"
|
||||
).collect(Collectors.toUnmodifiableSet());
|
||||
|
||||
|
||||
// Checks if the URL has executable file
|
||||
public String hasExecutableFile(String urlPath) {
|
||||
return Stream.of(urlPath)
|
||||
@@ -425,22 +427,48 @@ public class URLVerificationService {
|
||||
return INFO_NON_SECURE_CONNECTION;
|
||||
}
|
||||
|
||||
public URLVerificationResponse verifyURL(QRCodePayload payload) {
|
||||
URLVerificationResponse response = new URLVerificationResponse();
|
||||
try {
|
||||
java.net.URL url = new java.net.URL(payload.getData());
|
||||
String protocol = url.getProtocol();
|
||||
if ("https".equalsIgnoreCase(protocol)) {
|
||||
response.setSecure(true);
|
||||
response.setMessage("The connection is secure.");
|
||||
} else {
|
||||
response.setSecure(false);
|
||||
response.setMessage("The connection is not secure.");
|
||||
// Get Classification using ML Model
|
||||
public String getClassification(URLModel urlModel){
|
||||
String content = urlModel.getData().getContents();
|
||||
// if in whitelist, return Benign and Safe
|
||||
for (String domain : WHITELIST_DOMAINS) {
|
||||
if (content.contains(domain)) {
|
||||
// If in whitelist, set category to BENIGN and return SAFE
|
||||
urlModel.getDetails().setClassifications(CAT_BENIGN);
|
||||
return CLASSIFY_SAFE;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
response.setSecure(false);
|
||||
response.setMessage("Invalid URL.");
|
||||
}
|
||||
return response;
|
||||
|
||||
// Call ML model
|
||||
String category = predictionService.predict(urlModel);
|
||||
|
||||
//update in category in url table
|
||||
urlModel.getDetails().setClassifications(category);
|
||||
|
||||
// return classification results
|
||||
if (category.equals(CAT_BENIGN)) {
|
||||
if (!urlModel.getDetails().getTrackingDescriptions().isEmpty() || // contains tracking
|
||||
urlModel.getData().getInfo().getPrefix().equalsIgnoreCase("http://") || // uses http
|
||||
urlModel.getDetails().getSslStripping().contains(true) || // has SSL stripping
|
||||
urlModel.getDetails().getHasExecutable().equalsIgnoreCase("yes") || // contains executable
|
||||
!urlModel.getDetails().getJavascriptCheck().isEmpty() || // contains javascript
|
||||
!urlModel.getDetails().getHasIpAddress().isEmpty() || // contains IP address
|
||||
urlModel.getDetails().getHostnameEmbedding() != null // contains hostname embedding
|
||||
|
||||
) {
|
||||
return CLASSIFY_WARNING;
|
||||
}
|
||||
return CLASSIFY_SAFE;
|
||||
}
|
||||
return CLASSIFY_UNSAFE;
|
||||
}
|
||||
|
||||
// Static array for whitelist domains
|
||||
private static final List<String> WHITELIST_DOMAINS = Arrays.asList(
|
||||
"safeqr.github.io/marketing",
|
||||
"uow.edu.au",
|
||||
"nus.edu.sg",
|
||||
"sim.edu.sg",
|
||||
"sp.edu.sg"
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package com.safeqr.app.qrcodetips.controller;
|
||||
|
||||
import com.safeqr.app.qrcodetips.entity.QrCodeTipEntity;
|
||||
import com.safeqr.app.qrcodetips.service.QrCodeTipsService;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
|
||||
import static com.safeqr.app.constants.APIConstants.*;
|
||||
|
||||
@RestController
|
||||
@RequestMapping(API_VERSION)
|
||||
public class QRCodeTipsController {
|
||||
private static final Logger logger = LoggerFactory.getLogger(QRCodeTipsController.class);
|
||||
QrCodeTipsService qrCodeTipsService;
|
||||
|
||||
@Autowired
|
||||
public QRCodeTipsController (QrCodeTipsService qrCodeTipsService) { this.qrCodeTipsService = qrCodeTipsService;}
|
||||
|
||||
@GetMapping(value = API_URL_TIPS_GET)
|
||||
public ResponseEntity<QrCodeTipEntity> getRandomTips() {
|
||||
logger.info("Invoking GET QR Code tips endpoint");
|
||||
return ResponseEntity.ok(qrCodeTipsService.getTips());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.safeqr.app.qrcodetips.entity;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import jakarta.persistence.*;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@Entity
|
||||
@Table(name = "qr_code_tips", schema = "safeqr")
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class QrCodeTipEntity {
|
||||
|
||||
@Id
|
||||
@JsonIgnore
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
|
||||
private String tips;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.safeqr.app.qrcodetips.repository;
|
||||
|
||||
import com.safeqr.app.qrcodetips.entity.QrCodeTipEntity;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
@Repository
|
||||
public interface QrCodeTipRepository extends JpaRepository<QrCodeTipEntity, Long> {
|
||||
|
||||
@Query(value = "SELECT * FROM safeqr.qr_code_tips ORDER BY RANDOM() LIMIT 1", nativeQuery = true)
|
||||
QrCodeTipEntity findRandomTip();
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package com.safeqr.app.qrcodetips.service;
|
||||
|
||||
import com.safeqr.app.qrcodetips.entity.QrCodeTipEntity;
|
||||
import com.safeqr.app.qrcodetips.repository.QrCodeTipRepository;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
public class QrCodeTipsService {
|
||||
QrCodeTipRepository qrCodeTipRepository;
|
||||
public QrCodeTipsService (QrCodeTipRepository qrCodeTipRepository) { this.qrCodeTipRepository = qrCodeTipRepository; }
|
||||
public QrCodeTipEntity getTips() {
|
||||
return qrCodeTipRepository.findRandomTip();
|
||||
}
|
||||
}
|
||||
@@ -1,134 +0,0 @@
|
||||
package com.safeqr.app.spark.model;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class URLFeatures {
|
||||
private Long domain;
|
||||
private Long subdomain;
|
||||
private Long topLevelDomain;
|
||||
private Long query;
|
||||
private Long fragment;
|
||||
private Long redirect;
|
||||
private Long path;
|
||||
private Long redirectChain;
|
||||
private Long hstsHeader;
|
||||
private Long sslStripping;
|
||||
private Long hostnameEmbedding;
|
||||
private Long javascriptCheck;
|
||||
private Long shorteningService;
|
||||
private Long hasIpAddress;
|
||||
private Long trackingDescriptions;
|
||||
private Long urlEncoding;
|
||||
private Long hasExecutable;
|
||||
private Long tls;
|
||||
private Long contents;
|
||||
private String target; // This is the label, may be null if predicting
|
||||
|
||||
// Custom setter for tls (qr_code_type_id)
|
||||
public void setTls(Long tls) {
|
||||
if (tls != null) {
|
||||
this.tls = tls == 1 ? 0 : tls == 9 ? 1 : tls;
|
||||
} else {
|
||||
this.tls = 0L;
|
||||
}
|
||||
}
|
||||
|
||||
// Custom setter for hostnameEmbedding and other similar columns
|
||||
public void setHostnameEmbedding(Long hostnameEmbedding) {
|
||||
this.hostnameEmbedding = (hostnameEmbedding != null && hostnameEmbedding != 0) ? 1L : 0L;
|
||||
}
|
||||
|
||||
public void setJavascriptCheck(Long javascriptCheck) {
|
||||
this.javascriptCheck = (javascriptCheck != null && javascriptCheck != 0) ? 1L : 0L;
|
||||
}
|
||||
|
||||
public void setShorteningService(Long shorteningService) {
|
||||
this.shorteningService = (shorteningService != null && shorteningService != 0) ? 1L : 0L;
|
||||
}
|
||||
|
||||
public void setHasIpAddress(Long hasIpAddress) {
|
||||
this.hasIpAddress = (hasIpAddress != null && hasIpAddress != 0) ? 1L : 0L;
|
||||
}
|
||||
|
||||
public void setUrlEncoding(Long urlEncoding) {
|
||||
this.urlEncoding = (urlEncoding != null && urlEncoding != 0) ? 1L : 0L;
|
||||
}
|
||||
|
||||
public void setHasExecutable(Long hasExecutable) {
|
||||
this.hasExecutable = (hasExecutable != null && hasExecutable != 0) ? 1L : 0L;
|
||||
}
|
||||
|
||||
public void setTrackingDescriptions(Long trackingDescriptions) {
|
||||
this.trackingDescriptions = (trackingDescriptions != null && trackingDescriptions != 0) ? 1L : 0L;
|
||||
}
|
||||
|
||||
// Custom setter for sslStripping
|
||||
public void setSslStripping(String sslStripping) {
|
||||
if (sslStripping != null && "true".equalsIgnoreCase(sslStripping)) {
|
||||
this.sslStripping = 1L;
|
||||
} else {
|
||||
this.sslStripping = 0L;
|
||||
}
|
||||
}
|
||||
|
||||
// Custom setter for hstsHeader
|
||||
public void setHstsHeader(String hstsHeader) {
|
||||
if (hstsHeader == null || "0".equals(hstsHeader)) {
|
||||
this.hstsHeader = 0L;
|
||||
} else if (hstsHeader.startsWith("{") && hstsHeader.endsWith("}")) {
|
||||
Pattern pattern = Pattern.compile("\"(.*?)\"");
|
||||
Matcher matcher = pattern.matcher(hstsHeader);
|
||||
if (matcher.find() && matcher.group(1).toLowerCase().contains("no")) {
|
||||
this.hstsHeader = 0L;
|
||||
} else {
|
||||
this.hstsHeader = 1L;
|
||||
}
|
||||
} else {
|
||||
this.hstsHeader = 0L;
|
||||
}
|
||||
}
|
||||
|
||||
// Custom setters for calculating string lengths
|
||||
public void setDomain(String domain) {
|
||||
this.domain = (domain != null) ? (long) domain.length() : 0L;
|
||||
}
|
||||
|
||||
public void setSubdomain(String subdomain) {
|
||||
this.subdomain = (subdomain != null) ? (long) subdomain.length() : 0L;
|
||||
}
|
||||
|
||||
public void setTopLevelDomain(String topLevelDomain) {
|
||||
this.topLevelDomain = (topLevelDomain != null) ? (long) topLevelDomain.length() : 0L;
|
||||
}
|
||||
|
||||
public void setQuery(String query) {
|
||||
this.query = (query != null) ? (long) query.length() : 0L;
|
||||
}
|
||||
|
||||
public void setFragment(String fragment) {
|
||||
this.fragment = (fragment != null) ? (long) fragment.length() : 0L;
|
||||
}
|
||||
|
||||
public void setPath(String path) {
|
||||
this.path = (path != null) ? (long) path.length() : 0L;
|
||||
}
|
||||
|
||||
public void setRedirectChain(String redirectChain) {
|
||||
this.redirectChain = (redirectChain != null) ? (long) redirectChain.length() : 0L;
|
||||
}
|
||||
|
||||
public void setContents(String contents) {
|
||||
this.contents = (contents != null) ? (long) contents.length() : 0L;
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,7 @@ public class UserResponseDto {
|
||||
private String id;
|
||||
private String name;
|
||||
private String email;
|
||||
private String source;
|
||||
private OffsetDateTime dateJoined;
|
||||
private OffsetDateTime dateUpdated;
|
||||
private List<String> roles;
|
||||
|
||||
@@ -8,6 +8,7 @@ import jakarta.persistence.Table;
|
||||
import lombok.*;
|
||||
import org.hibernate.annotations.Type;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.time.OffsetDateTime;
|
||||
import java.util.List;
|
||||
|
||||
@@ -34,6 +35,9 @@ public class UserEntity {
|
||||
private List<String> roles;
|
||||
private String status;
|
||||
|
||||
@Column(name = "source")
|
||||
private String source;
|
||||
|
||||
@Column(name = "gmail_history_id")
|
||||
private Long gmailHistoryId;
|
||||
private BigInteger gmailHistoryId = BigInteger.ZERO;
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ package com.safeqr.app.user.service;
|
||||
|
||||
import com.safeqr.app.exceptions.ResourceAlreadyExists;
|
||||
import com.safeqr.app.exceptions.ResourceNotFoundExceptions;
|
||||
import com.safeqr.app.qrcode.entity.ScanBookmarkEntity;
|
||||
|
||||
import com.safeqr.app.qrcode.entity.ScanHistoryEntity;
|
||||
import com.safeqr.app.qrcode.repository.ScanBookmarkRepository;
|
||||
import com.safeqr.app.qrcode.repository.ScanHistoryRepository;
|
||||
@@ -14,7 +14,7 @@ import com.safeqr.app.user.repository.UserRepository;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.dao.DataIntegrityViolationException;
|
||||
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@@ -48,12 +48,20 @@ public class UserService {
|
||||
.id(userEntity.getId())
|
||||
.email(userEntity.getEmail())
|
||||
.name(userEntity.getName())
|
||||
.source(userEntity.getSource())
|
||||
.dateJoined(userEntity.getDateCreated())
|
||||
.dateUpdated(userEntity.getDateUpdated())
|
||||
.roles(userEntity.getRoles())
|
||||
.status(userEntity.getStatus())
|
||||
.build();
|
||||
}
|
||||
public UserEntity getUserByIdForGmail(String userId){
|
||||
return userRepository.findById(userId)
|
||||
.orElseThrow(()-> new ResourceNotFoundExceptions("User id not found: " + userId));
|
||||
}
|
||||
public UserEntity updateUserEntity(UserEntity userEntity) {
|
||||
return userRepository.save(userEntity);
|
||||
}
|
||||
public List<ScannedHistoriesDto> getUserScannedHistories(String userId) {
|
||||
return scanHistoryRepository.findAllQRCodesByUserId(userId);
|
||||
}
|
||||
|
||||
12
src/main/java/com/safeqr/app/utils/RestTemplateConfig.java
Normal file
12
src/main/java/com/safeqr/app/utils/RestTemplateConfig.java
Normal file
@@ -0,0 +1,12 @@
|
||||
package com.safeqr.app.utils;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
@Configuration
|
||||
public class RestTemplateConfig {
|
||||
@Bean
|
||||
public RestTemplate restTemplate() {
|
||||
return new RestTemplate();
|
||||
}
|
||||
}
|
||||
@@ -32,4 +32,4 @@ gmail.client.clientAuthenticationScheme=query
|
||||
gmail.client.scope=https://www.googleapis.com/auth/gmail.readonly
|
||||
gmail.resource.userInfoUri=https://www.googleapis.com/gmail/v1/users/me/profile
|
||||
gmail.resource.preferTokenInfo=true
|
||||
gmail.client.redirectUri=https://bk5wiynzsi.execute-api.ap-southeast-1.amazonaws.com/api/gmail/callback
|
||||
gmail.client.redirectUri=https://localhost/v1/gmail/callback
|
||||
Binary file not shown.
@@ -1 +0,0 @@
|
||||
{"class":"org.apache.spark.ml.PipelineModel","timestamp":1723422050490,"sparkVersion":"3.4.3","uid":"PipelineModel_4ecdd9f71524","paramMap":{"stageUids":["StringIndexer_d3c63289c493","VectorAssembler_517fc429fbfb","RandomForestClassifier_4909b7ca2bbe"]},"defaultParamMap":{}}
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1 +0,0 @@
|
||||
{"class":"org.apache.spark.ml.feature.StringIndexerModel","timestamp":1723422050930,"sparkVersion":"3.4.3","uid":"StringIndexer_d3c63289c493","paramMap":{"outputCol":"indexed_target","inputCol":"target"},"defaultParamMap":{"stringOrderType":"frequencyDesc","handleInvalid":"error","outputCol":"StringIndexer_d3c63289c493__output"}}
|
||||
Binary file not shown.
@@ -1 +0,0 @@
|
||||
{"class":"org.apache.spark.ml.feature.VectorAssembler","timestamp":1723422054241,"sparkVersion":"3.4.3","uid":"VectorAssembler_517fc429fbfb","paramMap":{"inputCols":["domain","subdomain","top_level_domain","query","fragment","redirect","path","redirect_chain","hsts_header","ssl_stripping","hostname_embedding","javascript_check","shortening_service","has_ip_address","tracking_descriptions","url_encoding","has_executable","tls","contents"],"outputCol":"features"},"defaultParamMap":{"handleInvalid":"error","outputCol":"VectorAssembler_517fc429fbfb__output"}}
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1 +0,0 @@
|
||||
{"class":"org.apache.spark.ml.classification.RandomForestClassificationModel","timestamp":1723422054559,"sparkVersion":"3.4.3","uid":"RandomForestClassifier_4909b7ca2bbe","paramMap":{"featuresCol":"features","numTrees":100,"maxDepth":10,"labelCol":"indexed_target"},"defaultParamMap":{"featuresCol":"features","rawPredictionCol":"rawPrediction","maxBins":32,"predictionCol":"prediction","minInstancesPerNode":1,"minWeightFractionPerNode":0.0,"cacheNodeIds":false,"minInfoGain":0.0,"numTrees":20,"maxDepth":5,"impurity":"gini","subsamplingRate":1.0,"leafCol":"","labelCol":"label","probabilityCol":"probability","bootstrap":true,"featureSubsetStrategy":"auto","checkpointInterval":10,"maxMemoryInMB":256,"seed":6182040365248539008},"numFeatures":19,"numClasses":4,"numTrees":100}
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1 +0,0 @@
|
||||
{"class":"org.apache.spark.ml.Pipeline","timestamp":1723422048367,"sparkVersion":"3.4.3","uid":"Pipeline_58a1fe22f286","paramMap":{"stageUids":["StringIndexer_d3c63289c493","VectorAssembler_517fc429fbfb","RandomForestClassifier_4909b7ca2bbe"]},"defaultParamMap":{}}
|
||||
Binary file not shown.
@@ -1 +0,0 @@
|
||||
{"class":"org.apache.spark.ml.feature.StringIndexer","timestamp":1723422049018,"sparkVersion":"3.4.3","uid":"StringIndexer_d3c63289c493","paramMap":{"outputCol":"indexed_target","inputCol":"target"},"defaultParamMap":{"stringOrderType":"frequencyDesc","handleInvalid":"error","outputCol":"StringIndexer_d3c63289c493__output"}}
|
||||
Binary file not shown.
@@ -1 +0,0 @@
|
||||
{"class":"org.apache.spark.ml.feature.VectorAssembler","timestamp":1723422049580,"sparkVersion":"3.4.3","uid":"VectorAssembler_517fc429fbfb","paramMap":{"inputCols":["domain","subdomain","top_level_domain","query","fragment","redirect","path","redirect_chain","hsts_header","ssl_stripping","hostname_embedding","javascript_check","shortening_service","has_ip_address","tracking_descriptions","url_encoding","has_executable","tls","contents"],"outputCol":"features"},"defaultParamMap":{"handleInvalid":"error","outputCol":"VectorAssembler_517fc429fbfb__output"}}
|
||||
Binary file not shown.
@@ -1 +0,0 @@
|
||||
{"class":"org.apache.spark.ml.classification.RandomForestClassifier","timestamp":1723422050089,"sparkVersion":"3.4.3","uid":"RandomForestClassifier_4909b7ca2bbe","paramMap":{"featuresCol":"features","labelCol":"indexed_target"},"defaultParamMap":{"featuresCol":"features","rawPredictionCol":"rawPrediction","maxBins":32,"predictionCol":"prediction","minInstancesPerNode":1,"minWeightFractionPerNode":0.0,"cacheNodeIds":false,"minInfoGain":0.0,"numTrees":20,"maxDepth":5,"impurity":"gini","subsamplingRate":1.0,"leafCol":"","labelCol":"label","probabilityCol":"probability","bootstrap":true,"featureSubsetStrategy":"auto","checkpointInterval":10,"maxMemoryInMB":256,"seed":6182040365248539008}}
|
||||
Binary file not shown.
@@ -1 +0,0 @@
|
||||
{"class":"org.apache.spark.ml.evaluation.MulticlassClassificationEvaluator","timestamp":1723422047764,"sparkVersion":"3.4.3","uid":"MulticlassClassificationEvaluator_f31cf4d2b0db","paramMap":{"metricName":"accuracy","labelCol":"indexed_target"},"defaultParamMap":{"eps":1.0E-15,"beta":1.0,"metricName":"f1","predictionCol":"prediction","labelCol":"label","metricLabel":0.0,"probabilityCol":"probability"}}
|
||||
Binary file not shown.
@@ -1 +0,0 @@
|
||||
{"class":"org.apache.spark.ml.tuning.CrossValidatorModel","timestamp":1723422046660,"sparkVersion":"3.4.3","uid":"CrossValidatorModel_5c96b33c8d82","paramMap":{"seed":-2084793586583917283,"numFolds":5,"foldCol":"","estimatorParamMaps":[[{"parent":"RandomForestClassifier_4909b7ca2bbe","name":"numTrees","value":"100","isJson":"true"},{"parent":"RandomForestClassifier_4909b7ca2bbe","name":"maxDepth","value":"10","isJson":"true"}]]},"defaultParamMap":{"seed":880116102,"numFolds":3,"foldCol":""},"avgMetrics":[0.8736361548764979],"persistSubModels":false}
|
||||
Reference in New Issue
Block a user