View Javadoc
1   /*
2    * DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway
3    * Copyright (C) 2010  Mickael Guessant
4    *
5    * This program is free software; you can redistribute it and/or
6    * modify it under the terms of the GNU General Public License
7    * as published by the Free Software Foundation; either version 2
8    * of the License, or (at your option) any later version.
9    *
10   * This program is distributed in the hope that it will be useful,
11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13   * GNU General Public License for more details.
14   *
15   * You should have received a copy of the GNU General Public License
16   * along with this program; if not, write to the Free Software
17   * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
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 org.apache.http.Consts;
26  import org.apache.http.NameValuePair;
27  import org.apache.http.client.entity.UrlEncodedFormEntity;
28  import org.apache.http.client.methods.CloseableHttpResponse;
29  import org.apache.http.message.BasicNameValuePair;
30  import org.apache.log4j.Logger;
31  import org.codehaus.jettison.json.JSONException;
32  import org.codehaus.jettison.json.JSONObject;
33  
34  import java.io.IOException;
35  import java.net.URI;
36  import java.util.ArrayList;
37  
38  public class O365DeviceCodeAuthenticator implements ExchangeAuthenticator {
39      protected static final Logger LOGGER = Logger.getLogger(O365DeviceCodeAuthenticator.class);
40  
41      protected static class DeviceCode {
42          final private String deviceCode;
43          final private String message;
44  
45          DeviceCode(String deviceCode, String message) {
46              this.deviceCode = deviceCode;
47              this.message = message;
48          }
49  
50          public String getDeviceCode() {
51              return deviceCode;
52          }
53  
54          public String getMessage() {
55              return message;
56          }
57      }
58  
59      private String username;
60      private String password;
61      private O365Token token;
62      URI ewsUrl = URI.create(Settings.getO365Url());
63  
64      @Override
65      public void setUsername(String username) {
66          this.username = username;
67      }
68  
69      @Override
70      public void setPassword(String password) {
71          this.password = password;
72      }
73  
74      @Override
75      public void authenticate() throws IOException {
76          // common DavMail client id
77          final String clientId = Settings.getProperty("davmail.oauth.clientId", "facd6cff-a294-4415-b59f-c5b01937d7bd");
78          String resource;
79          if (Settings.getBooleanProperty("davmail.enableGraph", false)) {
80              resource = Settings.getGraphUrl();
81          } else {
82              resource = Settings.getOutlookUrl();
83          }
84  
85          // company tenantId or common
86          String tenantId = Settings.getProperty("davmail.oauth.tenantId", "common");
87  
88          // first try to load a stored token, redirectUri is empty with device code
89          token = O365Token.load(tenantId, clientId, "", username, password);
90          if (token != null) {
91              return;
92           }
93  
94          // build device code authorize url;
95          String url;
96  
97          DeviceCode deviceCode;
98          ArrayList<NameValuePair> parameters = new ArrayList<>();
99          parameters.add(new BasicNameValuePair("client_id", clientId));
100         if (Settings.getBooleanProperty("davmail.enableOidc", false)) {
101             url = Settings.getO365LoginUrl() + "/" + tenantId + "/oauth2/v2.0/devicecode?api-version=1.0";
102             parameters.add(new BasicNameValuePair("scope", Settings.getOauthScope()));
103         } else {
104             url = Settings.getO365LoginUrl() + "/" + tenantId + "/oauth2/devicecode?api-version=1.0";
105             parameters.add(new BasicNameValuePair("resource", resource));
106         }
107 
108         RestRequest logonMethod = new RestRequest(url, new UrlEncodedFormEntity(parameters, Consts.UTF_8));
109         // Executes device code request; parses response into DeviceCode object
110         try (
111                 HttpClientAdapter httpClientAdapter = new HttpClientAdapter(url);
112                 CloseableHttpResponse response = httpClientAdapter.execute(logonMethod)
113         ) {
114 
115             JSONObject deviceCodeResponse = logonMethod.handleResponse(response);
116             final String error = deviceCodeResponse.optString("error_description", deviceCodeResponse.optString("error", null));
117             if (error != null) {
118                 throw new IOException("Exception getting device code: " + error);
119             }
120             deviceCode = new DeviceCode(deviceCodeResponse.getString("device_code"), deviceCodeResponse.getString("message"));
121         } catch (JSONException e) {
122             throw new IOException("Exception parsing device code", e);
123         }
124 
125         // Polls for authorization completion; builds token on success
126         try {
127             while (token == null) {
128                 System.out.println(deviceCode.getMessage());
129 
130                 //noinspection BusyWait
131                 Thread.sleep(5000);
132 
133                 try {
134                     token = O365Token.build(tenantId, clientId, deviceCode, password);
135                 } catch (O365AuthorizationPending e) {
136                     LOGGER.error("Authorization pending for device code");
137                 }
138             }
139         } catch (InterruptedException e) {
140             LOGGER.error("Interrupted waiting for token " + e.getMessage());
141             Thread.currentThread().interrupt();
142         }
143 
144     }
145 
146     @Override
147     public O365Token getToken() throws IOException {
148         return token;
149     }
150 
151     @Override
152     public URI getExchangeUri() {
153         return ewsUrl;
154     }
155 
156     @Override
157     public HttpClientAdapter getHttpClientAdapter() {
158         return new HttpClientAdapter(getExchangeUri(), username, password, true);
159     }
160 
161     public static void main(String[] argv) throws IOException {
162         Settings.setDefaultSettings();
163         Settings.setProperty("davmail.server", "false");
164         //Settings.setProperty("davmail.enableOidc", "true");
165 
166         O365DeviceCodeAuthenticator authenticator = new O365DeviceCodeAuthenticator();
167         authenticator.setUsername("");
168         authenticator.authenticate();
169     }
170 }