├── docs ├── DesktopUI1.PNG └── DesktopUI2.PNG ├── SECURITY.md ├── CONTRIBUTING.md ├── src ├── Email.java ├── GraphApiEmailDownloader.java ├── GraphApiTokenGenerator.java ├── GraphEmailSearcher.java └── Application.java ├── .github └── ISSUE_TEMPLATE │ ├── feature_request.md │ └── bug_report.md ├── ROADMAP.md ├── LICENSE └── README.md /docs/DesktopUI1.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ws-research/azure-data-guardian/HEAD/docs/DesktopUI1.PNG -------------------------------------------------------------------------------- /docs/DesktopUI2.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ws-research/azure-data-guardian/HEAD/docs/DesktopUI2.PNG -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Reporting Vulnerabilities 4 | **DO NOT** create public issues for security vulnerabilities! 5 | - Email: kien61365@gmail.com (include "SECURITY" in subject) 6 | - Expect response within 72 hours 7 | - We will coordinate CVE assignment and public disclosure 8 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contribution Guidelines 2 | 3 | ## Code Contributions 4 | 1. Fork the repository 5 | 2. Create feature branch (`git checkout -b feature/amazing-feature`) 6 | 3. Commit changes (`git commit -m 'Add amazing feature'`) 7 | 4. Push to branch (`git push origin feature/amazing-feature`) 8 | 5. Open pull request 9 | 10 | ## Development Setup 11 | ```bash 12 | git clone https://github.com/AzureSecResearcher/azure-data-guardian.git 13 | cd azure-data-guardian 14 | ./gradlew build 15 | -------------------------------------------------------------------------------- /src/Email.java: -------------------------------------------------------------------------------- 1 | public class Email { 2 | String id; 3 | String subject; 4 | String toRecipients; 5 | String from; 6 | String receivedDateTime; 7 | String bodyPreview; 8 | 9 | public Email(String id, String subject, String from,String toRecipients, String receivedDateTime, String bodyPreview) { 10 | this.id = id; 11 | this.subject = subject; 12 | this.from = from; 13 | this.toRecipients = toRecipients; 14 | this.receivedDateTime = receivedDateTime; 15 | this.bodyPreview = bodyPreview; 16 | } 17 | } -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: "[FEATURE]" 5 | labels: enhancement 6 | assignees: '' 7 | 8 | --- 9 | 10 | ## Is your feature request related to a problem? 11 | A clear and concise description of the problem. 12 | 13 | ## Describe the Solution 14 | A clear and concise description of what you want to happen. 15 | 16 | ## Alternatives Considered 17 | A clear and concise description of any alternative solutions. 18 | 19 | ## Additional Context 20 | Add any other context or screenshots about the feature request. 21 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Report unexpected behavior or errors 4 | title: "[BUG]" 5 | labels: bug, enhancement 6 | assignees: '' 7 | 8 | --- 9 | 10 | ## Describe the Bug 11 | A clear and concise description of the issue. 12 | 13 | ## Steps to Reproduce 14 | 1. Go to '...' 15 | 2. Click on '....' 16 | 3. Scroll down to '....' 17 | 4. See error 18 | 19 | ## Expected Behavior 20 | What you expected to happen. 21 | 22 | ## Environment 23 | - Java Version: [e.g. 17.0.8] 24 | - Application Version: [e.g. v0.1] 25 | 26 | ## Additional Context 27 | Add any other context about the problem here. 28 | -------------------------------------------------------------------------------- /ROADMAP.md: -------------------------------------------------------------------------------- 1 | ### Phase 1: EML Search (Current) 2 | - **Core AuthenticationFreamework** 3 | - **Mailbox Export Functionality** 4 | - **Basic Desktio UI** 5 | 6 | 7 | ### Phase 2: Search & Expand (Q3 2025) 8 | - **OneDrive Integration** 9 | - **SharePoint Access** 10 | - **File Type Filtering** 11 | - **Basic Search** 12 | 13 | ### Phase 3: Advanced Operations (Q4 2025) 14 | - **Multi-Format Exports** 15 | - **ZIP Compression** 16 | - **Export Statistics** 17 | 18 | ### Phase 4: Enterprise Readiness (Q1 2026) 19 | - **Office Previews** 20 | - **Transfer Reliability** 21 | - **Progress Tracking** 22 | - **Bulk Operations** 23 | 24 | ### Phase 5: Optimization & Scale (Q1 2026) 25 | - **Distributed Processing** 26 | - **Azure Blob Integration** 27 | - **Scheduled Backups** 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 AzureSecResearcher 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/GraphApiEmailDownloader.java: -------------------------------------------------------------------------------- 1 | import java.io.*; 2 | import java.net.*; 3 | import javax.net.ssl.HttpsURLConnection; 4 | import java.nio.charset.StandardCharsets; 5 | 6 | 7 | 8 | public class GraphApiEmailDownloader { 9 | private static final String GRAPH_ENDPOINT = "https://graph.microsoft.com/v1.0"; 10 | 11 | 12 | public static void downloadEmailAsEML(Email email, String accessToken,String savePath,String searchUser) throws IOException { 13 | String urlStr = GRAPH_ENDPOINT+ "/users/" + searchUser + "/messages/" + email.id + "/$value"; 14 | URL url = new URL(urlStr); 15 | HttpsURLConnection conn = (HttpsURLConnection) url.openConnection(); 16 | conn.setRequestMethod("GET"); 17 | conn.setRequestProperty("Authorization", "Bearer " + accessToken); 18 | conn.setRequestProperty("Accept", "message/rfc822"); 19 | 20 | int responseCode = conn.getResponseCode(); 21 | if (responseCode != 200) { 22 | String errorMsg = new String(conn.getErrorStream().readAllBytes(), StandardCharsets.UTF_8); 23 | System.err.println("Failed to download email: " + errorMsg); 24 | return; 25 | } 26 | 27 | byte[] emlBytes = conn.getInputStream().readAllBytes(); 28 | 29 | String safeSubject = sanitizeFilename(email.subject); 30 | String fromMail = sanitizeFilename(email.from); 31 | String receivedTime = email.receivedDateTime.replace(":", "-").replace("T", "_").replace("Z", ""); 32 | String filename = safeSubject + "_" + fromMail + "_" + receivedTime + ".eml"; 33 | 34 | if(savePath !=null) { 35 | File dir = new File(savePath); 36 | if (dir.canWrite()) { 37 | filename = savePath + "\\" + filename; 38 | } 39 | } 40 | 41 | try (FileOutputStream fos = new FileOutputStream(filename)) { 42 | fos.write(emlBytes); 43 | System.out.println("Saved email as " + filename); 44 | } 45 | } 46 | 47 | private static String sanitizeFilename(String input) { 48 | return input.replaceAll("[\\\\/:*?\"<>|]", "_"); 49 | } 50 | 51 | } 52 | 53 | -------------------------------------------------------------------------------- /src/GraphApiTokenGenerator.java: -------------------------------------------------------------------------------- 1 | import java.io.*; 2 | import java.net.*; 3 | import javax.net.ssl.HttpsURLConnection; 4 | import java.net.URLEncoder; 5 | 6 | public class GraphApiTokenGenerator { 7 | 8 | 9 | public static String getAccessToken(String TENANT_ID,String CLIENT_ID,String CLIENT_SECRET ) throws IOException { 10 | 11 | String tokenEndpoint = "https://login.microsoftonline.com/" + TENANT_ID + "/oauth2/v2.0/token"; 12 | 13 | String params = "client_id=" + URLEncoder.encode(CLIENT_ID, "UTF-8") 14 | + "&scope=" + URLEncoder.encode("https://graph.microsoft.com/.default", "UTF-8") 15 | + "&client_secret=" + URLEncoder.encode(CLIENT_SECRET, "UTF-8") 16 | + "&grant_type=client_credentials"; 17 | 18 | URL url = new URL(tokenEndpoint); 19 | HttpsURLConnection conn = (HttpsURLConnection) url.openConnection(); 20 | conn.setRequestMethod("POST"); 21 | conn.setDoOutput(true); 22 | conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded"); 23 | 24 | try (OutputStream os = conn.getOutputStream()) { 25 | os.write(params.getBytes("UTF-8")); 26 | } 27 | 28 | int responseCode = conn.getResponseCode(); 29 | InputStream is = (responseCode == 200) ? conn.getInputStream() : conn.getErrorStream(); 30 | 31 | StringBuilder response = new StringBuilder(); 32 | try (BufferedReader br = new BufferedReader(new InputStreamReader(is))) { 33 | String line; 34 | while ((line = br.readLine()) != null) { 35 | response.append(line); 36 | } 37 | } 38 | 39 | String jsonResponse = response.toString(); 40 | String token = null; 41 | 42 | String tokenKey = "\"access_token\":\""; 43 | int startIndex = jsonResponse.indexOf(tokenKey); 44 | if (startIndex != -1) { 45 | startIndex += tokenKey.length(); 46 | int endIndex = jsonResponse.indexOf("\"", startIndex); 47 | if (endIndex != -1) { 48 | token = jsonResponse.substring(startIndex, endIndex); 49 | } 50 | } 51 | return token; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## LEGAL WARNING 2 | This project is for **authorized penetration testing and educational purpose only**. 3 | 4 | Unlawful use is prohibited. The developer assumes no liability. 5 | 6 | # Azure Data Guardian 7 | Classify and protect sensitive data across your Azure Blob Storage and databases. Continuously manage your Azure data. 8 | 9 | **Enterprise Azure Data Management Suite (Phase 1)** 10 | 11 | [![Java 21](https://img.shields.io/badge/Java-21+-orange?logo=openjdk)](https://jdk.java.net/java-se-ri/21) 12 | [![Graph API](https://img.shields.io/badge/Microsoft_Graph_API-v1.0-blue?logo=microsoftazure)](https://learn.microsoft.com/graph) 13 | [![Release Phase](https://img.shields.io/badge/Release-Phase_1-yellow)](https://github.com/ws-research/azure-data-guardian/releases) 14 | [![License: MIT](https://img.shields.io/badge/License-MIT-green)](LICENSE) 15 | [![Roadmap](https://img.shields.io/badge/Roadmap-Public-brightgreen)](ROADMAP.md) 16 | 17 | > **Phase 1**: Advanced secure email management solution for Azure administrators. A comprehensive data management suite evolving through planned releases. 18 | 19 | --- 20 | 21 | ## 🚀 Phase 1: Core Foundation (Available Now) 22 | 23 | ### 🔐 Secure Authentication 24 | - Desktop UI for Azure credential input (Tenant ID, Client ID, Client Secret) 25 | - Microsoft Graph API integration with OAuth 2.0 26 | - Permission validation and error handling 27 | 28 | ### ✉️ Email Data Management 29 | - Full mailbox processing via Microsoft Graph API 30 | - User selection interface for targeted exports 31 | - Raw EML format export with metadata preservation 32 | 33 | ### 🖥️ Standalone Desktop Application 34 | - Java JAR executable (Java 21+ required) 35 | - Intuitive desktop UI (SwingX) 36 | 37 | 38 | 39 | 40 | ## 🔮 Future Release Roadmap 41 | 42 | ### Phase 2: Search & Expand (Q3 2025) 43 | - **OneDrive Integration**: Business file management and export 44 | - **SharePoint Access**: Document library retrieval 45 | - **File Type Filtering**: Selective exports (e.g., ".docx only") 46 | 47 | ### Phase 3: Advanced Operations (Q4 2025) 48 | - **Multi-Format Exports**: CSV, JSON, XML metadata reports 49 | - **ZIP Compression**: Archive creation options 50 | - **Export Statistics**: Performance metrics dashboard 51 | 52 | ### Phase 4: Enterprise Readiness (Q1 2026) 53 | - **Office Previews**: Native Word/Excel/PPT viewing 54 | - **Transfer Reliability**: Pause/resume functionality 55 | - **Progress Tracking**: Real-time export monitoring 56 | - **Bulk Operations**: Multi-user/data source processing 57 | 58 | ### Phase 5: Optimization & Scale (Q1 2026) 59 | - **Distributed Processing**: High-volume handling 60 | - **Azure Blob Integration**: Cloud storage options 61 | - **Scheduled Backups**: Automated export workflows 62 | 63 | ```mermaid 64 | gantt 65 | title Development Timeline 66 | dateFormat YYYY-MM-DD 67 | section Phase 1 (Now) 68 | Authentication :done, auth, 2025-07-01, 10d 69 | Email Export :done, email, after auth, 20d 70 | 71 | section Phase 2 (Q3 2025) 72 | OneDrive :active, 2025-08-01, 30d 73 | SharePoint : 2025-09-01, 20d 74 | File Type Filtering : 2025-09-20, 10d 75 | 76 | section Phase 3 (Q4 2025) 77 | Export Formats : 2025-10-01, 15d 78 | Export Statistics : 2025-10-15, 30d 79 | 80 | section Phase 4 (Q4 2025) 81 | Office Previews : 2025-11-15, 30d 82 | Transfer Management : 2025-12-15, 15d 83 | 84 | section Phase 5 (Q1 2026) 85 | Scalability Engine : 2026-01-01, 20d 86 | Admin Suite : 2026-01-20, 10d 87 | ``` 88 | 89 | --- 90 | 91 | ## ⚡ Getting Started with Phase 1 92 | 93 | ### Prerequisites 94 | - Java 21+ Runtime ([Download](https://jdk.java.net/java-se-ri/21)) 95 | - Azure AD App Registration with: 96 | - `Mail.Read` permission 97 | - Admin consent granted 98 | - Valid admin credentials (Tenant ID, Client ID, Client Secret) 99 | 100 | ### Installation & Double-Click 101 | Download latest JAR from [Releases](https://github.com/ws-research/azure-data-guardian/releases) 102 | ```bash 103 | wget https://github.com/ws-research/azure-data-guardian/releases/download/v1.0.0/Azure-Data-Guardian.jar 104 | ``` 105 | Download latest EXE from [Releases](https://github.com/ws-research/azure-data-guardian/releases) 106 | ```bash 107 | wget https://github.com/ws-research/azure-data-guardian/releases/download/v1.0.0/Azure-Data-Guardian.exe 108 | ``` 109 | Download latest EXE & Java 21 from [Releases](https://github.com/ws-research/azure-data-guardian/releases) 110 | ```bash 111 | wget https://github.com/ws-research/azure-data-guardian/releases/download/v1.0.0/Azure-Data-Guardian.zip 112 | ``` 113 | **Crucial Step:** For the executable to run correctly, you **must place it in the same directory as your `JDK` folder**. 114 | 115 | ### First-Time Workflow 116 | 1. Enter Azure credentials in authentication screen 117 | 2. Navigate to "Email Management" tab 118 | 3. Select target user mailbox 119 | 4. Initiate export (saves as EML files to local directory) 120 | 121 | --- 122 | 123 | ## 💼 Phase 1 Use Case: Email Archive Migration 124 | 125 | **Scenario**: Export emails from departed employee `j.smith@company.com` for legal retention 126 | 127 | **Workflow**: 128 | 1. Launch application and authenticate 129 | 2. Select "Email Export" module 130 | 3. Enter target email address 131 | 4. Export full mailbox as EML files 132 | 5. Preserve original folder structure 133 | 134 | **Key Benefits**: 135 | - No PowerShell expertise required 136 | - Native format preservation for compliance 137 | - Desktop-based secure processing 138 | 139 | --- 140 | 141 | ## 🛠️ Technical Specifications (Phase 1) 142 | 143 | ### Architecture 144 | ```mermaid 145 | graph LR 146 | A[SwingX] --> B[Auth Module] 147 | B --> C[Graph API Client] 148 | C --> D[File System] 149 | D --> E[Export Validation] 150 | ``` 151 | 152 | ### Supported Environments 153 | - Windows 10/11 (64-bit) 154 | - macOS 12+ (Intel/Apple Silicon) 155 | - Linux (Ubuntu 20.04+, Fedora 36+) 156 | 157 | --- 158 | 159 | ## 📬 What's Next in Phase 2? 160 | - OneDrive business data management 161 | - SharePoint document access 162 | - File type filtering capabilities 163 | 164 | **Contribute to our roadmap**: 165 | [![Feature Voting](https://img.shields.io/badge/Vote-Next_Features-blue)](https://github.com/ws-research/azure-data-guardian/discussions/1) 166 | 167 | --- 168 | 169 | ## 🧪 Testing & Feedback 170 | We invite administrators to: 171 | 1. Validate authentication workflows 172 | 2. Test email export integrity 173 | 3. Suggest UI improvements 174 | 175 | [![Report Issues](https://img.shields.io/badge/REPORT_ISSUES-Here-red)](https://github.com/ws-research/azure-data-guardian/issues) 176 | [![Join Discussion](https://img.shields.io/badge/COMMUNITY-Discussions-green)](https://github.com/ws-research/azure-data-guardian/discussions) 177 | 178 | --- 179 | 180 | ## 🤝 Contribution Guidelines 181 | 1. Fork the repository 182 | 2. Create feature branch (`git checkout -b feature/your-feature`) 183 | 3. Commit changes (`git commit -m 'Add amazing feature'`) 184 | 4. Push to branch (`git push origin feature/your-feature`) 185 | 5. Open pull request 186 | 187 | **Priority Contribution Areas**: 188 | - Graph API error handling 189 | - Export performance optimization 190 | - UI test automation 191 | - Localization framework 192 | 193 | --- 194 | 195 | ## 📜 License 196 | MIT License 197 | 198 | **Security Reports**: [kien61365@gmail.com](mailto:kien61365@gmail.com) 199 | 200 | --- 201 | 202 | **Join Our Evolution**: 203 | ⭐ **Star this repo to stay updated on new releases** ⭐ 204 | --- 205 | 206 | > "The journey of a thousand backups begins with a single export." - Phase 1 Motto 207 | -------------------------------------------------------------------------------- /src/GraphEmailSearcher.java: -------------------------------------------------------------------------------- 1 | import java.io.*; 2 | import java.net.*; 3 | import java.nio.charset.StandardCharsets; 4 | import java.util.*; 5 | 6 | import javax.net.ssl.HttpsURLConnection; 7 | import javax.swing.JOptionPane; 8 | 9 | public class GraphEmailSearcher { 10 | 11 | 12 | private static final String GRAPH_ENDPOINT = "https://graph.microsoft.com/v1.0"; 13 | private static int prevPageSize = 0 ; 14 | private static String nextLink; 15 | private static String prevSearchUser; 16 | private static String prevFilterQuery; 17 | 18 | 19 | private static String parseJsonForKey(String json, String key) { 20 | String searchKey = "\"" + key + "\":\""; 21 | int start = json.indexOf(searchKey); 22 | if (start == -1) return null; 23 | start += searchKey.length(); 24 | int end = json.indexOf("\"", start); 25 | if (end == -1) return null; 26 | return json.substring(start, end); 27 | } 28 | 29 | private static String extractAllEmailAddress(String json,String fieldName) { 30 | String address = ""; 31 | try { 32 | int currentIndex = 0; 33 | while (true) { 34 | int fieldIndex = json.indexOf("\"" + fieldName + "\"",currentIndex); 35 | if (fieldIndex == -1 ) break; 36 | int addressIndex = json.indexOf("\"address\"",fieldIndex); 37 | if (addressIndex == -1 ) break; 38 | int startQuote = json.indexOf("\"",addressIndex + 9); 39 | if (startQuote == -1 ) break; 40 | int endQuote = json.indexOf("\"",startQuote + 1); 41 | if (endQuote == -1 ) break; 42 | address = address + json.substring(startQuote + 1,endQuote) + ";"; 43 | currentIndex = endQuote + 1; 44 | } 45 | }catch (Exception e) { 46 | e.printStackTrace(); 47 | } 48 | return address; 49 | } 50 | 51 | 52 | public static String buildFilterQuery( 53 | String subject, 54 | String body, 55 | String from, 56 | String toRecipients, 57 | String receivedStart, 58 | String receivedEnd 59 | ) { 60 | List filters = new ArrayList<>(); 61 | 62 | if (subject != null && !subject.isEmpty()) { 63 | filters.add("contains(subject, '" + escapeFilterValue(subject) + "')"); 64 | } 65 | if (body != null && !body.isEmpty()) { 66 | filters.add("contains(body/content, '" + escapeFilterValue(body) + "')"); 67 | } 68 | if (from != null && !from.isEmpty()) { 69 | filters.add("from/emailAddress/address eq '" + escapeFilterValue(from) + "'"); 70 | } 71 | if (toRecipients != null && !toRecipients.isEmpty()) { 72 | filters.add("toRecipients/any(r: r/emailAddress/address eq '" + escapeFilterValue(toRecipients) + "')"); 73 | } 74 | if (receivedStart != null && !receivedStart.isEmpty() && receivedEnd != null && !receivedEnd.isEmpty()) { 75 | filters.add("receivedDateTime ge " + receivedStart + " and receivedDateTime le " + receivedEnd); 76 | } 77 | 78 | if (filters.isEmpty()) { 79 | return ""; 80 | } else { 81 | return "$filter=" + String.join(" and ", filters); 82 | } 83 | } 84 | 85 | private static String escapeFilterValue(String value) { 86 | return value.replace("'", "''"); 87 | } 88 | 89 | 90 | public static List searchEmails( 91 | String accessToken, 92 | String searchUser, 93 | String filterQuery, 94 | int pageSize, 95 | int pageNumber 96 | ) throws IOException { 97 | if (prevPageSize== pageSize && prevSearchUser.equals(searchUser) && filterQuery.equals(prevFilterQuery) && nextLink != null) { 98 | URL url = new URL(nextLink); 99 | HttpsURLConnection conn = (HttpsURLConnection) url.openConnection(); 100 | conn.setRequestMethod("GET"); 101 | conn.setRequestProperty("Authorization", "Bearer " + accessToken); 102 | conn.setRequestProperty("Accept", "application/json"); 103 | int responseCode = conn.getResponseCode(); 104 | String response = new String(conn.getInputStream().readAllBytes(), StandardCharsets.UTF_8); 105 | nextLink = parseEmailsNextLinkFromResponse(response); 106 | prevPageSize = pageSize; 107 | prevSearchUser = searchUser; 108 | prevFilterQuery = filterQuery; 109 | return parseEmailsFromResponse(response); 110 | } 111 | 112 | int skip = (pageNumber - 1) * pageSize; 113 | String urlStr = GRAPH_ENDPOINT+ "/users/" 114 | + searchUser 115 | +"/messages?" 116 | + "$top=" + pageSize 117 | + "&$skip=" + skip 118 | + "&$select=subject,from,receivedDateTime,toRecipients,bodyPreview"; 119 | //+ "&$orderby=receivedDateTime desc"; 120 | 121 | if (filterQuery != null && !filterQuery.isEmpty()) { 122 | urlStr += "&" + filterQuery; 123 | } 124 | urlStr = urlStr.replace(" ","%20"); 125 | 126 | URL url = new URL(urlStr); 127 | HttpsURLConnection conn = (HttpsURLConnection) url.openConnection(); 128 | conn.setRequestMethod("GET"); 129 | conn.setRequestProperty("Authorization", "Bearer " + accessToken); 130 | conn.setRequestProperty("Accept", "application/json"); 131 | 132 | int responseCode = conn.getResponseCode(); 133 | if (responseCode != 200) { 134 | String errorMsg = new String(conn.getErrorStream().readAllBytes(), StandardCharsets.UTF_8); 135 | throw new RuntimeException("Failed to fetch emails: " + errorMsg); 136 | } 137 | 138 | String response = new String(conn.getInputStream().readAllBytes(), StandardCharsets.UTF_8); 139 | nextLink = parseEmailsNextLinkFromResponse(response); 140 | prevPageSize = pageSize; 141 | prevSearchUser = searchUser; 142 | prevFilterQuery = filterQuery; 143 | return parseEmailsFromResponse(response); 144 | } 145 | 146 | 147 | public static List searchAllEmails( 148 | String accessToken, 149 | String searchUser, 150 | String filterQuery 151 | ) throws IOException { 152 | String urlStr = GRAPH_ENDPOINT + "/users/" 153 | + searchUser 154 | +"/messages?" 155 | + "&$select=subject,from,receivedDateTime,toRecipients,bodyPreview"; 156 | //+ "&$orderby=receivedDateTime desc"; 157 | 158 | if (filterQuery != null && !filterQuery.isEmpty()) { 159 | urlStr += "&" + filterQuery; 160 | } 161 | 162 | urlStr = urlStr.replace(" ","%20"); 163 | 164 | 165 | URL url = new URL(urlStr); 166 | HttpsURLConnection conn = (HttpsURLConnection) url.openConnection(); 167 | conn.setRequestMethod("GET"); 168 | conn.setRequestProperty("Authorization", "Bearer " + accessToken); 169 | conn.setRequestProperty("Accept", "application/json"); 170 | 171 | int responseCode = conn.getResponseCode(); 172 | if (responseCode != 200) { 173 | String errorMsg = new String(conn.getErrorStream().readAllBytes(), StandardCharsets.UTF_8); 174 | throw new RuntimeException("Failed to fetch emails: " + errorMsg); 175 | } 176 | List searchAllEmail = new ArrayList<>(); 177 | List searchEmail = new ArrayList<>(); 178 | 179 | String response = new String(conn.getInputStream().readAllBytes(), StandardCharsets.UTF_8); 180 | String nextLink = parseEmailsNextLinkFromResponse(response); 181 | if (nextLink != null) { 182 | searchEmail = parseEmailsFromResponse(response); 183 | searchAllEmail.addAll(searchEmail); 184 | while(true) { 185 | url = new URL(nextLink); 186 | conn = (HttpsURLConnection) url.openConnection(); 187 | conn.setRequestMethod("GET"); 188 | conn.setRequestProperty("Authorization", "Bearer " + accessToken); 189 | conn.setRequestProperty("Accept", "application/json"); 190 | responseCode = conn.getResponseCode(); 191 | response = new String(conn.getInputStream().readAllBytes(), StandardCharsets.UTF_8); 192 | nextLink = parseEmailsNextLinkFromResponse(response); 193 | searchEmail = parseEmailsFromResponse(response); 194 | searchAllEmail.addAll(searchEmail); 195 | if(nextLink == null) { 196 | break; 197 | } 198 | } 199 | 200 | }else { 201 | return parseEmailsFromResponse(response); 202 | } 203 | return searchAllEmail; 204 | } 205 | 206 | 207 | public static List parseEmailsFromResponse(String json) { 208 | List emails = new ArrayList<>(); 209 | String[] items = json.split("\\{\"@odata.etag\""); 210 | for (String item : items) { 211 | if (item.contains("subject") && item.contains("id")) { 212 | String subject = parseJsonForKey(item, "subject"); 213 | String fromAddress = extractAllEmailAddress(item, "from"); 214 | String toRecipientsAddress = extractAllEmailAddress(item, "toRecipients"); 215 | String receivedDateTime = parseJsonForKey(item, "receivedDateTime"); 216 | String bodyPreview = parseJsonForKey(item, "bodyPreview"); 217 | String messageId = parseJsonForKey(item, "id"); 218 | emails.add(new Email(messageId, subject, fromAddress, toRecipientsAddress, receivedDateTime, bodyPreview)); 219 | } 220 | } 221 | return emails; 222 | } 223 | 224 | 225 | private static String parseEmailsNextLinkFromResponse(String json) { 226 | String[] items = json.split("\"@odata.nextLink\":"); 227 | try { 228 | String nextLink = items[1].toString().substring(1,items[1].toString().length()-2); 229 | } 230 | catch(ArrayIndexOutOfBoundsException e){ 231 | nextLink = null; 232 | } 233 | return nextLink; 234 | } 235 | 236 | 237 | } 238 | -------------------------------------------------------------------------------- /src/Application.java: -------------------------------------------------------------------------------- 1 | import javax.swing.*; 2 | import javax.swing.table.*; 3 | import java.awt.*; 4 | import java.awt.event.*; 5 | import java.io.File; 6 | import java.io.IOException; 7 | import java.text.SimpleDateFormat; 8 | import java.util.*; 9 | import java.util.List; 10 | 11 | public class Application extends JFrame { 12 | 13 | private List searchEmaillist = new ArrayList<>(); 14 | private CardLayout cardLayout; 15 | private JPanel cardPanel; 16 | private JTextArea resultArea; 17 | private JTable dataTable; 18 | private DefaultTableModel tableModel; 19 | private JComboBox pageSizeComboBox; 20 | private JLabel pageInfoLabel; 21 | private JButton prevButton, nextButton; 22 | private int currentPage = 1; 23 | private int pageSize = 20; 24 | private int totalPages = 1; 25 | private String token; 26 | private List allData = new ArrayList<>(); 27 | private List searchFields = new ArrayList<>(); 28 | private List searchSpinner = new ArrayList<>(); 29 | 30 | public Application() { 31 | setTitle("Azure-Data-Guardian"); 32 | setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 33 | setSize(1100, 850); 34 | setLocationRelativeTo(null); 35 | 36 | cardLayout = new CardLayout(); 37 | cardPanel = new JPanel(cardLayout); 38 | 39 | cardPanel.add(createFirstPanel(), "panel1"); 40 | cardPanel.add(createSecondPanel(), "panel2"); 41 | 42 | add(cardPanel); 43 | } 44 | 45 | private JPanel createFirstPanel() { 46 | JPanel panel = new JPanel(new BorderLayout(10, 10)); 47 | panel.setBorder(BorderFactory.createEmptyBorder(15, 15, 15, 15)); 48 | 49 | JPanel topPanel = new JPanel(new GridLayout(4, 1, 10, 10)); 50 | JTextField fieldA = new JTextField(); 51 | JTextField fieldB = new JTextField(); 52 | JTextField fieldC = new JTextField(); 53 | 54 | topPanel.add(createLabeledField("TENANT_ID:", fieldA, 5)); 55 | topPanel.add(createLabeledField("CLIENT_ID:", fieldB, 5)); 56 | topPanel.add(createLabeledField("CLIENT_SECRET:", fieldC, 5)); 57 | 58 | JButton processButton = new JButton("Generate Token"); 59 | processButton.addActionListener(e -> { 60 | String result = null; 61 | try { 62 | result = GraphApiTokenGenerator.getAccessToken(fieldA.getText(),fieldB.getText(),fieldC.getText()); 63 | } catch (IOException e1) { 64 | result = e1.toString(); 65 | } 66 | resultArea.setText(result); 67 | }); 68 | topPanel.add(processButton); 69 | 70 | JPanel bottomPanel = new JPanel(new BorderLayout(10, 10)); 71 | resultArea = new JTextArea(5, 30); 72 | resultArea.setLineWrap(true); 73 | resultArea.setWrapStyleWord(true); 74 | bottomPanel.add(new JScrollPane(resultArea), BorderLayout.CENTER); 75 | 76 | JButton nextButton = new JButton("Email Management"); 77 | nextButton.addActionListener(e -> { 78 | token = resultArea.getText(); 79 | cardLayout.show(cardPanel, "panel2"); 80 | }); 81 | bottomPanel.add(nextButton, BorderLayout.SOUTH); 82 | 83 | panel.add(topPanel, BorderLayout.NORTH); 84 | panel.add(bottomPanel, BorderLayout.CENTER); 85 | 86 | return panel; 87 | } 88 | 89 | private JPanel createSecondPanel() { 90 | JPanel panel = new JPanel(new BorderLayout(10, 10)); 91 | panel.setBorder(BorderFactory.createEmptyBorder(15, 15, 15, 15)); 92 | 93 | JPanel topPanel = createTopSection(); 94 | JPanel bottomPanel = createBottomSection(); 95 | 96 | panel.add(topPanel, BorderLayout.NORTH); 97 | panel.add(bottomPanel, BorderLayout.CENTER); 98 | 99 | return panel; 100 | } 101 | 102 | private JPanel createTopSection() { 103 | JPanel topPanel = new JPanel(new BorderLayout(5, 5)); 104 | topPanel.setPreferredSize(new Dimension(0, 140)); 105 | topPanel.setBackground(new Color(245, 245, 245)); 106 | 107 | JPanel inputPanel = new JPanel(new GridLayout(3, 1, 0, 5)); 108 | inputPanel.setBorder(BorderFactory.createEmptyBorder(8, 10, 8, 10)); 109 | inputPanel.setBackground(new Color(245, 245, 245)); 110 | 111 | JPanel emailPanel = createInputPanel("Email Address:"); 112 | JPanel startDateTimePanel = createInputPanel("Start Date/Time:"); 113 | JPanel endDateTimePanel = createInputPanel("End Date/Time:"); 114 | JPanel subjectPanel = createInputPanel("Subject:"); 115 | JPanel FromAddressPanel = createInputPanel("from address:"); 116 | 117 | inputPanel.add(emailPanel); 118 | inputPanel.add(startDateTimePanel); 119 | inputPanel.add(endDateTimePanel); 120 | inputPanel.add(subjectPanel); 121 | inputPanel.add(FromAddressPanel); 122 | 123 | JButton searchButton = new JButton("Search"); 124 | searchButton.setBackground(new Color(70, 130, 180)); 125 | searchButton.setForeground(Color.WHITE); 126 | searchButton.setFont(new Font("Arial", Font.BOLD, 12)); 127 | searchButton.setPreferredSize(new Dimension(120, 32)); 128 | searchButton.addActionListener(this::performSearch); 129 | 130 | topPanel.add(inputPanel, BorderLayout.CENTER); 131 | topPanel.add(searchButton, BorderLayout.EAST); 132 | 133 | return topPanel; 134 | } 135 | 136 | private JPanel createInputPanel(String label) { 137 | JPanel panel = new JPanel(new BorderLayout(5, 0)); 138 | panel.setBackground(new Color(245, 245, 245)); 139 | JLabel lbl = new JLabel(label); 140 | lbl.setFont(new Font("Arial", Font.BOLD, 12)); 141 | panel.add(lbl, BorderLayout.WEST); 142 | 143 | if (label.contains("Date/Time")) { 144 | if (label.contains("Start Date/Time")){ 145 | JSpinner spinner = createDateTimeSpinner(); 146 | panel.add(spinner, BorderLayout.CENTER); 147 | searchSpinner.add(spinner); 148 | } 149 | if (label.contains("End Date/Time")){ 150 | JSpinner spinner = createEndDateTimeSpinner(); 151 | panel.add(spinner, BorderLayout.CENTER); 152 | searchSpinner.add(spinner); 153 | } 154 | } else { 155 | JTextField textField = new JTextField(); 156 | panel.add(textField, BorderLayout.CENTER); 157 | searchFields.add(textField); 158 | } 159 | 160 | return panel; 161 | } 162 | 163 | 164 | private JPanel createBottomSection() { 165 | JPanel bottomPanel = new JPanel(new BorderLayout(10, 10)); 166 | bottomPanel.setBackground(Color.WHITE); 167 | 168 | String[] columnNames = {"Select","id", "subject", "bodyPreview", "from address", "toRecipients address", "receivedDateTime"}; 169 | tableModel = new DefaultTableModel(columnNames, 0) { 170 | @Override 171 | public Class getColumnClass(int columnIndex) { 172 | return columnIndex == 0 ? Boolean.class : String.class; 173 | } 174 | @Override 175 | public boolean isCellEditable(int row, int column) { 176 | return column == 0; 177 | } 178 | }; 179 | 180 | dataTable = new JTable(tableModel); 181 | dataTable.setRowHeight(25); 182 | dataTable.setFont(new Font("Arial", Font.PLAIN, 12)); 183 | dataTable.getTableHeader().setFont(new Font("Arial", Font.BOLD, 12)); 184 | dataTable.setSelectionBackground(new Color(173, 216, 230)); 185 | dataTable.setFillsViewportHeight(true); 186 | 187 | JTableHeader header = dataTable.getTableHeader(); 188 | header.setBackground(new Color(70, 130, 180)); 189 | header.setForeground(Color.WHITE); 190 | header.setLayout(new BorderLayout()); 191 | 192 | JPanel headerPanel = new JPanel(new GridLayout(1, columnNames.length)); 193 | headerPanel.setBackground(new Color(70, 130, 180)); 194 | for (String columnName : columnNames) { 195 | JLabel label = new JLabel(columnName, SwingConstants.CENTER); 196 | label.setFont(new Font("Arial", Font.BOLD, 12)); 197 | label.setForeground(Color.WHITE); 198 | label.setBorder(BorderFactory.createLineBorder(Color.LIGHT_GRAY)); 199 | headerPanel.add(label); 200 | } 201 | header.add(headerPanel, BorderLayout.CENTER); 202 | 203 | JScrollPane scrollPane = new JScrollPane(dataTable); 204 | scrollPane.setBorder(BorderFactory.createLineBorder(Color.GRAY)); 205 | bottomPanel.add(scrollPane, BorderLayout.CENTER); 206 | 207 | JPanel controlPanel = new JPanel(new BorderLayout(10, 10)); 208 | controlPanel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); 209 | controlPanel.setBackground(Color.WHITE); 210 | 211 | JPanel paginationPanel = new JPanel(new BorderLayout(10, 10)); 212 | paginationPanel.setBackground(Color.WHITE); 213 | 214 | JPanel pageSizePanel = new JPanel(new FlowLayout(FlowLayout.LEFT, 5, 0)); 215 | pageSizePanel.setBackground(Color.WHITE); 216 | pageSizePanel.add(new JLabel("Items per page:")); 217 | pageSizeComboBox = new JComboBox<>(new Integer[]{20, 50, 100}); 218 | pageSizeComboBox.setSelectedItem(pageSize); 219 | pageSizeComboBox.addActionListener(e -> { 220 | pageSize = (Integer) pageSizeComboBox.getSelectedItem(); 221 | currentPage = 1; 222 | totalPages = 1; 223 | updateTableForCurrentPage(); 224 | }); 225 | pageSizePanel.add(pageSizeComboBox); 226 | 227 | JPanel pageNavPanel = new JPanel(new FlowLayout(FlowLayout.CENTER, 10, 0)); 228 | pageNavPanel.setBackground(Color.WHITE); 229 | prevButton = new JButton("Previous"); 230 | prevButton.setBackground(new Color(70, 130, 180)); 231 | prevButton.setForeground(Color.WHITE); 232 | prevButton.addActionListener(e -> { 233 | if (currentPage > 1) { 234 | currentPage--; 235 | updateTableForCurrentPage(); 236 | } 237 | }); 238 | 239 | pageInfoLabel = new JLabel("Page 1"); 240 | pageInfoLabel.setFont(new Font("Arial", Font.BOLD, 12)); 241 | 242 | nextButton = new JButton("Next"); 243 | nextButton.setBackground(new Color(70, 130, 180)); 244 | nextButton.setForeground(Color.WHITE); 245 | nextButton.addActionListener(e -> { 246 | currentPage++; 247 | updateTableForCurrentPage(); 248 | }); 249 | 250 | pageNavPanel.add(prevButton); 251 | pageNavPanel.add(pageInfoLabel); 252 | pageNavPanel.add(nextButton); 253 | 254 | paginationPanel.add(pageSizePanel, BorderLayout.WEST); 255 | paginationPanel.add(pageNavPanel, BorderLayout.CENTER); 256 | 257 | JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT, 15, 10)); 258 | buttonPanel.setBackground(Color.WHITE); 259 | 260 | JButton downloadSelectedButton = createActionButton("Download Selected", Color.GREEN.darker()); 261 | JButton downloadAllButton = createActionButton("Download All", Color.ORANGE.darker()); 262 | JButton backButton = createActionButton("Back to Generate Token", Color.GRAY); 263 | 264 | backButton.addActionListener(e -> cardLayout.show(cardPanel, "panel1")); 265 | downloadSelectedButton.addActionListener(this::downloadSelected); 266 | downloadAllButton.addActionListener(this::downloadAll); 267 | 268 | buttonPanel.add(downloadSelectedButton); 269 | buttonPanel.add(downloadAllButton); 270 | buttonPanel.add(backButton); 271 | 272 | controlPanel.add(paginationPanel, BorderLayout.CENTER); 273 | controlPanel.add(buttonPanel, BorderLayout.SOUTH); 274 | 275 | bottomPanel.add(controlPanel, BorderLayout.SOUTH); 276 | 277 | return bottomPanel; 278 | } 279 | 280 | private JButton createActionButton(String text, Color bgColor) { 281 | JButton button = new JButton(text); 282 | button.setBackground(bgColor); 283 | button.setForeground(Color.WHITE); 284 | button.setFont(new Font("Arial", Font.BOLD, 12)); 285 | button.setFocusPainted(false); 286 | return button; 287 | } 288 | 289 | private JSpinner createDateTimeSpinner() { 290 | Calendar calendar = Calendar.getInstance(); 291 | calendar.set(Calendar.HOUR_OF_DAY, 0); 292 | calendar.set(Calendar.MINUTE, 0); 293 | calendar.set(Calendar.SECOND, 0); 294 | calendar.set(Calendar.MILLISECOND, 0); 295 | 296 | JSpinner spinner = new JSpinner(new SpinnerDateModel( 297 | calendar.getTime(), 298 | null, 299 | null, 300 | Calendar.HOUR_OF_DAY 301 | )); 302 | 303 | JSpinner.DateEditor editor = new JSpinner.DateEditor(spinner, "MM/dd/yyyy HH:mm:ss"); 304 | spinner.setEditor(editor); 305 | 306 | return spinner; 307 | } 308 | 309 | private JSpinner createEndDateTimeSpinner() { 310 | Calendar calendar = Calendar.getInstance(); 311 | calendar.add(Calendar.DATE, 1); 312 | calendar.set(Calendar.HOUR_OF_DAY, 0); 313 | calendar.set(Calendar.MINUTE, 0); 314 | calendar.set(Calendar.SECOND, 0); 315 | calendar.set(Calendar.MILLISECOND, 0); 316 | 317 | JSpinner spinner = new JSpinner(new SpinnerDateModel( 318 | calendar.getTime(), 319 | null, 320 | null, 321 | Calendar.HOUR_OF_DAY 322 | )); 323 | 324 | JSpinner.DateEditor editor = new JSpinner.DateEditor(spinner, "MM/dd/yyyy HH:mm:ss"); 325 | spinner.setEditor(editor); 326 | 327 | return spinner; 328 | } 329 | 330 | private void performSearch(ActionEvent e) { 331 | allData.clear(); 332 | searchEmaillist.clear(); 333 | 334 | String searchFilter = GraphEmailSearcher.buildFilterQuery(searchFields.get(1).getText(),null,searchFields.get(2).getText(),null, searchSpinner.get(0).toString(),searchSpinner.get(1).toString()); 335 | try { 336 | List searchEmaillisttemp = GraphEmailSearcher.searchEmails(token,searchFields.get(0).getText(), searchFilter, pageSize, currentPage); 337 | for (int i = 0; i < searchEmaillisttemp.size(); i++) { 338 | searchEmaillist.add(searchEmaillisttemp.get(i)); 339 | } 340 | 341 | } catch (IOException e1) { 342 | e1.printStackTrace(); 343 | } 344 | 345 | for (int i = 0; i < searchEmaillist.size(); i++) { 346 | allData.add(new Object[]{ 347 | false, 348 | searchEmaillist.get(i).id, 349 | searchEmaillist.get(i).subject, 350 | searchEmaillist.get(i).bodyPreview, 351 | searchEmaillist.get(i).from, 352 | searchEmaillist.get(i).toRecipients, 353 | searchEmaillist.get(i).receivedDateTime 354 | });} 355 | 356 | currentPage = 1; 357 | totalPages = 1; 358 | updateTableForCurrentPage(); 359 | } 360 | 361 | private void updateTableForCurrentPage() { 362 | tableModel.setRowCount(0); 363 | pageInfoLabel.setText("Page " + currentPage); 364 | prevButton.setEnabled(currentPage > 1); 365 | int start = (currentPage - 1) * pageSize; 366 | int end = start + pageSize; 367 | 368 | if (end <= allData.size() || currentPage == 1 || currentPage == totalPages) { 369 | for (int i = start; i < min(end,allData.size()); i++) { 370 | tableModel.addRow(allData.get(i)); 371 | } 372 | return; 373 | } 374 | if (end > allData.size()) { 375 | 376 | String searchFilter = GraphEmailSearcher.buildFilterQuery(searchFields.get(1).getText(),null,searchFields.get(2).getText(),null,searchSpinner.get(0).toString(),searchSpinner.get(1).toString()); 377 | try { 378 | List searchEmaillisttemp = GraphEmailSearcher.searchEmails(token,searchFields.get(0).getText(), searchFilter, pageSize, currentPage); 379 | if (searchEmaillisttemp.size() == 0 ) { 380 | totalPages = currentPage -1; 381 | JOptionPane.showMessageDialog(null, "No more data avaliable","Information",JOptionPane.INFORMATION_MESSAGE); 382 | } 383 | for (int i = 0; i < searchEmaillisttemp.size(); i++) { 384 | searchEmaillist.add(searchEmaillisttemp.get(i)); 385 | }} catch (IOException e1) { 386 | e1.printStackTrace(); 387 | } 388 | 389 | for (int i = start; i < searchEmaillist.size(); i++) { 390 | allData.add(new Object[]{ 391 | false, 392 | searchEmaillist.get(i).id, 393 | searchEmaillist.get(i).subject, 394 | searchEmaillist.get(i).bodyPreview, 395 | searchEmaillist.get(i).from, 396 | searchEmaillist.get(i).toRecipients, 397 | searchEmaillist.get(i).receivedDateTime 398 | });} 399 | for (int i = start; i < allData.size(); i++) { 400 | tableModel.addRow(allData.get(i)); 401 | } 402 | } 403 | } 404 | 405 | private int min(int a, int b) { 406 | if (a<= b) { 407 | return a; 408 | } 409 | return b; 410 | } 411 | 412 | private void downloadSelected(ActionEvent e) { 413 | String savePath = showDirectorySelectionDialog(); 414 | 415 | 416 | StringBuilder sb = new StringBuilder("Downloading selected items:\n"); 417 | int count = 0; 418 | List selectedEmaillist = new ArrayList<>(); 419 | Email selectedTempEmail = new Email("","","","","",""); 420 | 421 | for (int i = 0; i < tableModel.getRowCount(); i++) { 422 | Boolean selected = (Boolean) tableModel.getValueAt(i, 0); 423 | if (selected != null && selected) { 424 | sb.append("Row ").append(i + 1).append(": ") 425 | .append(tableModel.getValueAt(i, 1)).append("\n"); 426 | selectedTempEmail.id = (String) tableModel.getValueAt(i, 1); 427 | selectedTempEmail.subject = (String) tableModel.getValueAt(i, 2); 428 | selectedTempEmail.from = (String) tableModel.getValueAt(i, 4); 429 | selectedTempEmail.receivedDateTime = (String) tableModel.getValueAt(i, 6); 430 | selectedEmaillist.add(selectedTempEmail); 431 | count++; 432 | } 433 | } 434 | 435 | if (count == 0) { 436 | sb.append("No items selected"); 437 | } 438 | try { 439 | for (int i = 0; i < selectedEmaillist.size(); i++) { 440 | GraphApiEmailDownloader.downloadEmailAsEML(selectedEmaillist.get(i), token,savePath,searchFields.get(0).getText()); 441 | } 442 | } catch (IOException e1) { 443 | e1.printStackTrace(); 444 | } 445 | 446 | JOptionPane.showMessageDialog(this, sb.toString(), "Download Selected", JOptionPane.INFORMATION_MESSAGE); 447 | } 448 | 449 | private void downloadAll(ActionEvent e) { 450 | List searchTempEmaillist = null; 451 | String savePath = showDirectorySelectionDialog(); 452 | 453 | 454 | String searchFilter = GraphEmailSearcher.buildFilterQuery(searchFields.get(1).getText(),null,searchFields.get(2).getText(),null,searchSpinner.get(0).toString(),searchSpinner.get(1).toString()); 455 | 456 | try { 457 | searchTempEmaillist = GraphEmailSearcher.searchAllEmails(token,searchFields.get(0).getText(), searchFilter); 458 | for (int i = 0; i < searchTempEmaillist.size(); i++) { 459 | GraphApiEmailDownloader.downloadEmailAsEML(searchTempEmaillist.get(i), token,savePath,searchFields.get(0).getText()); 460 | } 461 | } catch (IOException e1) { 462 | e1.printStackTrace(); 463 | } 464 | 465 | JOptionPane.showMessageDialog(this, 466 | "Downloading all " + searchTempEmaillist.size() + " items", 467 | "Download All", 468 | JOptionPane.INFORMATION_MESSAGE); 469 | } 470 | 471 | private static String showDirectorySelectionDialog() { 472 | JFileChooser chooser = new JFileChooser(); 473 | chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); 474 | chooser.setDialogTitle("choose path to save"); 475 | int result = chooser.showOpenDialog(null); 476 | if (result == JFileChooser.APPROVE_OPTION) { 477 | File selectedDir = chooser.getSelectedFile(); 478 | return selectedDir.getAbsolutePath(); 479 | } 480 | return null; 481 | } 482 | 483 | 484 | private JPanel createLabeledField(String label, JTextField field, int hgap) { 485 | JPanel panel = new JPanel(new BorderLayout(hgap, 0)); 486 | JLabel lbl = new JLabel(label); 487 | lbl.setFont(new Font("Arial", Font.BOLD, 12)); 488 | panel.add(lbl, BorderLayout.WEST); 489 | panel.add(field, BorderLayout.CENTER); 490 | return panel; 491 | } 492 | 493 | public static void main(String[] args) { 494 | SwingUtilities.invokeLater(() -> { 495 | Application app = new Application(); 496 | app.setVisible(true); 497 | }); 498 | } 499 | } 500 | --------------------------------------------------------------------------------