]> git.basschouten.com Git - openhab-addons.git/blob
20a4f4c5dc18614db862ad3de29f40b085fe7fca
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2022 Contributors to the openHAB project
3  *
4  * See the NOTICE file(s) distributed with this work for additional
5  * information.
6  *
7  * This program and the accompanying materials are made available under the
8  * terms of the Eclipse Public License 2.0 which is available at
9  * http://www.eclipse.org/legal/epl-2.0
10  *
11  * SPDX-License-Identifier: EPL-2.0
12  */
13 package org.openhab.binding.mybmw.internal.handler;
14
15 import static org.openhab.binding.mybmw.internal.utils.HTTPConstants.*;
16
17 import java.nio.charset.StandardCharsets;
18 import java.security.KeyFactory;
19 import java.security.MessageDigest;
20 import java.security.PublicKey;
21 import java.security.spec.X509EncodedKeySpec;
22 import java.util.Base64;
23 import java.util.Optional;
24 import java.util.concurrent.TimeUnit;
25
26 import javax.crypto.Cipher;
27
28 import org.eclipse.jdt.annotation.NonNullByDefault;
29 import org.eclipse.jdt.annotation.Nullable;
30 import org.eclipse.jetty.client.HttpClient;
31 import org.eclipse.jetty.client.HttpResponseException;
32 import org.eclipse.jetty.client.api.ContentResponse;
33 import org.eclipse.jetty.client.api.Request;
34 import org.eclipse.jetty.client.api.Result;
35 import org.eclipse.jetty.client.util.BufferingResponseListener;
36 import org.eclipse.jetty.client.util.StringContentProvider;
37 import org.eclipse.jetty.http.HttpHeader;
38 import org.eclipse.jetty.util.MultiMap;
39 import org.eclipse.jetty.util.UrlEncoded;
40 import org.openhab.binding.mybmw.internal.MyBMWConfiguration;
41 import org.openhab.binding.mybmw.internal.VehicleConfiguration;
42 import org.openhab.binding.mybmw.internal.dto.auth.AuthQueryResponse;
43 import org.openhab.binding.mybmw.internal.dto.auth.AuthResponse;
44 import org.openhab.binding.mybmw.internal.dto.auth.ChinaPublicKeyResponse;
45 import org.openhab.binding.mybmw.internal.dto.auth.ChinaTokenExpiration;
46 import org.openhab.binding.mybmw.internal.dto.auth.ChinaTokenResponse;
47 import org.openhab.binding.mybmw.internal.dto.network.NetworkError;
48 import org.openhab.binding.mybmw.internal.handler.simulation.Injector;
49 import org.openhab.binding.mybmw.internal.utils.BimmerConstants;
50 import org.openhab.binding.mybmw.internal.utils.Constants;
51 import org.openhab.binding.mybmw.internal.utils.Converter;
52 import org.openhab.binding.mybmw.internal.utils.HTTPConstants;
53 import org.openhab.binding.mybmw.internal.utils.ImageProperties;
54 import org.openhab.core.io.net.http.HttpClientFactory;
55 import org.slf4j.Logger;
56 import org.slf4j.LoggerFactory;
57
58 /**
59  * The {@link MyBMWProxy} This class holds the important constants for the BMW Connected Drive Authorization.
60  * They
61  * are taken from the Bimmercode from github {@link https://github.com/bimmerconnected/bimmer_connected}
62  * File defining these constants
63  * {@link https://github.com/bimmerconnected/bimmer_connected/blob/master/bimmer_connected/account.py}
64  * https://customer.bmwgroup.com/one/app/oauth.js
65  *
66  * @author Bernd Weymann - Initial contribution
67  * @author Norbert Truchsess - edit & send of charge profile
68  */
69 @NonNullByDefault
70 public class MyBMWProxy {
71     private final Logger logger = LoggerFactory.getLogger(MyBMWProxy.class);
72     private Optional<RemoteServiceHandler> remoteServiceHandler = Optional.empty();
73     private final Token token = new Token();
74     private final HttpClient httpClient;
75     private final MyBMWConfiguration configuration;
76
77     /**
78      * URLs taken from https://github.com/bimmerconnected/bimmer_connected/blob/master/bimmer_connected/const.py
79      */
80     final String vehicleUrl;
81     final String remoteCommandUrl;
82     final String remoteStatusUrl;
83     final String serviceExecutionAPI = "/executeService";
84     final String serviceExecutionStateAPI = "/serviceExecutionStatus";
85     final String remoteServiceEADRXstatusUrl = BimmerConstants.API_REMOTE_SERVICE_BASE_URL
86             + "eventStatus?eventId={event_id}";
87
88     public MyBMWProxy(HttpClientFactory httpClientFactory, MyBMWConfiguration config) {
89         httpClient = httpClientFactory.getCommonHttpClient();
90         configuration = config;
91
92         vehicleUrl = "https://" + BimmerConstants.EADRAX_SERVER_MAP.get(configuration.region)
93                 + BimmerConstants.API_VEHICLES;
94
95         remoteCommandUrl = "https://" + BimmerConstants.EADRAX_SERVER_MAP.get(configuration.region)
96                 + BimmerConstants.API_REMOTE_SERVICE_BASE_URL;
97         remoteStatusUrl = remoteCommandUrl + "eventStatus";
98     }
99
100     public synchronized void call(final String url, final boolean post, final @Nullable String encoding,
101             final @Nullable String params, final String brand, final ResponseCallback callback) {
102         // only executed in "simulation mode"
103         // SimulationTest.testSimulationOff() assures Injector is off when releasing
104         if (Injector.isActive()) {
105             if (url.equals(vehicleUrl)) {
106                 ((StringResponseCallback) callback).onResponse(Injector.getDiscovery());
107             } else if (url.endsWith(vehicleUrl)) {
108                 ((StringResponseCallback) callback).onResponse(Injector.getStatus());
109             } else {
110                 logger.debug("Simulation of {} not supported", url);
111             }
112             return;
113         }
114
115         // return in case of unknown brand
116         String userAgent = BimmerConstants.BRAND_USER_AGENTS_MAP.get(brand.toLowerCase());
117         if (userAgent == null) {
118             logger.warn("Unknown Brand {}", brand);
119             return;
120         }
121
122         final Request req;
123         final String completeUrl;
124
125         if (post) {
126             completeUrl = url;
127             req = httpClient.POST(url);
128             if (encoding != null) {
129                 req.header(HttpHeader.CONTENT_TYPE, encoding);
130                 if (CONTENT_TYPE_URL_ENCODED.equals(encoding)) {
131                     req.content(new StringContentProvider(CONTENT_TYPE_URL_ENCODED, params, StandardCharsets.UTF_8));
132                 } else if (CONTENT_TYPE_JSON_ENCODED.equals(encoding)) {
133                     req.content(new StringContentProvider(CONTENT_TYPE_JSON_ENCODED, params, StandardCharsets.UTF_8));
134                 }
135             }
136         } else {
137             completeUrl = params == null ? url : url + Constants.QUESTION + params;
138             req = httpClient.newRequest(completeUrl);
139         }
140         req.header(HttpHeader.AUTHORIZATION, getToken().getBearerToken());
141         req.header(HTTPConstants.X_USER_AGENT, userAgent);
142         req.header(HttpHeader.ACCEPT_LANGUAGE, configuration.language);
143         if (callback instanceof ByteResponseCallback) {
144             req.header(HttpHeader.ACCEPT, "image/png");
145         } else {
146             req.header(HttpHeader.ACCEPT, CONTENT_TYPE_JSON_ENCODED);
147         }
148
149         req.timeout(HTTP_TIMEOUT_SEC, TimeUnit.SECONDS).send(new BufferingResponseListener() {
150             @NonNullByDefault({})
151             @Override
152             public void onComplete(Result result) {
153                 if (result.getResponse().getStatus() != 200) {
154                     NetworkError error = new NetworkError();
155                     error.url = completeUrl;
156                     error.status = result.getResponse().getStatus();
157                     if (result.getResponse().getReason() != null) {
158                         error.reason = result.getResponse().getReason();
159                     } else {
160                         error.reason = result.getFailure().getMessage();
161                     }
162                     error.params = result.getRequest().getParams().toString();
163                     logger.debug("HTTP Error {}", error.toString());
164                     callback.onError(error);
165                 } else {
166                     if (callback instanceof StringResponseCallback) {
167                         ((StringResponseCallback) callback).onResponse(getContentAsString());
168                     } else if (callback instanceof ByteResponseCallback) {
169                         ((ByteResponseCallback) callback).onResponse(getContent());
170                     } else {
171                         logger.error("unexpected reponse type {}", callback.getClass().getName());
172                     }
173                 }
174             }
175         });
176     }
177
178     public void get(String url, @Nullable String coding, @Nullable String params, final String brand,
179             ResponseCallback callback) {
180         call(url, false, coding, params, brand, callback);
181     }
182
183     public void post(String url, @Nullable String coding, @Nullable String params, final String brand,
184             ResponseCallback callback) {
185         call(url, true, coding, params, brand, callback);
186     }
187
188     /**
189      * request all vehicles for one specific brand
190      *
191      * @param brand
192      * @param callback
193      */
194     public void requestVehicles(String brand, StringResponseCallback callback) {
195         // calculate necessary parameters for query
196         MultiMap<String> vehicleParams = new MultiMap<String>();
197         vehicleParams.put(BimmerConstants.TIRE_GUARD_MODE, Constants.ENABLED);
198         vehicleParams.put(BimmerConstants.APP_DATE_TIME, Long.toString(System.currentTimeMillis()));
199         vehicleParams.put(BimmerConstants.APP_TIMEZONE, Integer.toString(Converter.getOffsetMinutes()));
200         String params = UrlEncoded.encode(vehicleParams, StandardCharsets.UTF_8, false);
201         get(vehicleUrl + "?" + params, null, null, brand, callback);
202     }
203
204     /**
205      * request vehicles for all possible brands
206      *
207      * @param callback
208      */
209     public void requestVehicles(StringResponseCallback callback) {
210         BimmerConstants.ALL_BRANDS.forEach(brand -> {
211             requestVehicles(brand, callback);
212         });
213     }
214
215     public void requestImage(VehicleConfiguration config, ImageProperties props, ByteResponseCallback callback) {
216         final String localImageUrl = "https://" + BimmerConstants.EADRAX_SERVER_MAP.get(configuration.region)
217                 + "/eadrax-ics/v3/presentation/vehicles/" + config.vin + "/images?carView=" + props.viewport;
218         get(localImageUrl, null, null, config.vehicleBrand, callback);
219     }
220
221     /**
222      * request charge statistics for electric vehicles
223      *
224      * @param callback
225      */
226     public void requestChargeStatistics(VehicleConfiguration config, StringResponseCallback callback) {
227         MultiMap<String> chargeStatisticsParams = new MultiMap<String>();
228         chargeStatisticsParams.put("vin", config.vin);
229         chargeStatisticsParams.put("currentDate", Converter.getCurrentISOTime());
230         String params = UrlEncoded.encode(chargeStatisticsParams, StandardCharsets.UTF_8, false);
231         String chargeStatisticsUrl = "https://" + BimmerConstants.EADRAX_SERVER_MAP.get(configuration.region)
232                 + "/eadrax-chs/v1/charging-statistics?" + params;
233         get(chargeStatisticsUrl, null, null, config.vehicleBrand, callback);
234     }
235
236     /**
237      * request charge statistics for electric vehicles
238      *
239      * @param callback
240      */
241     public void requestChargeSessions(VehicleConfiguration config, StringResponseCallback callback) {
242         MultiMap<String> chargeSessionsParams = new MultiMap<String>();
243         chargeSessionsParams.put("vin", "WBY1Z81040V905639");
244         chargeSessionsParams.put("maxResults", "40");
245         chargeSessionsParams.put("include_date_picker", "true");
246         String params = UrlEncoded.encode(chargeSessionsParams, StandardCharsets.UTF_8, false);
247         String chargeSessionsUrl = "https://" + BimmerConstants.EADRAX_SERVER_MAP.get(configuration.region)
248                 + "/eadrax-chs/v1/charging-sessions?" + params;
249
250         get(chargeSessionsUrl, null, null, config.vehicleBrand, callback);
251     }
252
253     RemoteServiceHandler getRemoteServiceHandler(VehicleHandler vehicleHandler) {
254         remoteServiceHandler = Optional.of(new RemoteServiceHandler(vehicleHandler, this));
255         return remoteServiceHandler.get();
256     }
257
258     // Token handling
259
260     /**
261      * Gets new token if old one is expired or invalid. In case of error the token remains.
262      * So if token refresh fails the corresponding requests will also fail and update the
263      * Thing status accordingly.
264      *
265      * @return token
266      */
267     public Token getToken() {
268         if (!token.isValid()) {
269             boolean tokenUpdateSuccess = false;
270             switch (configuration.region) {
271                 case BimmerConstants.REGION_CHINA:
272                     tokenUpdateSuccess = updateTokenChina();
273                     break;
274                 case BimmerConstants.REGION_NORTH_AMERICA:
275                     tokenUpdateSuccess = updateToken();
276                     break;
277                 case BimmerConstants.REGION_ROW:
278                     tokenUpdateSuccess = updateToken();
279                     break;
280                 default:
281                     logger.warn("Region {} not supported", configuration.region);
282                     break;
283             }
284             if (!tokenUpdateSuccess) {
285                 logger.debug("Authorization failed!");
286             }
287         }
288         return token;
289     }
290
291     /**
292      * Everything is catched by surroundig try catch
293      * - HTTP Exceptions
294      * - JSONSyntax Exceptions
295      * - potential NullPointer Exceptions
296      *
297      * @return
298      */
299     @SuppressWarnings("null")
300     public synchronized boolean updateToken() {
301         try {
302             /*
303              * Step 1) Get basic values for further queries
304              */
305             String authValuesUrl = "https://" + BimmerConstants.EADRAX_SERVER_MAP.get(configuration.region)
306                     + BimmerConstants.API_OAUTH_CONFIG;
307             Request authValuesRequest = httpClient.newRequest(authValuesUrl);
308             authValuesRequest.header(ACP_SUBSCRIPTION_KEY, BimmerConstants.OCP_APIM_KEYS.get(configuration.region));
309             authValuesRequest.header(X_USER_AGENT, BimmerConstants.USER_AGENT_BMW);
310             ContentResponse authValuesResponse = authValuesRequest.send();
311             if (authValuesResponse.getStatus() != 200) {
312                 throw new HttpResponseException("URL: " + authValuesRequest.getURI() + ", Error: "
313                         + authValuesResponse.getStatus() + ", Message: " + authValuesResponse.getContentAsString(),
314                         authValuesResponse);
315             }
316             AuthQueryResponse aqr = Converter.getGson().fromJson(authValuesResponse.getContentAsString(),
317                     AuthQueryResponse.class);
318
319             /*
320              * Step 2) Calculate values for base parameters
321              */
322             String verfifierBytes = Converter.getRandomString(64);
323             String codeVerifier = Base64.getUrlEncoder().withoutPadding().encodeToString(verfifierBytes.getBytes());
324             MessageDigest digest = MessageDigest.getInstance("SHA-256");
325             byte[] hash = digest.digest(codeVerifier.getBytes(StandardCharsets.UTF_8));
326             String codeChallange = Base64.getUrlEncoder().withoutPadding().encodeToString(hash);
327             String stateBytes = Converter.getRandomString(16);
328             String state = Base64.getUrlEncoder().withoutPadding().encodeToString(stateBytes.getBytes());
329
330             MultiMap<String> baseParams = new MultiMap<String>();
331             baseParams.put(CLIENT_ID, aqr.clientId);
332             baseParams.put(RESPONSE_TYPE, CODE);
333             baseParams.put(REDIRECT_URI, aqr.returnUrl);
334             baseParams.put(STATE, state);
335             baseParams.put(NONCE, BimmerConstants.LOGIN_NONCE);
336             baseParams.put(SCOPE, String.join(Constants.SPACE, aqr.scopes));
337             baseParams.put(CODE_CHALLENGE, codeChallange);
338             baseParams.put(CODE_CHALLENGE_METHOD, "S256");
339
340             /**
341              * Step 3) Authorization with username and password
342              */
343             String loginUrl = aqr.gcdmBaseUrl + BimmerConstants.OAUTH_ENDPOINT;
344             Request loginRequest = httpClient.POST(loginUrl);
345             loginRequest.header(HttpHeader.CONTENT_TYPE, CONTENT_TYPE_URL_ENCODED);
346
347             MultiMap<String> loginParams = new MultiMap<String>(baseParams);
348             loginParams.put(GRANT_TYPE, BimmerConstants.AUTHORIZATION_CODE);
349             loginParams.put(USERNAME, configuration.userName);
350             loginParams.put(PASSWORD, configuration.password);
351             loginRequest.content(new StringContentProvider(CONTENT_TYPE_URL_ENCODED,
352                     UrlEncoded.encode(loginParams, StandardCharsets.UTF_8, false), StandardCharsets.UTF_8));
353             ContentResponse loginResponse = loginRequest.send();
354             if (loginResponse.getStatus() != 200) {
355                 throw new HttpResponseException("URL: " + loginRequest.getURI() + ", Error: "
356                         + loginResponse.getStatus() + ", Message: " + loginResponse.getContentAsString(),
357                         loginResponse);
358             }
359             String authCode = getAuthCode(loginResponse.getContentAsString());
360
361             /**
362              * Step 4) Authorize with code
363              */
364             Request authRequest = httpClient.POST(loginUrl).followRedirects(false);
365             MultiMap<String> authParams = new MultiMap<String>(baseParams);
366             authParams.put(AUTHORIZATION, authCode);
367             authRequest.header(HttpHeader.CONTENT_TYPE, CONTENT_TYPE_URL_ENCODED);
368             authRequest.content(new StringContentProvider(CONTENT_TYPE_URL_ENCODED,
369                     UrlEncoded.encode(authParams, StandardCharsets.UTF_8, false), StandardCharsets.UTF_8));
370             ContentResponse authResponse = authRequest.send();
371             if (authResponse.getStatus() != 302) {
372                 throw new HttpResponseException("URL: " + authRequest.getURI() + ", Error: " + authResponse.getStatus()
373                         + ", Message: " + authResponse.getContentAsString(), authResponse);
374             }
375             String code = MyBMWProxy.codeFromUrl(authResponse.getHeaders().get(HttpHeader.LOCATION));
376
377             /**
378              * Step 5) Request token
379              */
380             Request codeRequest = httpClient.POST(aqr.tokenEndpoint);
381             String basicAuth = "Basic "
382                     + Base64.getUrlEncoder().encodeToString((aqr.clientId + ":" + aqr.clientSecret).getBytes());
383             codeRequest.header(HttpHeader.CONTENT_TYPE, CONTENT_TYPE_URL_ENCODED);
384             codeRequest.header(AUTHORIZATION, basicAuth);
385
386             MultiMap<String> codeParams = new MultiMap<String>();
387             codeParams.put(CODE, code);
388             codeParams.put(CODE_VERIFIER, codeVerifier);
389             codeParams.put(REDIRECT_URI, aqr.returnUrl);
390             codeParams.put(GRANT_TYPE, BimmerConstants.AUTHORIZATION_CODE);
391             codeRequest.content(new StringContentProvider(CONTENT_TYPE_URL_ENCODED,
392                     UrlEncoded.encode(codeParams, StandardCharsets.UTF_8, false), StandardCharsets.UTF_8));
393             ContentResponse codeResponse = codeRequest.send();
394             if (codeResponse.getStatus() != 200) {
395                 throw new HttpResponseException("URL: " + codeRequest.getURI() + ", Error: " + codeResponse.getStatus()
396                         + ", Message: " + codeResponse.getContentAsString(), codeResponse);
397             }
398             AuthResponse ar = Converter.getGson().fromJson(codeResponse.getContentAsString(), AuthResponse.class);
399             token.setType(ar.tokenType);
400             token.setToken(ar.accessToken);
401             token.setExpiration(ar.expiresIn);
402             return true;
403         } catch (Exception e) {
404             logger.warn("Authorization Exception: {}", e.getMessage());
405         }
406         return false;
407     }
408
409     private String getAuthCode(String response) {
410         String[] keys = response.split("&");
411         for (int i = 0; i < keys.length; i++) {
412             if (keys[i].startsWith(AUTHORIZATION)) {
413                 String authCode = keys[i].split("=")[1];
414                 authCode = authCode.split("\"")[0];
415                 return authCode;
416             }
417         }
418         return Constants.EMPTY;
419     }
420
421     public static String codeFromUrl(String encodedUrl) {
422         final MultiMap<String> tokenMap = new MultiMap<String>();
423         UrlEncoded.decodeTo(encodedUrl, tokenMap, StandardCharsets.US_ASCII);
424         final StringBuilder codeFound = new StringBuilder();
425         tokenMap.forEach((key, value) -> {
426             if (value.size() > 0) {
427                 String val = value.get(0);
428                 if (key.endsWith(CODE)) {
429                     codeFound.append(val.toString());
430                 }
431             }
432         });
433         return codeFound.toString();
434     }
435
436     @SuppressWarnings("null")
437     public synchronized boolean updateTokenChina() {
438         try {
439             /**
440              * Step 1) get public key
441              */
442             String publicKeyUrl = "https://" + BimmerConstants.EADRAX_SERVER_MAP.get(BimmerConstants.REGION_CHINA)
443                     + BimmerConstants.CHINA_PUBLIC_KEY;
444             Request oauthQueryRequest = httpClient.newRequest(publicKeyUrl);
445             oauthQueryRequest.header(X_USER_AGENT, BimmerConstants.USER_AGENT_BMW);
446             ContentResponse publicKeyResponse = oauthQueryRequest.send();
447             if (publicKeyResponse.getStatus() != 200) {
448                 throw new HttpResponseException("URL: " + oauthQueryRequest.getURI() + ", Error: "
449                         + publicKeyResponse.getStatus() + ", Message: " + publicKeyResponse.getContentAsString(),
450                         publicKeyResponse);
451             }
452             ChinaPublicKeyResponse pkr = Converter.getGson().fromJson(publicKeyResponse.getContentAsString(),
453                     ChinaPublicKeyResponse.class);
454
455             /**
456              * Step 2) Encode password with public key
457              */
458             // https://www.baeldung.com/java-read-pem-file-keys
459             String publicKeyStr = pkr.data.value;
460             String publicKeyPEM = publicKeyStr.replace("-----BEGIN PUBLIC KEY-----", "")
461                     .replaceAll(System.lineSeparator(), "").replace("-----END PUBLIC KEY-----", "").replace("\\r", "")
462                     .replace("\\n", "").trim();
463             byte[] encoded = Base64.getDecoder().decode(publicKeyPEM);
464             X509EncodedKeySpec spec = new X509EncodedKeySpec(encoded);
465             KeyFactory kf = KeyFactory.getInstance("RSA");
466             PublicKey publicKey = kf.generatePublic(spec);
467             // https://www.thexcoders.net/java-ciphers-rsa/
468             Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
469             cipher.init(Cipher.ENCRYPT_MODE, publicKey);
470             byte[] encryptedBytes = cipher.doFinal(configuration.password.getBytes());
471             String encodedPassword = Base64.getEncoder().encodeToString(encryptedBytes);
472
473             /**
474              * Step 3) Send Auth with encoded password
475              */
476             String tokenUrl = "https://" + BimmerConstants.EADRAX_SERVER_MAP.get(BimmerConstants.REGION_CHINA)
477                     + BimmerConstants.CHINA_LOGIN;
478             Request loginRequest = httpClient.POST(tokenUrl);
479             loginRequest.header(X_USER_AGENT, BimmerConstants.USER_AGENT_BMW);
480             String jsonContent = "{ \"mobile\":\"" + configuration.userName + "\", \"password\":\"" + encodedPassword
481                     + "\"}";
482             loginRequest.content(new StringContentProvider(jsonContent));
483             ContentResponse tokenResponse = loginRequest.send();
484             if (tokenResponse.getStatus() != 200) {
485                 throw new HttpResponseException("URL: " + loginRequest.getURI() + ", Error: "
486                         + tokenResponse.getStatus() + ", Message: " + tokenResponse.getContentAsString(),
487                         tokenResponse);
488             }
489             String authCode = getAuthCode(tokenResponse.getContentAsString());
490
491             /**
492              * Step 4) Decode access token
493              */
494             ChinaTokenResponse cat = Converter.getGson().fromJson(authCode, ChinaTokenResponse.class);
495             String token = cat.data.accessToken;
496             // https://www.baeldung.com/java-jwt-token-decode
497             String[] chunks = token.split("\\.");
498             String tokenJwtDecodeStr = new String(Base64.getUrlDecoder().decode(chunks[1]));
499             ChinaTokenExpiration cte = Converter.getGson().fromJson(tokenJwtDecodeStr, ChinaTokenExpiration.class);
500             Token t = new Token();
501             t.setToken(token);
502             t.setType(cat.data.tokenType);
503             t.setExpirationTotal(cte.exp);
504             return true;
505         } catch (Exception e) {
506             logger.warn("Authorization Exception: {}", e.getMessage());
507         }
508         return false;
509     }
510 }