diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/tables/GetAction.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/tables/GetAction.java index 67cbecee..0a9801f9 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/tables/GetAction.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/tables/GetAction.java @@ -337,6 +337,17 @@ public class GetAction + /******************************************************************************* + ** Run a GetAction by using the QueryAction instead (e.g., with a filter made + ** from the pkey/ukey, and returning the single record if found). + *******************************************************************************/ + public GetOutput executeViaQuery(GetInput getInput) throws QException + { + return (new DefaultGetInterface().execute(getInput)); + } + + + /******************************************************************************* ** *******************************************************************************/ diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/QBackendMetaData.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/QBackendMetaData.java index ef534824..cd822e45 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/QBackendMetaData.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/QBackendMetaData.java @@ -38,7 +38,7 @@ import com.kingsrook.qqq.backend.core.modules.backend.QBackendModuleInterface; ** *******************************************************************************/ @JsonDeserialize(using = QBackendMetaDataDeserializer.class) -public class QBackendMetaData +public class QBackendMetaData implements TopLevelMetaDataInterface { private String name; private String backendType; @@ -409,4 +409,14 @@ public class QBackendMetaData return (this); } + + + /******************************************************************************* + ** + *******************************************************************************/ + @Override + public void addSelfToInstance(QInstance qInstance) + { + qInstance.addBackend(this); + } } diff --git a/qqq-backend-module-api/src/main/java/com/kingsrook/qqq/backend/module/api/actions/BaseAPIActionUtil.java b/qqq-backend-module-api/src/main/java/com/kingsrook/qqq/backend/module/api/actions/BaseAPIActionUtil.java index 7f793e71..7273094f 100644 --- a/qqq-backend-module-api/src/main/java/com/kingsrook/qqq/backend/module/api/actions/BaseAPIActionUtil.java +++ b/qqq-backend-module-api/src/main/java/com/kingsrook/qqq/backend/module/api/actions/BaseAPIActionUtil.java @@ -24,6 +24,8 @@ package com.kingsrook.qqq.backend.module.api.actions; import java.io.IOException; import java.io.Serializable; +import java.net.URI; +import java.net.URISyntaxException; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; import java.time.Instant; @@ -644,6 +646,20 @@ public class BaseAPIActionUtil request.setHeader("Authorization", "Bearer " + getOAuth2Token()); break; + case API_KEY_QUERY_PARAM: + try + { + String uri = request.getURI().toString(); + uri += (uri.contains("?") ? "&" : "?"); + uri += backendMetaData.getApiKeyQueryParamName() + "=" + backendMetaData.getApiKey(); + request.setURI(new URI(uri)); + } + catch(URISyntaxException e) + { + throw (new QException("Error setting authorization query parameter", e)); + } + break; + default: throw new IllegalArgumentException("Unexpected authorization type: " + backendMetaData.getAuthorizationType()); } @@ -1051,37 +1067,53 @@ public class BaseAPIActionUtil *******************************************************************************/ protected void logOutboundApiCall(HttpRequestBase request, QHttpResponse response) { - QTableMetaData table = QContext.getQInstance().getTable(OutboundAPILog.TABLE_NAME); - if(table == null) + try { - return; - } + QTableMetaData table = QContext.getQInstance().getTable(OutboundAPILog.TABLE_NAME); + if(table == null) + { + return; + } - String requestBody = null; - if(request instanceof HttpEntityEnclosingRequest entityRequest) + String requestBody = null; + if(request instanceof HttpEntityEnclosingRequest entityRequest) + { + try + { + requestBody = StringUtils.join("\n", IOUtils.readLines(entityRequest.getEntity().getContent())); + } + catch(Exception e) + { + // leave it null... + } + } + + //////////////////////////////////// + // mask api keys in query strings // + //////////////////////////////////// + String url = request.getURI().toString(); + if(backendMetaData.getAuthorizationType().equals(AuthorizationType.API_KEY_QUERY_PARAM)) + { + url = url.replaceFirst(backendMetaData.getApiKey(), "******"); + } + + InsertInput insertInput = new InsertInput(); + insertInput.setTableName(table.getName()); + insertInput.setRecords(List.of(new OutboundAPILog() + .withMethod(request.getMethod()) + .withUrl(url) + .withTimestamp(Instant.now()) + .withRequestBody(requestBody) + .withStatusCode(response.getStatusCode()) + .withResponseBody(response.getContent()) + .toQRecord() + )); + new InsertAction().executeAsync(insertInput); + } + catch(Exception e) { - try - { - requestBody = StringUtils.join("\n", IOUtils.readLines(entityRequest.getEntity().getContent())); - } - catch(Exception e) - { - // leave it null... - } + LOG.warn("Error logging outbound api call", e); } - - InsertInput insertInput = new InsertInput(); - insertInput.setTableName(table.getName()); - insertInput.setRecords(List.of(new OutboundAPILog() - .withMethod(request.getMethod()) - .withUrl(request.getURI().toString()) // todo - does this have the query string? - .withTimestamp(Instant.now()) - .withRequestBody(requestBody) - .withStatusCode(response.getStatusCode()) - .withResponseBody(response.getContent()) - .toQRecord() - )); - new InsertAction().executeAsync(insertInput); } diff --git a/qqq-backend-module-api/src/main/java/com/kingsrook/qqq/backend/module/api/model/AuthorizationType.java b/qqq-backend-module-api/src/main/java/com/kingsrook/qqq/backend/module/api/model/AuthorizationType.java index 0c542373..61497df4 100644 --- a/qqq-backend-module-api/src/main/java/com/kingsrook/qqq/backend/module/api/model/AuthorizationType.java +++ b/qqq-backend-module-api/src/main/java/com/kingsrook/qqq/backend/module/api/model/AuthorizationType.java @@ -31,5 +31,5 @@ public enum AuthorizationType BASIC_AUTH_API_KEY, BASIC_AUTH_USERNAME_PASSWORD, OAUTH2, - + API_KEY_QUERY_PARAM, } diff --git a/qqq-backend-module-api/src/main/java/com/kingsrook/qqq/backend/module/api/model/metadata/APIBackendMetaData.java b/qqq-backend-module-api/src/main/java/com/kingsrook/qqq/backend/module/api/model/metadata/APIBackendMetaData.java index 310dc704..5b954ae4 100644 --- a/qqq-backend-module-api/src/main/java/com/kingsrook/qqq/backend/module/api/model/metadata/APIBackendMetaData.java +++ b/qqq-backend-module-api/src/main/java/com/kingsrook/qqq/backend/module/api/model/metadata/APIBackendMetaData.java @@ -43,6 +43,7 @@ public class APIBackendMetaData extends QBackendMetaData private String clientSecret; private String username; private String password; + private String apiKeyQueryParamName; private AuthorizationType authorizationType; private String contentType; // todo enum? @@ -460,6 +461,16 @@ public class APIBackendMetaData extends QBackendMetaData public void performValidation(QInstanceValidator qInstanceValidator) { qInstanceValidator.assertCondition(StringUtils.hasContent(baseUrl), "Missing baseUrl for API backend: " + getName()); + + if(AuthorizationType.API_KEY_QUERY_PARAM.equals(authorizationType)) + { + qInstanceValidator.assertCondition(StringUtils.hasContent(apiKey), "Missing apiKey for API backend: " + getName() + " (required when using AuthorizationType=API_KEY_QUERY_PARAM))"); + qInstanceValidator.assertCondition(StringUtils.hasContent(apiKeyQueryParamName), "Missing apiKeyQueryParamName for API backend: " + getName() + " (required when using AuthorizationType=API_KEY_QUERY_PARAM))"); + } + else + { + qInstanceValidator.assertCondition(!StringUtils.hasContent(apiKeyQueryParamName), "Unexpected apiKeyQueryParamName for API backend: " + getName() + " (only allowed when using AuthorizationType=API_KEY_QUERY_PARAM))"); + } } @@ -473,4 +484,35 @@ public class APIBackendMetaData extends QBackendMetaData return (false); } + + + /******************************************************************************* + ** Getter for apiKeyQueryParamName + *******************************************************************************/ + public String getApiKeyQueryParamName() + { + return (this.apiKeyQueryParamName); + } + + + + /******************************************************************************* + ** Setter for apiKeyQueryParamName + *******************************************************************************/ + public void setApiKeyQueryParamName(String apiKeyQueryParamName) + { + this.apiKeyQueryParamName = apiKeyQueryParamName; + } + + + + /******************************************************************************* + ** Fluent setter for apiKeyQueryParamName + *******************************************************************************/ + public APIBackendMetaData withApiKeyQueryParamName(String apiKeyQueryParamName) + { + this.apiKeyQueryParamName = apiKeyQueryParamName; + return (this); + } + } diff --git a/qqq-backend-module-api/src/test/java/com/kingsrook/qqq/backend/module/api/model/metadata/APIBackendMetaDataTest.java b/qqq-backend-module-api/src/test/java/com/kingsrook/qqq/backend/module/api/model/metadata/APIBackendMetaDataTest.java index 00fa2842..473dd039 100644 --- a/qqq-backend-module-api/src/test/java/com/kingsrook/qqq/backend/module/api/model/metadata/APIBackendMetaDataTest.java +++ b/qqq-backend-module-api/src/test/java/com/kingsrook/qqq/backend/module/api/model/metadata/APIBackendMetaDataTest.java @@ -24,6 +24,7 @@ package com.kingsrook.qqq.backend.module.api.model.metadata; import com.kingsrook.qqq.backend.core.instances.QInstanceValidator; import com.kingsrook.qqq.backend.module.api.BaseTest; +import com.kingsrook.qqq.backend.module.api.model.AuthorizationType; import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -39,7 +40,7 @@ class APIBackendMetaDataTest extends BaseTest ** *******************************************************************************/ @Test - void test() + void testMissingBaseUrl() { APIBackendMetaData apiBackendMetaData = new APIBackendMetaData() .withName("test"); @@ -49,4 +50,45 @@ class APIBackendMetaDataTest extends BaseTest assertThat(qInstanceValidator.getErrors()).anyMatch(e -> e.contains("Missing baseUrl")); } + + + /******************************************************************************* + ** + *******************************************************************************/ + @Test + void testAuthorizationApiKeyQueryParam() + { + APIBackendMetaData apiBackendMetaData = new APIBackendMetaData() + .withAuthorizationType(AuthorizationType.API_KEY_QUERY_PARAM) + .withBaseUrl("http://localhost:8000/") + .withName("test"); + QInstanceValidator qInstanceValidator = new QInstanceValidator(); + apiBackendMetaData.performValidation(qInstanceValidator); + assertEquals(2, qInstanceValidator.getErrors().size()); + assertThat(qInstanceValidator.getErrors()).anyMatch(e -> e.contains("Missing apiKey for API backend")); + assertThat(qInstanceValidator.getErrors()).anyMatch(e -> e.contains("Missing apiKeyQueryParamName for API backend")); + + apiBackendMetaData = new APIBackendMetaData() + .withAuthorizationType(AuthorizationType.API_KEY_QUERY_PARAM) + .withApiKey("ABC-123") + .withApiKeyQueryParamName("key") + .withBaseUrl("http://localhost:8000/") + .withName("test"); + qInstanceValidator = new QInstanceValidator(); + apiBackendMetaData.performValidation(qInstanceValidator); + assertEquals(0, qInstanceValidator.getErrors().size()); + + apiBackendMetaData = new APIBackendMetaData() + .withAuthorizationType(AuthorizationType.API_KEY_HEADER) + .withApiKey("ABC-123") + .withApiKeyQueryParamName("key") + .withBaseUrl("http://localhost:8000/") + .withName("test"); + qInstanceValidator = new QInstanceValidator(); + apiBackendMetaData.performValidation(qInstanceValidator); + assertEquals(1, qInstanceValidator.getErrors().size()); + assertThat(qInstanceValidator.getErrors()).anyMatch(e -> e.contains("Unexpected apiKeyQueryParamName for API backend")); + + } + } \ No newline at end of file diff --git a/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/backend/javalin/QJavalinImplementation.java b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/backend/javalin/QJavalinImplementation.java index 297cffcc..04342ce9 100644 --- a/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/backend/javalin/QJavalinImplementation.java +++ b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/backend/javalin/QJavalinImplementation.java @@ -283,11 +283,11 @@ public class QJavalinImplementation } catch(QInstanceValidationException e) { - LOG.warn(e.getMessage()); + LOG.error("Validation Error while hot-swapping QInstance", e); } catch(Exception e) { - LOG.error("Error swapping QInstance", e); + LOG.error("Error hot-swapping QInstance", e); } } }