mirror of
https://github.com/Kingsrook/qqq.git
synced 2025-07-18 05:01:07 +00:00
Support api-key in query-string for backend-api;
This commit is contained in:
@ -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));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
|
@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -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,
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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"));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user