1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 package davmail.exchange.auth;
21
22 import davmail.Settings;
23 import davmail.http.HttpClientAdapter;
24 import davmail.http.request.RestRequest;
25 import davmail.util.IOUtil;
26 import davmail.util.StringEncryptor;
27 import org.apache.http.Consts;
28 import org.apache.http.NameValuePair;
29 import org.apache.http.client.entity.UrlEncodedFormEntity;
30 import org.apache.http.client.methods.CloseableHttpResponse;
31 import org.apache.http.message.BasicNameValuePair;
32 import org.apache.log4j.Logger;
33 import org.codehaus.jettison.json.JSONException;
34 import org.codehaus.jettison.json.JSONObject;
35
36 import java.io.IOException;
37 import java.net.UnknownHostException;
38 import java.util.ArrayList;
39 import java.util.Date;
40
41
42
43
44 public class O365Token {
45
46 protected static final Logger LOGGER = Logger.getLogger(O365Token.class);
47
48 private String clientId;
49 private String tokenUrl;
50 private String password;
51 private String redirectUri;
52 private String username;
53 private String refreshToken;
54 private String accessToken;
55 private long expiresOn;
56
57 public O365Token(String tenantId, String clientId, String redirectUri, String password) {
58 this.clientId = clientId;
59 this.redirectUri = redirectUri;
60 this.tokenUrl = Settings.getO365LoginUrl() + "/" + tenantId + "/oauth2/token";
61 this.password = password;
62 }
63
64 public O365Token(String tenantId, String clientId, String redirectUri, String code, String password) throws IOException {
65 this.clientId = clientId;
66 this.redirectUri = redirectUri;
67 this.tokenUrl = Settings.getO365LoginUrl() + "/" + tenantId + "/oauth2/token";
68 this.password = password;
69
70 ArrayList<NameValuePair> parameters = new ArrayList<>();
71 parameters.add(new BasicNameValuePair("grant_type", "authorization_code"));
72 parameters.add(new BasicNameValuePair("code", code));
73 parameters.add(new BasicNameValuePair("redirect_uri", redirectUri));
74 parameters.add(new BasicNameValuePair("client_id", clientId));
75
76 RestRequest tokenRequest = new RestRequest(tokenUrl, new UrlEncodedFormEntity(parameters, Consts.UTF_8));
77
78 executeRequest(tokenRequest);
79 }
80
81
82 public String getUsername() {
83 return username;
84 }
85
86 public void setJsonToken(JSONObject jsonToken) throws IOException {
87 try {
88 if (jsonToken.opt("error") != null) {
89 throw new IOException(jsonToken.optString("error") + " " + jsonToken.optString("error_description"));
90 }
91
92 accessToken = jsonToken.getString("access_token");
93
94 refreshToken = jsonToken.getString("refresh_token");
95
96 expiresOn = jsonToken.getLong("expires_on") * 1000;
97
98 LOGGER.debug("Access token expires " + new Date(expiresOn));
99
100
101 String idToken = jsonToken.optString("id_token");
102 if (idToken != null && idToken.contains(".")) {
103 String decodedJwt = IOUtil.decodeBase64AsString(idToken.substring(idToken.indexOf("."), idToken.lastIndexOf(".")));
104 try {
105 JSONObject tokenBody = new JSONObject(decodedJwt);
106 LOGGER.debug("Token: " + tokenBody);
107 username = tokenBody.getString("unique_name");
108
109 final String liveDotCom = "live.com#";
110 if (username.startsWith(liveDotCom)) {
111 username = username.substring(liveDotCom.length());
112 }
113 } catch (JSONException e) {
114 LOGGER.warn("Invalid id_token " + e.getMessage(), e);
115 }
116 }
117
118 if (username == null) {
119 String decodedBearer = IOUtil.decodeBase64AsString(accessToken.substring(accessToken.indexOf('.') + 1, accessToken.lastIndexOf('.')) + "==");
120 JSONObject tokenBody = new JSONObject(decodedBearer);
121 LOGGER.debug("Token: " + tokenBody);
122 username = tokenBody.getString("unique_name");
123 }
124
125 } catch (JSONException e) {
126 throw new IOException("Exception parsing token", e);
127 }
128 }
129
130 public void setClientId(String clientId) {
131 this.clientId = clientId;
132 }
133
134 public void setRedirectUri(String redirectUri) {
135 this.redirectUri = redirectUri;
136 }
137
138 public String getAccessToken() throws IOException {
139
140 if (isTokenExpired()) {
141 LOGGER.debug("Access token expires soon, trying to refresh it");
142 refreshToken();
143 }
144
145 return accessToken;
146 }
147
148 private boolean isTokenExpired() {
149 return System.currentTimeMillis() > (expiresOn - 60000);
150 }
151
152 public void setAccessToken(String accessToken) {
153 this.accessToken = accessToken;
154
155 expiresOn = System.currentTimeMillis() + 1000 * 60 * 60;
156 }
157
158 public void setRefreshToken(String refreshToken) {
159 this.refreshToken = refreshToken;
160 }
161
162 public String getRefreshToken() {
163 return refreshToken;
164 }
165
166 public void refreshToken() throws IOException {
167 ArrayList<NameValuePair> parameters = new ArrayList<>();
168 parameters.add(new BasicNameValuePair("grant_type", "refresh_token"));
169 parameters.add(new BasicNameValuePair("refresh_token", refreshToken));
170 parameters.add(new BasicNameValuePair("redirect_uri", redirectUri));
171 parameters.add(new BasicNameValuePair("client_id", clientId));
172 parameters.add(new BasicNameValuePair("resource", Settings.getOutlookUrl()));
173
174 RestRequest tokenRequest = new RestRequest(tokenUrl, new UrlEncodedFormEntity(parameters, Consts.UTF_8));
175
176 executeRequest(tokenRequest);
177
178
179 persistToken();
180 }
181
182 private void executeRequest(RestRequest tokenMethod) throws IOException {
183
184 try (
185 HttpClientAdapter httpClientAdapter = new HttpClientAdapter(tokenUrl);
186 CloseableHttpResponse response = httpClientAdapter.execute(tokenMethod)
187 ) {
188 setJsonToken(tokenMethod.handleResponse(response));
189 }
190 }
191
192 static O365Token build(String tenantId, String clientId, String redirectUri, String code, String password) throws IOException {
193 O365Token token = new O365Token(tenantId, clientId, redirectUri, code, password);
194 token.persistToken();
195 return token;
196 }
197
198
199 static O365Token load(String tenantId, String clientId, String redirectUri, String username, String password) throws UnknownHostException {
200 O365Token token = null;
201 if (Settings.getBooleanProperty("davmail.oauth.persistToken", true)) {
202 String encryptedRefreshToken = Settings.loadRefreshToken(username);
203 if (encryptedRefreshToken != null) {
204 String refreshToken;
205 try {
206 refreshToken = decryptToken(encryptedRefreshToken, password);
207 LOGGER.debug("Loaded stored token for " + username);
208 O365Token localToken = new O365Token(tenantId, clientId, redirectUri, password);
209
210 localToken.setRefreshToken(refreshToken);
211 localToken.refreshToken();
212 LOGGER.debug("Authenticated user " + localToken.getUsername() + " from stored token");
213 token = localToken;
214
215 } catch (UnknownHostException e) {
216
217 throw e;
218 } catch (IOException e) {
219 LOGGER.error("refresh token failed " + e.getMessage());
220
221 }
222 }
223 }
224 return token;
225 }
226
227 private void persistToken() throws IOException {
228 if (Settings.getBooleanProperty("davmail.oauth.persistToken", true)) {
229 if (password == null || password.isEmpty()) {
230
231 Settings.storeRefreshToken(username, refreshToken);
232 } else {
233 Settings.storeRefreshToken(username, O365Token.encryptToken(refreshToken, password));
234 }
235 }
236 }
237
238 private static String decryptToken(String encryptedRefreshToken, String password) throws IOException {
239 return new StringEncryptor(password).decryptString(encryptedRefreshToken);
240 }
241
242 private static String encryptToken(String refreshToken, String password) throws IOException {
243 return new StringEncryptor(password).encryptString(refreshToken);
244 }
245 }