1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package davmail.exchange.auth;
20
21 import davmail.Settings;
22 import davmail.exception.DavMailAuthenticationException;
23 import davmail.exception.DavMailException;
24 import davmail.exchange.ews.BaseShape;
25 import davmail.exchange.ews.DistinguishedFolderId;
26 import davmail.exchange.ews.GetFolderMethod;
27 import davmail.exchange.ews.GetUserConfigurationMethod;
28 import davmail.http.HttpClientAdapter;
29 import org.apache.http.client.methods.CloseableHttpResponse;
30 import org.apache.log4j.Logger;
31
32 import javax.swing.*;
33 import java.io.IOException;
34 import java.lang.reflect.InvocationTargetException;
35 import java.net.Authenticator;
36 import java.net.PasswordAuthentication;
37 import java.net.URI;
38 import java.security.Security;
39
40 public class O365InteractiveAuthenticator implements ExchangeAuthenticator {
41
42 private static final int MAX_COUNT = 300;
43 private static final Logger LOGGER = Logger.getLogger(O365InteractiveAuthenticator.class);
44
45 static {
46
47 System.setProperty("com.sun.webkit.useHTTP2Loader", "false");
48 }
49
50 boolean isAuthenticated = false;
51 String errorCode = null;
52 String code = null;
53
54 URI ewsUrl = URI.create(Settings.getO365Url());
55
56 private O365InteractiveAuthenticatorFrame o365InteractiveAuthenticatorFrame;
57 private O365ManualAuthenticatorDialog o365ManualAuthenticatorDialog;
58
59 private String username;
60 private String password;
61 private O365Token token;
62
63 public O365Token getToken() {
64 return token;
65 }
66
67 @Override
68 public URI getExchangeUri() {
69 return ewsUrl;
70 }
71
72 public String getUsername() {
73 return username;
74 }
75
76 public void setUsername(String username) {
77 this.username = username;
78 }
79
80 public void setPassword(String password) {
81 this.password = password;
82 }
83
84
85
86
87
88
89 @Override
90 public HttpClientAdapter getHttpClientAdapter() {
91 return new HttpClientAdapter(getExchangeUri(), username, password, true);
92 }
93
94 public void authenticate() throws IOException {
95
96
97 System.setProperty("sun.net.http.allowRestrictedHeaders", "true");
98
99 System.setProperty("jdk.http.ntlm.transparentAuth", "allHosts");
100
101
102 final String clientId = Settings.getProperty("davmail.oauth.clientId", "facd6cff-a294-4415-b59f-c5b01937d7bd");
103
104 final String redirectUri = Settings.getProperty("davmail.oauth.redirectUri", Settings.getO365LoginUrl()+"/common/oauth2/nativeclient");
105
106 String tenantId = Settings.getProperty("davmail.oauth.tenantId", "common");
107
108
109 token = O365Token.load(tenantId, clientId, redirectUri, username, password);
110 if (token != null) {
111 isAuthenticated = true;
112 return;
113 }
114
115 final String initUrl = O365Authenticator.buildAuthorizeUrl(tenantId, clientId, redirectUri, username);
116
117
118 Authenticator.setDefault(new Authenticator() {
119 @Override
120 public PasswordAuthentication getPasswordAuthentication() {
121 if (getRequestorType() == RequestorType.PROXY) {
122 String proxyUser = Settings.getProperty("davmail.proxyUser");
123 String proxyPassword = Settings.getProperty("davmail.proxyPassword");
124 if (proxyUser != null && proxyPassword != null) {
125 LOGGER.debug("Proxy authentication with user " + proxyUser);
126 return new PasswordAuthentication(proxyUser, proxyPassword.toCharArray());
127 } else {
128 LOGGER.debug("Missing proxy credentials ");
129 return null;
130 }
131 } else {
132 LOGGER.debug("Password authentication with user " + username);
133 return new PasswordAuthentication(username, password.toCharArray());
134 }
135 }
136 });
137
138 boolean isJFXAvailable = true;
139 try {
140 Class.forName("javafx.application.Platform");
141 } catch (ClassNotFoundException e) {
142 LOGGER.warn("Unable to load JavaFX (OpenJFX), switch to manual mode");
143 isJFXAvailable = false;
144 }
145
146 if (isJFXAvailable) {
147 SwingUtilities.invokeLater(() -> {
148 try {
149 o365InteractiveAuthenticatorFrame = new O365InteractiveAuthenticatorFrame();
150 o365InteractiveAuthenticatorFrame.setO365InteractiveAuthenticator(O365InteractiveAuthenticator.this);
151 o365InteractiveAuthenticatorFrame.authenticate(initUrl, redirectUri);
152 } catch (NoClassDefFoundError e) {
153 LOGGER.warn("Unable to load JavaFX (OpenJFX)");
154 } catch (IllegalAccessError e) {
155 LOGGER.warn("Unable to load JavaFX (OpenJFX), append --add-exports java.base/sun.net.www.protocol.https=ALL-UNNAMED to java options");
156 }
157
158 });
159 } else {
160 if (o365InteractiveAuthenticatorFrame == null) {
161 try {
162 SwingUtilities.invokeAndWait(() -> o365ManualAuthenticatorDialog = new O365ManualAuthenticatorDialog(initUrl));
163 } catch (InterruptedException e) {
164 Thread.currentThread().interrupt();
165 } catch (InvocationTargetException e) {
166 throw new IOException(e);
167 }
168 code = o365ManualAuthenticatorDialog.getCode();
169 isAuthenticated = code != null;
170 if (!isAuthenticated) {
171 errorCode = "User did not provide authentication code";
172 }
173 }
174 }
175
176 int count = 0;
177
178 while (!isAuthenticated && errorCode == null && count++ < MAX_COUNT) {
179 try {
180 Thread.sleep(1000);
181 } catch (InterruptedException e) {
182 Thread.currentThread().interrupt();
183 }
184 }
185
186 if (count > MAX_COUNT) {
187 errorCode = "Timed out waiting for interactive authentication";
188 }
189
190 if (o365InteractiveAuthenticatorFrame != null && o365InteractiveAuthenticatorFrame.isVisible()) {
191 o365InteractiveAuthenticatorFrame.close();
192 }
193
194 if (isAuthenticated) {
195 token = O365Token.build(tenantId, clientId, redirectUri, code, password);
196
197 LOGGER.debug("Authenticated username: " + token.getUsername());
198 if (username != null && !username.isEmpty() && !username.equalsIgnoreCase(token.getUsername())) {
199 throw new DavMailAuthenticationException("Authenticated username " + token.getUsername() + " does not match " + username);
200 }
201
202 } else {
203 LOGGER.error("Authentication failed " + errorCode);
204 throw new DavMailException("EXCEPTION_AUTHENTICATION_FAILED_REASON", errorCode);
205 }
206 }
207
208 public static void main(String[] argv) {
209
210 try {
211
212 Security.setProperty("ssl.SocketFactory.provider", "davmail.http.DavGatewaySSLSocketFactory");
213
214 Settings.setDefaultSettings();
215 Settings.setConfigFilePath("davmail-interactive.properties");
216 Settings.load();
217
218 O365InteractiveAuthenticator authenticator = new O365InteractiveAuthenticator();
219 authenticator.setUsername("");
220 authenticator.authenticate();
221
222 try (
223 HttpClientAdapter httpClientAdapter = new HttpClientAdapter(authenticator.getExchangeUri(), true)
224 ) {
225
226
227 GetFolderMethod checkMethod = new GetFolderMethod(BaseShape.ID_ONLY, DistinguishedFolderId.getInstance(null, DistinguishedFolderId.Name.root), null);
228 checkMethod.setHeader("Authorization", "Bearer " + authenticator.getToken().getAccessToken());
229 try (
230 CloseableHttpResponse response = httpClientAdapter.execute(checkMethod)
231 ) {
232 checkMethod.handleResponse(response);
233 checkMethod.checkSuccess();
234 }
235 LOGGER.info("Retrieved folder id " + checkMethod.getResponseItem().get("FolderId"));
236
237
238 int i = 0;
239 while (i++ < 12 * 60 * 2) {
240 GetUserConfigurationMethod getUserConfigurationMethod = new GetUserConfigurationMethod();
241 getUserConfigurationMethod.setHeader("Authorization", "Bearer " + authenticator.getToken().getAccessToken());
242 try (
243 CloseableHttpResponse response = httpClientAdapter.execute(getUserConfigurationMethod)
244 ) {
245 getUserConfigurationMethod.handleResponse(response);
246 getUserConfigurationMethod.checkSuccess();
247 }
248 LOGGER.info(getUserConfigurationMethod.getResponseItem());
249
250 Thread.sleep(5000);
251 }
252 }
253 } catch (InterruptedException e) {
254 LOGGER.warn("Thread interrupted", e);
255 Thread.currentThread().interrupt();
256 } catch (Exception e) {
257 LOGGER.error(e + " " + e.getMessage(), e);
258 }
259 System.exit(0);
260 }
261 }