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 O365InteractiveAuthenticatorSWT o365InteractiveAuthenticatorSWT;
58 private O365ManualAuthenticatorDialog o365ManualAuthenticatorDialog;
59
60 private String username;
61 private String password;
62 private O365Token token;
63
64 public O365Token getToken() {
65 return token;
66 }
67
68 @Override
69 public URI getExchangeUri() {
70 return ewsUrl;
71 }
72
73 public String getUsername() {
74 return username;
75 }
76
77 public void setUsername(String username) {
78 this.username = username;
79 }
80
81 public void setPassword(String password) {
82 this.password = password;
83 }
84
85
86
87
88
89
90 @Override
91 public HttpClientAdapter getHttpClientAdapter() {
92 return new HttpClientAdapter(getExchangeUri(), username, password, true);
93 }
94
95 public void authenticate() throws IOException {
96
97
98 System.setProperty("sun.net.http.allowRestrictedHeaders", "true");
99
100 System.setProperty("jdk.http.ntlm.transparentAuth", "allHosts");
101
102
103 final String clientId = Settings.getProperty("davmail.oauth.clientId", "facd6cff-a294-4415-b59f-c5b01937d7bd");
104
105 final String redirectUri = Settings.getProperty("davmail.oauth.redirectUri", "https://localhost/common/oauth2/nativeclient");
106
107 String tenantId = Settings.getProperty("davmail.oauth.tenantId", "common");
108
109
110 token = O365Token.load(tenantId, clientId, redirectUri, username, password);
111 if (token != null) {
112 isAuthenticated = true;
113 return;
114 }
115
116 final String initUrl = O365Authenticator.buildAuthorizeUrl(tenantId, clientId, redirectUri, username);
117
118
119 Authenticator.setDefault(new Authenticator() {
120 @Override
121 public PasswordAuthentication getPasswordAuthentication() {
122 if (getRequestorType() == RequestorType.PROXY) {
123 String proxyUser = Settings.getProperty("davmail.proxyUser");
124 String proxyPassword = Settings.getProperty("davmail.proxyPassword");
125 if (proxyUser != null && proxyPassword != null) {
126 LOGGER.debug("Proxy authentication with user " + proxyUser);
127 return new PasswordAuthentication(proxyUser, proxyPassword.toCharArray());
128 } else {
129 LOGGER.debug("Missing proxy credentials ");
130 return null;
131 }
132 } else {
133 LOGGER.debug("Password authentication with user " + username);
134 return new PasswordAuthentication(username, password.toCharArray());
135 }
136 }
137 });
138
139
140 boolean isSWTAvailable = Settings.isSWTAvailable();
141 boolean isDocker = Settings.isDocker();
142 boolean isJFXAvailable = Settings.isJFXAvailable();
143
144 if (isSWTAvailable && !isDocker) {
145 LOGGER.debug("Open SWT browser");
146 try {
147 o365InteractiveAuthenticatorSWT = new O365InteractiveAuthenticatorSWT();
148 o365InteractiveAuthenticatorSWT.setO365InteractiveAuthenticator(O365InteractiveAuthenticator.this);
149 o365InteractiveAuthenticatorSWT.authenticate(initUrl, redirectUri);
150 } catch (Error e) {
151 LOGGER.warn("Unable to load SWT browser: "+e.getMessage());
152 if (o365InteractiveAuthenticatorSWT != null) {
153 try {
154 o365InteractiveAuthenticatorSWT.dispose();
155 } catch (Throwable t) {
156 LOGGER.warn("Error disposing SWT frame: "+t.getMessage());
157 }
158 }
159 o365InteractiveAuthenticatorSWT = null;
160 }
161 }
162
163 if (o365InteractiveAuthenticatorSWT == null && isJFXAvailable
164
165 && (!(isSWTAvailable && Settings.isLinux() && !isDocker))) {
166 LOGGER.info("Open JavaFX (OpenJFX) browser");
167 SwingUtilities.invokeLater(() -> {
168 try {
169 o365InteractiveAuthenticatorFrame = new O365InteractiveAuthenticatorFrame();
170 o365InteractiveAuthenticatorFrame.setO365InteractiveAuthenticator(O365InteractiveAuthenticator.this);
171 o365InteractiveAuthenticatorFrame.authenticate(initUrl, redirectUri);
172 } catch (NoClassDefFoundError e) {
173 LOGGER.warn("Unable to load JavaFX (OpenJFX)");
174 } catch (IllegalAccessError e) {
175 LOGGER.warn("Unable to load JavaFX (OpenJFX), append --add-exports java.base/sun.net.www.protocol.https=ALL-UNNAMED to java options");
176 }
177
178 });
179 } else {
180 if (o365InteractiveAuthenticatorFrame == null && o365InteractiveAuthenticatorSWT == null) {
181 try {
182 SwingUtilities.invokeAndWait(() -> o365ManualAuthenticatorDialog = new O365ManualAuthenticatorDialog(initUrl));
183 } catch (InterruptedException e) {
184 Thread.currentThread().interrupt();
185 } catch (InvocationTargetException e) {
186 throw new IOException(e);
187 }
188 code = o365ManualAuthenticatorDialog.getCode();
189 isAuthenticated = code != null;
190 if (!isAuthenticated) {
191 errorCode = "User did not provide authentication code";
192 }
193 }
194 }
195
196 int count = 0;
197
198 while (!isAuthenticated && errorCode == null && count++ < MAX_COUNT) {
199 try {
200 Thread.sleep(1000);
201 } catch (InterruptedException e) {
202 Thread.currentThread().interrupt();
203 }
204 }
205
206 if (count > MAX_COUNT) {
207 errorCode = "Timed out waiting for interactive authentication";
208 }
209
210 if (o365InteractiveAuthenticatorFrame != null && o365InteractiveAuthenticatorFrame.isVisible()) {
211 o365InteractiveAuthenticatorFrame.close();
212 }
213
214 if (o365InteractiveAuthenticatorSWT != null) {
215 o365InteractiveAuthenticatorSWT.dispose();
216 }
217
218 if (isAuthenticated) {
219 token = O365Token.build(tenantId, clientId, redirectUri, code, password);
220
221 LOGGER.debug("Authenticated username: " + token.getUsername());
222 if (username != null && !username.isEmpty() && !username.equalsIgnoreCase(token.getUsername())) {
223 throw new DavMailAuthenticationException("EXCEPTION_AUTHENTICATION_FAILED_MISMATCH", token.getUsername(), username);
224 }
225
226 } else {
227 LOGGER.error("Authentication failed " + errorCode);
228 throw new DavMailException("EXCEPTION_AUTHENTICATION_FAILED_REASON", errorCode);
229 }
230 }
231
232
233
234
235
236 public void handleCode(String location) {
237 isAuthenticated = location.contains("code=");
238 if (!isAuthenticated && location.contains("error=")) {
239 errorCode = location.substring(location.indexOf("error="));
240 }
241
242 if (isAuthenticated) {
243 LOGGER.debug("Authenticated location: " + location);
244 if (location.contains("&session_state=")) {
245 code = location.substring(location.indexOf("code=") + 5, location.indexOf("&session_state="));
246 } else {
247 code = location.substring(location.indexOf("code=") + 5);
248 }
249 String sessionState = location.substring(location.lastIndexOf('=') + 1);
250
251 LOGGER.debug("Authentication Code: " + code);
252 LOGGER.debug("Authentication session state: " + sessionState);
253 }
254 }
255
256 public static void main(String[] argv) {
257
258 try {
259
260 Security.setProperty("ssl.SocketFactory.provider", "davmail.http.DavGatewaySSLSocketFactory");
261
262 Settings.setDefaultSettings();
263 Settings.setConfigFilePath("davmail-interactive.properties");
264 Settings.load();
265
266 O365InteractiveAuthenticator authenticator = new O365InteractiveAuthenticator();
267 authenticator.setUsername("demo@demo.onmicrosoft.com");
268 authenticator.authenticate();
269
270 try (
271 HttpClientAdapter httpClientAdapter = new HttpClientAdapter(authenticator.getExchangeUri(), true)
272 ) {
273
274
275 GetFolderMethod checkMethod = new GetFolderMethod(BaseShape.ID_ONLY, DistinguishedFolderId.getInstance(null, DistinguishedFolderId.Name.root), null);
276 checkMethod.setHeader("Authorization", "Bearer " + authenticator.getToken().getAccessToken());
277 try (
278 CloseableHttpResponse response = httpClientAdapter.execute(checkMethod)
279 ) {
280 checkMethod.handleResponse(response);
281 checkMethod.checkSuccess();
282 }
283 LOGGER.info("Retrieved folder id " + checkMethod.getResponseItem().get("FolderId"));
284
285
286 int i = 0;
287 while (i++ < 12 * 60 * 2) {
288 GetUserConfigurationMethod getUserConfigurationMethod = new GetUserConfigurationMethod();
289 getUserConfigurationMethod.setHeader("Authorization", "Bearer " + authenticator.getToken().getAccessToken());
290 try (
291 CloseableHttpResponse response = httpClientAdapter.execute(getUserConfigurationMethod)
292 ) {
293 getUserConfigurationMethod.handleResponse(response);
294 getUserConfigurationMethod.checkSuccess();
295 }
296 LOGGER.info(getUserConfigurationMethod.getResponseItem());
297
298 Thread.sleep(5000);
299 }
300 }
301 } catch (InterruptedException e) {
302 LOGGER.warn("Thread interrupted", e);
303 Thread.currentThread().interrupt();
304 } catch (Exception e) {
305 LOGGER.error(e + " " + e.getMessage(), e);
306 }
307 System.exit(0);
308 }
309 }