From 13b9fbedf517d606b1c72574ffa01417d4ea06c3 Mon Sep 17 00:00:00 2001 From: Manuel Jenny Date: Fri, 30 Apr 2021 09:18:07 +0200 Subject: [PATCH] feat(S3): add error handling --- .../data/cloud/s3/S3CloudApiErrorCodes.java | 22 ++++++++ .../data/cloud/s3/S3CloudApiExceptions.java | 19 +++---- .../cloud/s3/S3CloudContentRepository.java | 48 +++++++++------- .../org/cryptomator/data/cloud/s3/S3Impl.java | 55 ++++++++++++++----- 4 files changed, 100 insertions(+), 44 deletions(-) create mode 100644 data/src/main/java/org/cryptomator/data/cloud/s3/S3CloudApiErrorCodes.java diff --git a/data/src/main/java/org/cryptomator/data/cloud/s3/S3CloudApiErrorCodes.java b/data/src/main/java/org/cryptomator/data/cloud/s3/S3CloudApiErrorCodes.java new file mode 100644 index 00000000..52c37b52 --- /dev/null +++ b/data/src/main/java/org/cryptomator/data/cloud/s3/S3CloudApiErrorCodes.java @@ -0,0 +1,22 @@ +package org.cryptomator.data.cloud.s3; + +public enum S3CloudApiErrorCodes { + ACCESS_DENIED("AccessDenied"), + ACCOUNT_PROBLEM("AccountProblem"), + INTERNAL_ERROR("InternalError"), + INVALID_ACCESS_KEY_ID("InvalidAccessKeyId"), + INVALID_BUCKET_NAME("InvalidBucketName"), + INVALID_OBJECT_STATE("InvalidObjectState"), + NO_SUCH_BUCKET("NoSuchBucket"), + NO_SUCH_KEY("NoSuchKey"); + + private final String value; + + S3CloudApiErrorCodes(final String newValue) { + value = newValue; + } + + public String getValue() { + return value; + } +} diff --git a/data/src/main/java/org/cryptomator/data/cloud/s3/S3CloudApiExceptions.java b/data/src/main/java/org/cryptomator/data/cloud/s3/S3CloudApiExceptions.java index a101d6fd..6abe3337 100644 --- a/data/src/main/java/org/cryptomator/data/cloud/s3/S3CloudApiExceptions.java +++ b/data/src/main/java/org/cryptomator/data/cloud/s3/S3CloudApiExceptions.java @@ -2,18 +2,13 @@ package org.cryptomator.data.cloud.s3; public class S3CloudApiExceptions { - public enum S3CloudApiErrorCodes { - NO_SUCH_BUCKET("NoSuchBucket"); - - private final String value; - - S3CloudApiErrorCodes(final String newValue) { - value = newValue; - } - - public String getValue() { - return value; - } + public static boolean isAccessProblem(String errorCode) { + return errorCode.equals(S3CloudApiErrorCodes.ACCESS_DENIED.getValue()) + || errorCode.equals(S3CloudApiErrorCodes.ACCOUNT_PROBLEM.getValue()) + || errorCode.equals(S3CloudApiErrorCodes.INVALID_ACCESS_KEY_ID.getValue()); } + public static boolean isNoSuchBucketException(String errorCode) { + return errorCode.equals(S3CloudApiErrorCodes.NO_SUCH_BUCKET.getValue()); + } } diff --git a/data/src/main/java/org/cryptomator/data/cloud/s3/S3CloudContentRepository.java b/data/src/main/java/org/cryptomator/data/cloud/s3/S3CloudContentRepository.java index f97cc5ef..bec8eae8 100644 --- a/data/src/main/java/org/cryptomator/data/cloud/s3/S3CloudContentRepository.java +++ b/data/src/main/java/org/cryptomator/data/cloud/s3/S3CloudContentRepository.java @@ -2,11 +2,14 @@ package org.cryptomator.data.cloud.s3; import android.content.Context; +import com.amazonaws.services.s3.model.AmazonS3Exception; + import org.cryptomator.data.cloud.InterceptingCloudContentRepository; import org.cryptomator.domain.S3Cloud; import org.cryptomator.domain.exception.BackendException; import org.cryptomator.domain.exception.FatalBackendException; import org.cryptomator.domain.exception.NetworkConnectionException; +import org.cryptomator.domain.exception.NoSuchBucketException; import org.cryptomator.domain.exception.authentication.WrongCredentialsException; import org.cryptomator.domain.repository.CloudContentRepository; import org.cryptomator.domain.usecases.ProgressAware; @@ -31,29 +34,36 @@ class S3CloudContentRepository extends InterceptingCloudContentRepository { diff --git a/data/src/main/java/org/cryptomator/data/cloud/s3/S3Impl.java b/data/src/main/java/org/cryptomator/data/cloud/s3/S3Impl.java index a0dd07cd..ec4a6e10 100644 --- a/data/src/main/java/org/cryptomator/data/cloud/s3/S3Impl.java +++ b/data/src/main/java/org/cryptomator/data/cloud/s3/S3Impl.java @@ -8,6 +8,7 @@ import com.amazonaws.mobileconnectors.s3.transferutility.TransferState; import com.amazonaws.mobileconnectors.s3.transferutility.TransferUtility; import com.amazonaws.mobileconnectors.s3.transferutility.UploadOptions; import com.amazonaws.services.s3.AmazonS3; +import com.amazonaws.services.s3.model.AmazonS3Exception; import com.amazonaws.services.s3.model.CopyObjectResult; import com.amazonaws.services.s3.model.DeleteObjectsRequest; import com.amazonaws.services.s3.model.DeleteObjectsRequest.KeyVersion; @@ -26,8 +27,10 @@ import org.cryptomator.domain.S3Cloud; import org.cryptomator.domain.exception.BackendException; import org.cryptomator.domain.exception.CloudNodeAlreadyExistsException; import org.cryptomator.domain.exception.FatalBackendException; +import org.cryptomator.domain.exception.ForbiddenException; import org.cryptomator.domain.exception.NoSuchBucketException; import org.cryptomator.domain.exception.NoSuchCloudFileException; +import org.cryptomator.domain.exception.UnauthorizedException; import org.cryptomator.domain.exception.authentication.WrongCredentialsException; import org.cryptomator.domain.usecases.ProgressAware; import org.cryptomator.domain.usecases.cloud.DataSource; @@ -46,6 +49,7 @@ import java.io.InputStream; import java.io.OutputStream; import java.util.ArrayList; import java.util.List; +import java.util.Set; import java.util.concurrent.ExecutionException; import java.util.concurrent.atomic.AtomicLong; @@ -161,8 +165,12 @@ class S3Impl { InputStream emptyContent = new ByteArrayInputStream(new byte[0]); - PutObjectRequest putObjectRequest = new PutObjectRequest(cloud.s3Bucket(), folder.getKey(), emptyContent, metadata); - client().putObject(putObjectRequest); + try { + PutObjectRequest putObjectRequest = new PutObjectRequest(cloud.s3Bucket(), folder.getKey(), emptyContent, metadata); + client().putObject(putObjectRequest); + } catch(AmazonS3Exception ex) { + handleApiError(ex, folder.getName()); + } return S3CloudNodeFactory.folder(folder.getParent(), folder.getName()); } @@ -206,10 +214,14 @@ class S3Impl { final CompletableFuture> result = new CompletableFuture<>(); - if (size <= CHUNKED_UPLOAD_MAX_SIZE) { - uploadFile(file, data, progressAware, result, size); - } else { - uploadChunkedFile(file, data, progressAware, result, size); + try { + if (size <= CHUNKED_UPLOAD_MAX_SIZE) { + uploadFile(file, data, progressAware, result, size); + } else { + uploadChunkedFile(file, data, progressAware, result, size); + } + } catch(AmazonS3Exception ex) { + handleApiError(ex, file.getName()); } try { @@ -336,16 +348,20 @@ class S3Impl { GetObjectRequest request = new GetObjectRequest(cloud.s3Bucket(), file.getPath()); request.setGeneralProgressListener(listener); - S3Object s3Object = client().getObject(request); + try { + S3Object s3Object = client().getObject(request); - CopyStream.copyStreamToStream(s3Object.getObjectContent(), data); + CopyStream.copyStreamToStream(s3Object.getObjectContent(), data); - if (sharedPreferencesHandler.useLruCache() && encryptedTmpFile.isPresent() && cacheKey.isPresent()) { - try { - storeToLruCache(diskLruCache, cacheKey.get(), encryptedTmpFile.get()); - } catch (IOException e) { - Timber.tag("S3Impl").e(e, "Failed to write downloaded file in LRU cache"); + if (sharedPreferencesHandler.useLruCache() && encryptedTmpFile.isPresent() && cacheKey.isPresent()) { + try { + storeToLruCache(diskLruCache, cacheKey.get(), encryptedTmpFile.get()); + } catch (IOException e) { + Timber.tag("S3Impl").e(e, "Failed to write downloaded file in LRU cache"); + } } + } catch(AmazonS3Exception ex) { + handleApiError(ex, file.getName()); } } @@ -386,4 +402,17 @@ class S3Impl { return true; } + + private void handleApiError(AmazonS3Exception ex, String name) throws BackendException { + String errorCode = ex.getErrorCode(); + if (S3CloudApiExceptions.isAccessProblem(errorCode)) { + throw new ForbiddenException(); + } else if (S3CloudApiErrorCodes.NO_SUCH_BUCKET.getValue().equals(errorCode)) { + throw new NoSuchBucketException(name); + } else if (S3CloudApiErrorCodes.NO_SUCH_KEY.getValue().equals(errorCode)) { + throw new NoSuchCloudFileException(name); + } else { + throw new FatalBackendException(ex); + } + } }