mirror of
https://github.com/nextcloud/android.git
synced 2024-12-17 22:41:54 +03:00
Fixed visibility of authorization details when the device is turned aside; removed unused classes from old OAuth flow
This commit is contained in:
parent
ac07e35d8a
commit
1e5aa5345f
5 changed files with 3 additions and 486 deletions
|
@ -1,19 +0,0 @@
|
|||
package com.owncloud.android.authenticator.oauth2;
|
||||
|
||||
import org.json.JSONObject;
|
||||
|
||||
/**
|
||||
* Listener that expects results from OAuth2GetCodeRunnable class.
|
||||
*
|
||||
* @author SolidGear S.L.
|
||||
*
|
||||
*/
|
||||
public interface OnOAuth2GetCodeResultListener {
|
||||
|
||||
enum ResultOAuthType {
|
||||
OK_SSL, OK_NO_SSL, SSL_INIT_ERROR, HOST_NOT_AVAILABLE, TIMEOUT, NO_NETWORK_CONNECTION, INCORRECT_ADDRESS, INSTANCE_NOT_CONFIGURED, FILE_NOT_FOUND, UNKNOWN_ERROR, WRONG_CONNECTION, SSL_UNVERIFIED_SERVER, BAD_OC_VERSION
|
||||
}
|
||||
|
||||
public void onOAuth2GetCodeResult(ResultOAuthType type, JSONObject code);
|
||||
|
||||
}
|
|
@ -1,113 +0,0 @@
|
|||
package com.owncloud.android.authenticator.oauth2.connection;
|
||||
|
||||
import org.apache.http.HttpResponse;
|
||||
import org.apache.http.client.entity.UrlEncodedFormEntity;
|
||||
import org.apache.http.client.methods.HttpPost;
|
||||
import org.apache.http.client.params.ClientPNames;
|
||||
import org.apache.http.client.params.CookiePolicy;
|
||||
import org.apache.http.impl.client.DefaultHttpClient;
|
||||
import org.apache.http.params.BasicHttpParams;
|
||||
import org.apache.http.params.HttpConnectionParams;
|
||||
import org.apache.http.params.HttpParams;
|
||||
import org.apache.http.protocol.BasicHttpContext;
|
||||
import org.apache.http.protocol.HttpContext;
|
||||
import org.apache.http.util.EntityUtils;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
/**
|
||||
* Implements HTTP POST communications with an oAuth2 server.
|
||||
*
|
||||
* @author SolidGear S.L.
|
||||
*
|
||||
*/
|
||||
public class ConnectorOAuth2 {
|
||||
|
||||
private static final String TAG = "ConnectorOAuth2";
|
||||
/** Maximum time to wait for a response from the server when the connection is being tested, in MILLISECONDs. */
|
||||
private static final int TRY_CONNECTION_TIMEOUT = 5000;
|
||||
|
||||
private DefaultHttpClient httpClient;
|
||||
private HttpContext localContext;
|
||||
private String ConnectorOAuth2Url;
|
||||
|
||||
public ConnectorOAuth2 (String destUrl) {
|
||||
prepareConn();
|
||||
setConnectorOAuth2Url(destUrl);
|
||||
}
|
||||
|
||||
public ConnectorOAuth2 () {
|
||||
prepareConn();
|
||||
}
|
||||
|
||||
public String getConnectorOAuth2Url() {
|
||||
return ConnectorOAuth2Url;
|
||||
}
|
||||
|
||||
public void setConnectorOAuth2Url(String connectorOAuth2Url) {
|
||||
ConnectorOAuth2Url = connectorOAuth2Url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts the communication with the server.
|
||||
*
|
||||
* @param UrlEncodedFormEntity : parameters included in the POST call.
|
||||
* @return String : data returned from the server in String format.
|
||||
*/
|
||||
|
||||
public String connPost(UrlEncodedFormEntity data) {
|
||||
String dataOut = null;
|
||||
HttpPost httpPost = null;
|
||||
int responseCode = -1;
|
||||
HttpResponse response = null;
|
||||
|
||||
httpClient.getParams().setParameter(ClientPNames.COOKIE_POLICY, CookiePolicy.RFC_2109);
|
||||
|
||||
if (ConnectorOAuth2Url == null) {
|
||||
Log.e(TAG, "connPost error: destination URI could not be null");
|
||||
return null;
|
||||
}
|
||||
|
||||
if (data == null){
|
||||
Log.e(TAG, "connPost error: data to send to URI " + ConnectorOAuth2Url + "could not be null");
|
||||
return null;
|
||||
}
|
||||
|
||||
httpPost = new HttpPost(ConnectorOAuth2Url);
|
||||
|
||||
httpPost.setHeader("Accept","text/html,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5");
|
||||
httpPost.setHeader("Content-Type", "application/x-www-form-urlencoded");
|
||||
httpPost.setEntity((UrlEncodedFormEntity) data);
|
||||
|
||||
try {
|
||||
response = httpClient.execute(httpPost,localContext);
|
||||
|
||||
if (response == null) {
|
||||
Log.e(TAG, "connPost error: response from uri " + ConnectorOAuth2Url + " is null");
|
||||
return null;
|
||||
}
|
||||
|
||||
responseCode = response.getStatusLine().getStatusCode();
|
||||
|
||||
if ((responseCode != 200)) {
|
||||
Log.e(TAG, "connPost error: response from uri "+ ConnectorOAuth2Url + " returns status " + responseCode);
|
||||
return null;
|
||||
}
|
||||
|
||||
dataOut = EntityUtils.toString(response.getEntity());
|
||||
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "connPost Exception: " + e);
|
||||
}
|
||||
|
||||
return dataOut;
|
||||
}
|
||||
|
||||
private void prepareConn () {
|
||||
HttpParams localParams = new BasicHttpParams();
|
||||
HttpConnectionParams.setConnectionTimeout(localParams, TRY_CONNECTION_TIMEOUT);
|
||||
HttpConnectionParams.setSoTimeout(localParams, TRY_CONNECTION_TIMEOUT);
|
||||
httpClient = new DefaultHttpClient(localParams);
|
||||
localContext = new BasicHttpContext();
|
||||
}
|
||||
}
|
|
@ -1,200 +0,0 @@
|
|||
package com.owncloud.android.authenticator.oauth2.services;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
|
||||
import org.apache.http.NameValuePair;
|
||||
import org.apache.http.client.entity.UrlEncodedFormEntity;
|
||||
import org.apache.http.message.BasicNameValuePair;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import android.app.Service;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.os.IBinder;
|
||||
import android.util.Log;
|
||||
|
||||
import com.owncloud.android.authenticator.oauth2.OAuth2Context;
|
||||
import com.owncloud.android.authenticator.oauth2.connection.ConnectorOAuth2;
|
||||
|
||||
/**
|
||||
* Service class that implements the second communication with the oAuth2 server:
|
||||
* pooling for the token in an interval. It send a broadcast with the results when are positive;
|
||||
* otherwise, it continues asking to the server.
|
||||
*
|
||||
* @author Solid Gear S.L.
|
||||
*
|
||||
*/
|
||||
public class OAuth2GetTokenService extends Service {
|
||||
|
||||
public static final String TOKEN_RECEIVED_MESSAGE = "TOKEN_RECEIVED";
|
||||
public static final String TOKEN_RECEIVED_DATA = "TOKEN_DATA";
|
||||
public static final String TOKEN_URI = "TOKEN_URI";
|
||||
public static final String TOKEN_DEVICE_CODE = "device_code";
|
||||
public static final String TOKEN_INTERVAL = "interval";
|
||||
public static final String TOKEN_RECEIVED_ERROR = "error";
|
||||
public static final String TOKEN_RECEIVED_ERROR_AUTH_TOKEN = "authorization_pending";
|
||||
public static final String TOKEN_RECEIVED_ERROR_SLOW_DOWN = "slow_down";
|
||||
public static final String TOKEN_ACCESS_TOKEN = "access_token";
|
||||
public static final String TOKEN_TOKEN_TYPE = "token_type";
|
||||
public static final String TOKEN_EXPIRES_IN = "expires_in";
|
||||
public static final String TOKEN_REFRESH_TOKEN = "refresh_token";
|
||||
|
||||
private String requestDeviceCode;
|
||||
private int requestInterval = -1;
|
||||
private String requestBaseURI;
|
||||
private ConnectorOAuth2 connectorOAuth2;
|
||||
private static final String TAG = "OAuth2GetTokenService";
|
||||
private Timer timer = new Timer();
|
||||
|
||||
@Override
|
||||
public IBinder onBind(Intent arg0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||
Bundle param = intent.getExtras();
|
||||
|
||||
if (param != null) {
|
||||
String mUrl = param.getString(TOKEN_URI);
|
||||
if (!mUrl.startsWith("http://") || !mUrl.startsWith("https://")) {
|
||||
requestBaseURI = "https://" + mUrl;
|
||||
}
|
||||
requestDeviceCode = param.getString(TOKEN_DEVICE_CODE);
|
||||
requestInterval = param.getInt(TOKEN_INTERVAL);
|
||||
|
||||
Log.d(TAG, "onStartCommand -> requestDeviceCode=" + requestDeviceCode);
|
||||
Log.d(TAG, "onStartCommand -> requestInterval=" + requestInterval);
|
||||
} else {
|
||||
Log.e(TAG, "onStartCommand -> params could not be null");
|
||||
}
|
||||
startService();
|
||||
return Service.START_NOT_STICKY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
shutdownService();
|
||||
}
|
||||
|
||||
private void startService() {
|
||||
final UrlEncodedFormEntity params = prepareComm();
|
||||
timer.scheduleAtFixedRate(
|
||||
new TimerTask() {
|
||||
public void run() {
|
||||
requestToken(params);
|
||||
}
|
||||
}, 0, requestInterval * 1000);
|
||||
Log.d(TAG, "startService -> Timer started");
|
||||
}
|
||||
|
||||
private void shutdownService() {
|
||||
if (timer != null) timer.cancel();
|
||||
Log.d(TAG, "shutdownService -> Timer stopped");
|
||||
}
|
||||
|
||||
|
||||
private UrlEncodedFormEntity prepareComm() {
|
||||
|
||||
UrlEncodedFormEntity params = null;
|
||||
connectorOAuth2 = new ConnectorOAuth2();
|
||||
|
||||
if (requestBaseURI == null || requestBaseURI.trim().equals("")) {
|
||||
Log.e(TAG, "run -> request URI could not be null");
|
||||
postResult(null);
|
||||
}
|
||||
|
||||
if (requestInterval == -1) {
|
||||
Log.e(TAG, "run -> request Interval must have valid positive value");
|
||||
postResult(null);
|
||||
}
|
||||
|
||||
if (requestDeviceCode == null || requestDeviceCode.trim().equals("")) {
|
||||
Log.e(TAG, "run -> request DeviceCode could not be null");
|
||||
postResult(null);
|
||||
}
|
||||
|
||||
try{
|
||||
connectorOAuth2.setConnectorOAuth2Url(requestBaseURI + OAuth2Context.OAUTH2_G_DEVICE_GETTOKEN_URL);
|
||||
|
||||
List<NameValuePair> nameValuePairs = new ArrayList<NameValuePair>(2);
|
||||
nameValuePairs.add(new BasicNameValuePair("client_id", OAuth2Context.OAUTH2_G_DEVICE_CLIENT_ID));
|
||||
nameValuePairs.add(new BasicNameValuePair("client_secret", OAuth2Context.OAUTH2_G_DEVICE_CLIENT_SECRET));
|
||||
nameValuePairs.add(new BasicNameValuePair("code",requestDeviceCode));
|
||||
nameValuePairs.add(new BasicNameValuePair("grant_type",OAuth2Context.OAUTH_G_DEVICE_GETTOKEN_GRANT_TYPE));
|
||||
|
||||
params = new UrlEncodedFormEntity(nameValuePairs);
|
||||
}
|
||||
catch (Exception ex1){
|
||||
Log.w(TAG, ex1.toString());
|
||||
postResult(null);
|
||||
}
|
||||
|
||||
return params;
|
||||
}
|
||||
|
||||
protected void requestToken(UrlEncodedFormEntity params){
|
||||
JSONObject tokenJson = null;
|
||||
String error = null;
|
||||
HashMap<String, String> resultTokenMap;
|
||||
|
||||
String tokenResponse = connectorOAuth2.connPost(params);
|
||||
|
||||
try {
|
||||
tokenJson = new JSONObject(tokenResponse);
|
||||
} catch (JSONException e) {
|
||||
Log.e(TAG, "Exception converting to Json " + e.toString());
|
||||
}
|
||||
|
||||
try {
|
||||
// We try to get error string.
|
||||
if (tokenJson.has(TOKEN_RECEIVED_ERROR)) {
|
||||
error = tokenJson.getString(TOKEN_RECEIVED_ERROR);
|
||||
Log.d(TAG, "requestToken -> Obtained error "+ error);
|
||||
} else {
|
||||
//We have got the token. Parse the answer.
|
||||
resultTokenMap = parseResult(tokenJson);
|
||||
postResult(resultTokenMap);
|
||||
}
|
||||
} catch (JSONException e) {
|
||||
Log.e(TAG, "Exception converting to Json " + e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
private HashMap<String, String> parseResult (JSONObject tokenJson) {
|
||||
HashMap<String, String> resultTokenMap=new HashMap<String, String>();
|
||||
|
||||
try {
|
||||
resultTokenMap.put(TOKEN_ACCESS_TOKEN, tokenJson.getString(TOKEN_ACCESS_TOKEN));
|
||||
resultTokenMap.put(TOKEN_TOKEN_TYPE, tokenJson.getString(TOKEN_TOKEN_TYPE));
|
||||
resultTokenMap.put(TOKEN_EXPIRES_IN, tokenJson.getString(TOKEN_EXPIRES_IN));
|
||||
resultTokenMap.put(TOKEN_REFRESH_TOKEN, tokenJson.getString(TOKEN_REFRESH_TOKEN));
|
||||
} catch (JSONException e) {
|
||||
Log.e(TAG, "parseResult: Exception converting to Json " + e.toString());
|
||||
}
|
||||
return resultTokenMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns obtained values with a broadcast.
|
||||
*
|
||||
* @param tokenResponse : obtained values.
|
||||
*/
|
||||
private void postResult(HashMap<String, String> tokenResponse) {
|
||||
Intent intent = new Intent(TOKEN_RECEIVED_MESSAGE);
|
||||
intent.putExtra(TOKEN_RECEIVED_DATA,tokenResponse);
|
||||
sendBroadcast(intent);
|
||||
shutdownService();
|
||||
}
|
||||
}
|
|
@ -1,151 +0,0 @@
|
|||
package com.owncloud.android.operations;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.http.NameValuePair;
|
||||
import org.apache.http.client.entity.UrlEncodedFormEntity;
|
||||
import org.apache.http.message.BasicNameValuePair;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.ConnectivityManager;
|
||||
import android.net.Uri;
|
||||
import android.os.Handler;
|
||||
import android.util.Log;
|
||||
|
||||
import com.owncloud.android.authenticator.oauth2.OAuth2Context;
|
||||
import com.owncloud.android.authenticator.oauth2.OnOAuth2GetCodeResultListener;
|
||||
import com.owncloud.android.authenticator.oauth2.OnOAuth2GetCodeResultListener.ResultOAuthType;
|
||||
import com.owncloud.android.authenticator.oauth2.connection.ConnectorOAuth2;
|
||||
|
||||
/**
|
||||
* Implements the communication with oAuth2 server to get User Code and other useful values.
|
||||
*
|
||||
* @author SolidGear S.L.
|
||||
*
|
||||
*/
|
||||
public class OAuth2GetAuthorizationToken implements Runnable {
|
||||
|
||||
public static final String CODE_USER_CODE = "user_code";
|
||||
public static final String CODE_CLIENT_ID = "client_id";
|
||||
public static final String CODE_SCOPE = "scope";
|
||||
public static final String CODE_VERIFICATION_URL = "verification_url";
|
||||
public static final String CODE_EXPIRES_IN = "expires_in";
|
||||
public static final String CODE_DEVICE_CODE = "device_code";
|
||||
public static final String CODE_INTERVAL = "interval";
|
||||
|
||||
private static final String CODE_RESPONSE_TYPE = "response_type";
|
||||
private static final String CODE_REDIRECT_URI = "redirect_uri";
|
||||
|
||||
private String mGrantType = OAuth2Context.OAUTH2_AUTH_CODE_GRANT_TYPE;
|
||||
|
||||
private static final String TAG = "OAuth2GetCodeRunnable";
|
||||
private OnOAuth2GetCodeResultListener mListener;
|
||||
private String mUrl;
|
||||
private Handler mHandler;
|
||||
private Context mContext;
|
||||
//private JSONObject codeResponseJson = null;
|
||||
ResultOAuthType mLatestResult;
|
||||
|
||||
|
||||
public void setListener(OnOAuth2GetCodeResultListener listener, Handler handler) {
|
||||
mListener = listener;
|
||||
mHandler = handler;
|
||||
}
|
||||
|
||||
public OAuth2GetAuthorizationToken(String url, Context context) {
|
||||
mListener = null;
|
||||
mHandler = null;
|
||||
mUrl = url;
|
||||
mContext = context;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
|
||||
if (!isOnline()) {
|
||||
postResult(ResultOAuthType.NO_NETWORK_CONNECTION,null);
|
||||
return;
|
||||
}
|
||||
|
||||
if (mUrl.startsWith("http://") || mUrl.startsWith("https://")) {
|
||||
mLatestResult = (mUrl.startsWith("https://"))? ResultOAuthType.OK_SSL : ResultOAuthType.OK_NO_SSL;
|
||||
} else {
|
||||
mUrl = "https://" + mUrl;
|
||||
mLatestResult = ResultOAuthType.OK_SSL;
|
||||
}
|
||||
|
||||
if (mGrantType.equals(OAuth2Context.OAUTH2_AUTH_CODE_GRANT_TYPE)) {
|
||||
requestBrowserToGetAuthorizationCode();
|
||||
|
||||
} /*else if (mGrantType.equals(OAuth2Context.OAUTH_G_DEVICE_GETTOKEN_GRANT_TYPE)) {
|
||||
getAuthorizationCode();
|
||||
}*/
|
||||
}
|
||||
|
||||
/// open the authorization endpoint in a web browser!
|
||||
private void requestBrowserToGetAuthorizationCode() {
|
||||
Uri uri = Uri.parse(mUrl);
|
||||
Uri.Builder uriBuilder = uri.buildUpon();
|
||||
uriBuilder.appendQueryParameter(CODE_RESPONSE_TYPE, OAuth2Context.OAUTH2_CODE_RESPONSE_TYPE);
|
||||
uriBuilder.appendQueryParameter(CODE_REDIRECT_URI, OAuth2Context.MY_REDIRECT_URI);
|
||||
uriBuilder.appendQueryParameter(CODE_CLIENT_ID, OAuth2Context.OAUTH2_F_CLIENT_ID);
|
||||
uriBuilder.appendQueryParameter(CODE_SCOPE, OAuth2Context.OAUTH2_F_SCOPE);
|
||||
//uriBuilder.appendQueryParameter(CODE_STATE, whateverwewant);
|
||||
|
||||
uri = uriBuilder.build();
|
||||
Log.d(TAG, "Starting browser to view " + uri.toString());
|
||||
|
||||
Intent i = new Intent(Intent.ACTION_VIEW, uri);
|
||||
mContext.startActivity(i);
|
||||
|
||||
postResult(mLatestResult, null);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
private void getAuthorizationCode() {
|
||||
ConnectorOAuth2 connectorOAuth2 = new ConnectorOAuth2(mUrl);
|
||||
try {
|
||||
List<NameValuePair> nameValuePairs = new ArrayList<NameValuePair>(2);
|
||||
nameValuePairs.add(new BasicNameValuePair(CODE_CLIENT_ID, OAuth2Context.OAUTH2_G_DEVICE_CLIENT_ID));
|
||||
nameValuePairs.add(new BasicNameValuePair(CODE_SCOPE,OAuth2Context.OAUTH2_G_DEVICE_GETCODE_SCOPES));
|
||||
UrlEncodedFormEntity params = new UrlEncodedFormEntity(nameValuePairs);
|
||||
codeResponseJson = new JSONObject(connectorOAuth2.connPost(params));
|
||||
} catch (JSONException e) {
|
||||
Log.e(TAG, "JSONException converting to Json: " + e.toString());
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
Log.e(TAG, "UnsupportedEncodingException encoding URL values: " + e.toString());
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Exception : " + e.toString());
|
||||
}
|
||||
|
||||
if (codeResponseJson == null) {
|
||||
mLatestResult = ResultOAuthType.HOST_NOT_AVAILABLE;
|
||||
}
|
||||
postResult(mLatestResult, codeResponseJson);
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
private boolean isOnline() {
|
||||
ConnectivityManager cm = (ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
|
||||
return cm != null && cm.getActiveNetworkInfo() != null && cm.getActiveNetworkInfo().isConnectedOrConnecting();
|
||||
}
|
||||
|
||||
private void postResult(final ResultOAuthType result,final JSONObject codeResponseJson) {
|
||||
if (mHandler != null && mListener != null) {
|
||||
mHandler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
mListener.onOAuth2GetCodeResult(result, codeResponseJson);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -128,8 +128,6 @@ public class AuthenticatorActivity extends AccountAuthenticatorActivity
|
|||
private TextView mOAuthTokenEndpointText;
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
|
@ -264,7 +262,6 @@ public class AuthenticatorActivity extends AccountAuthenticatorActivity
|
|||
// state of oAuth2 components
|
||||
mOAuth2StatusIcon = savedInstanceState.getInt(KEY_OAUTH2_STATUS_ICON);
|
||||
mOAuth2StatusText = savedInstanceState.getInt(KEY_OAUTH2_STATUS_TEXT);
|
||||
changeViewByOAuth2Check(mOAuth2Check.isChecked());
|
||||
|
||||
/* Leave old OAuth flow
|
||||
// We store a JSon object with all the data returned from oAuth2 server when we get user_code.
|
||||
|
@ -305,6 +302,9 @@ public class AuthenticatorActivity extends AccountAuthenticatorActivity
|
|||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
changeViewByOAuth2Check(mOAuth2Check.isChecked());
|
||||
// the state of mOAuth2Check is automatically recovered between configuration changes, but not before onCreate() finishes
|
||||
|
||||
/* LEAVE OLD OAUTH FLOW ;
|
||||
// (old oauth code) Registering token receiver. We must listening to the service that is pooling to the oAuth server for a token.
|
||||
if (tokenReceiver == null) {
|
||||
|
|
Loading…
Reference in a new issue