Skip to content
Merged
Show file tree
Hide file tree
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 @@ -238,8 +238,14 @@ protected LineLoginResult doInBackground(@Nullable BrowserAuthenticationApi.Resu
userId = lineProfile.getUserId();
}

// Cache the acquired access token
accessTokenCache.saveAccessToken(accessToken);
// Cache the acquired access token. A broken device Keystore can throw here, so treat a
// persistence failure as a login failure instead of crashing the background task.
try {
accessTokenCache.saveAccessToken(accessToken);
} catch (final Exception e) {
return LineLoginResult.internalError(
e.getMessage() != null ? e.getMessage() : e.toString());
}

final LineIdToken idToken = issueAccessTokenResult.getIdToken();
if (idToken != null) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.linecorp.linesdk.internal;

import android.content.Context;
import android.util.Log;

import androidx.annotation.NonNull;

Expand All @@ -14,6 +15,7 @@
* This class prevents to generate secret keys repeatedly because it is very slow.
*/
public class EncryptorHolder {
private static final String TAG = "EncryptorHolder";
// TODO: Change to be able to specify the iteration count by LINE SDK user.
private static final StringCipher ENCRYPTOR = new StringAesCipher();
private static volatile boolean s_isInitializationStarted = false;
Expand Down Expand Up @@ -45,7 +47,13 @@ private static class EncryptorInitializationTask implements Runnable {

@Override
public void run() {
ENCRYPTOR.initialize(context);
try {
ENCRYPTOR.initialize(context);
} catch (Exception e) {
// Pre-init is only a latency optimization; a broken Keystore must not crash the
// host app on this background thread.
Log.w(TAG, "Encryptor pre-init failed", e);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
import android.content.Intent;
import android.net.Uri;

import androidx.annotation.NonNull;

import com.linecorp.linesdk.LineAccessToken;
import com.linecorp.linesdk.LineApiError;
import com.linecorp.linesdk.LineApiResponse;
Expand All @@ -24,10 +26,13 @@
import com.linecorp.linesdk.internal.nwclient.LineAuthenticationApiClient;
import com.linecorp.linesdk.internal.nwclient.TalkApiClient;
import com.linecorp.linesdk.internal.pkce.PKCECode;
import com.linecorp.linesdk.internal.security.encryption.EncryptionException;
import com.linecorp.linesdk.internal.security.encryption.StringCipher;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
Expand All @@ -36,6 +41,7 @@
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;

import java.security.ProviderException;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
Expand Down Expand Up @@ -110,6 +116,30 @@ public class LineAuthenticationControllerTest {

private static final Intent LOGIN_INTENT = new Intent();

private static class ThrowingStringCipher implements StringCipher {
@Override
public void initialize(@NonNull Context context) {
throw failure();
}

@NonNull
@Override
public String encrypt(@NonNull Context context, @NonNull String plainText) {
throw failure();
}

@NonNull
@Override
public String decrypt(@NonNull Context context, @NonNull String cipherText) {
throw failure();
}

private static EncryptionException failure() {
return new EncryptionException("keystore failure",
new ProviderException("Keystore key generation failed"));
}
}

private LineAuthenticationController target;

private LineAuthenticationConfig config;
Expand Down Expand Up @@ -230,6 +260,46 @@ public void testInternalErrorOfGettingAccessToken() throws Exception {
LineLoginResult.error(LineApiResponseCode.INTERNAL_ERROR, LineApiError.DEFAULT));
}

@Test
public void testKeystoreErrorOfSavingAccessToken() throws Exception {
AccessTokenCache failingCache = new AccessTokenCache(
RuntimeEnvironment.application, CHANNEL_ID, new ThrowingStringCipher());
LineAuthenticationStatus status = new LineAuthenticationStatus();
status.setOpenIdNonce(NONCE);
target = Mockito.spy(new LineAuthenticationController(
activity, config, authApiClient, talkApiClient, browserAuthenticationApi,
failingCache, status, LINE_AUTH_PARAMS));
doReturn(PKCE_CODE).when(target).createPKCECode();
doReturn(new BrowserAuthenticationApi.Request(
LOGIN_INTENT, null /* startActivityOption */, REDIRECT_URI, false))
.when(browserAuthenticationApi)
.getRequest(any(Context.class), any(LineAuthenticationConfig.class),
any(PKCECode.class), any(LineAuthenticationParams.class));

Intent newIntentData = new Intent();
doReturn(BrowserAuthenticationApi.Result.createAsSuccess(REQUEST_TOKEN_STR, false))
.when(browserAuthenticationApi)
.getAuthenticationResultFrom(newIntentData);
doReturn(LineApiResponse.createAsSuccess(ISSUE_ACCESS_TOKEN_RESULT))
.when(authApiClient)
.issueAccessToken(CHANNEL_ID, REQUEST_TOKEN_STR, PKCE_CODE, REDIRECT_URI);
doReturn(LineApiResponse.createAsSuccess(ACCOUNT_INFO))
.when(talkApiClient)
.getProfile(ACCESS_TOKEN);

target.startLineAuthentication();
Robolectric.getBackgroundThreadScheduler().runOneTask();
Robolectric.getForegroundThreadScheduler().runOneTask();

target.handleIntentFromLineApp(newIntentData);
Robolectric.getBackgroundThreadScheduler().runOneTask();
Robolectric.getForegroundThreadScheduler().runOneTask();

ArgumentCaptor<LineLoginResult> captor = ArgumentCaptor.forClass(LineLoginResult.class);
verify(activity, times(1)).onAuthenticationFinished(captor.capture());
assertEquals(LineApiResponseCode.INTERNAL_ERROR, captor.getValue().getResponseCode());
}

@Test
public void testAccessDeniedErrorOfGettingAccessToken() throws Exception {
Intent newIntentData = new Intent();
Expand Down
Loading