53 Commits

Author SHA1 Message Date
heyethereum
581f543e45 Merge remote-tracking branch 'origin/dev'
Some checks failed
Deploy jar to EC2 / build (push) Failing after 6m27s
2024-08-22 21:04:24 +08:00
heyethereum
61e8282e00 includes apk file executable 2024-08-22 21:00:56 +08:00
heyethereum
67ae4a439e Merge remote-tracking branch 'origin/dev' 2024-08-19 22:53:28 +08:00
heyethereum
22bf62473f added source for frontend 2024-08-19 22:52:30 +08:00
heyethereum
e68aaf322d Merge remote-tracking branch 'origin/dev' 2024-08-19 21:19:15 +08:00
heyethereum
04e1ca12c8 sort in scanned gmail in descending order 2024-08-19 21:18:37 +08:00
ltiongku
02d4570c9c Merge remote-tracking branch 'refs/remotes/origin/dev' 2024-08-19 16:53:29 +08:00
ltiongku
2876be0d52 added sp to whitelist 2024-08-19 16:52:32 +08:00
heyethereum
6007bfd4fe Merge remote-tracking branch 'origin/dev' 2024-08-19 08:31:01 +08:00
heyethereum
deab9666da refine refresh token flow 2024-08-19 08:30:15 +08:00
heyethereum
69fc121443 Merge remote-tracking branch 'origin/dev' 2024-08-18 22:41:17 +08:00
heyethereum
878bfce095 refine refresh token flow 2024-08-18 22:35:42 +08:00
heyethereum
115423c77c change application.properties 2024-08-18 16:59:47 +08:00
heyethereum
a1fee3bbe4 change application.properties 2024-08-18 16:15:00 +08:00
heyethereum
9f4771f16d intro historyId in gmail 2024-08-17 22:38:00 +08:00
heyethereum
b8ee29ce9a solved logic error 2024-08-17 15:20:03 +08:00
heyethereum
0716214a31 whitelist some url classified wrongly 2024-08-17 13:13:24 +08:00
heyethereum
e8836f1b5e implemented phone checks and sms checks 2024-08-17 12:49:19 +08:00
heyethereum
d24ece60fd implemented keyword search in sms 2024-08-17 10:43:59 +08:00
heyethereum
0301b0e1fb corrupted readme 2024-08-17 08:38:51 +08:00
heyethereum
180f53dcaf Added readme file 2024-08-17 08:36:23 +08:00
heyethereum
3e4f18849e Added readme file 2024-08-17 08:34:33 +08:00
heyethereum
69c8bbb450 Merge remote-tracking branch 'origin/feature-ml-integration' into dev 2024-08-17 08:32:38 +08:00
heyethereum
213a6dfc70 Added readme file 2024-08-17 08:32:11 +08:00
heyethereum
474d785c5c Merge branch 'feature-ml-integration' into dev 2024-08-14 23:30:08 +08:00
heyethereum
624bfdc2f9 added phone, email 2024-08-14 23:28:45 +08:00
heyethereum
3dda5fa770 Merge remote-tracking branch 'origin/feature-ml-integration' into dev 2024-08-14 21:16:45 +08:00
heyethereum
b176e5c54f fix gmail scan null results 2024-08-14 21:15:29 +08:00
ltiongku
9adb53e7ab write sms type to db 2024-08-14 19:44:18 +08:00
ltiongku
b86e680673 Merge remote-tracking branch 'refs/remotes/origin/feature-ml-integration' into dev 2024-08-14 11:59:17 +08:00
ltiongku
334b3867ec fixed email active NULL 2024-08-14 11:57:09 +08:00
ltiongku
e6899eafdb Merge remote-tracking branch 'refs/remotes/origin/feature-ml-integration' into dev 2024-08-14 11:36:31 +08:00
ltiongku
f5d6396b06 fixed email active NULL 2024-08-14 11:36:10 +08:00
ltiongku
393737e0f7 Merge remote-tracking branch 'refs/remotes/origin/feature-ml-integration' into dev 2024-08-14 11:12:23 +08:00
ltiongku
25711506b4 fixed email active NULL 2024-08-14 11:11:50 +08:00
heyethereum
0592f6bfd1 Merge remote-tracking branch 'origin/feature-ml-integration' into dev 2024-08-14 08:47:45 +08:00
heyethereum
28a16ead4f test 2024-08-14 08:47:02 +08:00
heyethereum
bbeb85bb0c Merge remote-tracking branch 'origin/feature-ml-integration' into dev 2024-08-14 00:56:44 +08:00
heyethereum
5f55b073f3 fixed incorrect mapping 2024-08-14 00:56:12 +08:00
heyethereum
08a9b3b630 Merge remote-tracking branch 'origin/feature-ml-integration' into dev 2024-08-13 21:03:45 +08:00
heyethereum
8665693642 complete url verifications 2024-08-13 21:03:15 +08:00
ltiongku
63e2d299fd implemented QR code tips controller, delete email message, delete all email messages 2024-08-13 19:19:56 +08:00
heyethereum
32b604b172 Merge remote-tracking branch 'origin/feature-ml-integration' into dev 2024-08-13 02:38:11 +08:00
heyethereum
c8cfe610a6 add new prediction service 2024-08-13 02:34:47 +08:00
ltiongku
53f9acd922 ml init 2024-08-12 23:55:08 +08:00
heyethereum
e2ca15f556 Merge remote-tracking branch 'origin/feature-gmail-scan' into dev 2024-08-12 23:04:00 +08:00
heyethereum
cd047b33af Merge remote-tracking branch 'origin/feature-gmail-scan' into dev 2024-08-12 00:52:06 +08:00
heyethereum
d2c2767578 Merge remote-tracking branch 'origin/feature-gmail-scan' into dev 2024-08-11 09:22:23 +08:00
heyethereum
0901e7f07f Merge remote-tracking branch 'origin/feature-gmail-scan' into dev 2024-08-10 16:25:36 +08:00
ltiongku
3fb5ad039a Merge remote-tracking branch 'origin/dev' into dev 2024-08-07 19:02:13 +08:00
heyethereum
31476e4d75 Merge remote-tracking branch 'origin/feature-gmail-scan' into dev 2024-08-06 20:23:33 +08:00
heyethereum
9d5b39e89c Merge remote-tracking branch 'origin/feature-gmail-scan' into dev 2024-08-04 19:37:47 +08:00
heyethereum
dcd5058f70 gmail scan to push to main 2024-08-03 13:35:57 +08:00
83 changed files with 1028 additions and 375 deletions

62
README.md Normal file
View 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
View File

@@ -121,26 +121,7 @@
<artifactId>jackson-annotations</artifactId> <artifactId>jackson-annotations</artifactId>
<version>2.17.2</version> <version>2.17.2</version>
</dependency> </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> </dependencies>
<build> <build>

View File

@@ -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_VIRUS_TOTAL_CHECK = "/qrcodetypes/virusTotalCheck";
public static final String API_URL_QRCODE_REDIRECT_COUNT = "/qrcodetypes/checkRedirects"; 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 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"; 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_SET_BOOKMARK = "/user/setBookmark";
public static final String API_URL_USER_DELETE_BOOKMARK = "/user/deleteBookmark"; 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_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_EMAILS = "/gmail/getEmails";
public static final String API_URL_GMAIL_GET_SCANNED_EMAILS = "/gmail/getScannedEmails"; 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";
} }

View File

@@ -22,4 +22,11 @@ public class CommonConstants {
public static final String CLASSIFY_WARNING = "WARNING"; public static final String CLASSIFY_WARNING = "WARNING";
public static final String CLASSIFY_UNSAFE = "UNSAFE"; public static final String CLASSIFY_UNSAFE = "UNSAFE";
public static final String CLASSIFY_UNKNOWN = "UNKNOWN"; 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;
} }

View File

@@ -16,4 +16,8 @@ public class GlobalExceptionHandler {
public ResponseEntity<ErrorResponse> handleResourceAlreadyExistsException(ResourceAlreadyExists e) { public ResponseEntity<ErrorResponse> handleResourceAlreadyExistsException(ResourceAlreadyExists e) {
return new ResponseEntity<>(new ErrorResponse(e.getMessage(), HttpStatus.BAD_REQUEST.value()), HttpStatus.BAD_REQUEST); 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);
}
} }

View File

@@ -0,0 +1,7 @@
package com.safeqr.app.exceptions;
public class InvalidFormatExceptions extends RuntimeException {
public InvalidFormatExceptions(String message){
super(message);
}
}

View File

@@ -1,8 +1,8 @@
package com.safeqr.app.gmail.controller; 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 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 org.json.JSONObject;
import com.google.api.client.auth.oauth2.AuthorizationCodeRequestUrl; import com.google.api.client.auth.oauth2.AuthorizationCodeRequestUrl;
import com.google.api.client.auth.oauth2.Credential; 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.bind.annotation.*;
import org.springframework.web.servlet.view.RedirectView; import org.springframework.web.servlet.view.RedirectView;
import static com.safeqr.app.constants.APIConstants.*; import static com.safeqr.app.constants.APIConstants.*;
import java.io.IOException; import static com.safeqr.app.constants.CommonConstants.HEADER_USER_ID;
import java.lang.Thread;
import java.util.*; import java.util.*;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
@RestController @RestController
@@ -129,5 +129,17 @@ public class GmailController {
return new ResponseEntity<>("Scan Gmail Request is being processed", HttpStatus.ACCEPTED); 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));
}
} }

View 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;
}

View File

@@ -0,0 +1,10 @@
package com.safeqr.app.gmail.dto;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
public class MessageRequestDto {
private String messageId;
}

View File

@@ -5,6 +5,7 @@ import lombok.AllArgsConstructor;
import lombok.Builder; import lombok.Builder;
import lombok.Data; import lombok.Data;
import java.util.Comparator;
import java.util.List; import java.util.List;
@Builder @Builder

View File

@@ -7,6 +7,7 @@ import lombok.Data;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import org.hibernate.annotations.UuidGenerator; import org.hibernate.annotations.UuidGenerator;
import java.math.BigInteger;
import java.time.OffsetDateTime; import java.time.OffsetDateTime;
import java.util.UUID; import java.util.UUID;
@@ -33,7 +34,7 @@ public class GmailEmailEntity {
private String threadId; private String threadId;
@Column(name = "history_id") @Column(name = "history_id")
private Long historyId; private BigInteger historyId;
@Column(name= "subject") @Column(name= "subject")
private String subject; private String subject;
@@ -44,6 +45,9 @@ public class GmailEmailEntity {
@Column(name = "date_created") @Column(name = "date_created")
private OffsetDateTime dateCreated; private OffsetDateTime dateCreated;
@Column(name = "active")
private int active = 1;
@PrePersist @PrePersist
public void prePersist() { public void prePersist() {
dateCreated = OffsetDateTime.now(); dateCreated = OffsetDateTime.now();

View File

@@ -13,6 +13,7 @@ public class EmailMessage {
private String subject; private String subject;
private String historyId; private String historyId;
private String date; private String date;
private int active;
@JsonInclude(JsonInclude.Include.NON_EMPTY) @JsonInclude(JsonInclude.Include.NON_EMPTY)
List<QRCodeByContentId> qrCodeByContentId; List<QRCodeByContentId> qrCodeByContentId;
@@ -29,6 +30,7 @@ public class EmailMessage {
this.subject = subject; this.subject = subject;
this.historyId = historyId; this.historyId = historyId;
this.date = date; this.date = date;
this.active = 1;
this.qrCodeByContentId = new ArrayList<>(); this.qrCodeByContentId = new ArrayList<>();
this.qrCodeByURL = new ArrayList<>(); this.qrCodeByURL = new ArrayList<>();
this.decodedContentsDetails = new ArrayList<>(); this.decodedContentsDetails = new ArrayList<>();

View File

@@ -3,10 +3,31 @@ package com.safeqr.app.gmail.repository;
import com.safeqr.app.gmail.entity.GmailEmailEntity; import com.safeqr.app.gmail.entity.GmailEmailEntity;
import org.springframework.data.jpa.repository.JpaRepository; 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.List;
import java.util.UUID; import java.util.UUID;
public interface GmailEmailRespository extends JpaRepository<GmailEmailEntity, 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);
} }

View File

@@ -1,10 +1,7 @@
package com.safeqr.app.gmail.service; package com.safeqr.app.gmail.service;
import com.google.api.client.auth.oauth2.BearerToken; import com.google.api.client.auth.oauth2.*;
import com.google.api.client.auth.oauth2.ClientParametersAuthentication;
import com.google.api.client.auth.oauth2.Credential;
import com.google.api.client.auth.oauth2.TokenResponse;
import com.google.api.client.googleapis.auth.oauth2.GoogleRefreshTokenRequest; import com.google.api.client.googleapis.auth.oauth2.GoogleRefreshTokenRequest;
import com.google.api.client.googleapis.json.GoogleJsonResponseException; import com.google.api.client.googleapis.json.GoogleJsonResponseException;
import com.google.api.client.http.GenericUrl; 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.client.j2se.BufferedImageLuminanceSource;
import com.google.zxing.common.HybridBinarizer; import com.google.zxing.common.HybridBinarizer;
import com.google.zxing.multi.qrcode.QRCodeMultiReader; 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.dto.ScannedGmailResponseDto;
import com.safeqr.app.gmail.entity.GmailCidEntity; import com.safeqr.app.gmail.entity.GmailCidEntity;
import com.safeqr.app.gmail.entity.GmailEmailEntity; 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.gmail.repository.GmailUrlsRespository;
import com.safeqr.app.qrcode.model.QRCodeModel; import com.safeqr.app.qrcode.model.QRCodeModel;
import com.safeqr.app.qrcode.service.QRCodeTypeService; 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.user.service.UserService;
import com.safeqr.app.utils.DateParsingUtils; import com.safeqr.app.utils.DateParsingUtils;
import io.hypersistence.utils.common.StringUtils;
import org.apache.commons.codec.binary.Base64; import org.apache.commons.codec.binary.Base64;
import org.jsoup.Jsoup; import org.jsoup.Jsoup;
import org.jsoup.nodes.Document; import org.jsoup.nodes.Document;
@@ -43,12 +44,14 @@ import org.springframework.beans.factory.annotation.Value;
import org.springframework.dao.DataIntegrityViolationException; import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.scheduling.annotation.Async; import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.imageio.ImageIO; import javax.imageio.ImageIO;
import java.awt.image.BufferedImage; import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.IOException; import java.io.IOException;
import java.lang.Thread; import java.lang.Thread;
import java.math.BigInteger;
import java.net.ConnectException; import java.net.ConnectException;
import java.net.URI; import java.net.URI;
import java.net.URISyntaxException; import java.net.URISyntaxException;
@@ -61,9 +64,11 @@ import java.util.*;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream; 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.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.APIConstants.APPLICATION_NAME;
import static com.safeqr.app.constants.CommonConstants.GMAIL_ACTIVE;
@Service @Service
public class GmailService { public class GmailService {
@@ -110,22 +115,67 @@ public class GmailService {
.build(); .build();
} }
// Renew the access token if it has expired using the refresh token. // 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 {
TokenResponse response = new GoogleRefreshTokenRequest( logger.info("Refresh token in refreshAccessToken: {}", refreshToken);
httpTransport, JSON_FACTORY, refreshToken, clientId, clientSecret) if (StringUtils.isBlank(refreshToken)) {
.execute(); logger.error("Refresh token is null or empty");
return response.getAccessToken(); 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();
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.");
}
if (++retryCount >= maxRetries) {
logger.error("Max retries reached. Unable to refresh access token.");
throw e;
}
// Implement exponential backoff
try {
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 { private Gmail refreshAndGetGmailService(String accessToken, String refreshToken) throws IOException, AuthenticationException {
try { try {
return getGmailService(accessToken, refreshToken); Gmail service = getGmailService(accessToken, refreshToken);
service.users().getProfile("me").execute();
logger.info("Gmail service authenticated with provided access token.");
return service;
} catch (GoogleJsonResponseException e) { } catch (GoogleJsonResponseException e) {
if (e.getStatusCode() == 401) { if (e.getStatusCode() == 401) {
logger.info("Access token expired. Refreshing token..."); logger.info("Access token expired. Refreshing...");
String newAccessToken = refreshAccessToken(refreshToken); 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; throw e;
} }
} }
@@ -138,48 +188,73 @@ public class GmailService {
CompletableFuture.completedFuture(result); CompletableFuture.completedFuture(result);
} catch (IOException e) { } catch (IOException e) {
logger.error("Error processing Gmail", e); logger.error("Error processing Gmail", e);
} catch (AuthenticationException e) {
logger.error("Error Authenticating", e);
} }
} }
// Scan all emails in the user's inbox. // 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); Gmail service = refreshAndGetGmailService(accessToken, refreshToken);
logger.info("Gmail service initialized: {}", service); logger.info("Gmail service initialized: {}", service);
List<EmailMessage> emailMessagesList = new ArrayList<>(); List<EmailMessage> emailMessagesList = new ArrayList<>();
String meUserId = "me"; String meUserId = "me";
String nextPageToken = null; String nextPageToken = null;
// Fetching email messages with page token and setting max results, Default value is 100. UserEntity userEntity = userService.getUserByIdForGmail(userId);
do { BigInteger historyId = userEntity.getGmailHistoryId();
logger.info("history id: {}", historyId);
// 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) ListHistoryResponse historyResponse = service.users().history().list(meUserId)
// .setStartHistoryId(BigInteger.valueOf(689335)) .setStartHistoryId(historyId)
// .execute(); .execute();
// List<History> historyList = historyResponse.getHistory();
// List<History> historyList = historyResponse.getHistory(); if (historyList != null) {
// for (History history : historyList) {
// for (History history : historyList) { logger.info("History Response - History Id: {}, Message Id: {}", history.getId(), history.getMessages().get(0).getId());
// logger.info("History Id: {}, Message Id: {}, Message Snippet: {}", history.getId(), history.getMessages().get(0).getId(), history.getMessages().get(0).getHistoryId()); List<Message> messages = history.getMessages();
// } for (Message message : messages) {
ListMessagesResponse listResponse = fetchMessages(service, meUserId, nextPageToken); EmailMessage emailMessage = processMessage(service, meUserId, message);
List<Message> messages = listResponse.getMessages(); if (emailMessage != null) {
nextPageToken = listResponse.getNextPageToken(); emailMessagesList.add(emailMessage);
saveEmailMessageAndScanQRCode(userId, emailMessage);
// Iterate all the messages and save to gmail db only if it has a valid QR code. }
for (Message message : messages) { }
EmailMessage emailMessage = processMessage(service, meUserId, message);
if (emailMessage != null) {
emailMessagesList.add(emailMessage);
// Save email message to database.
saveEmailMessageAndScanQRCode(userId, emailMessage);
} }
} }
} while (nextPageToken != null); } else {
// Fetching email messages with page token and setting max results, Default value is 100.
do {
ListMessagesResponse listResponse = fetchMessages(service, meUserId, nextPageToken);
List<Message> messages = listResponse.getMessages();
nextPageToken = listResponse.getNextPageToken();
// Iterate all the messages and save to gmail db only if it has a valid QR code.
for (Message message : messages) {
EmailMessage emailMessage = processMessage(service, meUserId, message);
if (emailMessage != null) {
emailMessagesList.add(emailMessage);
// Save email message to database.
saveEmailMessageAndScanQRCode(userId, emailMessage);
}
}
} while (nextPageToken != null);
}
// Update user's history id. // 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); 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. // Save email message to database and scan QR code.
private void saveEmailMessageAndScanQRCode(String userId, EmailMessage emailMessage) { private void saveEmailMessageAndScanQRCode(String userId, EmailMessage emailMessage) {
GmailEmailEntity gmailEmailEntity = saveEmailMessage(userId, emailMessage); GmailEmailEntity gmailEmailEntity = saveEmailMessage(userId, emailMessage);
@@ -203,9 +278,10 @@ public class GmailService {
.userId(userId) .userId(userId)
.messageId(emailMessage.getMessageId()) .messageId(emailMessage.getMessageId())
.threadId(emailMessage.getThreadId()) .threadId(emailMessage.getThreadId())
.historyId(Long.valueOf(emailMessage.getHistoryId())) .historyId(new BigInteger(emailMessage.getHistoryId()))
.subject(emailMessage.getSubject()) .subject(emailMessage.getSubject())
.dateReceived(dateReceived) .dateReceived(dateReceived)
.active(emailMessage.getActive())
.build(); .build();
return gmailEmailRespository.save(gmailEmailEntity); return gmailEmailRespository.save(gmailEmailEntity);
} catch (DataIntegrityViolationException e) { } catch (DataIntegrityViolationException e) {
@@ -263,7 +339,7 @@ public class GmailService {
// Fetching Scanned Gmail from database // Fetching Scanned Gmail from database
public ScannedGmailResponseDto fetchScannedGmail(String userId){ public ScannedGmailResponseDto fetchScannedGmail(String userId){
// Fetching all emails from gmail_email table // 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<>(); List<EmailMessage> emailMessageList = new ArrayList<>();
if (userEmailsList != null && !userEmailsList.isEmpty()) { if (userEmailsList != null && !userEmailsList.isEmpty()) {
@@ -335,6 +411,7 @@ public class GmailService {
emailMessageList.add(emailMessage); emailMessageList.add(emailMessage);
}); });
emailMessageList.sort(Comparator.comparing(EmailMessage::getDate, Comparator.nullsLast(Comparator.reverseOrder())));
} }
return ScannedGmailResponseDto.builder().messages(emailMessageList).build(); return ScannedGmailResponseDto.builder().messages(emailMessageList).build();
} }
@@ -346,29 +423,42 @@ public class GmailService {
.execute(); .execute();
} }
// Processing email message and returning EmailMessage object if it has a valid QR code. // 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) {
message = service.users().messages().get(userId, message.getId()).setFormat("full").execute(); try {
List<MessagePart> parts = message.getPayload().getParts(); message = service.users().messages().get(userId, message.getId()).setFormat("full").execute();
Set<String> attachmentIds = new HashSet<>(); List<MessagePart> parts = message.getPayload().getParts();
Set<String> imageUrls = new HashSet<>(); Set<String> attachmentIds = new HashSet<>();
processPartsRecursively(parts, attachmentIds, imageUrls); Set<String> imageUrls = new HashSet<>();
processPartsRecursively(parts, attachmentIds, imageUrls);
if (attachmentIds.isEmpty() && imageUrls.isEmpty()) { if (attachmentIds.isEmpty() && imageUrls.isEmpty()) {
return null; return null;
}
String subject = getHeader(message, "Subject");
String emailDate = getHeader(message, "Date");
logger.info("Email Subject: {}", subject);
logger.info("Message ID: {}", message.getId());
logger.info("History ID: {}", message.getHistoryId());
EmailMessage emailMessage = new EmailMessage(message.getId(), message.getThreadId(), subject, String.valueOf(message.getHistoryId()), emailDate);
processAttachments(service, message.getId(), parts, attachmentIds, emailMessage);
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);
} }
String subject = getHeader(message, "Subject");
String emailDate = getHeader(message, "Date");
logger.info("Email Subject: {}", subject);
logger.info("Message ID: {}", message.getId());
logger.info("History ID: {}", message.getHistoryId());
EmailMessage emailMessage = new EmailMessage(message.getId(), message.getThreadId(), subject, String.valueOf(message.getHistoryId()), emailDate);
processAttachments(service, message.getId(), parts, attachmentIds, emailMessage);
processImageUrls(imageUrls, emailMessage);
return emailMessage.hasQRCodes() ? emailMessage : null;
} }
// Process all the attachments. // Process all the attachments.
private void processAttachments(Gmail service, String messageId, List<MessagePart> parts, Set<String> attachmentIds, EmailMessage emailMessage) throws IOException { 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(); String lowerUrl = url.toLowerCase();
return lowerUrl.endsWith(".jpg") || lowerUrl.endsWith(".jpeg") || lowerUrl.endsWith(".png") || lowerUrl.endsWith(".gif") || lowerUrl.endsWith(".bmp"); 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();
}
} }

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -3,12 +3,9 @@ package com.safeqr.app.qrcode.controller;
import static com.safeqr.app.constants.APIConstants.*; import static com.safeqr.app.constants.APIConstants.*;
import static com.safeqr.app.constants.CommonConstants.*; import static com.safeqr.app.constants.CommonConstants.*;
import com.safeqr.app.qrcode.dto.request.QRCodePayload; 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.dto.response.BaseScanResponse;
import com.safeqr.app.qrcode.entity.QRCodeTypeEntity; import com.safeqr.app.qrcode.entity.QRCodeTypeEntity;
import com.safeqr.app.qrcode.service.QRCodeTypeService; 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.URLVerificationService;
import com.safeqr.app.qrcode.service.VirusTotalService; import com.safeqr.app.qrcode.service.VirusTotalService;
import org.slf4j.Logger; import org.slf4j.Logger;
@@ -36,9 +33,6 @@ public class QRCodeTypeController {
@Autowired @Autowired
private VirusTotalService virusTotalService; private VirusTotalService virusTotalService;
@Autowired
private RedirectCountService redirectCountService;
@GetMapping(value = API_URL_QRCODE_GET_ALL) @GetMapping(value = API_URL_QRCODE_GET_ALL)
public ResponseEntity<List<QRCodeTypeEntity>> getAllTypes() { public ResponseEntity<List<QRCodeTypeEntity>> getAllTypes() {
return ResponseEntity.ok(qrCodeTypeService.getAllTypes()); return ResponseEntity.ok(qrCodeTypeService.getAllTypes());
@@ -56,15 +50,12 @@ public class QRCodeTypeController {
return ResponseEntity.ok(qrCodeTypeService.scanQRCode(userId, payload)); return ResponseEntity.ok(qrCodeTypeService.scanQRCode(userId, payload));
} }
@PostMapping(API_URL_QRCODE_DETECT) @PostMapping(value = API_URL_QRCODE_VERIFY_URL, produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<String> detectType(@RequestBody QRCodePayload payload) { public ResponseEntity<BaseScanResponse> verifyURL(@RequestBody QRCodePayload payload,
return ResponseEntity.ok(qrCodeTypeService.detectType(payload).block()); @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) @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());
}
} }

View File

@@ -1,9 +0,0 @@
package com.safeqr.app.qrcode.dto;
import lombok.Data;
@Data
public class RedirectCountResponse {
private int redirectCount;
private String message;
}

View File

@@ -2,6 +2,7 @@ package com.safeqr.app.qrcode.entity;
import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonInclude;
import jakarta.persistence.*; import jakarta.persistence.*;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Data; import lombok.Data;
@@ -30,4 +31,8 @@ public class PhoneEntity {
private UUID qrCodeId; private UUID qrCodeId;
private String phone; private String phone;
@JsonInclude(JsonInclude.Include.NON_EMPTY)
@Column(name = "remarks")
private String remarks;
} }

View File

@@ -2,6 +2,7 @@
package com.safeqr.app.qrcode.entity; package com.safeqr.app.qrcode.entity;
import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonInclude;
import jakarta.persistence.*; import jakarta.persistence.*;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Builder; import lombok.Builder;
@@ -39,6 +40,7 @@ public class QRCodeEntity {
@Column(name = "created_at", insertable = false, updatable = false) @Column(name = "created_at", insertable = false, updatable = false)
private LocalDateTime createdAt; private LocalDateTime createdAt;
@JsonInclude(JsonInclude.Include.NON_EMPTY)
@Column(name = "result_category") @Column(name = "result_category")
private String result = CLASSIFY_UNKNOWN; private String result = CLASSIFY_UNKNOWN;
} }

View File

@@ -2,6 +2,7 @@ package com.safeqr.app.qrcode.entity;
import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonInclude;
import jakarta.persistence.*; import jakarta.persistence.*;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Data; import lombok.Data;
@@ -31,4 +32,8 @@ public class SMSEntity {
private String phone; private String phone;
private String message; private String message;
@JsonInclude(JsonInclude.Include.NON_NULL)
@Column(name = "keyword_detected")
private String keywordDetected;
} }

View File

@@ -52,4 +52,9 @@ public class ScanHistoryEntity {
dateCreated = now; dateCreated = now;
dateUpdated = now; dateUpdated = now;
} }
@PreUpdate
public void preUpdate() {
dateUpdated = OffsetDateTime.now();
}
} }

View File

@@ -23,6 +23,10 @@ import java.util.UUID;
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
public class URLEntity { public class URLEntity {
@JsonInclude(JsonInclude.Include.NON_EMPTY)
@Column(name="classifications")
private String classifications;
@Id @Id
@JsonIgnore @JsonIgnore
@GeneratedValue(generator = "UUID") @GeneratedValue(generator = "UUID")

View File

@@ -27,6 +27,8 @@ public final class EmailModel extends QRCodeModel<EmailEntity> {
@Override @Override
public void setDetails() { public void setDetails() {
details = EmailEntity.builder().qrCodeId(data.getId()).build(); details = EmailEntity.builder().qrCodeId(data.getId()).build();
emailVerificationService.parseEmailString(details, data.getContents());
// Insert into email table // Insert into email table
emailVerificationService.insertDB(details); emailVerificationService.insertDB(details);
} }

View File

@@ -27,6 +27,8 @@ public final class PhoneModel extends QRCodeModel<PhoneEntity> {
@Override @Override
public void setDetails() { public void setDetails() {
details = PhoneEntity.builder().qrCodeId(data.getId()).build(); details = PhoneEntity.builder().qrCodeId(data.getId()).build();
phoneVerificationService.parsePhoneString(details, data.getContents());
// Insert into phone table // Insert into phone table
phoneVerificationService.insertDB(details); phoneVerificationService.insertDB(details);
} }
@@ -36,6 +38,6 @@ public final class PhoneModel extends QRCodeModel<PhoneEntity> {
} }
@Override @Override
public String retrieveClassification() { public String retrieveClassification() {
return ""; return phoneVerificationService.checkPhoneNumber(details);
} }
} }

View File

@@ -27,6 +27,8 @@ public final class SMSModel extends QRCodeModel<SMSEntity> {
@Override @Override
public void setDetails() { public void setDetails() {
details = SMSEntity.builder().qrCodeId(data.getId()).build(); details = SMSEntity.builder().qrCodeId(data.getId()).build();
smsVerificationService.parseSMSString(details, data.getContents());
// Insert into sms table // Insert into sms table
smsVerificationService.insertDB(details); smsVerificationService.insertDB(details);
} }
@@ -36,6 +38,6 @@ public final class SMSModel extends QRCodeModel<SMSEntity> {
} }
@Override @Override
public String retrieveClassification() { public String retrieveClassification() {
return ""; return smsVerificationService.getClassification(details);
} }
} }

View File

@@ -47,6 +47,6 @@ public final class URLModel extends QRCodeModel<URLEntity> {
@Override @Override
public String retrieveClassification() { public String retrieveClassification() {
return ""; return urlVerificationService.getClassification(this);
} }
} }

View File

@@ -25,7 +25,8 @@ public class QRCodeFactoryProvider {
case QR_CODE_TYPE_EMAIL -> applicationContext.getBean(EmailFactory.class).create(scannedQRCodeEntity); case QR_CODE_TYPE_EMAIL -> applicationContext.getBean(EmailFactory.class).create(scannedQRCodeEntity);
case QR_CODE_TYPE_WIFI -> applicationContext.getBean(WifiFactory.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); 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);
}; };
} }
} }

View File

@@ -1,5 +1,6 @@
package com.safeqr.app.qrcode.service; package com.safeqr.app.qrcode.service;
import com.safeqr.app.exceptions.InvalidFormatExceptions;
import com.safeqr.app.exceptions.ResourceNotFoundExceptions; import com.safeqr.app.exceptions.ResourceNotFoundExceptions;
import com.safeqr.app.qrcode.entity.EmailEntity; import com.safeqr.app.qrcode.entity.EmailEntity;
import com.safeqr.app.qrcode.repository.EmailRepository; 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.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import java.util.Arrays;
import java.util.Map;
import java.util.Optional;
import java.util.UUID; import java.util.UUID;
import java.util.stream.Collectors;
@Service @Service
public class EmailVerificationService { public class EmailVerificationService {
@@ -28,4 +33,35 @@ public class EmailVerificationService {
emailRepository.save(emailEntity); 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>");
}
);
}
} }

View File

@@ -1,5 +1,6 @@
package com.safeqr.app.qrcode.service; package com.safeqr.app.qrcode.service;
import com.safeqr.app.exceptions.InvalidFormatExceptions;
import com.safeqr.app.exceptions.ResourceNotFoundExceptions; import com.safeqr.app.exceptions.ResourceNotFoundExceptions;
import com.safeqr.app.qrcode.entity.PhoneEntity; import com.safeqr.app.qrcode.entity.PhoneEntity;
import com.safeqr.app.qrcode.repository.PhoneRepository; import com.safeqr.app.qrcode.repository.PhoneRepository;
@@ -10,6 +11,8 @@ import org.springframework.stereotype.Service;
import java.util.UUID; import java.util.UUID;
import static com.safeqr.app.constants.CommonConstants.*;
@Service @Service
public class PhoneVerificationService { public class PhoneVerificationService {
private final PhoneRepository phoneRepository; private final PhoneRepository phoneRepository;
@@ -28,4 +31,49 @@ public class PhoneVerificationService {
phoneRepository.save(phoneEntity); 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;
}
} }

View File

@@ -88,41 +88,33 @@ public class QRCodeTypeService {
String data = payload.getData(); String data = payload.getData();
logger.info("scanQRCode: userId={}, data={}", userId, data); logger.info("scanQRCode: userId={}, data={}", userId, data);
// Get the QR Code Type QRCodeModel<?> qrCodeModel = scanAndClassify(userId, data);
QRCodeTypeEntity qrType = getQRCodeType(data); UUID qrId = qrCodeModel.getData().getId();
// Insert the QR Code into main qrcode table
QRCodeEntity scannedQR = qrCodeRepository.save(QRCodeEntity.builder()
.userId(userId)
.contents(data)
.info(qrType)
.createdAt(LocalDateTime.now())
.build());
// Insert into Scan History table if userId is not null // 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) { if (userId != null) {
scanHistoryRepository.save(ScanHistoryEntity.builder() scanHistoryRepository.save(ScanHistoryEntity.builder()
.qrCodeId(scannedQR.getId()) .qrCodeId(qrId)
.userId(userId) .userId(userId)
.scanStatus(ScanHistoryEntity.ScanStatus.ACTIVE) .scanStatus(ScanHistoryEntity.ScanStatus.ACTIVE)
.build()); .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(); return BaseScanResponse.builder().qrcode(qrCodeModel).build();
} }
// Scan decoded contents from email message
@Transactional @Transactional
public QRCodeModel<?> scanGmailDecodedContents(String userId, String data) { public QRCodeModel<?> scanGmailDecodedContents(String userId, String data) {
logger.info("Scan Gmail content: userId={}, data={}", userId, 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 // Get the QR Code Type
QRCodeTypeEntity qrType = getQRCodeType(data); QRCodeTypeEntity qrType = getQRCodeType(data);
@@ -138,8 +130,12 @@ public class QRCodeTypeService {
QRCodeModel<?> qrCodeModel = qrCodeFactoryProvider.createQRCodeInstance(scannedQR); QRCodeModel<?> qrCodeModel = qrCodeFactoryProvider.createQRCodeInstance(scannedQR);
qrCodeModel.setDetails(); qrCodeModel.setDetails();
// Get classifications based on verifications
scannedQR.setResult(qrCodeModel.retrieveClassification());
return qrCodeModel; return qrCodeModel;
} }
// Returns Default type as text if it does not fit into any of the category // Returns Default type as text if it does not fit into any of the category
private QRCodeTypeEntity getQRCodeType(String data) { private QRCodeTypeEntity getQRCodeType(String data) {
return configs.stream() return configs.stream()
@@ -147,27 +143,4 @@ public class QRCodeTypeService {
.findFirst() .findFirst()
.orElse(defaultQRCodeTypeEntity); .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");
}
} }

View File

@@ -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);
});
}
}

View File

@@ -1,20 +1,51 @@
package com.safeqr.app.qrcode.service; package com.safeqr.app.qrcode.service;
import com.safeqr.app.exceptions.InvalidFormatExceptions;
import com.safeqr.app.exceptions.ResourceNotFoundExceptions; 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.entity.SMSEntity;
import com.safeqr.app.qrcode.repository.SMSRepository; import com.safeqr.app.qrcode.repository.SMSRepository;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; 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 @Service
public class SMSVerificationService { public class SMSVerificationService {
private final SMSRepository smsRepository; private final SMSRepository smsRepository;
private static final Logger logger = LoggerFactory.getLogger(SMSVerificationService.class); 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 @Autowired
public SMSVerificationService(SMSRepository smsRepository) { public SMSVerificationService(SMSRepository smsRepository) {
this.smsRepository = smsRepository; this.smsRepository = smsRepository;
@@ -29,4 +60,76 @@ public class SMSVerificationService {
smsRepository.save(smsEntity); 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;
}
} }

View File

@@ -1,11 +1,10 @@
package com.safeqr.app.qrcode.service; package com.safeqr.app.qrcode.service;
import static com.safeqr.app.constants.CommonConstants.*; 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.entity.URLEntity;
import com.safeqr.app.qrcode.model.URLModel;
import com.safeqr.app.qrcode.repository.URLRepository; import com.safeqr.app.qrcode.repository.URLRepository;
import com.safeqr.app.prediction.service.PredictionService;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; 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 int READ_TIMEOUT_MS = 10000;
private static final Logger logger = LoggerFactory.getLogger(URLVerificationService.class); private static final Logger logger = LoggerFactory.getLogger(URLVerificationService.class);
private final URLRepository urlRepository; private final URLRepository urlRepository;
private final PredictionService predictionService;
@Autowired @Autowired
public URLVerificationService(URLRepository urlRepository) { public URLVerificationService(URLRepository urlRepository, PredictionService predictionService) {
this.urlRepository = urlRepository; this.urlRepository = urlRepository;
this.predictionService = predictionService;
} }
// Regular expression pattern for shortening services // Regular expression pattern for shortening services
@@ -59,10 +60,11 @@ public class URLVerificationService {
// Define a Set of suspicious file extensions // Define a Set of suspicious file extensions
private static final Set<String> SUSPICIOUS_EXTENSIONS = Stream.of( private static final Set<String> SUSPICIOUS_EXTENSIONS = Stream.of(
".exe", ".bat", ".sh", ".cmd", ".scr", ".pif", ".application", ".gadget", ".exe", ".bat", ".sh", ".cmd", ".scr", ".pif", ".application", ".gadget",
".vb", ".vbs", ".js", ".jse", ".ws", ".wsf", ".msc", ".com", ".cpl", ".vb", ".vbs", ".js", ".jse", ".ws", ".wsf", ".msc", ".cpl",
".msi", ".ps1", ".py", ".pyc", ".pyo", ".rb", ".app", ".bin", ".run" ".msi", ".ps1", ".py", ".pyc", ".pyo", ".rb", ".bin", ".run", "apk"
).collect(Collectors.toUnmodifiableSet()); ).collect(Collectors.toUnmodifiableSet());
// Checks if the URL has executable file // Checks if the URL has executable file
public String hasExecutableFile(String urlPath) { public String hasExecutableFile(String urlPath) {
return Stream.of(urlPath) return Stream.of(urlPath)
@@ -425,22 +427,48 @@ public class URLVerificationService {
return INFO_NON_SECURE_CONNECTION; return INFO_NON_SECURE_CONNECTION;
} }
public URLVerificationResponse verifyURL(QRCodePayload payload) { // Get Classification using ML Model
URLVerificationResponse response = new URLVerificationResponse(); public String getClassification(URLModel urlModel){
try { String content = urlModel.getData().getContents();
java.net.URL url = new java.net.URL(payload.getData()); // if in whitelist, return Benign and Safe
String protocol = url.getProtocol(); for (String domain : WHITELIST_DOMAINS) {
if ("https".equalsIgnoreCase(protocol)) { if (content.contains(domain)) {
response.setSecure(true); // If in whitelist, set category to BENIGN and return SAFE
response.setMessage("The connection is secure."); urlModel.getDetails().setClassifications(CAT_BENIGN);
} else { return CLASSIFY_SAFE;
response.setSecure(false);
response.setMessage("The connection is not secure.");
} }
} 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"
);
} }

View File

@@ -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());
}
}

View File

@@ -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;
}

View File

@@ -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();
}

View File

@@ -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();
}
}

View File

@@ -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;
}
}

View File

@@ -12,6 +12,7 @@ public class UserResponseDto {
private String id; private String id;
private String name; private String name;
private String email; private String email;
private String source;
private OffsetDateTime dateJoined; private OffsetDateTime dateJoined;
private OffsetDateTime dateUpdated; private OffsetDateTime dateUpdated;
private List<String> roles; private List<String> roles;

View File

@@ -8,6 +8,7 @@ import jakarta.persistence.Table;
import lombok.*; import lombok.*;
import org.hibernate.annotations.Type; import org.hibernate.annotations.Type;
import java.math.BigInteger;
import java.time.OffsetDateTime; import java.time.OffsetDateTime;
import java.util.List; import java.util.List;
@@ -34,6 +35,9 @@ public class UserEntity {
private List<String> roles; private List<String> roles;
private String status; private String status;
@Column(name = "source")
private String source;
@Column(name = "gmail_history_id") @Column(name = "gmail_history_id")
private Long gmailHistoryId; private BigInteger gmailHistoryId = BigInteger.ZERO;
} }

View File

@@ -2,7 +2,7 @@ package com.safeqr.app.user.service;
import com.safeqr.app.exceptions.ResourceAlreadyExists; import com.safeqr.app.exceptions.ResourceAlreadyExists;
import com.safeqr.app.exceptions.ResourceNotFoundExceptions; 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.entity.ScanHistoryEntity;
import com.safeqr.app.qrcode.repository.ScanBookmarkRepository; import com.safeqr.app.qrcode.repository.ScanBookmarkRepository;
import com.safeqr.app.qrcode.repository.ScanHistoryRepository; 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.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
@@ -48,12 +48,20 @@ public class UserService {
.id(userEntity.getId()) .id(userEntity.getId())
.email(userEntity.getEmail()) .email(userEntity.getEmail())
.name(userEntity.getName()) .name(userEntity.getName())
.source(userEntity.getSource())
.dateJoined(userEntity.getDateCreated()) .dateJoined(userEntity.getDateCreated())
.dateUpdated(userEntity.getDateUpdated()) .dateUpdated(userEntity.getDateUpdated())
.roles(userEntity.getRoles()) .roles(userEntity.getRoles())
.status(userEntity.getStatus()) .status(userEntity.getStatus())
.build(); .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) { public List<ScannedHistoriesDto> getUserScannedHistories(String userId) {
return scanHistoryRepository.findAllQRCodesByUserId(userId); return scanHistoryRepository.findAllQRCodesByUserId(userId);
} }

View 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();
}
}

View File

@@ -1,14 +1,14 @@
spring.application.name=SafeQR-app spring.application.name=SafeQR-app
spring.profiles.active=local #spring.profiles.active=local
#http.port=${HTTP_PORT} http.port=${HTTP_PORT}
#server.port=${SERVER_PORT} server.port=${SERVER_PORT}
#server.ssl.enabled=true server.ssl.enabled=true
#server.ssl.key-store-type=${SERVER_SSL_KEY_STORE_TYPE} server.ssl.key-store-type=${SERVER_SSL_KEY_STORE_TYPE}
#server.ssl.key-store=${SERVER_SSL_KEY_STORE_LOCATION} server.ssl.key-store=${SERVER_SSL_KEY_STORE_LOCATION}
#server.ssl.key-store-password=${SERVER_SSL_KEY_STORE_PASSWORD} server.ssl.key-store-password=${SERVER_SSL_KEY_STORE_PASSWORD}
#server.ssl.key-alias=${SERVER_SSL_KEY_ALIAS} server.ssl.key-alias=${SERVER_SSL_KEY_ALIAS}
#trust.store=${SERVER_SSL_TRUST_STORE_LOCATION} trust.store=${SERVER_SSL_TRUST_STORE_LOCATION}
#trust.store.password=${SERVER_SSL_TRUST_STORE_PASSWORD} trust.store.password=${SERVER_SSL_TRUST_STORE_PASSWORD}
spring.datasource.url=${SERVER_DB_URL} spring.datasource.url=${SERVER_DB_URL}
spring.datasource.username=${SERVER_DB_USERNAME} spring.datasource.username=${SERVER_DB_USERNAME}
@@ -32,4 +32,4 @@ gmail.client.clientAuthenticationScheme=query
gmail.client.scope=https://www.googleapis.com/auth/gmail.readonly gmail.client.scope=https://www.googleapis.com/auth/gmail.readonly
gmail.resource.userInfoUri=https://www.googleapis.com/gmail/v1/users/me/profile gmail.resource.userInfoUri=https://www.googleapis.com/gmail/v1/users/me/profile
gmail.resource.preferTokenInfo=true gmail.resource.preferTokenInfo=true
gmail.client.redirectUri=https://bk5wiynzsi.execute-api.ap-southeast-1.amazonaws.com/api/gmail/callback gmail.client.redirectUri=https://bk5wiynzsi.execute-api.ap-southeast-1.amazonaws.com/api/v1/gmail/callback

View File

@@ -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":{}}

View File

@@ -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"}}

View File

@@ -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"}}

View File

@@ -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}

View File

@@ -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":{}}

View File

@@ -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"}}

View File

@@ -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"}}

View File

@@ -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}}

View File

@@ -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"}}

View File

@@ -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}