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 } catch (JSONException e) {
109 LOGGER.warn("Invalid id_token " + e.getMessage(), e);
110 }
111 }
112
113 if (username == null) {
114 String decodedBearer = IOUtil.decodeBase64AsString(accessToken.substring(accessToken.indexOf('.') + 1, accessToken.lastIndexOf('.')) + "==");
115 JSONObject tokenBody = new JSONObject(decodedBearer);
116 LOGGER.debug("Token: " + tokenBody);
117 username = tokenBody.getString("unique_name");
118 }
119
120 } catch (JSONException e) {
121 throw new IOException("Exception parsing token", e);
122 }
123 }
124
125 public void setClientId(String clientId) {
126 this.clientId = clientId;
127 }
128
129 public void setRedirectUri(String redirectUri) {
130 this.redirectUri = redirectUri;
131 }
132
133 public String getAccessToken() throws IOException {
134
135 if (isTokenExpired()) {
136 LOGGER.debug("Access token expires soon, trying to refresh it");
137 refreshToken();
138 }
139
140 return accessToken;
141 }
142
143 private boolean isTokenExpired() {
144 return System.currentTimeMillis() > (expiresOn - 60000);
145 }
146
147 public void setAccessToken(String accessToken) {
148 this.accessToken = accessToken;
149
150 expiresOn = System.currentTimeMillis() + 1000 * 60 * 60;
151 }
152
153 public void setRefreshToken(String refreshToken) {
154 this.refreshToken = refreshToken;
155 }
156
157 public String getRefreshToken() {
158 return refreshToken;
159 }
160
161 public void refreshToken() throws IOException {
162 ArrayList<NameValuePair> parameters = new ArrayList<>();
163 parameters.add(new BasicNameValuePair("grant_type", "refresh_token"));
164 parameters.add(new BasicNameValuePair("refresh_token", refreshToken));
165 parameters.add(new BasicNameValuePair("redirect_uri", redirectUri));
166 parameters.add(new BasicNameValuePair("client_id", clientId));
167 parameters.add(new BasicNameValuePair("resource", Settings.OUTLOOK_URL));
168
169 RestRequest tokenRequest = new RestRequest(tokenUrl, new UrlEncodedFormEntity(parameters, Consts.UTF_8));
170
171 executeRequest(tokenRequest);
172
173
174 persistToken();
175 }
176
177 private void executeRequest(RestRequest tokenMethod) throws IOException {
178
179 try (
180 HttpClientAdapter httpClientAdapter = new HttpClientAdapter(tokenUrl);
181 CloseableHttpResponse response = httpClientAdapter.execute(tokenMethod)
182 ) {
183 setJsonToken(tokenMethod.handleResponse(response));
184 }
185 }
186
187 static O365Token build(String tenantId, String clientId, String redirectUri, String code, String password) throws IOException {
188 O365Token token = new O365Token(tenantId, clientId, redirectUri, code, password);
189 token.persistToken();
190 return token;
191 }
192
193
194 static O365Token load(String tenantId, String clientId, String redirectUri, String username, String password) throws UnknownHostException {
195 O365Token token = null;
196 if (Settings.getBooleanProperty("davmail.oauth.persistToken", true)) {
197 String encryptedRefreshToken = Settings.loadRefreshToken(username);
198 if (encryptedRefreshToken != null) {
199 String refreshToken;
200 try {
201 refreshToken = decryptToken(encryptedRefreshToken, password);
202 LOGGER.debug("Loaded stored token for " + username);
203 O365Token localToken = new O365Token(tenantId, clientId, redirectUri, password);
204
205 localToken.setRefreshToken(refreshToken);
206 localToken.refreshToken();
207 LOGGER.debug("Authenticated user " + localToken.getUsername() + " from stored token");
208 token = localToken;
209
210 } catch (UnknownHostException e) {
211
212 throw e;
213 } catch (IOException e) {
214 LOGGER.error("refresh token failed " + e.getMessage());
215
216 }
217 }
218 }
219 return token;
220 }
221
222 private void persistToken() throws IOException {
223 if (Settings.getBooleanProperty("davmail.oauth.persistToken", true)) {
224 if (password == null || password.isEmpty()) {
225
226 Settings.storeRefreshToken(username, refreshToken);
227 } else {
228 Settings.storeRefreshToken(username, O365Token.encryptToken(refreshToken, password));
229 }
230 }
231 }
232
233 private static String decryptToken(String encryptedRefreshToken, String password) throws IOException {
234 return new StringEncryptor(password).decryptString(encryptedRefreshToken);
235 }
236
237 private static String encryptToken(String refreshToken, String password) throws IOException {
238 return new StringEncryptor(password).encryptString(refreshToken);
239 }
240 }