Support api-key in query-string for backend-api;

This commit is contained in:
2023-06-29 11:15:24 -05:00
parent b75fd29a57
commit 3ae938ac6e
7 changed files with 169 additions and 32 deletions

View File

@ -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));
}
/******************************************************************************* /*******************************************************************************
** **
*******************************************************************************/ *******************************************************************************/

View File

@ -38,7 +38,7 @@ import com.kingsrook.qqq.backend.core.modules.backend.QBackendModuleInterface;
** **
*******************************************************************************/ *******************************************************************************/
@JsonDeserialize(using = QBackendMetaDataDeserializer.class) @JsonDeserialize(using = QBackendMetaDataDeserializer.class)
public class QBackendMetaData public class QBackendMetaData implements TopLevelMetaDataInterface
{ {
private String name; private String name;
private String backendType; private String backendType;
@ -409,4 +409,14 @@ public class QBackendMetaData
return (this); return (this);
} }
/*******************************************************************************
**
*******************************************************************************/
@Override
public void addSelfToInstance(QInstance qInstance)
{
qInstance.addBackend(this);
}
} }

View File

@ -24,6 +24,8 @@ package com.kingsrook.qqq.backend.module.api.actions;
import java.io.IOException; import java.io.IOException;
import java.io.Serializable; import java.io.Serializable;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URLEncoder; import java.net.URLEncoder;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.time.Instant; import java.time.Instant;
@ -644,6 +646,20 @@ public class BaseAPIActionUtil
request.setHeader("Authorization", "Bearer " + getOAuth2Token()); request.setHeader("Authorization", "Bearer " + getOAuth2Token());
break; 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: default:
throw new IllegalArgumentException("Unexpected authorization type: " + backendMetaData.getAuthorizationType()); throw new IllegalArgumentException("Unexpected authorization type: " + backendMetaData.getAuthorizationType());
} }
@ -1050,6 +1066,8 @@ public class BaseAPIActionUtil
** **
*******************************************************************************/ *******************************************************************************/
protected void logOutboundApiCall(HttpRequestBase request, QHttpResponse response) protected void logOutboundApiCall(HttpRequestBase request, QHttpResponse response)
{
try
{ {
QTableMetaData table = QContext.getQInstance().getTable(OutboundAPILog.TABLE_NAME); QTableMetaData table = QContext.getQInstance().getTable(OutboundAPILog.TABLE_NAME);
if(table == null) if(table == null)
@ -1070,11 +1088,20 @@ public class BaseAPIActionUtil
} }
} }
////////////////////////////////////
// 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 insertInput = new InsertInput();
insertInput.setTableName(table.getName()); insertInput.setTableName(table.getName());
insertInput.setRecords(List.of(new OutboundAPILog() insertInput.setRecords(List.of(new OutboundAPILog()
.withMethod(request.getMethod()) .withMethod(request.getMethod())
.withUrl(request.getURI().toString()) // todo - does this have the query string? .withUrl(url)
.withTimestamp(Instant.now()) .withTimestamp(Instant.now())
.withRequestBody(requestBody) .withRequestBody(requestBody)
.withStatusCode(response.getStatusCode()) .withStatusCode(response.getStatusCode())
@ -1083,6 +1110,11 @@ public class BaseAPIActionUtil
)); ));
new InsertAction().executeAsync(insertInput); new InsertAction().executeAsync(insertInput);
} }
catch(Exception e)
{
LOG.warn("Error logging outbound api call", e);
}
}

View File

@ -31,5 +31,5 @@ public enum AuthorizationType
BASIC_AUTH_API_KEY, BASIC_AUTH_API_KEY,
BASIC_AUTH_USERNAME_PASSWORD, BASIC_AUTH_USERNAME_PASSWORD,
OAUTH2, OAUTH2,
API_KEY_QUERY_PARAM,
} }

View File

@ -43,6 +43,7 @@ public class APIBackendMetaData extends QBackendMetaData
private String clientSecret; private String clientSecret;
private String username; private String username;
private String password; private String password;
private String apiKeyQueryParamName;
private AuthorizationType authorizationType; private AuthorizationType authorizationType;
private String contentType; // todo enum? private String contentType; // todo enum?
@ -460,6 +461,16 @@ public class APIBackendMetaData extends QBackendMetaData
public void performValidation(QInstanceValidator qInstanceValidator) public void performValidation(QInstanceValidator qInstanceValidator)
{ {
qInstanceValidator.assertCondition(StringUtils.hasContent(baseUrl), "Missing baseUrl for API backend: " + getName()); 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); 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);
}
} }

View File

@ -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.core.instances.QInstanceValidator;
import com.kingsrook.qqq.backend.module.api.BaseTest; import com.kingsrook.qqq.backend.module.api.BaseTest;
import com.kingsrook.qqq.backend.module.api.model.AuthorizationType;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
@ -39,7 +40,7 @@ class APIBackendMetaDataTest extends BaseTest
** **
*******************************************************************************/ *******************************************************************************/
@Test @Test
void test() void testMissingBaseUrl()
{ {
APIBackendMetaData apiBackendMetaData = new APIBackendMetaData() APIBackendMetaData apiBackendMetaData = new APIBackendMetaData()
.withName("test"); .withName("test");
@ -49,4 +50,45 @@ class APIBackendMetaDataTest extends BaseTest
assertThat(qInstanceValidator.getErrors()).anyMatch(e -> e.contains("Missing baseUrl")); 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"));
}
} }

View File

@ -283,11 +283,11 @@ public class QJavalinImplementation
} }
catch(QInstanceValidationException e) catch(QInstanceValidationException e)
{ {
LOG.warn(e.getMessage()); LOG.error("Validation Error while hot-swapping QInstance", e);
} }
catch(Exception e) catch(Exception e)
{ {
LOG.error("Error swapping QInstance", e); LOG.error("Error hot-swapping QInstance", e);
} }
} }
} }