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 (Settings.getBooleanProperty("davmail.webview.debug", false)) {
155 LOGGER.debug(dumpDocument(webViewEngine.getDocument()));
156 }
157 if (location.startsWith(redirectUri)) {
158 LOGGER.debug("Location starts with redirectUri, check code");
159 authenticator.handleCode(location);
160
161 close();
162 }
163 } else if (newState == Worker.State.FAILED) {
164 location = webViewEngine.getLocation();
165 LOGGER.debug("Webview location: " + location);
166 if (location.startsWith(redirectUri)) {
167
168 LOGGER.debug("Location starts with redirectUri, check code");
169 authenticator.handleCode(location);
170 } else {
171 Throwable e = webViewEngine.getLoadWorker().getException();
172 if (e != null) {
173 handleError(e);
174 }
175 }
176 close();
177 } else {
178 LOGGER.debug(webViewEngine.getLoadWorker().getState() + " " + webViewEngine.getLoadWorker().getMessage() + " " + webViewEngine.getLocation() + " ");
179 }
180
181 });
182 webViewEngine.load(url);
183 }
184
185 private void updateTitleAndFocus(final String location) {
186 SwingUtilities.invokeLater(() -> {
187 setState(Frame.NORMAL);
188 setAlwaysOnTop(true);
189 setAlwaysOnTop(false);
190 setTitle("DavMail: " + location);
191 });
192 }
193
194 public String dumpDocument(Document document) {
195 String result;
196 try {
197 ByteArrayOutputStream baos = new ByteArrayOutputStream();
198 TransformerFactory transformerFactory = TransformerFactory.newInstance();
199 transformerFactory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
200 transformerFactory.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, "");
201 transformerFactory.setAttribute(XMLConstants.ACCESS_EXTERNAL_STYLESHEET, "");
202 Transformer transformer = transformerFactory.newTransformer();
203 transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "no");
204 transformer.setOutputProperty(OutputKeys.METHOD, "xml");
205 transformer.setOutputProperty(OutputKeys.INDENT, "yes");
206 transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
207 transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4");
208
209 transformer.transform(new DOMSource(document),
210 new StreamResult(new OutputStreamWriter(baos, StandardCharsets.UTF_8)));
211 result = baos.toString("UTF-8");
212 } catch (Exception e) {
213 result = e + " " + e.getMessage();
214 }
215 return result;
216 }
217
218 public void authenticate(final String initUrl, final String redirectUri) {
219
220 Platform.runLater(() -> {
221 try {
222 Platform.setImplicitExit(false);
223
224 initFX(fxPanel, initUrl, redirectUri);
225 } catch (Exception e) {
226 handleError(e);
227 close();
228 }
229 });
230 }
231
232 public void handleError(Throwable t) {
233 LOGGER.error(t + " " + t.getMessage());
234 authenticator.errorCode = t.getMessage();
235 if (authenticator.errorCode == null) {
236 authenticator.errorCode = t.toString();
237 }
238 }
239
240 public void close() {
241 SwingUtilities.invokeLater(() -> {
242 setVisible(false);
243 dispose();
244 });
245 }
246
247 }