diff --git a/bundles/org.openhab.binding.folderwatcher/src/main/java/org/openhab/binding/folderwatcher/internal/api/S3Actions.java b/bundles/org.openhab.binding.folderwatcher/src/main/java/org/openhab/binding/folderwatcher/internal/api/S3Actions.java index 2ba0fd5fe..e876617b1 100644 --- a/bundles/org.openhab.binding.folderwatcher/src/main/java/org/openhab/binding/folderwatcher/internal/api/S3Actions.java +++ b/bundles/org.openhab.binding.folderwatcher/src/main/java/org/openhab/binding/folderwatcher/internal/api/S3Actions.java @@ -15,6 +15,7 @@ package org.openhab.binding.folderwatcher.internal.api; import static org.eclipse.jetty.http.HttpHeader.*; import static org.eclipse.jetty.http.HttpMethod.*; +import java.io.IOException; import java.io.StringReader; import java.net.MalformedURLException; import java.net.URL; @@ -23,10 +24,13 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jetty.client.HttpClient; @@ -34,10 +38,14 @@ import org.eclipse.jetty.client.api.ContentResponse; import org.eclipse.jetty.client.api.Request; import org.openhab.binding.folderwatcher.internal.api.auth.AWS4SignerBase; import org.openhab.binding.folderwatcher.internal.api.auth.AWS4SignerForAuthorizationHeader; +import org.openhab.binding.folderwatcher.internal.api.exception.APIException; +import org.openhab.binding.folderwatcher.internal.api.exception.AuthException; +import org.openhab.binding.folderwatcher.internal.api.util.HttpUtilException; import org.openhab.core.io.net.http.HttpClientFactory; import org.w3c.dom.Document; import org.w3c.dom.NodeList; import org.xml.sax.InputSource; +import org.xml.sax.SAXException; /** * The {@link S3Actions} class contains AWS S3 API implementation. @@ -54,39 +62,44 @@ public class S3Actions { private String awsAccessKey; private String awsSecretKey; - public S3Actions(HttpClientFactory httpClientFactory, String bucketName, String region) { + public S3Actions(HttpClientFactory httpClientFactory, String bucketName, String region) throws APIException { this(httpClientFactory, bucketName, region, "", ""); } public S3Actions(HttpClientFactory httpClientFactory, String bucketName, String region, String awsAccessKey, - String awsSecretKey) { + String awsSecretKey) throws APIException { this.httpClient = httpClientFactory.getCommonHttpClient(); try { this.bucketUri = new URL("http://" + bucketName + ".s3." + region + ".amazonaws.com"); } catch (MalformedURLException e) { - throw new RuntimeException("Unable to parse service endpoint: " + e.getMessage()); + throw new APIException("Unable to parse service endpoint: " + e.getMessage()); } this.region = region; this.awsAccessKey = awsAccessKey; this.awsSecretKey = awsSecretKey; } - public List listBucket(String prefix) throws Exception { + public List listBucket(String prefix) throws APIException, AuthException { Map headers = new HashMap(); Map params = new HashMap(); return listObjectsV2(prefix, headers, params); } private List listObjectsV2(String prefix, Map headers, Map params) - throws Exception { + throws APIException, AuthException { params.put("list-type", "2"); params.put("prefix", prefix); if (!awsAccessKey.isEmpty() || !awsSecretKey.isEmpty()) { headers.put("x-amz-content-sha256", AWS4SignerBase.EMPTY_BODY_SHA256); AWS4SignerForAuthorizationHeader signer = new AWS4SignerForAuthorizationHeader(this.bucketUri, "GET", "s3", region); - String authorization = signer.computeSignature(headers, params, AWS4SignerBase.EMPTY_BODY_SHA256, - awsAccessKey, awsSecretKey); + String authorization; + try { + authorization = signer.computeSignature(headers, params, AWS4SignerBase.EMPTY_BODY_SHA256, awsAccessKey, + awsSecretKey); + } catch (HttpUtilException e) { + throw new AuthException(e); + } headers.put("Authorization", authorization); } @@ -102,15 +115,31 @@ public class S3Actions { request.param(paramKey, params.get(paramKey)); } - ContentResponse contentResponse = request.send(); + ContentResponse contentResponse; + try { + contentResponse = request.send(); + } catch (InterruptedException | TimeoutException | ExecutionException e) { + throw new APIException(e); + } + if (contentResponse.getStatus() != 200) { - throw new Exception("HTTP Response is not 200"); + throw new APIException("HTTP Response is not 200"); } DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory.newInstance(); - DocumentBuilder docBuilder = docBuilderFactory.newDocumentBuilder(); + DocumentBuilder docBuilder; + try { + docBuilder = docBuilderFactory.newDocumentBuilder(); + } catch (ParserConfigurationException e) { + throw new APIException(e); + } InputSource is = new InputSource(new StringReader(contentResponse.getContentAsString())); - Document doc = docBuilder.parse(is); + Document doc; + try { + doc = docBuilder.parse(is); + } catch (SAXException | IOException e) { + throw new APIException(e); + } NodeList nameNodesList = doc.getElementsByTagName("Key"); List returnList = new ArrayList<>(); diff --git a/bundles/org.openhab.binding.folderwatcher/src/main/java/org/openhab/binding/folderwatcher/internal/api/auth/AWS4SignerBase.java b/bundles/org.openhab.binding.folderwatcher/src/main/java/org/openhab/binding/folderwatcher/internal/api/auth/AWS4SignerBase.java index cd872a2fa..6cb2fbe6a 100644 --- a/bundles/org.openhab.binding.folderwatcher/src/main/java/org/openhab/binding/folderwatcher/internal/api/auth/AWS4SignerBase.java +++ b/bundles/org.openhab.binding.folderwatcher/src/main/java/org/openhab/binding/folderwatcher/internal/api/auth/AWS4SignerBase.java @@ -28,7 +28,9 @@ import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.folderwatcher.internal.api.exception.AuthException; import org.openhab.binding.folderwatcher.internal.api.util.BinaryUtils; +import org.openhab.binding.folderwatcher.internal.api.util.HttpUtilException; import org.openhab.binding.folderwatcher.internal.api.util.HttpUtils; /** @@ -46,8 +48,8 @@ public abstract class AWS4SignerBase { public static final String SCHEME = "AWS4"; public static final String ALGORITHM = "HMAC-SHA256"; public static final String TERMINATOR = "aws4_request"; - public static final String ISO8601BasicFormat = "yyyyMMdd'T'HHmmss'Z'"; - public static final String DateStringFormat = "yyyyMMdd"; + public static final String ISO8601_BASIC_FORMAT = "yyyyMMdd'T'HHmmss'Z'"; + public static final String DATESTRING_FORMAT = "yyyyMMdd"; protected URL endpointUrl; protected String httpMethod; protected String serviceName; @@ -61,9 +63,9 @@ public abstract class AWS4SignerBase { this.serviceName = serviceName; this.regionName = regionName; - dateTimeFormat = new SimpleDateFormat(ISO8601BasicFormat); + dateTimeFormat = new SimpleDateFormat(ISO8601_BASIC_FORMAT); dateTimeFormat.setTimeZone(new SimpleTimeZone(0, "UTC")); - dateStampFormat = new SimpleDateFormat(DateStringFormat); + dateStampFormat = new SimpleDateFormat(DATESTRING_FORMAT); dateStampFormat.setTimeZone(new SimpleTimeZone(0, "UTC")); } @@ -100,12 +102,12 @@ public abstract class AWS4SignerBase { } protected static String getCanonicalRequest(URL endpoint, String httpMethod, String queryParameters, - String canonicalizedHeaderNames, String canonicalizedHeaders, String bodyHash) { + String canonicalizedHeaderNames, String canonicalizedHeaders, String bodyHash) throws HttpUtilException { return httpMethod + "\n" + getCanonicalizedResourcePath(endpoint) + "\n" + queryParameters + "\n" + canonicalizedHeaders + "\n" + canonicalizedHeaderNames + "\n" + bodyHash; } - protected static String getCanonicalizedResourcePath(URL endpoint) { + protected static String getCanonicalizedResourcePath(URL endpoint) throws HttpUtilException { if (endpoint == null) { return "/"; } @@ -122,7 +124,7 @@ public abstract class AWS4SignerBase { } } - public static String getCanonicalizedQueryString(Map parameters) { + public static String getCanonicalizedQueryString(Map parameters) throws HttpUtilException { if (parameters == null || parameters.isEmpty()) { return ""; } @@ -152,39 +154,39 @@ public abstract class AWS4SignerBase { } protected static String getStringToSign(String scheme, String algorithm, String dateTime, String scope, - String canonicalRequest) { + String canonicalRequest) throws AuthException { return scheme + "-" + algorithm + "\n" + dateTime + "\n" + scope + "\n" + BinaryUtils.toHex(hash(canonicalRequest)); } - public static byte[] hash(String text) { + public static byte[] hash(String text) throws AuthException { try { MessageDigest md = MessageDigest.getInstance("SHA-256"); md.update(text.getBytes("UTF-8")); return md.digest(); } catch (Exception e) { - throw new RuntimeException("Unable to compute hash while signing request: " + e.getMessage(), e); + throw new AuthException("Unable to compute hash while signing request: " + e.getMessage(), e); } } - public static byte[] hash(byte[] data) { + public static byte[] hash(byte[] data) throws AuthException { try { MessageDigest md = MessageDigest.getInstance("SHA-256"); md.update(data); return md.digest(); } catch (Exception e) { - throw new RuntimeException("Unable to compute hash while signing request: " + e.getMessage(), e); + throw new AuthException("Unable to compute hash while signing request: " + e.getMessage(), e); } } - protected static byte[] sign(String stringData, byte[] key, String algorithm) { + protected static byte[] sign(String stringData, byte[] key, String algorithm) throws AuthException { try { byte[] data = stringData.getBytes("UTF-8"); Mac mac = Mac.getInstance(algorithm); mac.init(new SecretKeySpec(key, algorithm)); return mac.doFinal(data); } catch (Exception e) { - throw new RuntimeException("Unable to calculate a request signature: " + e.getMessage(), e); + throw new AuthException("Unable to calculate a request signature: " + e.getMessage(), e); } } } diff --git a/bundles/org.openhab.binding.folderwatcher/src/main/java/org/openhab/binding/folderwatcher/internal/api/auth/AWS4SignerForAuthorizationHeader.java b/bundles/org.openhab.binding.folderwatcher/src/main/java/org/openhab/binding/folderwatcher/internal/api/auth/AWS4SignerForAuthorizationHeader.java index db6b7cb26..278ebf197 100644 --- a/bundles/org.openhab.binding.folderwatcher/src/main/java/org/openhab/binding/folderwatcher/internal/api/auth/AWS4SignerForAuthorizationHeader.java +++ b/bundles/org.openhab.binding.folderwatcher/src/main/java/org/openhab/binding/folderwatcher/internal/api/auth/AWS4SignerForAuthorizationHeader.java @@ -17,7 +17,9 @@ import java.util.Date; import java.util.Map; import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.folderwatcher.internal.api.exception.AuthException; import org.openhab.binding.folderwatcher.internal.api.util.BinaryUtils; +import org.openhab.binding.folderwatcher.internal.api.util.HttpUtilException; /** * The {@link AWS4SignerForAuthorizationHeader} class contains methods for AWS S3 API authentication using HTTP(S) @@ -35,7 +37,7 @@ public class AWS4SignerForAuthorizationHeader extends AWS4SignerBase { } public String computeSignature(Map headers, Map queryParameters, String bodyHash, - String awsAccessKey, String awsSecretKey) { + String awsAccessKey, String awsSecretKey) throws AuthException, HttpUtilException { Date now = new Date(); String dateTimeStamp = dateTimeFormat.format(now); headers.put("x-amz-date", dateTimeStamp); diff --git a/bundles/org.openhab.binding.folderwatcher/src/main/java/org/openhab/binding/folderwatcher/internal/api/exception/APIException.java b/bundles/org.openhab.binding.folderwatcher/src/main/java/org/openhab/binding/folderwatcher/internal/api/exception/APIException.java new file mode 100644 index 000000000..645033ee5 --- /dev/null +++ b/bundles/org.openhab.binding.folderwatcher/src/main/java/org/openhab/binding/folderwatcher/internal/api/exception/APIException.java @@ -0,0 +1,39 @@ +/** + * Copyright (c) 2010-2023 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.folderwatcher.internal.api.exception; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * The {@link APIException} signal's there was a problem with interacting with the API + * + * @author Leo Siepel - initial contribution + * + */ +@NonNullByDefault +public class APIException extends Exception { + + private static final long serialVersionUID = 1L; + + public APIException(String message) { + super(message); + } + + public APIException(String message, Throwable cause) { + super(message, cause); + } + + public APIException(Throwable cause) { + super(cause); + } +} diff --git a/bundles/org.openhab.binding.folderwatcher/src/main/java/org/openhab/binding/folderwatcher/internal/api/exception/AuthException.java b/bundles/org.openhab.binding.folderwatcher/src/main/java/org/openhab/binding/folderwatcher/internal/api/exception/AuthException.java new file mode 100644 index 000000000..335763bc0 --- /dev/null +++ b/bundles/org.openhab.binding.folderwatcher/src/main/java/org/openhab/binding/folderwatcher/internal/api/exception/AuthException.java @@ -0,0 +1,39 @@ +/** + * Copyright (c) 2010-2023 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.folderwatcher.internal.api.exception; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * The {@link AuthException} signal's there was a problem with authentication + * + * @author Leo Siepel - initial contribution + * + */ +@NonNullByDefault +public class AuthException extends Exception { + + private static final long serialVersionUID = 1L; + + public AuthException(String message) { + super(message); + } + + public AuthException(String message, Throwable cause) { + super(message, cause); + } + + public AuthException(Throwable cause) { + super(cause); + } +} diff --git a/bundles/org.openhab.binding.folderwatcher/src/main/java/org/openhab/binding/folderwatcher/internal/api/util/HttpUtilException.java b/bundles/org.openhab.binding.folderwatcher/src/main/java/org/openhab/binding/folderwatcher/internal/api/util/HttpUtilException.java new file mode 100644 index 000000000..22699c6b7 --- /dev/null +++ b/bundles/org.openhab.binding.folderwatcher/src/main/java/org/openhab/binding/folderwatcher/internal/api/util/HttpUtilException.java @@ -0,0 +1,39 @@ +/** + * Copyright (c) 2010-2023 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.folderwatcher.internal.api.util; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * The {@link HttpUtilException} signal;s there was a problem with contacting the API + * + * @author Leo Siepel - initial contribution + * + */ +@NonNullByDefault +public class HttpUtilException extends Exception { + + private static final long serialVersionUID = 1L; + + public HttpUtilException(String message) { + super(message); + } + + public HttpUtilException(String message, Throwable cause) { + super(message, cause); + } + + public HttpUtilException(Throwable cause) { + super(cause); + } +} diff --git a/bundles/org.openhab.binding.folderwatcher/src/main/java/org/openhab/binding/folderwatcher/internal/api/util/HttpUtils.java b/bundles/org.openhab.binding.folderwatcher/src/main/java/org/openhab/binding/folderwatcher/internal/api/util/HttpUtils.java index bd323a238..2d964f36f 100644 --- a/bundles/org.openhab.binding.folderwatcher/src/main/java/org/openhab/binding/folderwatcher/internal/api/util/HttpUtils.java +++ b/bundles/org.openhab.binding.folderwatcher/src/main/java/org/openhab/binding/folderwatcher/internal/api/util/HttpUtils.java @@ -26,12 +26,12 @@ import org.eclipse.jdt.annotation.NonNullByDefault; */ @NonNullByDefault public class HttpUtils { - public static String urlEncode(String url, boolean keepPathSlash) { + public static String urlEncode(String url, boolean keepPathSlash) throws HttpUtilException { String encoded; try { encoded = URLEncoder.encode(url, "UTF-8"); } catch (UnsupportedEncodingException e) { - throw new RuntimeException("UTF-8 encoding is not supported.", e); + throw new HttpUtilException("UTF-8 encoding is not supported.", e); } if (keepPathSlash) { encoded = encoded.replace("%2F", "/"); diff --git a/bundles/org.openhab.binding.folderwatcher/src/main/java/org/openhab/binding/folderwatcher/internal/handler/S3BucketWatcherHandler.java b/bundles/org.openhab.binding.folderwatcher/src/main/java/org/openhab/binding/folderwatcher/internal/handler/S3BucketWatcherHandler.java index 9d89786bb..c3ffe6cf4 100644 --- a/bundles/org.openhab.binding.folderwatcher/src/main/java/org/openhab/binding/folderwatcher/internal/handler/S3BucketWatcherHandler.java +++ b/bundles/org.openhab.binding.folderwatcher/src/main/java/org/openhab/binding/folderwatcher/internal/handler/S3BucketWatcherHandler.java @@ -24,6 +24,7 @@ import java.util.concurrent.TimeUnit; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import org.openhab.binding.folderwatcher.internal.api.S3Actions; +import org.openhab.binding.folderwatcher.internal.api.exception.APIException; import org.openhab.binding.folderwatcher.internal.common.WatcherCommon; import org.openhab.binding.folderwatcher.internal.config.S3BucketWatcherConfiguration; import org.openhab.core.OpenHAB; @@ -71,12 +72,16 @@ public class S3BucketWatcherHandler extends BaseThingHandler { @Override public void initialize() { config = getConfigAs(S3BucketWatcherConfiguration.class); - - if (config.s3Anonymous) { - s3 = new S3Actions(httpClientFactory, config.s3BucketName, config.awsRegion); - } else { - s3 = new S3Actions(httpClientFactory, config.s3BucketName, config.awsRegion, config.awsKey, - config.awsSecret); + try { + if (config.s3Anonymous) { + s3 = new S3Actions(httpClientFactory, config.s3BucketName, config.awsRegion); + } else { + s3 = new S3Actions(httpClientFactory, config.s3BucketName, config.awsRegion, config.awsKey, + config.awsSecret); + } + } catch (APIException e) { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.HANDLER_INITIALIZING_ERROR, e.getMessage()); + return; } try {