New upstream version 5.1.0.2891
Alexandre Rossi
5 years ago
0 | # DavMail POP/IMAP/SMTP/Caldav/Carddav/LDAP Exchange Gateway | |
0 | # DavMail POP/IMAP/SMTP/Caldav/Carddav/LDAP Exchange and Office 365 Gateway | |
1 | 1 | |
2 | 2 | [![Build Status: Linux](https://travis-ci.org/mguessan/davmail.svg?branch=master)](https://travis-ci.org/mguessan/davmail) |
3 | 3 | [![Build status: Windows](https://ci.appveyor.com/api/projects/status/d7tx645gwqvprd4g?svg=true)](https://ci.appveyor.com/project/mguessan/davmail) |
13 | 13 | |
14 | 14 | ## Download |
15 | 15 | Download latest DavMail release on Sourceforge |
16 | [http://davmail.sourceforge.net/download.html](http://davmail.sourceforge.net/download.html) | |
16 | [https://sourceforge.net/projects/davmail/files/davmail/5.1.0/](https://sourceforge.net/projects/davmail/files/davmail/5.1.0/) | |
17 | 17 | |
18 | 18 | ## Trunk builds |
19 | 19 | Latest working builds are now available on Appveyor: |
20 | 20 | |
21 | * Windows setup [davmail-5.0.0-trunk-setup.exe](https://ci.appveyor.com/api/projects/mguessan/davmail/artifacts/dist%2Fdavmail-5.0.0-trunk-setup.exe?job=Environment%3A%20JAVA_HOME%3DC%3A%5CProgram%20Files%5CJava%5Cjdk1.8.0) | |
22 | * Windows 64 bits setup [davmail-5.0.0-trunk-setup64.exe](https://ci.appveyor.com/api/projects/mguessan/davmail/artifacts/dist%2Fdavmail-5.0.0-trunk-setup64.exe?job=Environment%3A%20JAVA_HOME%3DC%3A%5CProgram%20Files%5CJava%5Cjdk1.8.0) | |
23 | * Windows noinstall package [davmail-5.0.0-trunk-windows-noinstall.zip](https://ci.appveyor.com/api/projects/mguessan/davmail/artifacts/dist%2Fdavmail-5.0.0-trunk-windows-noinstall.zip?job=Environment%3A%20JAVA_HOME%3DC%3A%5CProgram%20Files%5CJava%5Cjdk1.8.0) | |
21 | * Windows setup [davmail-5.1.0-trunk-setup.exe](https://ci.appveyor.com/api/projects/mguessan/davmail/artifacts/dist%2Fdavmail-5.1.0-trunk-setup.exe?job=Environment%3A%20JAVA_HOME%3DC%3A%5CProgram%20Files%5CJava%5Cjdk1.8.0) | |
22 | * Windows 64 bits setup [davmail-5.1.0-trunk-setup64.exe](https://ci.appveyor.com/api/projects/mguessan/davmail/artifacts/dist%2Fdavmail-5.1.0-trunk-setup64.exe?job=Environment%3A%20JAVA_HOME%3DC%3A%5CProgram%20Files%5CJava%5Cjdk1.8.0) | |
23 | * Windows noinstall package [davmail-5.1.0-trunk-windows-noinstall.zip](https://ci.appveyor.com/api/projects/mguessan/davmail/artifacts/dist%2Fdavmail-5.1.0-trunk-windows-noinstall.zip?job=Environment%3A%20JAVA_HOME%3DC%3A%5CProgram%20Files%5CJava%5Cjdk1.8.0) | |
24 | 24 | |
25 | * Platform independent package [davmail-5.0.0-trunk.zip](https://ci.appveyor.com/api/projects/mguessan/davmail/artifacts/dist%2Fdavmail-5.0.0-trunk.zip?job=Environment%3A%20JAVA_HOME%3DC%3A%5CProgram%20Files%5CJava%5Cjdk1.8.0) | |
25 | * Platform independent package [davmail-5.1.0-trunk.zip](https://ci.appveyor.com/api/projects/mguessan/davmail/artifacts/dist%2Fdavmail-5.1.0-trunk.zip?job=Environment%3A%20JAVA_HOME%3DC%3A%5CProgram%20Files%5CJava%5Cjdk1.8.0) | |
26 | 26 | |
27 | * Debian package [davmail_5.0.0-trunk-1_all.deb](https://ci.appveyor.com/api/projects/mguessan/davmail/artifacts/dist%2Fdavmail_5.0.0-trunk-1_all.deb?job=Environment%3A%20JAVA_HOME%3DC%3A%5CProgram%20Files%5CJava%5Cjdk1.8.0) | |
27 | * Debian package [davmail_5.1.0-trunk-1_all.deb](https://ci.appveyor.com/api/projects/mguessan/davmail/artifacts/dist%2Fdavmail_5.1.0-trunk-1_all.deb?job=Environment%3A%20JAVA_HOME%3DC%3A%5CProgram%20Files%5CJava%5Cjdk1.8.0) | |
28 | 28 | |
29 | * OSX application [DavMail-MacOSX-5.0.0-trunk.app.zip](https://ci.appveyor.com/api/projects/mguessan/davmail/artifacts/dist%2FDavMail-MacOSX-5.0.0-trunk.app.zip?job=Environment%3A%20JAVA_HOME%3DC%3A%5CProgram%20Files%5CJava%5Cjdk1.8.0) | |
29 | * OSX application [DavMail-MacOSX-5.1.0-trunk.app.zip](https://ci.appveyor.com/api/projects/mguessan/davmail/artifacts/dist%2FDavMail-MacOSX-5.1.0-trunk.app.zip?job=Environment%3A%20JAVA_HOME%3DC%3A%5CProgram%20Files%5CJava%5Cjdk1.8.0) | |
30 | 30 | |
31 | 31 | ## Contribute |
32 | 32 | Contributions are welcome, you can either [submit a patch](https://sourceforge.net/p/davmail/patches/) or create a [Github pull request](https://github.com/mguessan/davmail/pulls). |
0 | ## DavMail 5.1.0 2018-12-18 | |
1 | Much improved interactive O365 authentication with OpenJFX bug workaround, | |
2 | experimental stored Oauth refresh token support. More Linux distributions were tested | |
3 | to make DavMail work with recent KDE and Gnome environments. | |
4 | ||
5 | ### EWS: | |
6 | - EWS: more progress on ADFS authentication | |
7 | - EWS: cleanup warning message | |
8 | - EWS: experimental, implement davmail.oauth.persistToken to store Oauth refresh token | |
9 | - EWS: make progress on O365 ADFS authentication, fix method | |
10 | - EWS: stored token authentication, load token by username | |
11 | - EWS: make progress on O365 ADFS authentication, enable NTLM and pass credentials | |
12 | - EWS: O365 authentication, set resource url on token refresh | |
13 | - EWS: set default access token expiration | |
14 | - EWS: implement stored access token in addition to refresh token (will only last one hour) | |
15 | - EWS: experimental, load Oauth refresh token from setting davmail.oauth.refreshToken | |
16 | - EWS: fix https://github.com/mguessan/davmail/issues/15 empty domain in NTLM authentication | |
17 | - EWS: revert to 4.9.0 behavior for EWS mode | |
18 | - EWS: fix regression in token handling | |
19 | - EWS: allow urn protocol in O365 authenticator | |
20 | - EWS: fix regression when main authentication relies on OWA and/or ADFS | |
21 | - EWS: check for errors in returned json token | |
22 | - EWS: Fix warning message | |
23 | - EWS: workaround for JavaFX bug, add one more URL | |
24 | - EWS: call setAlwaysOnTop(true); on page load success | |
25 | - EWS: in addition to requestFocus, call toFront | |
26 | - EWS: workaround for JavaFX bug, handle more methods in connection wrapper | |
27 | - EWS: workaround for JavaFX bug, handle post requests | |
28 | - EWS: workaround for JavaFX bug, add additional microsoft url | |
29 | - EWS: workaround for JavaFX bug, fix java 8 regression | |
30 | - EWS: Add export compiler arg java.base/sun.net.www.protocol.https for webview bug workaround | |
31 | - EWS: improve interactive authenticator focus handling and remove reflection calls | |
32 | - EWS: workaround for JavaFX bug, use reflection to avoid java 9 errors | |
33 | - EWS: workaround for JavaFX bug, drop reference to internal sun class HttpsURLConnectionImpl | |
34 | - EWS: workaround for JavaFX bug, disable integrity check on external resources in O365 authentication form | |
35 | - EWS: javafx test can also trigger NoClassDefFoundError | |
36 | - EWS: Rename JSLogger | |
37 | - EWS: improve O365 interactive error handling | |
38 | - EWS: override console.log to send error messages to Log4J | |
39 | - EWS: More EWS test cases | |
40 | - EWS: new authenticator test cases | |
41 | - EWS: detect when user settings validation is required by Office 365 | |
42 | - EWS: detect manual window close event | |
43 | - EWS: Make sure we close frame on timeout, improve error message | |
44 | - EWS: refactor O365 authenticator to do all gui calls in Swing thread | |
45 | - EWS: cleanup from audit | |
46 | - EWS: encode slash inside folder names | |
47 | - EWS: convert date without SimpleDateFormat during load messages to improve performance and reduce memory footprint | |
48 | - EWS: Send authentication failed instead of generic error in case of username mismatch in O365Authenticator | |
49 | ||
50 | ### Documentation: | |
51 | - Doc: update project description in README.md | |
52 | - Doc: update project description | |
53 | - Doc: adjust IntelliJ link according to JetBrains recommendation | |
54 | - Doc: revert openhub change, was a target side issue | |
55 | - Doc: add YourKit Java Profiler logo to home page | |
56 | - Doc: improve IntelliJ IDEA home page logo | |
57 | - Doc: fix swt gtk version in documentation | |
58 | - Doc: fix openhub link | |
59 | - Doc: add link to https://apps.dev.microsoft.com/ | |
60 | - Doc: direct link to latest release package download list in README.md | |
61 | - Doc: fix link in server setup documentation | |
62 | ||
63 | ### OSX: | |
64 | - OSX: upgrade universalJavaApplicationStub to 3.0.4 | |
65 | ||
66 | ### Linux: | |
67 | - Linux: prepare systemd service | |
68 | - Linux: missing openjfx dependency | |
69 | - Linux: set cross platform look and feel on Linux, except is swing.defaultlaf is set | |
70 | - Linux: enable anti aliasing in GUI | |
71 | - Linux: improve launch scripts to handle more cases (OpenJDK 11 with or without SWT) | |
72 | - Linux: remove swt4 suggests and revert gtk force, does not work under debian sid | |
73 | - Linux: Force gtk version no longer required with cross platform look and feel | |
74 | - Linux: use hi res icon images in frame mode | |
75 | - Linux: Add JavaFX classpath to launch script | |
76 | - Linux: add libopenjfx-java dependency to debian package | |
77 | - Linux: switch swt dependency to suggests | |
78 | ||
79 | ### Caldav: | |
80 | - Caldav: another NullPointerException fix | |
81 | - Caldav: fix #694 Null pointer exception writing days of week | |
82 | ||
83 | ### Enhancements: | |
84 | - Avoid nullpointerexception on missing credentials | |
85 | - Move isLinux method to Settings | |
86 | - Revert back to Java 6 build in all cases | |
87 | - Restore Java 6 compatibility | |
88 | - Add utility methods in Settings | |
89 | - Do not try SWT when O365 interactive mode is selected. | |
90 | ||
91 | ### Windows: | |
92 | - Windows: update winrun4j 64 wrapper to support java > 8, see https://github.com/poidasmith/winrun4j/pull/81 | |
93 | ||
94 | ### GUI: | |
95 | - GUI: dispose notification dialog on close | |
96 | - GUI: increase default frame size | |
97 | - GUI: add hi res icon images | |
98 | - GUI: use setLocationRelativeTo to set frame location | |
99 | ||
100 | ### SWT: | |
101 | - SWT: O365Interactive is not compatible with SWT, do not try to create SWT tray | |
102 | - SWT: call GDK.gdk_error_trap_push() to avoid crash | |
103 | - SWT: Enable debug mode | |
104 | - SWT: upgrade SWT to 4.9 | |
105 | - SWT: drop deprecated SWT 3 calls and adjust tray icon image to 22px | |
106 | ||
107 | ### Appveyor: | |
108 | - Appveyor: build with JDK11 | |
109 | - Merge patch #51 Check for javafx in compile classpath | |
110 | ||
111 | ||
0 | 112 | ## DavMail 5.0.0 2018-11-21 |
1 | 113 | Major release with Office 365 modern authentication (Oauth2) and MFA support. |
2 | 114 | DavMail now supports IMAP SPECIAL-USE RFC6154. |
6 | 6 | - JAVA_HOME: C:\Program Files\Java\jdk9 |
7 | 7 | - JAVA_HOME: C:\Program Files\Java\jdk1.8.0 |
8 | 8 | - JAVA_HOME: C:\Program Files\Java\jdk10 |
9 | - JAVA_HOME: C:\Program Files\Java\jdk11 | |
9 | 10 | |
10 | 11 | install: |
11 | 12 | - ps: | |
0 | 0 | <project name="DavMail" default="dist" basedir="."> |
1 | 1 | <property file="user.properties"/> |
2 | <property name="version" value="5.0.0"/> | |
2 | <property name="version" value="5.1.0"/> | |
3 | 3 | |
4 | 4 | <path id="classpath"> |
5 | 5 | <pathelement location="classes"/> |
29 | 29 | </condition> |
30 | 30 | |
31 | 31 | <condition property="is.javafx"> |
32 | <available classname="javafx.embed.swing.JFXPanel"/> | |
32 | <available classname="javafx.embed.swing.JFXPanel" classpathref="classpath"/> | |
33 | 33 | </condition> |
34 | 34 | |
35 | 35 | <condition property="is.javafx.message" value="available" else="missing"> |
36 | <available classname="javafx.embed.swing.JFXPanel" property="is.javafx"/> | |
36 | <available classname="javafx.embed.swing.JFXPanel" property="is.javafx" classpathref="classpath"/> | |
37 | 37 | </condition> |
38 | 38 | |
39 | 39 | <condition property="is.java6"> |
87 | 87 | <mkdir dir="target/classes"/> |
88 | 88 | </target> |
89 | 89 | |
90 | <target name="compile-java9" depends="init" if="is.java9"> | |
90 | <target name="compile-java9" depends="init"> | |
91 | 91 | <mkdir dir="target/classes"/> |
92 | 92 | <javac srcdir="src/java" destdir="target/classes" source="9" debug="on" encoding="UTF-8" |
93 | 93 | includeantruntime="false"> |
94 | 94 | <exclude name="davmail/exchange/auth/*Interactive*" unless="is.javafx"/> |
95 | 95 | <compilerarg value="--add-exports" /> |
96 | 96 | <compilerarg value="java.naming/com.sun.jndi.ldap=ALL-UNNAMED" /> |
97 | <compilerarg value="--add-exports" /> | |
98 | <compilerarg value="java.base/sun.net.www.protocol.https=ALL-UNNAMED" /> | |
97 | 99 | <classpath> |
98 | 100 | <path refid="classpath"/> |
99 | 101 | </classpath> |
100 | 102 | </javac> |
101 | 103 | </target> |
102 | 104 | |
103 | <target name="compile-java" depends="init" unless="is.java9"> | |
105 | <target name="compile-java" depends="init"> | |
104 | 106 | <mkdir dir="target/classes"/> |
105 | 107 | <javac srcdir="src/java" destdir="target/classes" source="1.6" target="1.6" debug="on" encoding="UTF-8" |
106 | 108 | includeantruntime="false"> |
111 | 113 | </javac> |
112 | 114 | </target> |
113 | 115 | |
114 | <target name="compile" depends="compile-java,compile-java9"> | |
116 | <target name="compile" depends="compile-java"> | |
115 | 117 | <copy todir="target/classes"> |
116 | 118 | <fileset dir="src/java"> |
117 | 119 | <include name="**/*"/> |
208 | 210 | <deb todir="dist" |
209 | 211 | package="davmail" |
210 | 212 | section="mail" |
211 | depends="openjdk-11-jre|openjdk-8-jre|openjdk-7-jre|openjdk-6-jre|oracle-java7-jre|sun-java6-jre|default-jre,libswt-gtk-3-java|libswt-gtk-3.6-java|libswt-gtk-3.5-java|libswt-gtk-3.4-java,libswt-cairo-gtk-3-jni|libswt-cairo-gtk-3.5-jni"> | |
213 | depends="openjdk-11-jre|openjdk-8-jre|openjdk-7-jre|openjdk-6-jre|oracle-java7-jre|sun-java6-jre|default-jre,libopenjfx-java,openjfx" | |
214 | suggests="libswt-gtk-4-java,libswt-cairo-gtk-4-jni,libswt-gtk2-4-jni"> | |
212 | 215 | <version upstream="${release-name}"/> |
213 | 216 | <maintainer email="mguessan@free.fr" name="Mickaël Guessant"/> |
214 | 217 | <description synopsis="DavMail POP/IMAP/SMTP/Caldav/Carddav/LDAP Exchange Gateway"> |
0 | %{?!davrel: %define davrel 5.0.0} | |
1 | %{?!davsvn: %define davsvn 2801} | |
0 | %{?!davrel: %define davrel 5.1.0} | |
1 | %{?!davsvn: %define davsvn 2890} | |
2 | 2 | %define davver %{davrel}-%{davsvn} |
3 | 3 | |
4 | 4 | Summary: DavMail is a POP/IMAP/SMTP/Caldav/Carddav/LDAP gateway for Microsoft Exchange |
186 | 186 | %attr(0775,davmail,davmail) %{_localstatedir}/lib/davmail |
187 | 187 | |
188 | 188 | %changelog |
189 | * Thu Dec 20 2018 Mickael Guessant <mguessan@free.fr> | |
190 | - update to 5.1.0 | |
191 | ||
189 | 192 | * Wed Nov 21 2018 Mickael Guessant <mguessan@free.fr> |
190 | 193 | - update to 5.0.0 |
191 | 194 | - merge files in trunk |
4 | 4 | <groupId>davmail</groupId> |
5 | 5 | <artifactId>davmail</artifactId> |
6 | 6 | <packaging>jar</packaging> |
7 | <version>5.0.0</version> | |
7 | <version>5.1.0</version> | |
8 | 8 | <name>DavMail POP/IMAP/SMTP/Caldav/Carddav/LDAP Exchange Gateway</name> |
9 | 9 | <organization> |
10 | 10 | <name>Mickaël Guessant</name> |
231 | 231 | <version>4.6.3</version> |
232 | 232 | <optional>true</optional> |
233 | 233 | <scope>system</scope> |
234 | <systemPath>${project.basedir}/lib/swt-4.6.3-gtk-linux-x86_64.jar</systemPath> | |
234 | <systemPath>${project.basedir}/lib/swt-4.9-gtk-linux-x86_64.jar</systemPath> | |
235 | 235 | </dependency> |
236 | 236 | <dependency> |
237 | 237 | <groupId>javax.servlet</groupId> |
58 | 58 | <content_attribute id="money-gambling">none</content_attribute> |
59 | 59 | </content_rating> |
60 | 60 | <releases> |
61 | <release version="5.0.0" date="2018-11-21"> | |
61 | <release version="5.1.0" date="2018-12-18"> | |
62 | 62 | <description> |
63 | 63 | <p> |
64 | Major release with Office 365 modern authentication (Oauth2) and MFA support. | |
65 | DavMail now supports IMAP SPECIAL-USE RFC6154. | |
66 | On the packaging side, RPM files are now included in source package and more | |
67 | distributions are supported by the spec file. An appveyor configuration is in place to | |
68 | provide up to date trunk builds. Thanks to wberrier DavMail is now available as a | |
69 | flatpack package, see https://flathub.org/apps/details/org.davmail.DavMail | |
70 | This release also includes many bug fixes and enhancements, see below. | |
64 | Much improved interactive O365 authentication with OpenJFX bug workaround, | |
65 | experimental stored Oauth refresh token support. More Linux distributions were tested | |
66 | to make DavMail work with recent KDE and Gnome environments. | |
71 | 67 | |
72 | Known issues/limitations: | |
73 | - Office 365 interactive authentication is based on OpenJFX (JavaFX), | |
74 | which is available in Oracle JDK but not in OpenJDK. On windows use latest Oracle JDK (>=9), | |
75 | on Linux OpenJDK 8 + JavaFX is the best option. This is obviously not available in server mode. | |
76 | - Office 365 modern authentication does not have those constraints, however it will only work | |
77 | with native Office 365 authentication, and not with ADFS. | |
68 | ### EWS: | |
69 | - EWS: more progress on ADFS authentication | |
70 | - EWS: cleanup warning message | |
71 | - EWS: experimental, implement davmail.oauth.persistToken to store Oauth refresh token | |
72 | - EWS: make progress on O365 ADFS authentication, fix method | |
73 | - EWS: stored token authentication, load token by username | |
74 | - EWS: make progress on O365 ADFS authentication, enable NTLM and pass credentials | |
75 | - EWS: O365 authentication, set resource url on token refresh | |
76 | - EWS: set default access token expiration | |
77 | - EWS: implement stored access token in addition to refresh token (will only last one hour) | |
78 | - EWS: experimental, load Oauth refresh token from setting davmail.oauth.refreshToken | |
79 | - EWS: fix https://github.com/mguessan/davmail/issues/15 empty domain in NTLM authentication | |
80 | - EWS: revert to 4.9.0 behavior for EWS mode | |
81 | - EWS: fix regression in token handling | |
82 | - EWS: allow urn protocol in O365 authenticator | |
83 | - EWS: fix regression when main authentication relies on OWA and/or ADFS | |
84 | - EWS: check for errors in returned json token | |
85 | - EWS: Fix warning message | |
86 | - EWS: workaround for JavaFX bug, add one more URL | |
87 | - EWS: call setAlwaysOnTop(true); on page load success | |
88 | - EWS: in addition to requestFocus, call toFront | |
89 | - EWS: workaround for JavaFX bug, handle more methods in connection wrapper | |
90 | - EWS: workaround for JavaFX bug, handle post requests | |
91 | - EWS: workaround for JavaFX bug, add additional microsoft url | |
92 | - EWS: workaround for JavaFX bug, fix java 8 regression | |
93 | - EWS: Add export compiler arg java.base/sun.net.www.protocol.https for webview bug workaround | |
94 | - EWS: improve interactive authenticator focus handling and remove reflection calls | |
95 | - EWS: workaround for JavaFX bug, use reflection to avoid java 9 errors | |
96 | - EWS: workaround for JavaFX bug, drop reference to internal sun class HttpsURLConnectionImpl | |
97 | - EWS: workaround for JavaFX bug, disable integrity check on external resources in O365 authentication form | |
98 | - EWS: javafx test can also trigger NoClassDefFoundError | |
99 | - EWS: Rename JSLogger | |
100 | - EWS: improve O365 interactive error handling | |
101 | - EWS: override console.log to send error messages to Log4J | |
102 | - EWS: More EWS test cases | |
103 | - EWS: new authenticator test cases | |
104 | - EWS: detect when user settings validation is required by Office 365 | |
105 | - EWS: detect manual window close event | |
106 | - EWS: Make sure we close frame on timeout, improve error message | |
107 | - EWS: refactor O365 authenticator to do all gui calls in Swing thread | |
108 | - EWS: cleanup from audit | |
109 | - EWS: encode slash inside folder names | |
110 | - EWS: convert date without SimpleDateFormat during load messages to improve performance and reduce memory footprint | |
111 | - EWS: Send authentication failed instead of generic error in case of username mismatch in O365Authenticator | |
78 | 112 | |
79 | ### EWS | |
80 | - EWS: catch errors in setURLStreamHandlerFactory | |
81 | - EWS: custom proxy selector, do not return proxies for direct socket connections | |
82 | - EWS: create a custom proxy selector to manage O365 interactive authentication proxy | |
83 | - EWS: improve error handling in O365 interactive authenticator, do not implicitly close JavaFX thread | |
84 | - EWS: cleanup O365 interactive | |
85 | - EWS: Set http.nonProxyHosts from davmail.noProxyFor in O365 interactive authentication | |
86 | - EWS: improve error handling in O365 interactive authentication | |
87 | - EWS: implement proxy support in O365 interactive authentication | |
88 | - EWS: username with @ is email | |
89 | - Do not try form authentication with direct EWS | |
90 | - EWS: Force dispose of interactive frame | |
91 | - EWS: improve interactive authentication error handling | |
92 | - Fix main test case to support new authentication modes | |
93 | - EWS: Enable DavMail custom SSLSocketFactory in O365 interactive authentication | |
94 | - EWS: Add Oauth authentication section in DavMail settings interface | |
95 | - EWS: Experimental ADFS authentication, not yet functional | |
96 | - EWS: log page content on error in O365Authenticator | |
97 | - EWS: register a stream handler for msauth protocol | |
98 | - EWS: Allow clientId override in interactive authenticator | |
99 | - EWS: Send authentication failed on phone MFA denied/no response | |
100 | - EWS: enable progress bar on first page load only | |
101 | - EWS: Office 365 unit test with loop | |
102 | - EWS: make sure httpclient connections are closed, remove duplicated code | |
103 | - EWS: use renewable token in EwsExchangeSession | |
104 | - EWS: refactor O365 authentication to implement token refresh | |
105 | - EWS: improve headless Office 365 authenticator error handling | |
106 | - EWS: implement progress bar in interactive authentication frame | |
107 | - EWS: check username in Office365 interactive authenticator | |
108 | - EWS: Encode username in Office365 authenticator | |
109 | - EWS: exclude JavaFX authenticator from Maven pom | |
110 | - EWS: Remove reference to JavaFX authenticator in ExchangeSessionFactory | |
111 | - EWS: Reorganise authenticators | |
112 | - JavaFX dependencies are Java 11 only, revert and exclude JavaFX authenticator from Maven build | |
113 | - Add JavaFX scene web dependency | |
114 | - Add JavaFX swing dependency in POM | |
115 | - EWS: add jettison dependency in pom | |
116 | - Do not try interactive authentication in server mode | |
117 | - EWS: Merge non interactive Oauth2 authentication | |
118 | - EWS: Office365 modern authentication (Oauth2) with phone application MFA support | |
119 | - EWS: Implement REST/Json method for Oauth authentication | |
120 | - EWS: send username to interactive authentication frame | |
121 | - EWS: implement interactive OAuth2 authentication (still experimental) | |
122 | - EWS: Add jettison library for Oauth support | |
123 | - EWS: First working prototype of interactive Oauth2 authentication | |
113 | ### Documentation: | |
114 | - Doc: update project description in README.md | |
115 | - Doc: update project description | |
116 | - Doc: adjust IntelliJ link according to JetBrains recommendation | |
117 | - Doc: revert openhub change, was a target side issue | |
118 | - Doc: add YourKit Java Profiler logo to home page | |
119 | - Doc: improve IntelliJ IDEA home page logo | |
120 | - Doc: fix swt gtk version in documentation | |
121 | - Doc: fix openhub link | |
122 | - Doc: add link to https://apps.dev.microsoft.com/ | |
123 | - Doc: direct link to latest release package download list in README.md | |
124 | - Doc: fix link in server setup documentation | |
124 | 125 | |
125 | ### Enhancements | |
126 | - Improve SWT not available message | |
127 | - Detect headless to force server mode, do not allow O365 interactive authentication in this case | |
128 | - Javafx cleanup | |
129 | - Fix empty setting handling: return default setting on empty value | |
130 | - Implement headless choose client certificate and PKCS11 password prompt | |
131 | - Package hi res image | |
132 | - Merge #50 Assume notray in server mode | |
133 | - Display connection mode help as a tooltip | |
134 | - Merge DesktopBrowser: add support for xdg-open directly, see https://github.com/mguessan/davmail/pull/5 | |
135 | - Workaround for login.microsoftonline.com cookie domain | |
136 | - i18n new davmail.mode setting | |
137 | - Drop davmail.enableEws to create a new davmail.mode setting that can be EWS, WebDav, O365, O365Modern, O365Interactive or Auto | |
138 | - Another JavaFX message fix | |
139 | - Fix is.javafx default value | |
140 | - Default is.javafx value | |
141 | - Improve version check message | |
142 | - Add a JavaFX check message | |
143 | - Drop JavaFX runtime and use conditional build instead | |
144 | - Add JavaFX runtime as a compile time dependency | |
145 | - Remove last jsmooth dependency | |
146 | - Adjust default davmail.properties for server mode usage | |
147 | - Drop jarbundler | |
148 | - Add jettison dependency to windows wrappers and installers | |
149 | - Fix RFE #101: Add a new davmail.userAgent setting to let users force DavMail user agent | |
150 | - Add oraclejdk11 and openjdk11 to Travis CI targets | |
151 | - Try to add Javafx dependency for OpenJDK 11 | |
152 | - Make message info level in ant build | |
126 | ### OSX: | |
127 | - OSX: upgrade universalJavaApplicationStub to 3.0.4 | |
153 | 128 | |
154 | ### Linux | |
155 | - Linux: Move spec file to root | |
156 | - Fix relative path in launch script | |
157 | - Copy davmail launch script to dist | |
158 | - Linux: Drop old davmail.sh script | |
159 | - Linux: merge external source files in main source tree | |
160 | - Linux: Move init files from contribs to src/init | |
161 | - Linux: compile with JavaFX on Fedora | |
162 | - Linux: force Java 7 on RHEL 6 and do not deploy appstream on openSUSE_Leap_42.3 | |
163 | - Linux: drop reference to old architecture specific package | |
164 | - Remove old hardcoded uids reference | |
165 | - Linux: drop dependency to LSB functions in init script | |
166 | - Linux: merge pull request https://github.com/mguessan/davmail/pull/4 include appdata file in rpm and deb packages | |
167 | - Linux: merge davmail.sh to use a single script in all cases | |
168 | - Linux: improve wrapper according to audit | |
169 | - Linux: adjust desktop categories according to OpenSuse constraints, see https://en.opensuse.org/openSUSE:Packaging_desktop_menu_categories | |
170 | - Linux: Simplify DavMail wrapper | |
171 | - Linux: make spec file compatible with more distributions | |
172 | - Linux: Additional notes on running DavMail with systray on Ubuntu 18 | |
173 | - Linux: merge RPM and Debian desktop files | |
174 | - Linux: use simple name instead of path in desktop file | |
175 | - Linux: drop desktopentry ant task | |
176 | - Linux: move old desktop file to src/desktop | |
177 | - Linux: Prepare desktop file merge | |
178 | - Linux: merge pull request https://github.com/mguessan/davmail/pull/2 remove deprecations and duplicate main categories in desktop file, missing lf | |
179 | - Linux: merge pull request https://github.com/mguessan/davmail/pull/2 remove deprecations and duplicate main categories in desktop file | |
180 | - Linux: Add changelog entry for release in spec file | |
181 | - Linux: fix spec file changelog date | |
182 | - RPM: update init and logrotate from build.opensuse.org | |
129 | ### Linux: | |
130 | - Linux: prepare systemd service | |
131 | - Linux: missing openjfx dependency | |
132 | - Linux: set cross platform look and feel on Linux, except is swing.defaultlaf is set | |
133 | - Linux: enable anti aliasing in GUI | |
134 | - Linux: improve launch scripts to handle more cases (OpenJDK 11 with or without SWT) | |
135 | - Linux: remove swt4 suggests and revert gtk force, does not work under debian sid | |
136 | - Linux: Force gtk version no longer required with cross platform look and feel | |
137 | - Linux: use hi res icon images in frame mode | |
138 | - Linux: Add JavaFX classpath to launch script | |
139 | - Linux: add libopenjfx-java dependency to debian package | |
140 | - Linux: switch swt dependency to suggests | |
183 | 141 | |
184 | ### Caldav | |
185 | - Caldav: another fix for #344 Problem with Calendar and tasks, fix properties list | |
186 | - Caldav: fix for #344 Problem with Calendar and tasks, calendar:MyResponseType is also calendar only on Exchange 2007 | |
187 | - Caldav: fix for #344 Problem with Calendar and tasks, Exchange 2007 does not accept ismeeting property request on non calendar items | |
142 | ### Caldav: | |
143 | - Caldav: another NullPointerException fix | |
144 | - Caldav: fix #694 Null pointer exception writing days of week | |
188 | 145 | |
189 | ### Documentation | |
190 | - Doc: Convert release notes to markdown format | |
191 | - Doc: add contribute section in README.md | |
192 | - Doc: fix appveyor link | |
193 | - Add download links to README.md | |
194 | - Doc: fix typo | |
195 | - Doc: update linux instructions, remove obsolete content | |
196 | - Doc: reference official debian package and build.opensuse RPM packages in server setup documentation | |
197 | - Doc: Drop piwik reference from site, no longer available on Sourceforge | |
198 | - Doc: Add appveyor badge in README.md | |
199 | - Doc: Add an FAQ entry on Office 365 modern authentication and MFA | |
200 | - Doc: adjust indentation to match pull request | |
201 | - Doc: appdata file from https://github.com/mguessan/davmail/pull/3 | |
202 | - Doc: make image link relative in README.md | |
203 | - Doc: update release notes | |
204 | - Add Sourceforge download badge to README.md | |
146 | ### Enhancements: | |
147 | - Avoid nullpointerexception on missing credentials | |
148 | - Move isLinux method to Settings | |
149 | - Revert back to Java 6 build in all cases | |
150 | - Restore Java 6 compatibility | |
151 | - Add utility methods in Settings | |
152 | - Do not try SWT when O365 interactive mode is selected. | |
205 | 153 | |
206 | ### IMAP | |
207 | - IMAP: implement #341 imap SPECIAL-USE | |
154 | ### Windows: | |
155 | - Windows: update winrun4j 64 wrapper to support java > 8, see https://github.com/poidasmith/winrun4j/pull/81 | |
208 | 156 | |
209 | ### Appveyor | |
210 | - appveyor: add Java 10 in matrix | |
211 | - Build: use -trunk suffix for all artifacts | |
212 | - appveyor: get artifacts | |
213 | - Back to ant dist | |
214 | - appveyor: fix for nsis 3 | |
215 | - appveyor: separate makensis from build file | |
216 | - appveyor: copy processwork nsis plugin | |
217 | - appveyor: switch from compile to dist target | |
218 | - appveyor: disable test | |
219 | - appveyor: fix ANT_HOME | |
220 | - appveyor: debug | |
221 | - appveyor: fix ant path | |
222 | - Try to create an appveyor build descriptor | |
157 | ### GUI: | |
158 | - GUI: dispose notification dialog on close | |
159 | - GUI: increase default frame size | |
160 | - GUI: add hi res icon images | |
161 | - GUI: use setLocationRelativeTo to set frame location | |
223 | 162 | |
224 | ### Carddav | |
225 | - Carddav: prefer urlcompname (client provided item name) for contacts over EWS | |
163 | ### SWT: | |
164 | - SWT: O365Interactive is not compatible with SWT, do not try to create SWT tray | |
165 | - SWT: call GDK.gdk_error_trap_push() to avoid crash | |
166 | - SWT: Enable debug mode | |
167 | - SWT: upgrade SWT to 4.9 | |
168 | - SWT: drop deprecated SWT 3 calls and adjust tray icon image to 22px | |
226 | 169 | |
227 | ### OSX | |
228 | - OSX: restore OSX greyscale icons | |
170 | ### Appveyor: | |
171 | - Appveyor: build with JDK11 | |
172 | - Merge patch #51 Check for javafx in compile classpath | |
229 | 173 | </p> |
230 | 174 | </description> |
231 | 175 | </release> |
1 | 1 | # |
2 | 2 | # Usage: davmail [</path/to/davmail.properties>] |
3 | 3 | # |
4 | # In case of SWT crash under JDK 9, uninstall SWT or remove the second case below | |
4 | # In case of SWT crash under JDK 9/11, uninstall SWT or remove SWT cases below | |
5 | 5 | # |
6 | 6 | BASE=`dirname $0` |
7 | JAVA_OPTS="-Xmx512M -Dsun.net.inetaddr.ttl=60" | |
7 | # force GTK2 to avoid crash with OpenJDK 11 | |
8 | JAVA_OPTS="-Xmx512M -Dsun.net.inetaddr.ttl=60 -Djdk.gtk.version=2.2" | |
9 | JAVA=java | |
10 | # uncomment this to force JDK 8 | |
11 | #JAVA=/usr/lib/jvm/java-8-openjdk-amd64/jre/bin/java | |
12 | # add JFX to classpath with OpenJDK 11 | |
13 | JFX_CLASSPATH=/usr/share/java/javafx-base.jar:/usr/share/java/javafx-controls.jar:/usr/share/java/javafx-fxml.jar:/usr/share/java/javafx-graphics.jar:/usr/share/java/javafx-media.jar:/usr/share/java/javafx-swing.jar:/usr/share/java/javafx-web.jar | |
8 | 14 | if [ -f $BASE/davmail.jar ]; then |
9 | 15 | # this is the platform independent package |
10 | exec java $JAVA_OPTS -cp $BASE/davmail.jar:$BASE/lib/* davmail.DavGateway "$@" | |
16 | exec ${JAVA} $JAVA_OPTS -cp $BASE/davmail.jar:$BASE/lib/*:${JFX_CLASSPATH} davmail.DavGateway "$@" | |
11 | 17 | elif [ -f /usr/share/java/swt.jar ]; then |
12 | # standard install with SWT | |
18 | # SWT 3 is available | |
13 | 19 | export LD_LIBRARY_PATH=/usr/lib/jni |
14 | exec java $JAVA_OPTS -cp /usr/share/davmail/davmail.jar:/usr/share/java/swt.jar:/usr/share/davmail/lib/* davmail.DavGateway "$@" | |
20 | exec ${JAVA} $JAVA_OPTS -cp /usr/share/davmail/davmail.jar:/usr/share/java/swt.jar:/usr/share/davmail/lib/* davmail.DavGateway "$@" | |
21 | elif [ -f /usr/share/java/swt4.jar ]; then | |
22 | # SWT 4 is available, force GTK 2 in SWT | |
23 | export LD_LIBRARY_PATH=/usr/lib/jni | |
24 | export SWT_GTK3=0 | |
25 | exec ${JAVA} $JAVA_OPTS -cp /usr/share/davmail/davmail.jar:/usr/share/java/swt4.jar:${JFX_CLASSPATH}:/usr/share/davmail/lib/* davmail.DavGateway "$@" | |
15 | 26 | else |
16 | exec java $JAVA_OPTS -cp /usr/share/davmail/davmail.jar:/usr/share/davmail/lib/* davmail.DavGateway "$@" | |
27 | exec ${JAVA} $JAVA_OPTS -cp /usr/share/davmail/davmail.jar:/usr/share/davmail/lib/*:${JFX_CLASSPATH} davmail.DavGateway "$@" | |
17 | 28 | fi |
0 | [Unit] | |
1 | Description=Davmail Exchange gateway | |
2 | Documentation=http://davmail.sourceforge.net/serversetup.html | |
3 | Documentation=http://davmail.sourceforge.net/advanced.html | |
4 | Documentation=http://davmail.sourceforge.net/sslsetup.html | |
5 | After=network.target | |
6 | ||
7 | [Service] | |
8 | Type=simple | |
9 | User=davmail | |
10 | PermissionsStartOnly=true | |
11 | ExecStartPre=/usr/bin/touch /var/log/davmail.log | |
12 | ExecStartPre=/bin/chown davmail:adm /var/log/davmail.log | |
13 | ExecStart=/usr/bin/davmail /etc/davmail.properties | |
14 | SuccessExitStatus=143 | |
15 | ||
16 | [Install] | |
17 | WantedBy=multi-user.target |
539 | 539 | Settings.save(); |
540 | 540 | } |
541 | 541 | |
542 | /** | |
543 | * Test if running on Windows | |
544 | * | |
545 | * @return true on Windows | |
546 | */ | |
547 | public static boolean isWindows() { | |
548 | return System.getProperty("os.name").toLowerCase().startsWith("windows"); | |
549 | } | |
550 | ||
551 | /** | |
552 | * Test if running on Linux | |
553 | * | |
554 | * @return true on Linux | |
555 | */ | |
556 | public static boolean isLinux() { | |
557 | return System.getProperty("os.name").toLowerCase().startsWith("linux"); | |
558 | } | |
559 | ||
560 | public static boolean isJava8() { | |
561 | return "1.8".equals(System.getProperty("java.specification.version")); | |
562 | } | |
542 | 563 | } |
173 | 173 | // TODO: refactor buildSessionInfo |
174 | 174 | session.buildSessionInfo(null); |
175 | 175 | |
176 | } else if (Settings.EWS.equals(mode) || poolKey.url.toLowerCase().endsWith("/ews/exchange.asmx")) { | |
177 | HttpClient httpClient = DavGatewayHttpClientFacade.getInstance(poolKey.url); | |
178 | DavGatewayHttpClientFacade.setCredentials(httpClient, poolKey.userName, poolKey.password); | |
179 | DavGatewayHttpClientFacade.addNTLM(httpClient); | |
180 | session = new EwsExchangeSession(httpClient, poolKey.userName); | |
181 | session.buildSessionInfo(null); | |
176 | } else if (Settings.EWS.equals(mode)) { | |
177 | if (poolKey.url.toLowerCase().endsWith("/ews/exchange.asmx")) { | |
178 | ExchangeSession.LOGGER.debug("Direct EWS authentication"); | |
179 | HttpClient httpClient = DavGatewayHttpClientFacade.getInstance(poolKey.url); | |
180 | DavGatewayHttpClientFacade.setCredentials(httpClient, poolKey.userName, poolKey.password); | |
181 | DavGatewayHttpClientFacade.addNTLM(httpClient); | |
182 | session = new EwsExchangeSession(httpClient, poolKey.userName); | |
183 | session.buildSessionInfo(null); | |
184 | } else { | |
185 | ExchangeSession.LOGGER.debug("OWA authentication in EWS mode"); | |
186 | session = new EwsExchangeSession(poolKey.url, poolKey.userName, poolKey.password); | |
187 | } | |
182 | 188 | } else { |
183 | 189 | try { |
184 | 190 | session = new DavExchangeSession(poolKey.url, poolKey.userName, poolKey.password); |
133 | 133 | String code; |
134 | 134 | if (federationRedirectUrl != null && !federationRedirectUrl.isEmpty()) { |
135 | 135 | LOGGER.debug("Detected ADFS, redirecting to " + federationRedirectUrl); |
136 | code = authenticateADFS(httpClient, federationRedirectUrl); | |
136 | code = authenticateADFS(httpClient, federationRedirectUrl, url); | |
137 | 137 | } else { |
138 | 138 | PostMethod logonMethod = new PostMethod("https://login.microsoftonline.com/common/login"); |
139 | 139 | logonMethod.setRequestHeader("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"); |
161 | 161 | if (locationHeader == null || !locationHeader.getValue().startsWith(redirectUri)) { |
162 | 162 | // extract response |
163 | 163 | config = extractConfig(logonMethod.getResponseBodyAsString()); |
164 | if (config.optJSONArray("arrScopes") != null) { | |
165 | LOGGER.debug("Authentication successful but user consent needed, please open the following url in a browser"); | |
164 | if (config.optJSONArray("arrScopes") != null || config.optJSONArray("urlPostRedirect") != null) { | |
165 | LOGGER.debug("Authentication successful but user consent or validation needed, please open the following url in a browser"); | |
166 | 166 | LOGGER.debug(url); |
167 | 167 | throw new DavMailAuthenticationException("EXCEPTION_AUTHENTICATION_FAILED"); |
168 | 168 | } else if ("50126".equals(config.optString("sErrorCode"))) { |
198 | 198 | |
199 | 199 | } |
200 | 200 | |
201 | private String authenticateADFS(HttpClient httpClient, String federationRedirectUrl) throws IOException { | |
201 | private String authenticateADFS(HttpClient httpClient, String federationRedirectUrl, String authorizeUrl) throws IOException { | |
202 | // set NTLM credentials | |
203 | DavGatewayHttpClientFacade.setCredentials(httpClient, userid, password); | |
204 | DavGatewayHttpClientFacade.addNTLM(httpClient); | |
202 | 205 | String responseBodyAsString; |
203 | 206 | |
204 | 207 | // get ADFS login form |
211 | 214 | logonFormMethod.releaseConnection(); |
212 | 215 | } |
213 | 216 | |
214 | // parse form to get target url, authenticate as userid | |
215 | PostMethod logonMethod = new PostMethod(extract("method=\"post\" action=\"([^\"]+)\"", responseBodyAsString)); | |
216 | logonMethod.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); | |
217 | ||
218 | logonMethod.setParameter("UserName", userid); | |
219 | logonMethod.setParameter("Password", password); | |
220 | logonMethod.setParameter("AuthMethod", "FormsAuthentication"); | |
221 | ||
222 | try { | |
223 | httpClient.executeMethod(logonMethod); | |
224 | } finally { | |
225 | logonMethod.releaseConnection(); | |
226 | } | |
227 | ||
228 | if (logonMethod.getStatusCode() != HttpStatus.SC_MOVED_TEMPORARILY || logonMethod.getResponseHeader("Location") == null) { | |
229 | throw new DavMailAuthenticationException("EXCEPTION_AUTHENTICATION_FAILED"); | |
230 | } | |
231 | ||
232 | GetMethod redirectMethod = new GetMethod(logonMethod.getResponseHeader("Location").getValue()); | |
233 | try { | |
234 | httpClient.executeMethod(redirectMethod); | |
235 | responseBodyAsString = redirectMethod.getResponseBodyAsString(); | |
236 | } finally { | |
237 | redirectMethod.releaseConnection(); | |
217 | String location; | |
218 | ||
219 | if (responseBodyAsString.contains("login.microsoftonline.com")) { | |
220 | LOGGER.info("Already authenticated through Basic or NTLM"); | |
221 | } else { | |
222 | // parse form to get target url, authenticate as userid | |
223 | PostMethod logonMethod = new PostMethod(extract("method=\"post\" action=\"([^\"]+)\"", responseBodyAsString)); | |
224 | logonMethod.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); | |
225 | ||
226 | logonMethod.setParameter("UserName", userid); | |
227 | logonMethod.setParameter("Password", password); | |
228 | logonMethod.setParameter("AuthMethod", "FormsAuthentication"); | |
229 | ||
230 | try { | |
231 | httpClient.executeMethod(logonMethod); | |
232 | } finally { | |
233 | logonMethod.releaseConnection(); | |
234 | } | |
235 | ||
236 | if (logonMethod.getStatusCode() != HttpStatus.SC_MOVED_TEMPORARILY || logonMethod.getResponseHeader("Location") == null) { | |
237 | throw new DavMailAuthenticationException("EXCEPTION_AUTHENTICATION_FAILED"); | |
238 | } | |
239 | location = logonMethod.getResponseHeader("Location").getValue(); | |
240 | GetMethod redirectMethod = new GetMethod(location); | |
241 | try { | |
242 | httpClient.executeMethod(redirectMethod); | |
243 | responseBodyAsString = redirectMethod.getResponseBodyAsString(); | |
244 | } finally { | |
245 | redirectMethod.releaseConnection(); | |
246 | } | |
238 | 247 | } |
239 | 248 | |
240 | 249 | if (!responseBodyAsString.contains("login.microsoftonline.com")) { |
257 | 266 | targetMethod.setParameter("wctx", wctx); |
258 | 267 | try { |
259 | 268 | httpClient.executeMethod(targetMethod); |
260 | responseBodyAsString = logonMethod.getResponseBodyAsString(); | |
269 | responseBodyAsString = targetMethod.getResponseBodyAsString(); | |
261 | 270 | } finally { |
262 | 271 | targetMethod.releaseConnection(); |
263 | 272 | } |
266 | 275 | LOGGER.debug(targetMethod.getStatusLine()); |
267 | 276 | LOGGER.debug(responseBodyAsString); |
268 | 277 | |
269 | throw new IOException("ADFS authentication not yet implemented"); | |
278 | if (targetMethod.getStatusCode() == HttpStatus.SC_OK) { | |
279 | JSONObject config = extractConfig(responseBodyAsString); | |
280 | if (config.optJSONArray("arrScopes") != null || config.optJSONArray("urlPostRedirect") != null) { | |
281 | LOGGER.debug("Authentication successful but user consent or validation needed, please open the following url in a browser"); | |
282 | LOGGER.debug(authorizeUrl); | |
283 | throw new DavMailAuthenticationException("EXCEPTION_AUTHENTICATION_FAILED"); | |
284 | } | |
285 | } else if (targetMethod.getStatusCode() != HttpStatus.SC_MOVED_TEMPORARILY || targetMethod.getRequestHeader("Location") == null) { | |
286 | throw new IOException("Unknown ADFS authentication failure"); | |
287 | } | |
288 | ||
289 | location = targetMethod.getRequestHeader("Location").getValue(); | |
290 | if (location.contains("code=") && location.contains("&session_state=")) { | |
291 | String code = location.substring(location.indexOf("code=") + 5, location.indexOf("&session_state=")); | |
292 | LOGGER.debug("Authentication Code: " + code); | |
293 | return code; | |
294 | } | |
295 | throw new IOException("Unknown ADFS authentication failure"); | |
270 | 296 | } |
271 | 297 | |
272 | 298 | private PostMethod handleMfa(HttpClient httpClient, PostMethod logonMethod, String username, String clientRequestId) throws JSONException, IOException { |
17 | 17 | */ |
18 | 18 | package davmail.exchange.auth; |
19 | 19 | |
20 | import davmail.BundleMessage; | |
21 | 20 | import davmail.Settings; |
21 | import davmail.exception.DavMailAuthenticationException; | |
22 | import davmail.exception.DavMailException; | |
22 | 23 | import davmail.exchange.ews.BaseShape; |
23 | 24 | import davmail.exchange.ews.DistinguishedFolderId; |
24 | 25 | import davmail.exchange.ews.GetFolderMethod; |
25 | 26 | import davmail.exchange.ews.GetUserConfigurationMethod; |
26 | 27 | import davmail.http.DavGatewayHttpClientFacade; |
27 | import davmail.ui.tray.DavGatewayTray; | |
28 | import javafx.application.Platform; | |
29 | import javafx.beans.value.ChangeListener; | |
30 | import javafx.beans.value.ObservableValue; | |
31 | import javafx.concurrent.Worker.State; | |
32 | import javafx.embed.swing.JFXPanel; | |
33 | import javafx.scene.Scene; | |
34 | import javafx.scene.control.ProgressBar; | |
35 | import javafx.scene.layout.StackPane; | |
36 | import javafx.scene.web.WebEngine; | |
37 | import javafx.scene.web.WebView; | |
38 | 28 | import org.apache.commons.httpclient.HttpClient; |
39 | 29 | import org.apache.commons.httpclient.util.URIUtil; |
40 | 30 | import org.apache.log4j.Logger; |
41 | import org.w3c.dom.Document; | |
42 | 31 | |
43 | 32 | import javax.swing.*; |
44 | import javax.xml.transform.OutputKeys; | |
45 | import javax.xml.transform.Transformer; | |
46 | import javax.xml.transform.TransformerFactory; | |
47 | import javax.xml.transform.dom.DOMSource; | |
48 | import javax.xml.transform.stream.StreamResult; | |
49 | import java.awt.*; | |
50 | import java.io.ByteArrayOutputStream; | |
51 | 33 | import java.io.IOException; |
52 | import java.io.OutputStreamWriter; | |
53 | import java.net.*; | |
54 | ||
55 | public class O365InteractiveAuthenticator extends JFrame implements ExchangeAuthenticator { | |
56 | ||
34 | import java.net.Authenticator; | |
35 | import java.net.PasswordAuthentication; | |
36 | ||
37 | public class O365InteractiveAuthenticator implements ExchangeAuthenticator { | |
38 | ||
39 | private static final int MAX_COUNT = 300; | |
57 | 40 | private static final Logger LOGGER = Logger.getLogger(O365InteractiveAuthenticator.class); |
58 | 41 | |
59 | private static final int MAX_COUNT = 300; | |
60 | ||
61 | static { | |
62 | // register a stream handler for msauth protocol | |
63 | try { | |
64 | URL.setURLStreamHandlerFactory(new URLStreamHandlerFactory() { | |
65 | @Override | |
66 | public URLStreamHandler createURLStreamHandler(String protocol) { | |
67 | if ("msauth".equals(protocol)) { | |
68 | return new URLStreamHandler() { | |
69 | @Override | |
70 | protected URLConnection openConnection(URL u) { | |
71 | return new URLConnection(u) { | |
72 | @Override | |
73 | public void connect() { | |
74 | // ignore | |
75 | } | |
76 | }; | |
77 | } | |
78 | }; | |
79 | } | |
80 | return null; | |
81 | } | |
82 | }); | |
83 | } catch (Throwable t) { | |
84 | LOGGER.warn("Unable to register msauth protocol handler"); | |
85 | } | |
86 | } | |
87 | ||
88 | String location; | |
89 | 42 | boolean isAuthenticated = false; |
90 | String errorCode = ""; | |
91 | final JFXPanel fxPanel = new JFXPanel(); | |
92 | ||
93 | public O365InteractiveAuthenticator() { | |
94 | setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); | |
95 | setTitle(BundleMessage.format("UI_DAVMAIL_GATEWAY")); | |
96 | try { | |
97 | setIconImages(DavGatewayTray.getFrameIcons()); | |
98 | } catch (NoSuchMethodError error) { | |
99 | DavGatewayTray.debug(new BundleMessage("LOG_UNABLE_TO_SET_ICON_IMAGE")); | |
100 | } | |
101 | ||
102 | JPanel mainPanel = new JPanel(); | |
103 | ||
104 | mainPanel.add(fxPanel); | |
105 | add(BorderLayout.CENTER, mainPanel); | |
106 | ||
107 | pack(); | |
108 | setResizable(true); | |
109 | // center frame | |
110 | setSize(600, 600); | |
111 | setLocation(getToolkit().getScreenSize().width / 2 - | |
112 | getSize().width / 2, | |
113 | getToolkit().getScreenSize().height / 2 - | |
114 | getSize().height / 2); | |
115 | setVisible(true); | |
116 | } | |
117 | ||
118 | private void initFX(final JFXPanel fxPanel, final String url, final String redirectUri) { | |
119 | WebView webView = new WebView(); | |
120 | final WebEngine webViewEngine = webView.getEngine(); | |
121 | ||
122 | final ProgressBar loadProgress = new ProgressBar(); | |
123 | loadProgress.progressProperty().bind(webViewEngine.getLoadWorker().progressProperty()); | |
124 | ||
125 | StackPane hBox = new StackPane(); | |
126 | hBox.getChildren().setAll(webView, loadProgress); | |
127 | Scene scene = new Scene(hBox); | |
128 | fxPanel.setScene(scene); | |
129 | ||
130 | webViewEngine.setUserAgent(DavGatewayHttpClientFacade.getUserAgent()); | |
131 | ||
132 | webViewEngine.getLoadWorker().stateProperty().addListener(new ChangeListener<State>() { | |
133 | @Override | |
134 | public void changed(ObservableValue ov, State oldState, State newState) { | |
135 | LOGGER.debug(webViewEngine.getLocation()); | |
136 | LOGGER.debug(dumpDocument(webViewEngine.getDocument())); | |
137 | if (newState == State.SUCCEEDED) { | |
138 | loadProgress.setVisible(false); | |
139 | // bring window to top | |
140 | setAlwaysOnTop(true); | |
141 | setAlwaysOnTop(false); | |
142 | location = webViewEngine.getLocation(); | |
143 | setTitle("DavMail: " + location); | |
144 | LOGGER.debug("Webview location: " + location); | |
145 | if (LOGGER.isDebugEnabled()) { | |
146 | LOGGER.debug(dumpDocument(webViewEngine.getDocument())); | |
147 | } | |
148 | if (location.startsWith(redirectUri)) { | |
149 | LOGGER.debug("Location starts with redirectUri, check code"); | |
150 | ||
151 | isAuthenticated = location.contains("code=") && location.contains("&session_state="); | |
152 | if (!isAuthenticated && location.contains("error=")) { | |
153 | errorCode = location.substring(location.indexOf("error=")); | |
154 | } | |
155 | close(); | |
156 | } | |
157 | } else if (newState == State.FAILED) { | |
158 | Throwable e = webViewEngine.getLoadWorker().getException(); | |
159 | if (e != null) { | |
160 | LOGGER.error(e + " " + e.getMessage()); | |
161 | errorCode = e.getMessage(); | |
162 | } | |
163 | close(); | |
164 | } | |
165 | ||
166 | } | |
167 | ||
168 | }); | |
169 | webViewEngine.load(url); | |
170 | ||
171 | ||
172 | } | |
173 | ||
174 | public String dumpDocument(Document document) { | |
175 | String result; | |
176 | try { | |
177 | ByteArrayOutputStream baos = new ByteArrayOutputStream(); | |
178 | Transformer transformer = TransformerFactory.newInstance().newTransformer(); | |
179 | transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "no"); | |
180 | transformer.setOutputProperty(OutputKeys.METHOD, "xml"); | |
181 | transformer.setOutputProperty(OutputKeys.INDENT, "yes"); | |
182 | transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8"); | |
183 | transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4"); | |
184 | ||
185 | transformer.transform(new DOMSource(document), | |
186 | new StreamResult(new OutputStreamWriter(baos, "UTF-8"))); | |
187 | result = baos.toString("UTF-8"); | |
188 | } catch (Exception e) { | |
189 | result = e + " " + e.getMessage(); | |
190 | } | |
191 | return result; | |
192 | } | |
43 | String errorCode = null; | |
44 | String code = null; | |
193 | 45 | |
194 | 46 | String resource = "https://outlook.office365.com"; |
195 | 47 | String ewsUrl = resource + "/EWS/Exchange.asmx"; |
196 | 48 | String authorizeUrl = "https://login.microsoftonline.com/common/oauth2/authorize"; |
49 | ||
50 | private O365InteractiveAuthenticatorFrame o365InteractiveAuthenticatorFrame; | |
197 | 51 | |
198 | 52 | private String username; |
199 | 53 | private String password; |
255 | 109 | } |
256 | 110 | } |
257 | 111 | }); |
258 | Platform.setImplicitExit(false); | |
259 | ||
260 | // Run initFX as JavaFX-Thread | |
261 | Platform.runLater(new Runnable() { | |
112 | ||
113 | SwingUtilities.invokeLater(new Runnable() { | |
262 | 114 | @Override |
263 | 115 | public void run() { |
264 | 116 | try { |
265 | initFX(fxPanel, initUrl, redirectUri); | |
266 | } catch (Throwable e) { | |
267 | LOGGER.error(e + " " + e.getMessage()); | |
268 | errorCode = e.getMessage(); | |
269 | close(); | |
117 | o365InteractiveAuthenticatorFrame = new O365InteractiveAuthenticatorFrame(); | |
118 | o365InteractiveAuthenticatorFrame.setO365InteractiveAuthenticator(O365InteractiveAuthenticator.this); | |
119 | o365InteractiveAuthenticatorFrame.authenticate(initUrl, redirectUri); | |
120 | } catch (NoClassDefFoundError e) { | |
121 | errorCode = "Unable to load JavaFX (OpenJFX)"; | |
270 | 122 | } |
271 | 123 | } |
272 | 124 | }); |
273 | 125 | |
274 | 126 | int count = 0; |
275 | 127 | |
276 | while (!isAuthenticated && isVisible() && count++ < MAX_COUNT) { | |
128 | while (!isAuthenticated && errorCode == null && count++ < MAX_COUNT) { | |
277 | 129 | try { |
278 | 130 | Thread.sleep(1000); |
279 | 131 | } catch (InterruptedException e) { |
280 | 132 | // ignore |
281 | 133 | } |
282 | 134 | } |
283 | if (isVisible()) { | |
284 | close(); | |
135 | ||
136 | if (count > MAX_COUNT) { | |
137 | errorCode = "Timed out waiting for interactive authentication"; | |
138 | } | |
139 | ||
140 | if (o365InteractiveAuthenticatorFrame != null && o365InteractiveAuthenticatorFrame.isVisible()) { | |
141 | o365InteractiveAuthenticatorFrame.close(); | |
285 | 142 | } |
286 | 143 | |
287 | 144 | if (isAuthenticated) { |
288 | LOGGER.debug("Authenticated location: " + location); | |
289 | String code = location.substring(location.indexOf("code=") + 5, location.indexOf("&session_state=")); | |
290 | String sessionState = location.substring(location.lastIndexOf('=')); | |
291 | ||
292 | LOGGER.debug("Authentication Code: " + code); | |
293 | LOGGER.debug("Authentication session state: " + sessionState); | |
294 | ||
295 | 145 | token = new O365Token(clientId, redirectUri, code); |
296 | 146 | |
297 | 147 | LOGGER.debug("Authenticated username: " + token.getUsername()); |
298 | 148 | if (username != null && !username.isEmpty() && !username.equalsIgnoreCase(token.getUsername())) { |
299 | throw new IOException("Authenticated username " + token.getUsername() + " does not match " + username); | |
149 | throw new DavMailAuthenticationException("Authenticated username " + token.getUsername() + " does not match " + username); | |
300 | 150 | } |
301 | 151 | |
302 | 152 | } else { |
303 | 153 | LOGGER.error("Authentication failed " + errorCode); |
304 | throw new IOException("Authentication failed " + errorCode); | |
305 | } | |
306 | } | |
307 | ||
308 | void close() { | |
309 | setVisible(false); | |
310 | dispose(); | |
154 | throw new DavMailException("EXCEPTION_AUTHENTICATION_FAILED_REASON", errorCode); | |
155 | } | |
311 | 156 | } |
312 | 157 | |
313 | 158 | public static void main(String[] argv) { |
315 | 160 | Settings.setDefaultSettings(); |
316 | 161 | //Settings.setLoggingLevel("httpclient.wire", Level.DEBUG); |
317 | 162 | |
318 | O365InteractiveAuthenticator authenticationFrame = new O365InteractiveAuthenticator(); | |
319 | authenticationFrame.setUsername(""); | |
320 | authenticationFrame.authenticate(); | |
163 | O365InteractiveAuthenticator authenticator = new O365InteractiveAuthenticator(); | |
164 | authenticator.setUsername(""); | |
165 | authenticator.authenticate(); | |
321 | 166 | |
322 | 167 | // switch to EWS url |
323 | HttpClient httpClient = DavGatewayHttpClientFacade.getInstance(authenticationFrame.ewsUrl); | |
324 | DavGatewayHttpClientFacade.createMultiThreadedHttpConnectionManager(httpClient); | |
168 | HttpClient httpClient = DavGatewayHttpClientFacade.getInstance(authenticator.ewsUrl); | |
325 | 169 | |
326 | 170 | GetFolderMethod checkMethod = new GetFolderMethod(BaseShape.ID_ONLY, DistinguishedFolderId.getInstance(null, DistinguishedFolderId.Name.root), null); |
327 | checkMethod.setRequestHeader("Authorization", "Bearer " + authenticationFrame.getToken().getAccessToken()); | |
171 | checkMethod.setRequestHeader("Authorization", "Bearer " + authenticator.getToken().getAccessToken()); | |
328 | 172 | try { |
329 | 173 | //checkMethod.setServerVersion(serverVersion); |
330 | 174 | httpClient.executeMethod(checkMethod); |
339 | 183 | int i = 0; |
340 | 184 | while (i++ < 12 * 60 * 2) { |
341 | 185 | GetUserConfigurationMethod getUserConfigurationMethod = new GetUserConfigurationMethod(); |
342 | getUserConfigurationMethod.setRequestHeader("Authorization", "Bearer " + authenticationFrame.getToken().getAccessToken()); | |
186 | getUserConfigurationMethod.setRequestHeader("Authorization", "Bearer " + authenticator.getToken().getAccessToken()); | |
343 | 187 | httpClient.executeMethod(getUserConfigurationMethod); |
344 | 188 | System.out.println(getUserConfigurationMethod.getResponseItem()); |
345 | 189 |
0 | /* | |
1 | * DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway | |
2 | * Copyright (C) 2010 Mickael Guessant | |
3 | * | |
4 | * This program is free software; you can redistribute it and/or | |
5 | * modify it under the terms of the GNU General Public License | |
6 | * as published by the Free Software Foundation; either version 2 | |
7 | * of the License, or (at your option) any later version. | |
8 | * | |
9 | * This program is distributed in the hope that it will be useful, | |
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
12 | * GNU General Public License for more details. | |
13 | * | |
14 | * You should have received a copy of the GNU General Public License | |
15 | * along with this program; if not, write to the Free Software | |
16 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |
17 | */ | |
18 | ||
19 | package davmail.exchange.auth; | |
20 | ||
21 | import davmail.BundleMessage; | |
22 | import davmail.http.DavGatewayHttpClientFacade; | |
23 | import davmail.ui.tray.DavGatewayTray; | |
24 | import davmail.util.IOUtil; | |
25 | import javafx.application.Platform; | |
26 | import javafx.beans.value.ChangeListener; | |
27 | import javafx.beans.value.ObservableValue; | |
28 | import javafx.concurrent.Worker; | |
29 | import javafx.embed.swing.JFXPanel; | |
30 | import javafx.scene.Scene; | |
31 | import javafx.scene.control.ProgressBar; | |
32 | import javafx.scene.layout.StackPane; | |
33 | import javafx.scene.web.WebEngine; | |
34 | import javafx.scene.web.WebView; | |
35 | import org.apache.log4j.Logger; | |
36 | import org.w3c.dom.Document; | |
37 | ||
38 | import javax.swing.*; | |
39 | import javax.xml.transform.OutputKeys; | |
40 | import javax.xml.transform.Transformer; | |
41 | import javax.xml.transform.TransformerFactory; | |
42 | import javax.xml.transform.dom.DOMSource; | |
43 | import javax.xml.transform.stream.StreamResult; | |
44 | import java.awt.*; | |
45 | import java.awt.event.WindowAdapter; | |
46 | import java.awt.event.WindowEvent; | |
47 | import java.io.*; | |
48 | import java.net.*; | |
49 | import java.util.List; | |
50 | import java.util.Map; | |
51 | ||
52 | public class O365InteractiveAuthenticatorFrame extends JFrame { | |
53 | private static final Logger LOGGER = Logger.getLogger(O365InteractiveAuthenticatorFrame.class); | |
54 | ||
55 | private O365InteractiveAuthenticator authenticator; | |
56 | ||
57 | static { | |
58 | // register a stream handler for msauth protocol | |
59 | try { | |
60 | URL.setURLStreamHandlerFactory(new URLStreamHandlerFactory() { | |
61 | @Override | |
62 | public URLStreamHandler createURLStreamHandler(String protocol) { | |
63 | if ("msauth".equals(protocol) || "urn".equals(protocol)) { | |
64 | return new URLStreamHandler() { | |
65 | @Override | |
66 | protected URLConnection openConnection(URL u) { | |
67 | return new URLConnection(u) { | |
68 | @Override | |
69 | public void connect() { | |
70 | // ignore | |
71 | } | |
72 | }; | |
73 | } | |
74 | }; | |
75 | } else if ("https".equals(protocol)) { | |
76 | return new sun.net.www.protocol.https.Handler() { | |
77 | @Override | |
78 | protected URLConnection openConnection(URL url) throws IOException { | |
79 | return openConnection(url, null); | |
80 | } | |
81 | ||
82 | @Override | |
83 | protected URLConnection openConnection(URL url, Proxy proxy) throws IOException { | |
84 | LOGGER.debug("openConnection " + url); | |
85 | ||
86 | if (url.toExternalForm().endsWith("/common/handlers/watson")) { | |
87 | LOGGER.warn("Failed: form calls watson"); | |
88 | } | |
89 | final HttpURLConnection httpsURLConnection = (HttpURLConnection) super.openConnection(url, proxy); | |
90 | if (("login.microsoftonline.com".equals(url.getHost()) && "/common/oauth2/authorize".equals(url.getPath())) | |
91 | || ("login.live.com".equals(url.getHost()) && "/oauth20_authorize.srf".equals(url.getPath())) | |
92 | || ("login.live.com".equals(url.getHost()) && "/ppsecure/post.srf".equals(url.getPath())) | |
93 | || ("login.microsoftonline.com".equals(url.getHost()) && "/common/login".equals(url.getPath())) | |
94 | || ("login.microsoftonline.com".equals(url.getHost()) && "/common/SAS/ProcessAuth".equals(url.getPath())) | |
95 | ) { | |
96 | LOGGER.debug("Disable integrity check on external resources"); | |
97 | ||
98 | return new HttpURLConnection(url) { | |
99 | @Override | |
100 | public void setRequestMethod(String method) throws ProtocolException { | |
101 | httpsURLConnection.setRequestMethod(method); | |
102 | } | |
103 | ||
104 | @Override | |
105 | public void setInstanceFollowRedirects(boolean followRedirects) { | |
106 | httpsURLConnection.setInstanceFollowRedirects(followRedirects); | |
107 | } | |
108 | ||
109 | @Override | |
110 | public boolean getInstanceFollowRedirects() { | |
111 | return httpsURLConnection.getInstanceFollowRedirects(); | |
112 | } | |
113 | ||
114 | @Override | |
115 | public String getRequestMethod() { | |
116 | return httpsURLConnection.getRequestMethod(); | |
117 | } | |
118 | ||
119 | @Override | |
120 | public int getResponseCode() throws IOException { | |
121 | return httpsURLConnection.getResponseCode(); | |
122 | } | |
123 | ||
124 | @Override | |
125 | public String getResponseMessage() throws IOException { | |
126 | return httpsURLConnection.getResponseMessage(); | |
127 | } | |
128 | ||
129 | @Override | |
130 | public Map<String,List<String>> getHeaderFields() { | |
131 | LOGGER.debug(httpsURLConnection.getHeaderFields()); | |
132 | return httpsURLConnection.getHeaderFields(); | |
133 | } | |
134 | ||
135 | @Override | |
136 | public String getHeaderField(String name) { | |
137 | return httpsURLConnection.getHeaderField(name); | |
138 | } | |
139 | ||
140 | @Override | |
141 | public String getHeaderField(int n) { | |
142 | return httpsURLConnection.getHeaderField(n); | |
143 | } | |
144 | ||
145 | @Override | |
146 | public void disconnect() { | |
147 | httpsURLConnection.disconnect(); | |
148 | } | |
149 | ||
150 | @Override | |
151 | public void setDoOutput(boolean dooutput) { | |
152 | httpsURLConnection.setDoOutput(dooutput); | |
153 | } | |
154 | ||
155 | @Override | |
156 | public boolean usingProxy() { | |
157 | return httpsURLConnection.usingProxy(); | |
158 | } | |
159 | ||
160 | @Override | |
161 | public void connect() throws IOException { | |
162 | httpsURLConnection.connect(); | |
163 | } | |
164 | ||
165 | @Override | |
166 | public InputStream getInputStream() throws IOException { | |
167 | byte[] content = IOUtil.readFully(httpsURLConnection.getInputStream()); | |
168 | String contentAsString = new String(content, "UTF-8"); | |
169 | LOGGER.debug(contentAsString); | |
170 | ByteArrayOutputStream baos = new ByteArrayOutputStream(); | |
171 | baos.write(contentAsString.replaceAll("integrity", "integrity.disabled").getBytes("UTF-8")); | |
172 | return new ByteArrayInputStream(baos.toByteArray()); | |
173 | } | |
174 | ||
175 | @Override | |
176 | public OutputStream getOutputStream() throws IOException { | |
177 | return httpsURLConnection.getOutputStream(); | |
178 | } | |
179 | ||
180 | @Override | |
181 | public InputStream getErrorStream() { | |
182 | return httpsURLConnection.getErrorStream(); | |
183 | } | |
184 | ||
185 | }; | |
186 | ||
187 | } else { | |
188 | return httpsURLConnection; | |
189 | } | |
190 | } | |
191 | ||
192 | }; | |
193 | } | |
194 | return null; | |
195 | } | |
196 | }); | |
197 | } catch (Throwable t) { | |
198 | LOGGER.warn("Unable to register protocol handler"); | |
199 | } | |
200 | } | |
201 | ||
202 | String location; | |
203 | final JFXPanel fxPanel = new JFXPanel(); | |
204 | ||
205 | public O365InteractiveAuthenticatorFrame() { | |
206 | setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); | |
207 | addWindowListener(new WindowAdapter() { | |
208 | @Override | |
209 | public void windowClosing(WindowEvent e) { | |
210 | if (!authenticator.isAuthenticated && authenticator.errorCode == null) { | |
211 | authenticator.errorCode = "user closed authentication window"; | |
212 | } | |
213 | } | |
214 | }); | |
215 | ||
216 | setTitle(BundleMessage.format("UI_DAVMAIL_GATEWAY")); | |
217 | try { | |
218 | setIconImages(DavGatewayTray.getFrameIcons()); | |
219 | } catch (NoSuchMethodError error) { | |
220 | DavGatewayTray.debug(new BundleMessage("LOG_UNABLE_TO_SET_ICON_IMAGE")); | |
221 | } | |
222 | ||
223 | JPanel mainPanel = new JPanel(); | |
224 | ||
225 | mainPanel.add(fxPanel); | |
226 | add(BorderLayout.CENTER, mainPanel); | |
227 | ||
228 | pack(); | |
229 | setResizable(true); | |
230 | // center frame | |
231 | setSize(600, 600); | |
232 | setLocationRelativeTo(null); | |
233 | setVisible(true); | |
234 | // bring window to top | |
235 | setAlwaysOnTop(true); | |
236 | setAlwaysOnTop(false); | |
237 | } | |
238 | ||
239 | public void setO365InteractiveAuthenticator(O365InteractiveAuthenticator authenticator) { | |
240 | this.authenticator = authenticator; | |
241 | } | |
242 | ||
243 | private void initFX(final JFXPanel fxPanel, final String url, final String redirectUri) { | |
244 | WebView webView = new WebView(); | |
245 | final WebEngine webViewEngine = webView.getEngine(); | |
246 | ||
247 | final ProgressBar loadProgress = new ProgressBar(); | |
248 | loadProgress.progressProperty().bind(webViewEngine.getLoadWorker().progressProperty()); | |
249 | ||
250 | StackPane hBox = new StackPane(); | |
251 | hBox.getChildren().setAll(webView, loadProgress); | |
252 | Scene scene = new Scene(hBox); | |
253 | fxPanel.setScene(scene); | |
254 | ||
255 | webViewEngine.setUserAgent(DavGatewayHttpClientFacade.getUserAgent()); | |
256 | ||
257 | webViewEngine.getLoadWorker().stateProperty().addListener(new ChangeListener<Worker.State>() { | |
258 | @Override | |
259 | public void changed(ObservableValue ov, Worker.State oldState, Worker.State newState) { | |
260 | if (newState == Worker.State.SUCCEEDED) { | |
261 | loadProgress.setVisible(false); | |
262 | location = webViewEngine.getLocation(); | |
263 | updateTitleAndFocus(location); | |
264 | LOGGER.debug("Webview location: " + location); | |
265 | // override console.log | |
266 | O365InteractiveJSLogger.register(webViewEngine); | |
267 | if (LOGGER.isDebugEnabled()) { | |
268 | LOGGER.debug(dumpDocument(webViewEngine.getDocument())); | |
269 | } | |
270 | if (location.startsWith(redirectUri)) { | |
271 | LOGGER.debug("Location starts with redirectUri, check code"); | |
272 | ||
273 | authenticator.isAuthenticated = location.contains("code=") && location.contains("&session_state="); | |
274 | if (!authenticator.isAuthenticated && location.contains("error=")) { | |
275 | authenticator.errorCode = location.substring(location.indexOf("error=")); | |
276 | } | |
277 | if (authenticator.isAuthenticated) { | |
278 | LOGGER.debug("Authenticated location: " + location); | |
279 | String code = location.substring(location.indexOf("code=") + 5, location.indexOf("&session_state=")); | |
280 | String sessionState = location.substring(location.lastIndexOf('=')); | |
281 | ||
282 | LOGGER.debug("Authentication Code: " + code); | |
283 | LOGGER.debug("Authentication session state: " + sessionState); | |
284 | authenticator.code = code; | |
285 | } | |
286 | close(); | |
287 | } | |
288 | } else if (newState == Worker.State.FAILED) { | |
289 | Throwable e = webViewEngine.getLoadWorker().getException(); | |
290 | if (e != null) { | |
291 | LOGGER.error(e + " " + e.getMessage()); | |
292 | authenticator.errorCode = e.getMessage(); | |
293 | if (authenticator.errorCode == null) { | |
294 | authenticator.errorCode = e.toString(); | |
295 | } | |
296 | } | |
297 | close(); | |
298 | } | |
299 | ||
300 | } | |
301 | ||
302 | }); | |
303 | webViewEngine.load(url); | |
304 | } | |
305 | ||
306 | private void updateTitleAndFocus(final String location) { | |
307 | SwingUtilities.invokeLater(new Runnable() { | |
308 | @Override | |
309 | public void run() { | |
310 | setState(JFrame.NORMAL); | |
311 | setAlwaysOnTop(true); | |
312 | setAlwaysOnTop(false); | |
313 | setTitle("DavMail: " + location); | |
314 | } | |
315 | }); | |
316 | } | |
317 | ||
318 | public String dumpDocument(Document document) { | |
319 | String result; | |
320 | try { | |
321 | ByteArrayOutputStream baos = new ByteArrayOutputStream(); | |
322 | Transformer transformer = TransformerFactory.newInstance().newTransformer(); | |
323 | transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "no"); | |
324 | transformer.setOutputProperty(OutputKeys.METHOD, "xml"); | |
325 | transformer.setOutputProperty(OutputKeys.INDENT, "yes"); | |
326 | transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8"); | |
327 | transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4"); | |
328 | ||
329 | transformer.transform(new DOMSource(document), | |
330 | new StreamResult(new OutputStreamWriter(baos, "UTF-8"))); | |
331 | result = baos.toString("UTF-8"); | |
332 | } catch (Exception e) { | |
333 | result = e + " " + e.getMessage(); | |
334 | } | |
335 | return result; | |
336 | } | |
337 | ||
338 | public void authenticate(final String initUrl, final String redirectUri) { | |
339 | // Run initFX as JavaFX-Thread | |
340 | Platform.runLater(new Runnable() { | |
341 | @Override | |
342 | public void run() { | |
343 | try { | |
344 | Platform.setImplicitExit(false); | |
345 | ||
346 | initFX(fxPanel, initUrl, redirectUri); | |
347 | } catch (Throwable e) { | |
348 | LOGGER.error(e + " " + e.getMessage()); | |
349 | authenticator.errorCode = e.getMessage(); | |
350 | if (authenticator.errorCode == null) { | |
351 | authenticator.errorCode = e.toString(); | |
352 | } | |
353 | close(); | |
354 | } | |
355 | } | |
356 | }); | |
357 | ||
358 | ||
359 | } | |
360 | ||
361 | public void close() { | |
362 | SwingUtilities.invokeLater(new Runnable() { | |
363 | @Override | |
364 | public void run() { | |
365 | setVisible(false); | |
366 | dispose(); | |
367 | } | |
368 | }); | |
369 | } | |
370 | ||
371 | } |
0 | /* | |
1 | * DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway | |
2 | * Copyright (C) 2010 Mickael Guessant | |
3 | * | |
4 | * This program is free software; you can redistribute it and/or | |
5 | * modify it under the terms of the GNU General Public License | |
6 | * as published by the Free Software Foundation; either version 2 | |
7 | * of the License, or (at your option) any later version. | |
8 | * | |
9 | * This program is distributed in the hope that it will be useful, | |
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
12 | * GNU General Public License for more details. | |
13 | * | |
14 | * You should have received a copy of the GNU General Public License | |
15 | * along with this program; if not, write to the Free Software | |
16 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |
17 | */ | |
18 | ||
19 | package davmail.exchange.auth; | |
20 | ||
21 | import javafx.scene.web.WebEngine; | |
22 | import netscape.javascript.JSObject; | |
23 | import org.apache.log4j.Logger; | |
24 | ||
25 | public class O365InteractiveJSLogger { | |
26 | private static final Logger LOGGER = Logger.getLogger(O365InteractiveJSLogger.class); | |
27 | public void log(String message) { | |
28 | LOGGER.info(message); | |
29 | } | |
30 | ||
31 | public static void register(WebEngine webEngine) { | |
32 | JSObject window = (JSObject) webEngine.executeScript("window"); | |
33 | window.setMember("davmail", new O365InteractiveJSLogger()); | |
34 | webEngine.executeScript("console.log = function(message) { davmail.log(message); }"); | |
35 | } | |
36 | } |
0 | /* | |
1 | * DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway | |
2 | * Copyright (C) 2010 Mickael Guessant | |
3 | * | |
4 | * This program is free software; you can redistribute it and/or | |
5 | * modify it under the terms of the GNU General Public License | |
6 | * as published by the Free Software Foundation; either version 2 | |
7 | * of the License, or (at your option) any later version. | |
8 | * | |
9 | * This program is distributed in the hope that it will be useful, | |
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
12 | * GNU General Public License for more details. | |
13 | * | |
14 | * You should have received a copy of the GNU General Public License | |
15 | * along with this program; if not, write to the Free Software | |
16 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |
17 | */ | |
18 | ||
19 | package davmail.exchange.auth; | |
20 | ||
21 | import davmail.Settings; | |
22 | import org.apache.log4j.Logger; | |
23 | ||
24 | import java.io.IOException; | |
25 | ||
26 | /** | |
27 | * Experimental: load Oauth2 token from settings | |
28 | */ | |
29 | public class O365StoredTokenAuthenticator implements ExchangeAuthenticator { | |
30 | private static final Logger LOGGER = Logger.getLogger(O365StoredTokenAuthenticator.class); | |
31 | ||
32 | String resource = "https://outlook.office365.com"; | |
33 | String ewsUrl = resource + "/EWS/Exchange.asmx"; | |
34 | ||
35 | private String username; | |
36 | private O365Token token; | |
37 | ||
38 | @Override | |
39 | public void setUsername(String username) { | |
40 | this.username = username; | |
41 | } | |
42 | ||
43 | @Override | |
44 | public void setPassword(String password) { | |
45 | // unused | |
46 | } | |
47 | ||
48 | @Override | |
49 | public void authenticate() throws IOException { | |
50 | // common DavMail client id | |
51 | final String clientId = Settings.getProperty("davmail.oauth.clientId", "facd6cff-a294-4415-b59f-c5b01937d7bd"); | |
52 | // standard native app redirectUri | |
53 | final String redirectUri = Settings.getProperty("davmail.oauth.redirectUri", "https://login.microsoftonline.com/common/oauth2/nativeclient"); | |
54 | String refreshToken = Settings.getProperty("davmail.oauth."+username.toLowerCase()+".refreshToken"); | |
55 | if (refreshToken == null) { | |
56 | // single user mode | |
57 | refreshToken = Settings.getProperty("davmail.oauth.refreshToken"); | |
58 | } | |
59 | String accessToken = Settings.getProperty("davmail.oauth.accessToken"); | |
60 | if (refreshToken == null && accessToken == null) { | |
61 | throw new IOException("Missing token"); | |
62 | } | |
63 | ||
64 | token = new O365Token(clientId, redirectUri); | |
65 | if (accessToken != null) { | |
66 | // for tests only: load access token, will expire in at most one hour | |
67 | token.setAccessToken(accessToken); | |
68 | } else { | |
69 | token.setRefreshToken(refreshToken); | |
70 | token.refreshToken(); | |
71 | } | |
72 | } | |
73 | ||
74 | @Override | |
75 | public O365Token getToken() throws IOException { | |
76 | return token; | |
77 | } | |
78 | ||
79 | @Override | |
80 | public String getEWSUrl() { | |
81 | return ewsUrl; | |
82 | } | |
83 | } |
18 | 18 | |
19 | 19 | package davmail.exchange.auth; |
20 | 20 | |
21 | import davmail.Settings; | |
21 | 22 | import davmail.http.DavGatewayHttpClientFacade; |
22 | 23 | import davmail.http.RestMethod; |
23 | 24 | import davmail.util.IOUtil; |
31 | 32 | import java.util.Date; |
32 | 33 | |
33 | 34 | public class O365Token { |
34 | protected final String URL = "https://login.microsoftonline.com/common/oauth2/token"; | |
35 | protected final String TOKEN_URL = "https://login.microsoftonline.com/common/oauth2/token"; | |
36 | protected final String RESOURCE_URL = "https://outlook.office365.com/"; | |
35 | 37 | |
36 | 38 | protected static final Logger LOGGER = Logger.getLogger(O365Token.class); |
37 | 39 | |
42 | 44 | private String accessToken; |
43 | 45 | private long expireson; |
44 | 46 | |
47 | public O365Token(String clientId, String redirectUri) { | |
48 | this.clientId = clientId; | |
49 | this.redirectUri = redirectUri; | |
50 | } | |
51 | ||
45 | 52 | public O365Token(String clientId, String redirectUri, String code) throws IOException { |
46 | 53 | this.clientId = clientId; |
47 | 54 | this.redirectUri = redirectUri; |
48 | 55 | |
49 | RestMethod tokenMethod = new RestMethod(URL); | |
56 | RestMethod tokenMethod = new RestMethod(TOKEN_URL); | |
50 | 57 | tokenMethod.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); |
51 | 58 | tokenMethod.addParameter("grant_type", "authorization_code"); |
52 | 59 | tokenMethod.addParameter("code", code); |
63 | 70 | |
64 | 71 | public void setJsonToken(JSONObject jsonToken) throws IOException { |
65 | 72 | try { |
73 | if (jsonToken.opt("error") != null) { | |
74 | throw new IOException(jsonToken.optString("error")+" "+jsonToken.optString("error_description")); | |
75 | } | |
66 | 76 | // access token expires after one hour |
67 | 77 | accessToken = jsonToken.getString("access_token"); |
68 | 78 | // precious refresh token |
76 | 86 | JSONObject tokenBody = new JSONObject(decodedBearer); |
77 | 87 | LOGGER.debug("Token: " + tokenBody); |
78 | 88 | username = tokenBody.getString("unique_name"); |
89 | ||
90 | if (Settings.getBooleanProperty("davmail.oauth.persistToken", false)) { | |
91 | Settings.setProperty("davmail.oauth."+username.toLowerCase()+".refreshToken", refreshToken); | |
92 | Settings.save(); | |
93 | } | |
79 | 94 | } catch (JSONException e) { |
80 | 95 | throw new IOException("Exception parsing token", e); |
81 | 96 | } |
101 | 116 | |
102 | 117 | public void setAccessToken(String accessToken) { |
103 | 118 | this.accessToken = accessToken; |
119 | // assume unexpired token | |
120 | expireson = System.currentTimeMillis() + 1000 * 60 * 60; | |
104 | 121 | } |
105 | 122 | |
106 | private void refreshToken() throws IOException { | |
107 | String url = "https://login.microsoftonline.com/common/oauth2/token"; | |
108 | RestMethod tokenMethod = new RestMethod(url); | |
123 | public void refreshToken() throws IOException { | |
124 | RestMethod tokenMethod = new RestMethod(TOKEN_URL); | |
109 | 125 | tokenMethod.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); |
110 | 126 | tokenMethod.addParameter("grant_type", "refresh_token"); |
111 | 127 | tokenMethod.addParameter("refresh_token", refreshToken); |
112 | 128 | tokenMethod.addParameter("redirect_uri", redirectUri); |
113 | 129 | tokenMethod.addParameter("client_id", clientId); |
130 | tokenMethod.addParameter("resource", "https://outlook.office365.com/"); | |
114 | 131 | |
115 | 132 | executeMethod(tokenMethod); |
116 | 133 | } |
118 | 135 | private void executeMethod(RestMethod tokenMethod) throws IOException { |
119 | 136 | HttpClient httpClient = null; |
120 | 137 | try { |
121 | httpClient = DavGatewayHttpClientFacade.getInstance(URL); | |
138 | httpClient = DavGatewayHttpClientFacade.getInstance(RESOURCE_URL); | |
122 | 139 | httpClient.executeMethod(tokenMethod); |
123 | 140 | setJsonToken(tokenMethod.getJsonResponse()); |
124 | 141 | |
131 | 148 | } |
132 | 149 | |
133 | 150 | } |
151 | ||
152 | public void setRefreshToken(String refreshToken) { | |
153 | this.refreshToken = refreshToken; | |
154 | } | |
134 | 155 | } |
1152 | 1152 | protected Folder buildFolder(EWSMethod.Item item) { |
1153 | 1153 | Folder folder = new Folder(); |
1154 | 1154 | folder.folderId = new FolderId(item); |
1155 | folder.displayName = item.get(Field.get("folderDisplayName").getResponseName()); | |
1155 | folder.displayName = encodeSlash(item.get(Field.get("folderDisplayName").getResponseName())); | |
1156 | 1156 | folder.folderClass = item.get(Field.get("folderclass").getResponseName()); |
1157 | 1157 | folder.etag = item.get(Field.get("lastmodified").getResponseName()); |
1158 | 1158 | folder.ctag = item.get(Field.get("ctag").getResponseName()); |
1246 | 1246 | EWSMethod.Item folder = new EWSMethod.Item(); |
1247 | 1247 | folder.type = "Folder"; |
1248 | 1248 | folder.put("FolderClass", folderClass); |
1249 | folder.put("DisplayName", path.folderName); | |
1249 | folder.put("DisplayName", decodeSlash(path.folderName)); | |
1250 | 1250 | // TODO: handle properties |
1251 | 1251 | CreateFolderMethod createFolderMethod = new CreateFolderMethod(getFolderId(path.parentPath), folder); |
1252 | 1252 | executeMethod(createFolderMethod); |
2763 | 2763 | CreateItemMethod createItemMethod = new CreateItemMethod(MessageDisposition.SaveOnly, SendMeetingInvitations.SendToNone, getFolderId("davmailtemp"), item); |
2764 | 2764 | executeMethod(createItemMethod); |
2765 | 2765 | item = createItemMethod.getResponseItem(); |
2766 | if (item == null) { | |
2767 | throw new IOException("Empty timezone item"); | |
2768 | } | |
2766 | 2769 | VCalendar vCalendar = new VCalendar(getContent(new ItemId(item)), email, null); |
2767 | 2770 | this.vTimezone = vCalendar.getVTimezone(); |
2768 | 2771 | // delete temporary folder |
2908 | 2911 | parentFolderId, |
2909 | 2912 | FOLDER_PROPERTIES, |
2910 | 2913 | new TwoOperandExpression(TwoOperandExpression.Operator.IsEqualTo, |
2911 | Field.get("folderDisplayName"), folderName), | |
2914 | Field.get("folderDisplayName"), decodeSlash(folderName)), | |
2912 | 2915 | 0, 1 |
2913 | 2916 | ); |
2914 | 2917 | executeMethod(findFolderMethod); |
2917 | 2920 | folderId = new FolderId(item); |
2918 | 2921 | } |
2919 | 2922 | return folderId; |
2923 | } | |
2924 | ||
2925 | private String decodeSlash(String folderName) { | |
2926 | if (folderName.contains("_xF8FF_")) { | |
2927 | return folderName.replaceAll("_xF8FF_", "/"); | |
2928 | } else { | |
2929 | return folderName; | |
2930 | } | |
2931 | } | |
2932 | ||
2933 | private String encodeSlash(String folderName) { | |
2934 | if (folderName.contains("/")) { | |
2935 | return folderName.replaceAll("/", "_xF8FF_"); | |
2936 | } else { | |
2937 | return folderName; | |
2938 | } | |
2920 | 2939 | } |
2921 | 2940 | |
2922 | 2941 | long throttlingTimestamp = 0; |
3093 | 3112 | } |
3094 | 3113 | |
3095 | 3114 | protected String convertDateFromExchange(String exchangeDateValue) throws DavMailException { |
3096 | String zuluDateValue = null; | |
3097 | if (exchangeDateValue != null) { | |
3098 | try { | |
3099 | zuluDateValue = getZuluDateFormat().format(getExchangeZuluDateFormat().parse(exchangeDateValue)); | |
3100 | } catch (ParseException e) { | |
3115 | // yyyy-MM-dd'T'HH:mm:ss'Z' to yyyyMMdd'T'HHmmss'Z' | |
3116 | if (exchangeDateValue == null) { | |
3117 | return null; | |
3118 | } else { | |
3119 | if (exchangeDateValue.length() != 20) { | |
3101 | 3120 | throw new DavMailException("EXCEPTION_INVALID_DATE", exchangeDateValue); |
3102 | 3121 | } |
3103 | } | |
3104 | return zuluDateValue; | |
3122 | StringBuilder buffer = new StringBuilder(); | |
3123 | for (int i = 0; i < exchangeDateValue.length(); i++) { | |
3124 | if (i == 4 || i == 7 || i == 13 || i == 16) { | |
3125 | i++; | |
3126 | } | |
3127 | buffer.append(exchangeDateValue.charAt(i)); | |
3128 | } | |
3129 | return buffer.toString(); | |
3130 | } | |
3105 | 3131 | } |
3106 | 3132 | |
3107 | 3133 | protected String convertCalendarDateToExchange(String vcalendarDateValue) throws DavMailException { |
58 | 58 | |
59 | 59 | } |
60 | 60 | |
61 | protected Item createResponseItem() { | |
62 | if (responseItems.isEmpty()) { | |
63 | Item responseItem = new Item(); | |
64 | responseItems.add(responseItem); | |
65 | return responseItem; | |
66 | } else { | |
67 | return responseItems.get(0); | |
68 | } | |
69 | } | |
70 | ||
71 | 61 | @Override |
72 | 62 | protected void handleCustom(XMLStreamReader reader) throws XMLStreamException { |
73 | 63 | if (XMLStreamUtil.isStartTag(reader, "PictureData")) { |
19 | 19 | package davmail.exchange.ews; |
20 | 20 | |
21 | 21 | import davmail.exchange.ExchangeSession; |
22 | import org.apache.log4j.Logger; | |
22 | 23 | |
23 | 24 | import java.io.IOException; |
24 | 25 | import java.io.Writer; |
29 | 30 | * Handle calendar item recurrence update |
30 | 31 | */ |
31 | 32 | public class RecurrenceFieldUpdate extends FieldUpdate { |
33 | public static final Logger LOGGER = Logger.getLogger(RecurrenceFieldUpdate.class); | |
32 | 34 | static final HashMap<String, String> calDayToDayOfWeek = new HashMap<String, String>(); |
33 | 35 | static { |
34 | 36 | calDayToDayOfWeek.put("SU", "Sunday"); |
54 | 56 | public void setByDay(String[] days) { |
55 | 57 | byDays = new HashSet<String>(); |
56 | 58 | for (String day: days) { |
57 | byDays.add(calDayToDayOfWeek.get(day)); | |
59 | String value = calDayToDayOfWeek.get(day); | |
60 | if (value == null) { | |
61 | LOGGER.warn("Invalid day value: "+day); | |
62 | } else { | |
63 | byDays.add(value); | |
64 | } | |
58 | 65 | } |
59 | 66 | } |
60 | 67 |
641 | 641 | // separate domain from username in credentials |
642 | 642 | AuthScope authScope = new AuthScope(null, -1); |
643 | 643 | NTCredentials credentials = (NTCredentials) httpClient.getState().getCredentials(authScope); |
644 | setCredentials(httpClient, credentials.getUserName(), credentials.getPassword()); | |
644 | if (credentials != null && credentials.getDomain() == null) { | |
645 | setCredentials(httpClient, credentials.getUserName(), credentials.getPassword()); | |
646 | } | |
645 | 647 | } |
646 | 648 | |
647 | 649 | /** |
78 | 78 | public NotificationDialog(String to, String cc, String subject, String description) { |
79 | 79 | hasRecipients = to != null || cc != null; |
80 | 80 | setModal(true); |
81 | setDefaultCloseOperation(JFrame.HIDE_ON_CLOSE); | |
81 | setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); | |
82 | 82 | setTitle(BundleMessage.format("UI_CALDAV_NOTIFICATION")); |
83 | 83 | try { |
84 | 84 | setIconImages(DavGatewayTray.getFrameIcons()); |
266 | 266 | Class.forName("javafx.embed.swing.JFXPanel"); |
267 | 267 | isJavaFXAvailable = true; |
268 | 268 | } catch (ClassNotFoundException e) { |
269 | // ignore | |
270 | } catch (NoClassDefFoundError e) { | |
269 | 271 | // ignore |
270 | 272 | } |
271 | 273 | return isJavaFXAvailable; |
901 | 903 | pack(); |
902 | 904 | //setResizable(false); |
903 | 905 | // center frame |
904 | setLocation(getToolkit().getScreenSize().width / 2 - | |
905 | getSize().width / 2, | |
906 | getToolkit().getScreenSize().height / 2 - | |
907 | getSize().height / 2); | |
906 | setLocationRelativeTo(null); | |
908 | 907 | urlField.requestFocus(); |
909 | 908 | } |
910 | 909 | } |
40 | 40 | */ |
41 | 41 | public class AwtGatewayTray implements DavGatewayTrayInterface { |
42 | 42 | protected static final String TRAY_PNG = "tray.png"; |
43 | protected static final String TRAY128_PNG = "tray128.png"; | |
44 | 43 | |
45 | 44 | protected static final String TRAY_ACTIVE_PNG = "tray2.png"; |
46 | 45 | protected static final String TRAY_INACTIVE_PNG = "trayinactive.png"; |
47 | 46 | |
47 | protected static final String TRAY128_PNG = "tray128.png"; | |
48 | protected static final String TRAY128_ACTIVE_PNG = "tray128active.png"; | |
49 | protected static final String TRAY128_INACTIVE_PNG = "tray128inactive.png"; | |
48 | 50 | |
49 | 51 | protected AwtGatewayTray() { |
50 | 52 | } |
56 | 58 | static TrayIcon trayIcon; |
57 | 59 | protected static ArrayList<Image> frameIcons; |
58 | 60 | protected static BufferedImage image; |
59 | protected static BufferedImage image2; | |
61 | protected static BufferedImage activeImage; | |
60 | 62 | protected static BufferedImage inactiveImage; |
61 | 63 | static LogBrokerMonitor logBrokerMonitor; |
62 | 64 | private boolean isActive = true; |
79 | 81 | SwingUtilities.invokeLater(new Runnable() { |
80 | 82 | public void run() { |
81 | 83 | if (trayIcon.getImage().equals(image)) { |
82 | trayIcon.setImage(image2); | |
84 | trayIcon.setImage(activeImage); | |
83 | 85 | } else { |
84 | 86 | trayIcon.setImage(image); |
85 | 87 | } |
199 | 201 | |
200 | 202 | protected void loadIcons() { |
201 | 203 | image = DavGatewayTray.adjustTrayIcon(DavGatewayTray.loadImage(AwtGatewayTray.TRAY_PNG)); |
202 | image2 = DavGatewayTray.adjustTrayIcon(DavGatewayTray.loadImage(AwtGatewayTray.TRAY_ACTIVE_PNG)); | |
204 | activeImage = DavGatewayTray.adjustTrayIcon(DavGatewayTray.loadImage(AwtGatewayTray.TRAY_ACTIVE_PNG)); | |
203 | 205 | inactiveImage = DavGatewayTray.adjustTrayIcon(DavGatewayTray.loadImage(AwtGatewayTray.TRAY_INACTIVE_PNG)); |
204 | 206 | |
205 | 207 | frameIcons = new ArrayList<Image>(); |
228 | 228 | String javaVersion = System.getProperty("java.version"); |
229 | 229 | String arch = System.getProperty("sun.arch.data.model"); |
230 | 230 | LOGGER.debug("OS Name: " + System.getProperty("os.name") + |
231 | " Java version: " + javaVersion + ((arch != null) ? ' ' + arch : "") + | |
232 | " System tray " + (SystemTray.isSupported() ? "" : "not ") + "supported " + | |
233 | ((currentDesktop == null) ? "" : "Current Desktop: " + currentDesktop) | |
231 | " Java version: " + javaVersion + ((arch != null) ? ' ' + arch : "") + | |
232 | " System tray " + (SystemTray.isSupported() ? "" : "not ") + "supported " + | |
233 | ((currentDesktop == null) ? "" : "Current Desktop: " + currentDesktop) | |
234 | 234 | ); |
235 | ||
236 | if (Settings.isLinux()) { | |
237 | // enable anti aliasing on linux | |
238 | System.setProperty("awt.useSystemAAFontSettings", "on"); | |
239 | System.setProperty("swing.aatext", "true"); | |
240 | } | |
235 | 241 | |
236 | 242 | if (!Settings.getBooleanProperty("davmail.server")) { |
237 | 243 | if ("GNOME-Classic:GNOME".equals(currentDesktop) || "ubuntu:GNOME".equals(currentDesktop)) { |
238 | 244 | LOGGER.info("System tray is not supported on Gnome, will switch to frame mode"); |
239 | } else | |
240 | if (!notray) { | |
245 | } else if (!notray) { | |
241 | 246 | if ("Unity".equals(currentDesktop)) { |
242 | 247 | LOGGER.info("Detected Unity desktop, please follow instructions at " + |
243 | 248 | "http://davmail.sourceforge.net/linuxsetup.html to restore normal systray " + |
244 | 249 | "or run DavMail in server mode"); |
245 | 250 | } |
246 | // first try to load SWT before with Java AWT | |
247 | ClassLoader classloader = DavGatewayTray.class.getClassLoader(); | |
248 | try { | |
249 | // trigger ClassNotFoundException | |
250 | classloader.loadClass("org.eclipse.swt.SWT"); | |
251 | // SWT available, create tray | |
252 | davGatewayTray = new SwtGatewayTray(); | |
253 | davGatewayTray.init(); | |
254 | } catch (ClassNotFoundException e) { | |
255 | DavGatewayTray.info(new BundleMessage("LOG_SWT_NOT_AVAILABLE")); | |
256 | } catch (Throwable e) { | |
257 | DavGatewayTray.info(new BundleMessage("LOG_SWT_NOT_AVAILABLE")); | |
258 | davGatewayTray = null; | |
251 | if (Settings.O365_INTERACTIVE.equals(Settings.getProperty("davmail.mode"))) { | |
252 | LOGGER.info("O365Interactive is not compatible with SWT, do not try to create SWT tray"); | |
253 | } else { | |
254 | // first try to load SWT before with Java AWT | |
255 | ClassLoader classloader = DavGatewayTray.class.getClassLoader(); | |
256 | try { | |
257 | // trigger ClassNotFoundException | |
258 | classloader.loadClass("org.eclipse.swt.SWT"); | |
259 | // SWT available, create tray | |
260 | davGatewayTray = new SwtGatewayTray(); | |
261 | davGatewayTray.init(); | |
262 | } catch (ClassNotFoundException e) { | |
263 | DavGatewayTray.info(new BundleMessage("LOG_SWT_NOT_AVAILABLE")); | |
264 | } catch (Throwable e) { | |
265 | DavGatewayTray.info(new BundleMessage("LOG_SWT_NOT_AVAILABLE")); | |
266 | davGatewayTray = null; | |
267 | } | |
259 | 268 | } |
260 | // try java6 tray support | |
261 | if (davGatewayTray == null) { | |
269 | // try java6 tray support, except on Linux | |
270 | if (davGatewayTray == null /*&& !isLinux()*/) { | |
262 | 271 | try { |
263 | 272 | if (SystemTray.isSupported()) { |
264 | 273 | if (isOSX()) { |
305 | 314 | } |
306 | 315 | |
307 | 316 | /** |
317 | * Test if running on Linux | |
318 | * | |
319 | * @return true on Linux | |
320 | */ | |
321 | public static boolean isLinux() { | |
322 | return System.getProperty("os.name").toLowerCase().startsWith("linux"); | |
323 | } | |
324 | ||
325 | /** | |
308 | 326 | * Load image with current class loader. |
309 | 327 | * |
310 | 328 | * @param fileName image resource file name |
344 | 362 | int imageType = BufferedImage.TYPE_INT_ARGB; |
345 | 363 | if (backgroundColorString != null && backgroundColorString.length() == 7 |
346 | 364 | && backgroundColorString.startsWith("#")) { |
347 | int red = Integer.parseInt(backgroundColorString.substring(1,3), 16); | |
348 | int green = Integer.parseInt(backgroundColorString.substring(3,5), 16); | |
349 | int blue = Integer.parseInt(backgroundColorString.substring(5,7), 16); | |
365 | int red = Integer.parseInt(backgroundColorString.substring(1, 3), 16); | |
366 | int green = Integer.parseInt(backgroundColorString.substring(3, 5), 16); | |
367 | int blue = Integer.parseInt(backgroundColorString.substring(5, 7), 16); | |
350 | 368 | backgroundColor = new Color(red, green, blue); |
351 | 369 | imageType = BufferedImage.TYPE_INT_RGB; |
352 | 370 | } |
265 | 265 | } |
266 | 266 | |
267 | 267 | protected void createAndShowGUI() { |
268 | System.setProperty("swing.defaultlaf", UIManager.getSystemLookAndFeelClassName()); | |
269 | ||
270 | image = DavGatewayTray.loadImage(AwtGatewayTray.TRAY_PNG); | |
271 | activeImage = DavGatewayTray.loadImage(AwtGatewayTray.TRAY_ACTIVE_PNG); | |
272 | inactiveImage = DavGatewayTray.loadImage(AwtGatewayTray.TRAY_INACTIVE_PNG); | |
268 | // set cross platform look and feel on Linux, except is swing.defaultlaf is set | |
269 | if (Settings.isLinux() && System.getProperty("swing.defaultlaf") == null) { | |
270 | System.setProperty("swing.defaultlaf", UIManager.getCrossPlatformLookAndFeelClassName()); | |
271 | } else { | |
272 | System.setProperty("swing.defaultlaf", UIManager.getSystemLookAndFeelClassName()); | |
273 | } | |
274 | String imageName = AwtGatewayTray.TRAY_PNG; | |
275 | String activeImageName = AwtGatewayTray.TRAY_ACTIVE_PNG; | |
276 | String inactiveImageName = AwtGatewayTray.TRAY_INACTIVE_PNG; | |
277 | // use hi res icons on Linux | |
278 | if (Settings.isLinux()) { | |
279 | imageName = AwtGatewayTray.TRAY128_PNG; | |
280 | activeImageName = AwtGatewayTray.TRAY128_ACTIVE_PNG; | |
281 | inactiveImageName = AwtGatewayTray.TRAY128_INACTIVE_PNG; | |
282 | } | |
283 | image = DavGatewayTray.loadImage(imageName); | |
284 | activeImage = DavGatewayTray.loadImage(activeImageName); | |
285 | inactiveImage = DavGatewayTray.loadImage(inactiveImageName); | |
273 | 286 | |
274 | 287 | frameIcons = new ArrayList<Image>(); |
275 | 288 | frameIcons.add(image); |
310 | 323 | settingsFrame = new SettingsFrame(); |
311 | 324 | buildMenu(); |
312 | 325 | |
313 | mainFrame.setMinimumSize(new Dimension(400, 180)); | |
326 | mainFrame.setMinimumSize(new Dimension(400, 250)); | |
314 | 327 | mainFrame.pack(); |
315 | 328 | // workaround MacOSX |
316 | 329 | if (mainFrame.getSize().width < 400 || mainFrame.getSize().height < 180) { |
63 | 63 | @Override |
64 | 64 | protected void loadIcons() { |
65 | 65 | image = DavGatewayTray.adjustTrayIcon(DavGatewayTray.loadImage(OSX_TRAY_PNG)); |
66 | image2 = DavGatewayTray.adjustTrayIcon(DavGatewayTray.loadImage(OSX_TRAY_ACTIVE_PNG)); | |
66 | activeImage = DavGatewayTray.adjustTrayIcon(DavGatewayTray.loadImage(OSX_TRAY_ACTIVE_PNG)); | |
67 | 67 | inactiveImage = DavGatewayTray.adjustTrayIcon(DavGatewayTray.loadImage(OSX_TRAY_INACTIVE_PNG)); |
68 | 68 | |
69 | 69 | frameIcons = new ArrayList<Image>(); |
31 | 31 | import org.eclipse.swt.SWT; |
32 | 32 | import org.eclipse.swt.events.DisposeEvent; |
33 | 33 | import org.eclipse.swt.events.DisposeListener; |
34 | import org.eclipse.swt.graphics.DeviceData; | |
35 | import org.eclipse.swt.graphics.GC; | |
34 | 36 | import org.eclipse.swt.graphics.Image; |
35 | import org.eclipse.swt.internal.gtk.GdkRectangle; | |
36 | import org.eclipse.swt.internal.gtk.OS; | |
37 | import org.eclipse.swt.graphics.ImageData; | |
38 | import org.eclipse.swt.internal.gtk.GDK; | |
39 | import org.eclipse.swt.internal.gtk.GTK; | |
37 | 40 | import org.eclipse.swt.widgets.*; |
38 | 41 | |
39 | 42 | import javax.swing.*; |
46 | 49 | * Tray icon handler based on SWT |
47 | 50 | */ |
48 | 51 | public class SwtGatewayTray implements DavGatewayTrayInterface { |
52 | private static final Logger LOGGER = Logger.getLogger(SwtGatewayTray.class); | |
53 | ||
49 | 54 | protected SwtGatewayTray() { |
50 | 55 | } |
51 | 56 | |
183 | 188 | throw new IOException(fileName); |
184 | 189 | } |
185 | 190 | byte[] imageContent = IOUtil.readFully(imageUrl.openStream()); |
186 | result = new Image(display, new ByteArrayInputStream(imageContent)); | |
191 | Image tempImage = new Image(display, new ByteArrayInputStream(imageContent)); | |
192 | Image backgroundImage = new Image(null, 24, 24); | |
193 | ImageData imageData = backgroundImage.getImageData(); | |
194 | imageData.transparentPixel = imageData.getPixel(0, 0); | |
195 | backgroundImage.dispose(); | |
196 | result = new Image(null, imageData); | |
197 | ||
198 | GC gc = new GC(result); | |
199 | gc.drawImage(tempImage, 4, 4); | |
200 | tempImage.dispose(); | |
187 | 201 | |
188 | 202 | } catch (IOException e) { |
189 | 203 | DavGatewayTray.warn(new BundleMessage("LOG_UNABLE_TO_LOAD_IMAGE"), e); |
195 | 209 | * Create tray icon and register frame listeners. |
196 | 210 | */ |
197 | 211 | public void init() { |
198 | // register error handler to avoid application crash on concurrent X access from SWT and AWT | |
199 | try { | |
200 | OS.gdk_error_trap_push(); | |
201 | } catch (NoClassDefFoundError e) { | |
202 | // ignore | |
212 | if (GTK.GTK3) { | |
213 | throw new RuntimeException("GTK 3 not supported, please set SWT_GTK=0"); | |
203 | 214 | } |
204 | final String systemLookAndFeelClassName = UIManager.getSystemLookAndFeelClassName(); | |
215 | GDK.gdk_error_trap_push(); | |
205 | 216 | try { |
206 | 217 | // workaround for bug when SWT and AWT both try to access Gtk |
207 | if (systemLookAndFeelClassName.contains("gtk")) { | |
208 | System.setProperty("swing.defaultlaf", UIManager.getCrossPlatformLookAndFeelClassName()); | |
209 | } else { | |
210 | System.setProperty("swing.defaultlaf", systemLookAndFeelClassName); | |
211 | } | |
218 | UIManager.setLookAndFeel(UIManager.getCrossPlatformLookAndFeelClassName()); | |
212 | 219 | } catch (Exception e) { |
213 | 220 | DavGatewayTray.warn(new BundleMessage("LOG_UNABLE_TO_SET_LOOK_AND_FEEL")); |
214 | 221 | } |
217 | 224 | @Override |
218 | 225 | public void run() { |
219 | 226 | try { |
220 | display = new Display(); | |
227 | DeviceData data = new DeviceData(); | |
228 | data.debug = true; | |
229 | display = new Display(data); | |
221 | 230 | shell = new Shell(display); |
222 | 231 | |
223 | 232 | final Tray tray = display.getSystemTray(); |
225 | 234 | |
226 | 235 | trayItem = new TrayItem(tray, SWT.NONE); |
227 | 236 | trayItem.setToolTipText(BundleMessage.format("UI_DAVMAIL_GATEWAY")); |
228 | ||
229 | // Under Unity, check if tray is indeed available | |
230 | if (systemLookAndFeelClassName.contains("gtk") && | |
231 | "Unity".equals(System.getenv("XDG_CURRENT_DESKTOP"))) { | |
232 | GdkRectangle area = new GdkRectangle(); | |
233 | OS.gtk_status_icon_get_geometry(trayItem.handle, 0, area, 0); | |
234 | ||
235 | if (area.x == 0 && area.y == 0) { | |
236 | throw new Error("System tray not available"); | |
237 | } | |
238 | } | |
239 | 237 | |
240 | 238 | frameIcons = new ArrayList<java.awt.Image>(); |
241 | 239 | frameIcons.add(DavGatewayTray.loadImage(AwtGatewayTray.TRAY128_PNG)); |
277 | 275 | aboutItem.setText(BundleMessage.format("UI_ABOUT")); |
278 | 276 | aboutItem.addListener(SWT.Selection, new Listener() { |
279 | 277 | public void handleEvent(Event event) { |
280 | display.asyncExec( | |
278 | SwingUtilities.invokeLater( | |
281 | 279 | new Runnable() { |
282 | 280 | public void run() { |
283 | 281 | if (aboutFrame == null) { |
292 | 290 | } |
293 | 291 | }); |
294 | 292 | |
293 | // create menu item for the default action | |
295 | 294 | trayItem.addListener(SWT.DefaultSelection, new Listener() { |
296 | 295 | public void handleEvent(Event event) { |
297 | display.asyncExec( | |
296 | SwingUtilities.invokeLater( | |
298 | 297 | new Runnable() { |
299 | 298 | public void run() { |
300 | 299 | // create frame on first call |
310 | 309 | } |
311 | 310 | }); |
312 | 311 | |
313 | // create menu item for the default action | |
314 | 312 | MenuItem defaultItem = new MenuItem(popup, SWT.PUSH); |
315 | 313 | defaultItem.setText(BundleMessage.format("UI_SETTINGS")); |
316 | 314 | defaultItem.addListener(SWT.Selection, new Listener() { |
317 | 315 | public void handleEvent(Event event) { |
318 | display.asyncExec( | |
316 | SwingUtilities.invokeLater( | |
319 | 317 | new Runnable() { |
320 | 318 | public void run() { |
321 | 319 | // create frame on first call |
335 | 333 | logItem.setText(BundleMessage.format("UI_SHOW_LOGS")); |
336 | 334 | logItem.addListener(SWT.Selection, new Listener() { |
337 | 335 | public void handleEvent(Event event) { |
338 | display.asyncExec( | |
336 | SwingUtilities.invokeLater( | |
339 | 337 | new Runnable() { |
340 | 338 | public void run() { |
341 | 339 | |
368 | 366 | |
369 | 367 | // display settings frame on first start |
370 | 368 | if (Settings.isFirstStart()) { |
371 | // create frame on first call | |
372 | if (settingsFrame == null) { | |
373 | settingsFrame = new SettingsFrame(); | |
374 | } | |
375 | settingsFrame.setVisible(true); | |
376 | settingsFrame.toFront(); | |
377 | settingsFrame.requestFocus(); | |
369 | SwingUtilities.invokeLater(new Runnable() { | |
370 | @Override | |
371 | public void run() { | |
372 | // create frame on first call | |
373 | if (settingsFrame == null) { | |
374 | settingsFrame = new SettingsFrame(); | |
375 | } | |
376 | settingsFrame.setVisible(true); | |
377 | settingsFrame.toFront(); | |
378 | settingsFrame.requestFocus(); | |
379 | } | |
380 | }); | |
381 | ||
378 | 382 | } |
379 | 383 | |
380 | 384 | synchronized (mainThread) { |
Binary diff not shown
Binary diff not shown
55 | 55 | <li>You will have to give your consent to DavMail access on first call, |
56 | 56 | check davmail log for exact url or use O365Interactive once</li> |
57 | 57 | <li>You can use your own application client id instead of DavMail provided value, |
58 | just create your application and add in davmail.properties:<br/> | |
58 | just create your application at <a href="https://apps.dev.microsoft.com/">https://apps.dev.microsoft.com/</a> | |
59 | or directly on Azure AD if you have the rights, and add in davmail.properties:<br/> | |
59 | 60 | <source> |
60 | 61 | davmail.oauth.clientId=<i>yourappid</i> |
61 | 62 | davmail.oauth.redirectUri=https://login.microsoftonline.com/common/oauth2/nativeclient |
8 | 8 | </properties> |
9 | 9 | |
10 | 10 | <body> |
11 | <section name="DavMail POP/IMAP/SMTP/Caldav/Carddav/LDAP Exchange Gateway"> | |
11 | <section name="DavMail POP/IMAP/SMTP/Caldav/Carddav/LDAP Exchange and Office 365 Gateway"> | |
12 | 12 | <table class="about" border="0" style="width: 98%"> |
13 | 13 | <tr> |
14 | 14 | <td> |
20 | 20 | contacts to allow recipient address completion in mail compose window and full calendar |
21 | 21 | support with attendees free/busy display. |
22 | 22 | </p> |
23 | <p>DavMail also supports the CardDav protocol to sync address books. This new feature | |
24 | is sponsored by | |
25 | <a href="http://www.defense.gouv.fr/dga/">French Defense / DGA</a> | |
26 | through project | |
27 | <a href="http://www.trustedbird.org">Trustedbird</a> | |
23 | <p>DavMail is developed with <a href="http://www.jetbrains.com/idea/?from=DavMail">JetBrains IntelliJ IDEA</a>. | |
24 | YourKit also supports open source projects, including DavMail, with its full-featured | |
25 | <a href="https://www.yourkit.com/java/profiler/">YourKit Java Profiler</a>. | |
28 | 26 | </p> |
29 | 27 | <div style="width: 100%;text-align: center"> |
30 | 28 | <img src="images/davmailArchitecture.png" alt="DavMail Architecture" width="550" |
40 | 38 | on Windows, Linux (Ubuntu) and Mac OSX. Tested successfully with the Iphone |
41 | 39 | (gateway running on a server). |
42 | 40 | </p> |
41 | <p>DavMail CardDav implementation was sponsored by | |
42 | <a href="http://www.defense.gouv.fr/dga/">French Defense / DGA</a> | |
43 | through project <a href="http://www.trustedbird.org">Trustedbird</a> | |
44 | </p> | |
43 | 45 | </td> |
44 | 46 | <td> |
45 | <div style="text-align: center"> | |
47 | <div style="text-align: center;padding: 8px;"> | |
46 | 48 | <script type="text/javascript" |
47 | 49 | src="https://www.openhub.net/p/davmail/widgets/project_thin_badge?format=js"> |
48 | 50 | </script> |
58 | 60 | <a class="download" href="download.html"> |
59 | 61 | Download<br/>DavMail<br/>Gateway |
60 | 62 | </a> |
61 | <div style='padding: 20px;'> | |
62 | <a href="http://www.jetbrains.com/idea/" | |
63 | style="position: relative;display:block; width:88px; height:31px; border:0; margin:0;padding:0;text-decoration:none;text-indent:0;"> | |
64 | <span style="margin: 0;padding: 0;position: absolute;top: -1px;left: 4px;font-size: 10px;cursor:pointer; background-image:none;border:0;color: #0d3a9e; font-family: trebuchet ms,arial,sans-serif;font-weight: normal;text-align:left;"> | |
65 | Developed with | |
66 | </span> | |
67 | <img src="http://www.jetbrains.com/idea/opensource/img/all/banners/idea88x31_white.gif" | |
68 | alt="The best Java IDE" border="0"/> | |
69 | </a> | |
63 | <div style='text-align: center;padding: 20px;'> | |
64 | <a target="_blank" href="http://www.jetbrains.com/idea/?from=DavMail" style="background-image: none;position: relative;display:block; height:32px; border:0; margin:0;padding:0;text-decoration:none;text-indent:0;"> | |
65 | <span style="margin: 0;padding: 0;font-size: 10px;cursor:pointer; background-image:none;border:0;color: #0d3a9e; font-family: Gotham SSm A, Gotham SSm B, Helvetica, Arial, sans-serif;font-weight: normal;text-align:left;"> | |
66 | Developed with | |
67 | </span> | |
68 | ||
69 | <span style="white-space: nowrap;font-size: 18px;font-family: Gotham SSm A, Gotham SSm B, Helvetica, Arial, sans-serif"> | |
70 | <img src="http://resources.jetbrains.com/storage/products/intellij-idea/img/meta/intellij-idea_logo_300x300.png" width="32" height="32" alt="The best Java IDE" border="0" /> | |
71 | IntelliJ IDEA</span> | |
72 | </a> | |
73 | </div> | |
74 | <div style="text-align: center;padding: 20px;"> | |
75 | <a target="_blank" style="background-image: none;" href="https://www.yourkit.com/java/profiler/"><img width="128" alt='Yourkit Java Profiler' border='0' src='https://www.yourkit.com/images/yklogo.png'/></a> | |
70 | 76 | </div> |
71 | 77 | </td> |
72 | 78 | </tr> |
63 | 63 | <p>Download the the platform independent package from Sourceforge and uncompress it with |
64 | 64 | your favorite tool. If you want improved SWT tray icon add the appropriate SWT library in lib |
65 | 65 | folder from <a href="http://www.eclipse.org/swt/">http://www.eclipse.org/swt/</a>, e.g. |
66 | swt-4.6.3-gtk-linux-x86_64.jar | |
66 | swt-4.9-gtk-linux-x86_64.jar | |
67 | 67 | </p> |
68 | 68 | <p>Just run <code>davmail</code> to launch DavMail. |
69 | 69 | You should now see the DavMail gateway icon in the tray : |
23 | 23 | |
24 | 24 | <p>The following documentation describes how to run DavMail as a non root user. |
25 | 25 | The <a href="https://packages.debian.org/sid/davmail">DavMail Debian package</a> |
26 | and <a href="https://software.opensuse.org//download.html?project=home%3Amguessan%3Adavmail&package=davmail">DavMail RPM package</a> | |
26 | and <a href="https://software.opensuse.org/download.html?project=home%3Amguessan%3Adavmail&package=davmail">DavMail RPM package</a> | |
27 | 27 | include init scripts to run DavMail as a service. |
28 | 28 | </p> |
29 | 29 |
0 | /* | |
1 | * DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway | |
2 | * Copyright (C) 2010 Mickael Guessant | |
3 | * | |
4 | * This program is free software; you can redistribute it and/or | |
5 | * modify it under the terms of the GNU General Public License | |
6 | * as published by the Free Software Foundation; either version 2 | |
7 | * of the License, or (at your option) any later version. | |
8 | * | |
9 | * This program is distributed in the hope that it will be useful, | |
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
12 | * GNU General Public License for more details. | |
13 | * | |
14 | * You should have received a copy of the GNU General Public License | |
15 | * along with this program; if not, write to the Free Software | |
16 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |
17 | */ | |
18 | ||
19 | package davmail.exchange; | |
20 | ||
21 | import davmail.AbstractDavMailTestCase; | |
22 | import davmail.Settings; | |
23 | ||
24 | import java.io.IOException; | |
25 | ||
26 | public class TestAuthenticators extends AbstractDavMailTestCase { | |
27 | ||
28 | public void testEWSAuthenticator() throws IOException { | |
29 | Settings.setProperty("davmail.mode", Settings.EWS); | |
30 | ExchangeSessionFactory.checkConfig(); | |
31 | // try application password for MFA enabledusers | |
32 | ExchangeSessionFactory.getInstance(Settings.getProperty("davmail.username"), | |
33 | Settings.getProperty("davmail.app.password")); | |
34 | } | |
35 | ||
36 | public void testO365Authenticator() throws IOException { | |
37 | Settings.setProperty("davmail.mode", Settings.O365); | |
38 | ExchangeSessionFactory.checkConfig(); | |
39 | // try application password for MFA enabledusers | |
40 | ExchangeSessionFactory.getInstance(Settings.getProperty("davmail.username"), | |
41 | Settings.getProperty("davmail.app.password")); | |
42 | } | |
43 | ||
44 | public void testO365ModernAuthenticator() throws IOException { | |
45 | Settings.setProperty("davmail.mode", Settings.O365_MODERN); | |
46 | ExchangeSessionFactory.checkConfig(); | |
47 | // use normal user password | |
48 | ExchangeSessionFactory.getInstance(Settings.getProperty("davmail.username"), | |
49 | Settings.getProperty("davmail.password")); | |
50 | } | |
51 | ||
52 | public void testO365InteractiveAuthenticator() throws IOException { | |
53 | Settings.setProperty("davmail.mode", Settings.O365_INTERACTIVE); | |
54 | ExchangeSessionFactory.checkConfig(); | |
55 | // password entered by end user | |
56 | ExchangeSessionFactory.getInstance(Settings.getProperty("davmail.username"), | |
57 | "unused"); | |
58 | } | |
59 | } |
132 | 132 | assertNotNull(folder); |
133 | 133 | } |
134 | 134 | |
135 | public void testCreateSlashFolder() throws IOException { | |
136 | String folderName = "test_xF8FF_slash"; | |
137 | session.deleteFolder(folderName); | |
138 | session.createMessageFolder(folderName); | |
139 | ExchangeSession.Folder folder = session.getFolder(folderName); | |
140 | assertEquals(folderName, folder.displayName); | |
141 | } | |
142 | ||
135 | 143 | } |