6 Commits

Author SHA1 Message Date
zhiqiand
6fb30ce7e4 add lastAccess and created
Signed-off-by: zhiqiand <zhiqian@fb.com>
2022-10-05 20:30:25 -07:00
zhiqiand
350a45b616 fix refresh token logic and sychronizing
Signed-off-by: zhiqiand <zhiqian@fb.com>
2022-10-04 19:00:24 -07:00
zhiqiand
52dae760d8 fix some comments
Signed-off-by: zhiqiand <zhiqian@fb.com>
2022-10-04 16:08:03 -07:00
zhiqiand
343fc7b6ee fix comments on thread-safe and getting refreshAccessToken called
Signed-off-by: zhiqiand <zhiqian@fb.com>
2022-10-04 16:08:03 -07:00
zhiqiand
2a952f56a9 fix some comments
Signed-off-by: zhiqiand <zhiqian@fb.com>
2022-10-04 16:08:03 -07:00
zhiqiand
52a2258c2d initial commit
Signed-off-by: zhiqiand <zhiqian@fb.com>
2022-10-04 16:08:03 -07:00
6 changed files with 185 additions and 17 deletions

View File

@@ -171,6 +171,7 @@ public class ConfigManager implements Runnable {
return;
}
}
client.refreshAccessToken();
// Fetch device list
List<DeviceWithStatus> devices = client.getDevices();

View File

@@ -218,6 +218,7 @@ public class DataCollector implements Runnable {
return;
}
}
client.refreshAccessToken();
// Fetch device list
List<DeviceWithStatus> devices = client.getDevices();

View File

@@ -238,6 +238,7 @@ public class Modeler implements Runnable {
return;
}
}
client.refreshAccessToken();
// TODO: backfill data from database?

View File

@@ -103,6 +103,7 @@ public class ProvMonitor implements Runnable {
return;
}
}
client.refreshAccessToken();
// Fetch data from owprov
// TODO: this may change later - for now, we only fetch inventory and

View File

@@ -8,6 +8,7 @@
package com.facebook.openwifirrm.ucentral;
import java.time.Instant;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
@@ -30,6 +31,8 @@ import com.facebook.openwifirrm.ucentral.gw.models.ServiceEvent;
import com.facebook.openwifirrm.ucentral.gw.models.StatisticsRecords;
import com.facebook.openwifirrm.ucentral.gw.models.SystemInfoResults;
import com.facebook.openwifirrm.ucentral.gw.models.TokenValidationResult;
import com.facebook.openwifirrm.ucentral.gw.models.WebTokenRefreshRequest;
import com.facebook.openwifirrm.ucentral.gw.models.WebTokenResult;
import com.facebook.openwifirrm.ucentral.gw.models.WifiScanRequest;
import com.facebook.openwifirrm.ucentral.prov.models.EntityList;
import com.facebook.openwifirrm.ucentral.prov.models.InventoryTagList;
@@ -137,7 +140,18 @@ public class UCentralClient {
* The access token obtained from uCentralSec, needed only when using public
* endpoints.
*/
private String accessToken;
private WebTokenResult accessToken;
/**
* The unix timestamp (in seconds) keeps track of when the accessToken is created.
*/
private long created;
/**
* The unix timestamp (in seconds) keeps track of last time when the accessToken
* is accessed.
*/
private long lastAccess;
/**
* Constructor.
@@ -184,7 +198,8 @@ public class UCentralClient {
Map<String, Object> body = new HashMap<>();
body.put("userId", username);
body.put("password", password);
HttpResponse<String> response = httpPost("oauth2", OWSEC_SERVICE, body);
HttpResponse<String> response =
httpPost("oauth2", OWSEC_SERVICE, body, null);
if (!response.isSuccess()) {
logger.error(
"Login failed: Response code {}, body: {}",
@@ -195,27 +210,146 @@ public class UCentralClient {
}
// Parse access token from response
JSONObject respBody;
WebTokenResult token;
try {
respBody = new JSONObject(response.getBody());
} catch (JSONException e) {
token = gson.fromJson(response.getBody(), WebTokenResult.class);
} catch (JsonSyntaxException e) {
logger.error("Login failed: Unexpected response", e);
logger.debug("Response body: {}", response.getBody());
return false;
}
if (!respBody.has("access_token")) {
if (
token == null || token.access_token == null ||
token.access_token.isEmpty()
) {
logger.error("Login failed: Missing access token");
logger.debug("Response body: {}", respBody.toString());
logger.debug("Response body: {}", response.getBody());
return false;
}
this.accessToken = respBody.getString("access_token");
this.accessToken = token;
this.created = accessToken.created;
this.lastAccess = accessToken.created;
logger.info("Login successful as user: {}", username);
logger.debug("Access token: {}", accessToken);
logger.debug("Access token: {}", accessToken.access_token);
logger.debug("Refresh token: {}", accessToken.refresh_token);
// Load system endpoints
return loadSystemEndpoints();
}
/**
* when using public endpoints, refresh the access token if it's expired.
*/
public synchronized void refreshAccessToken() {
if (usePublicEndpoints) {
refreshAccessTokenImpl();
}
}
/**
* Check if the token is completely expired even if
* for a token refresh request
*
* @return true if the refresh token is expired
*/
private boolean isAccessTokenExpired() {
if (accessToken == null) {
return true;
}
return created + accessToken.expires_in <
Instant.now().getEpochSecond();
}
/**
* Check if an access token is expired.
*
* @return true if the access token is expired
*/
private boolean isAccessTokenTimedOut() {
if (accessToken == null) {
return true;
}
return lastAccess + accessToken.idle_timeout <
Instant.now().getEpochSecond();
}
/**
* Refresh the access toke when time out. If the refresh token is expired, login again.
* If the access token is expired, POST a WebTokenRefreshRequest to refresh token.
*/
private void refreshAccessTokenImpl() {
if (!usePublicEndpoints) {
return;
}
if (isAccessTokenExpired()) {
synchronized (this) {
if (isAccessTokenExpired()) {
logger.info("Token is expired, login again");
login();
}
}
} else if (isAccessTokenTimedOut()) {
synchronized (this) {
if (isAccessTokenTimedOut()) {
logger.debug("Access token timed out, refreshing the token");
accessToken = refreshToken();
created = Instant.now().getEpochSecond();
lastAccess = created;
if (accessToken != null) {
logger.debug("Successfully refresh token.");
}else{
logger.error(
"Fail to refresh token with access token: {}",
accessToken.access_token
);
}
}
}
}
}
/**
* POST a WebTokenRefreshRequest to refresh the access token.
*
* @return valid access token if success, otherwise return null.
*/
private WebTokenResult refreshToken() {
if (accessToken == null) {
return null;
}
WebTokenRefreshRequest refreshRequest = new WebTokenRefreshRequest();
refreshRequest.userId = username;
refreshRequest.refreshToken = accessToken.refresh_token;
logger.debug("refresh token: {}", accessToken.refresh_token);
Map<String, Object> parameters =
Collections.singletonMap("grant_type", "refresh_token");
HttpResponse<String> response =
httpPost(
"oauth2",
OWSEC_SERVICE,
refreshRequest,
parameters
);
if (!response.isSuccess()) {
logger.error(
"Failed to refresh token: Response code {}, body: {}",
response.getStatus(),
response.getBody()
);
return null;
}
try {
return gson.fromJson(response.getBody(), WebTokenResult.class);
} catch (JsonSyntaxException e) {
logger.error(
"Failed to serialize WebTokenResult: Unexpected response:",
e
);
logger.debug("Response body: {}", response.getBody());
return null;
}
}
/** Read system endpoint URLs from uCentralSec. */
private boolean loadSystemEndpoints() {
// Make request
@@ -324,8 +458,12 @@ public class UCentralClient {
.connectTimeout(connectTimeoutMs)
.socketTimeout(socketTimeoutMs);
if (usePublicEndpoints) {
if (accessToken != null) {
req.header("Authorization", "Bearer " + accessToken);
if (!isAccessTokenExpired()) {
req.header(
"Authorization",
"Bearer " + accessToken.access_token
);
lastAccess = Instant.now().getEpochSecond();
}
} else {
req
@@ -339,26 +477,29 @@ public class UCentralClient {
}
}
/** Send a POST request with a JSON body. */
/** Send a POST request with a JSON body and query params. */
private HttpResponse<String> httpPost(
String endpoint,
String service,
Object body
Object body,
Map<String, Object> parameters
) {
return httpPost(
endpoint,
service,
body,
parameters,
socketParams.connectTimeoutMs,
socketParams.socketTimeoutMs
);
}
/** Send a POST request with a JSON body using given timeout values. */
/** Send a POST request with a JSON body and query params using given timeout values. */
private HttpResponse<String> httpPost(
String endpoint,
String service,
Object body,
Map<String, Object> parameters,
int connectTimeoutMs,
int socketTimeoutMs
) {
@@ -367,9 +508,16 @@ public class UCentralClient {
.header("accept", "application/json")
.connectTimeout(connectTimeoutMs)
.socketTimeout(socketTimeoutMs);
if (parameters != null && !parameters.isEmpty()) {
req.queryString(parameters);
}
if (usePublicEndpoints) {
if (accessToken != null) {
req.header("Authorization", "Bearer " + accessToken);
if (!isAccessTokenExpired()) {
req.header(
"Authorization",
"Bearer " + accessToken.access_token
);
lastAccess = Instant.now().getEpochSecond();
}
} else {
req
@@ -454,6 +602,7 @@ public class UCentralClient {
String.format("device/%s/wifiscan", serialNumber),
OWGW_SERVICE,
req,
null,
socketParams.connectTimeoutMs,
socketParams.wifiScanTimeoutMs
);
@@ -482,7 +631,8 @@ public class UCentralClient {
HttpResponse<String> response = httpPost(
String.format("device/%s/configure", serialNumber),
OWGW_SERVICE,
req
req,
null
);
if (!response.isSuccess()) {
logger.error("Error: {}", response.getBody());

View File

@@ -0,0 +1,14 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.openwifirrm.ucentral.gw.models;
public class WebTokenRefreshRequest {
public String userId;
public String refreshToken;
}