1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 package davmail.exchange.auth;
21
22 import davmail.BundleMessage;
23 import davmail.Settings;
24 import davmail.ui.tray.DavGatewayTray;
25 import javafx.application.Platform;
26 import javafx.concurrent.Worker;
27 import javafx.embed.swing.JFXPanel;
28 import javafx.scene.Scene;
29 import javafx.scene.control.ProgressBar;
30 import javafx.scene.layout.StackPane;
31 import javafx.scene.web.WebEngine;
32 import javafx.scene.web.WebView;
33 import org.apache.log4j.Logger;
34 import org.w3c.dom.Document;
35
36 import javax.swing.*;
37 import javax.xml.XMLConstants;
38 import javax.xml.transform.OutputKeys;
39 import javax.xml.transform.Transformer;
40 import javax.xml.transform.TransformerFactory;
41 import javax.xml.transform.dom.DOMSource;
42 import javax.xml.transform.stream.StreamResult;
43 import java.awt.*;
44 import java.awt.event.WindowAdapter;
45 import java.awt.event.WindowEvent;
46 import java.io.ByteArrayOutputStream;
47 import java.io.OutputStreamWriter;
48 import java.net.URL;
49 import java.net.URLConnection;
50 import java.net.URLStreamHandler;
51 import java.nio.charset.StandardCharsets;
52
53
54
55
56
57 public class O365InteractiveAuthenticatorFrame extends JFrame {
58 private static final Logger LOGGER = Logger.getLogger(O365InteractiveAuthenticatorFrame.class);
59
60 private O365InteractiveAuthenticator authenticator;
61
62 static {
63
64 URL.setURLStreamHandlerFactory((String protocol) -> {
65 if ("msauth".equals(protocol) || "urn".equals(protocol)) {
66 return new URLStreamHandler() {
67 @Override
68 protected URLConnection openConnection(URL u) {
69 return new URLConnection(u) {
70 @Override
71 public void connect() {
72
73 }
74 };
75 }
76 };
77 }
78 return null;
79 }
80 );
81 }
82
83 String location;
84 final JFXPanel fxPanel = new JFXPanel();
85
86 public O365InteractiveAuthenticatorFrame() {
87 setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
88 addWindowListener(new WindowAdapter() {
89 @Override
90 public void windowClosing(WindowEvent e) {
91 if (!authenticator.isAuthenticated && authenticator.errorCode == null) {
92 authenticator.errorCode = "user closed authentication window";
93 }
94 }
95 });
96
97 setTitle(BundleMessage.format("UI_DAVMAIL_GATEWAY"));
98 try {
99 setIconImages(DavGatewayTray.getFrameIcons());
100 } catch (NoSuchMethodError error) {
101 DavGatewayTray.debug(new BundleMessage("LOG_UNABLE_TO_SET_ICON_IMAGE"));
102 }
103
104 JPanel mainPanel = new JPanel();
105
106 mainPanel.add(fxPanel);
107 add(BorderLayout.CENTER, mainPanel);
108
109 pack();
110 setResizable(true);
111
112 setSize(600, 600);
113 setLocationRelativeTo(null);
114 setVisible(true);
115
116 setAlwaysOnTop(true);
117 setAlwaysOnTop(false);
118 }
119
120 public void setO365InteractiveAuthenticator(O365InteractiveAuthenticator authenticator) {
121 this.authenticator = authenticator;
122 }
123
124 private void initFX(final JFXPanel fxPanel, final String url, final String redirectUri) {
125 WebView webView = new WebView();
126 final WebEngine webViewEngine = webView.getEngine();
127
128 final ProgressBar loadProgress = new ProgressBar();
129 loadProgress.progressProperty().bind(webViewEngine.getLoadWorker().progressProperty());
130
131 StackPane hBox = new StackPane();
132 hBox.getChildren().setAll(webView, loadProgress);
133 Scene scene = new Scene(hBox);
134 fxPanel.setScene(scene);
135
136 webViewEngine.setUserAgent(Settings.getUserAgent());
137
138 webViewEngine.setOnAlert(stringWebEvent -> SwingUtilities.invokeLater(() -> {
139 String message = stringWebEvent.getData();
140 JOptionPane.showMessageDialog(O365InteractiveAuthenticatorFrame.this, message);
141 }));
142 webViewEngine.setOnError(event -> LOGGER.error(event.getMessage()));
143
144
145 webViewEngine.getLoadWorker().stateProperty().addListener((ov, oldState, newState) -> {
146
147 if (newState == Worker.State.SUCCEEDED || newState == Worker.State.CANCELLED) {
148 loadProgress.setVisible(false);
149 location = webViewEngine.getLocation();
150 updateTitleAndFocus(location);
151 LOGGER.debug("Webview location: " + location);
152
153 O365InteractiveJSLogger.register(webViewEngine);
154 if (LOGGER.isDebugEnabled()) {
155 LOGGER.debug(dumpDocument(webViewEngine.getDocument()));
156 }
157 if (location.startsWith(redirectUri)) {
158 LOGGER.debug("Location starts with redirectUri, check code");
159
160 authenticator.isAuthenticated = location.contains("code=") && location.contains("&session_state=");
161 if (!authenticator.isAuthenticated && location.contains("error=")) {
162 authenticator.errorCode = location.substring(location.indexOf("error="));
163 }
164 if (authenticator.isAuthenticated) {
165 LOGGER.debug("Authenticated location: " + location);
166 String code = location.substring(location.indexOf("code=") + 5, location.indexOf("&session_state="));
167 String sessionState = location.substring(location.lastIndexOf('='));
168
169 LOGGER.debug("Authentication Code: " + code);
170 LOGGER.debug("Authentication session state: " + sessionState);
171 authenticator.code = code;
172 }
173 close();
174 }
175 } else if (newState == Worker.State.FAILED) {
176 Throwable e = webViewEngine.getLoadWorker().getException();
177 if (e != null) {
178 handleError(e);
179 }
180 close();
181 } else {
182 LOGGER.debug(webViewEngine.getLoadWorker().getState() + " " + webViewEngine.getLoadWorker().getMessage() + " " + webViewEngine.getLocation() + " ");
183 }
184
185 });
186 webViewEngine.load(url);
187 }
188
189 private void updateTitleAndFocus(final String location) {
190 SwingUtilities.invokeLater(() -> {
191 setState(Frame.NORMAL);
192 setAlwaysOnTop(true);
193 setAlwaysOnTop(false);
194 setTitle("DavMail: " + location);
195 });
196 }
197
198 public String dumpDocument(Document document) {
199 String result;
200 try {
201 ByteArrayOutputStream baos = new ByteArrayOutputStream();
202 TransformerFactory transformerFactory = TransformerFactory.newInstance();
203 transformerFactory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
204 transformerFactory.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, "");
205 transformerFactory.setAttribute(XMLConstants.ACCESS_EXTERNAL_STYLESHEET, "");
206 Transformer transformer = transformerFactory.newTransformer();
207 transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "no");
208 transformer.setOutputProperty(OutputKeys.METHOD, "xml");
209 transformer.setOutputProperty(OutputKeys.INDENT, "yes");
210 transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
211 transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4");
212
213 transformer.transform(new DOMSource(document),
214 new StreamResult(new OutputStreamWriter(baos, StandardCharsets.UTF_8)));
215 result = baos.toString("UTF-8");
216 } catch (Exception e) {
217 result = e + " " + e.getMessage();
218 }
219 return result;
220 }
221
222 public void authenticate(final String initUrl, final String redirectUri) {
223
224 Platform.runLater(() -> {
225 try {
226 Platform.setImplicitExit(false);
227
228 initFX(fxPanel, initUrl, redirectUri);
229 } catch (Exception e) {
230 handleError(e);
231 close();
232 }
233 });
234 }
235
236 public void handleError(Throwable t) {
237 LOGGER.error(t + " " + t.getMessage());
238 authenticator.errorCode = t.getMessage();
239 if (authenticator.errorCode == null) {
240 authenticator.errorCode = t.toString();
241 }
242 }
243
244 public void close() {
245 SwingUtilities.invokeLater(() -> {
246 setVisible(false);
247 dispose();
248 });
249 }
250
251 }