Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,7 @@ public class AuthenticatorActivity extends AccountAuthenticatorActivity
AuthenticatorAsyncTask.OnAuthenticatorTaskListener, Injectable {

private static final String TAG = AuthenticatorActivity.class.getSimpleName();
private static final String LOGIN_FLOW_LOG = "LoginFlowV2 |";

public static final String EXTRA_ACTION = "ACTION";
public static final String EXTRA_ACCOUNT = "ACCOUNT";
Expand Down Expand Up @@ -256,6 +257,7 @@ public AccountSetupBinding getAccountSetupBinding() {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log_OC.d(TAG, LOGIN_FLOW_LOG + " Lifecycle onCreate (recreated=" + (savedInstanceState != null) + ")");
viewThemeUtils = viewThemeUtilsFactory.withPrimaryAsBackground();
viewThemeUtils.platform.colorStatusBar(this, getResources().getColor(R.color.primary));

Expand Down Expand Up @@ -406,7 +408,9 @@ private void deleteCookies() {
private String baseUrl;

private void poolLogin() {
Log_OC.d(TAG, LOGIN_FLOW_LOG + " Step 6: Polling started (interval=30s)");
loginFlowExecutorService.scheduleWithFixedDelay(() -> {
Log_OC.d(TAG, LOGIN_FLOW_LOG + " Poll tick (completed=" + isLoginProcessCompleted + ")");
if (!isLoginProcessCompleted) {
performLoginFlowV2();
}
Expand All @@ -429,9 +433,12 @@ private void anonymouslyPostLoginRequest(String url) {
return;
}
baseUrl = url;
Log_OC.d(TAG, LOGIN_FLOW_LOG + " Step 1: POST /login/v2 -> " + url);

singleThreadExecutor.execute(() -> {
String response = getResponseOfAnonymouslyPostLoginRequest();
Log_OC.d(TAG, LOGIN_FLOW_LOG + " Step 2: /login/v2 response received (empty="
+ TextUtils.isEmpty(response) + ")");
if (TextUtils.isEmpty(response)) {
DisplayUtils.showSnackMessage(AuthenticatorActivity.this, R.string.authenticator_activity_empty_response_message);
return;
Expand All @@ -449,6 +456,7 @@ private String extractLoginUrl(String response) {
try {
authObject = gson.fromJson(response, AuthObject.class);
if (authObject != null && !TextUtils.isEmpty(authObject.getLogin())) {
Log_OC.d(TAG, LOGIN_FLOW_LOG + " Step 3: login URL + poll token extracted");
return authObject.getLogin();
} else {
Log_OC.e(TAG, "AuthObject parsing failed or login empty, trying JSONObject fallback");
Expand Down Expand Up @@ -498,6 +506,7 @@ private void launchDefaultWebBrowser(String url) {
try {
int toolbarColor = ContextCompat.getColor(this, R.color.primary);
AuthTabIntent authTabIntent = new AuthTabIntent.Builder().setColorScheme(toolbarColor).build();
Log_OC.d(TAG, LOGIN_FLOW_LOG + " Step 5: launching browser via Auth Tab (scheme=" + loginScheme + ")");
authTabIntent.launch(authTabResultLauncher, uri, loginScheme);
return;
} catch (Exception e) {
Expand All @@ -508,13 +517,15 @@ private void launchDefaultWebBrowser(String url) {
Intent intent = new Intent(Intent.ACTION_VIEW, uri);
PackageManager packageManager = getPackageManager();
if (intent.resolveActivity(packageManager) != null) {
Log_OC.d(TAG, LOGIN_FLOW_LOG + " Step 5 (fallback): launching external browser");
startActivity(intent);
return;
}
} catch (Exception e) {
Log_OC.e(TAG, "External browser launch failed: " + e);
}

Log_OC.d(TAG, LOGIN_FLOW_LOG + " Step 5 (failed): no web browser found");
DisplayUtils.showSnackMessage(this, R.string.authenticator_activity_no_web_browser_found);
}

Expand Down Expand Up @@ -548,25 +559,32 @@ private void performLoginFlowV2() {

PlainClient client = clientFactory.createPlainClient();
PostMethod post = new PostMethod(pollUrlAndToken.first, false, requestBody);
Log_OC.d(TAG, LOGIN_FLOW_LOG + " Step 7: Poll request -> " + pollUrlAndToken.first);
int status = post.execute(client);
String response = post.getResponseBodyAsString();

Log_OC.d(TAG, LOGIN_FLOW_LOG + " Step 8: Poll response status=" + status + " empty=" + response.isEmpty());
Log_OC.d(TAG, "performLoginFlowV2 status: " + status);
Log_OC.d(TAG, "performLoginFlowV2 response: " + response);

if (!response.isEmpty()) {
Log_OC.d(TAG, LOGIN_FLOW_LOG + " Step 8: non-empty poll response -> completeLoginFlow()");
runOnUiThread(() -> completeLoginFlow(response, status));
}
}

private void completeLoginFlow(String response, int status) {
Log_OC.d(TAG, LOGIN_FLOW_LOG + " Step 9: completeLoginFlow (status=" + status + ")");
try {
LoginUrlInfo loginUrlInfo = gson.fromJson(response, LoginUrlInfo.class);
if (loginUrlInfo == null) {
Log_OC.e(TAG, "cannot complete login flow loginUrl is null");
return;
}
isLoginProcessCompleted = loginUrlInfo.isValid(status);
Log_OC.d(TAG, LOGIN_FLOW_LOG + " Step 9: credentials received (valid=" + isLoginProcessCompleted
+ ", loginName=" + !TextUtils.isEmpty(loginUrlInfo.getLoginName())
+ ", appPassword=" + !TextUtils.isEmpty(loginUrlInfo.getAppPassword()) + ")");

if (accountSetupBinding != null) {
accountSetupBinding.hostUrlInput.setText("");
Expand All @@ -585,12 +603,16 @@ private void completeLoginFlow(String response, int status) {
checkOcServer();
loginFlowExecutorService.shutdown();
ProcessLifecycleOwner.get().getLifecycle().removeObserver(lifecycleEventObserver);
Log_OC.d(TAG, LOGIN_FLOW_LOG + " Step 9: polling stopped and lifecycle observer removed");
}

private final LifecycleEventObserver lifecycleEventObserver = ((lifecycleOwner, event) -> {
if (event == Lifecycle.Event.ON_START && authObject != null && !TextUtils.isEmpty(authObject.getPoll().getToken())) {
Log_OC.d(TAG, "Start poolLogin");
Log_OC.d(TAG, LOGIN_FLOW_LOG + " ProcessLifecycleOwner ON_START -> starting polling");
poolLogin();
} else if (event == Lifecycle.Event.ON_START) {
Log_OC.d(TAG, LOGIN_FLOW_LOG + " ProcessLifecycleOwner ON_START ignored (authObject="
+ (authObject != null) + ")");
}
});
// endregion
Expand Down Expand Up @@ -840,6 +862,7 @@ public void onRestoreInstanceState(@NonNull Bundle savedInstanceState) {
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
Log_OC.d(TAG, LOGIN_FLOW_LOG + " Lifecycle onNewIntent (browser return candidate)");
Log_OC.d(TAG, "onNewIntent()");

if (intent.getBooleanExtra(FirstRunActivity.EXTRA_EXIT, false)) {
Expand All @@ -859,6 +882,8 @@ protected void onNewIntent(Intent intent) {
finish();
return;
} else {
Log_OC.d(TAG, LOGIN_FLOW_LOG
+ " Browser returned via onNewIntent deep link (nc://) -> parsing credentials");
parseAndLoginFromWebView(data.toString());
}
}
Expand Down Expand Up @@ -907,13 +932,20 @@ private boolean checkIfViaSSO(Intent intent) {
}


@Override
protected void onStart() {
super.onStart();
Log_OC.d(TAG, LOGIN_FLOW_LOG + " Lifecycle onStart");
}

/**
* The redirection triggered by the OAuth authentication server as response to the GET AUTHORIZATION, and deferred
* in {@link #onNewIntent(Intent)}, is processed here.
*/
@Override
protected void onResume() {
super.onResume();
Log_OC.d(TAG, LOGIN_FLOW_LOG + " Lifecycle onResume");

// bind to Operations Service
mOperationsServiceConnection = new OperationsServiceConnection();
Expand All @@ -932,6 +964,7 @@ protected void onResume() {

@Override
protected void onPause() {
Log_OC.d(TAG, LOGIN_FLOW_LOG + " Lifecycle onPause");
if (mOperationsServiceBinder != null) {
mOperationsServiceBinder.removeOperationListener(this);
}
Expand All @@ -946,6 +979,7 @@ protected void onDestroy() {
mOperationsServiceBinder = null;
}

Log_OC.d(TAG, LOGIN_FLOW_LOG + " Lifecycle onDestroy (isFinishing=" + isFinishing() + ")");
Log_OC.d(TAG, "AuthenticatorActivity onDestroy called");
singleThreadExecutor.shutdown();
super.onDestroy();
Expand Down Expand Up @@ -1118,6 +1152,7 @@ private void onGetServerInfoFinish(RemoteOperationResult result) {

// region LoginInfoView
private void initLoginInfoView() {
Log_OC.d(TAG, LOGIN_FLOW_LOG + " Step 4: 'Waiting for browser' UI shown");
LinearLayout loginFlowLayout = accountSetupWebviewBinding.loginFlowV2.getRoot();
MaterialButton cancelButton = accountSetupWebviewBinding.loginFlowV2.cancelButton;
loginFlowLayout.setVisibility(View.VISIBLE);
Expand All @@ -1136,6 +1171,7 @@ private void initLoginInfoView() {
ViewCompat.requestApplyInsets(loginFlowLayout);

cancelButton.setOnClickListener(v -> {
Log_OC.d(TAG, LOGIN_FLOW_LOG + " Polling cancelled by user (cancel button)");
loginFlowExecutorService.shutdown();
ProcessLifecycleOwner.get().getLifecycle().removeObserver(lifecycleEventObserver);
recreate();
Expand Down Expand Up @@ -1563,7 +1599,8 @@ private void startQRScanner() {

private final ActivityResultLauncher<Intent> authTabResultLauncher = AuthTabIntent.registerActivityResultLauncher(
this,
result -> Log_OC.d(TAG, "Auth Tab result code: " + result.resultCode)
result -> Log_OC.d(TAG, LOGIN_FLOW_LOG + " Browser returned via Auth Tab (resultCode=" + result.resultCode
+ ", hasUri=" + (result.resultUri != null) + ")")
);

private final ActivityResultLauncher<Intent> qrScanResultLauncher = registerForActivityResult(
Expand Down
Loading