fix: Added support for self signed certificate during OAuth2 flows (#14719)

* Added support for self signed certificate during OAuth2 flows, server side changes

* fix: authentication.useSelfSignedCert key added

* Merging from release

* Fixed issue with dependencies, plus bug with using ssl

* Cypress test attempt 1

* Cypress test attempt 2

* Cypress test attempt 3

* Clean up

* Review comments

* Enabled compression again

Co-authored-by: Aman Agarwal <aman@appsmith.com>
This commit is contained in:
Nidhi 2022-07-21 16:10:36 +05:30 committed by GitHub
parent b16fadc763
commit 50172ecd64
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 183 additions and 57 deletions

View File

@ -17,6 +17,7 @@ describe("Create a rest datasource", function() {
.trigger("click") .trigger("click")
.wait(1000); .wait(1000);
agHelper.ValidateToastMessage("datasource created"); //verifying there is no error toast, Bug 14566 agHelper.ValidateToastMessage("datasource created"); //verifying there is no error toast, Bug 14566
cy.testSelfSignedCertificateSettingsInREST(false);
cy.saveDatasource(); cy.saveDatasource();
cy.contains(".datasource-highlight", "https://mock-api.appsmith.com"); cy.contains(".datasource-highlight", "https://mock-api.appsmith.com");
cy.SaveAndRunAPI(); cy.SaveAndRunAPI();

View File

@ -65,5 +65,9 @@
"mongoUriDropdown": "//p[text()='Use Mongo Connection String URI']/following-sibling::div", "mongoUriDropdown": "//p[text()='Use Mongo Connection String URI']/following-sibling::div",
"mongoUriYes": "//div[text()='Yes']", "mongoUriYes": "//div[text()='Yes']",
"mongoUriInput":"//p[text()='Connection String URI']/following-sibling::div//input", "mongoUriInput":"//p[text()='Connection String URI']/following-sibling::div//input",
"advancedSettings": "[data-cy='section-Advanced Settings']",
"useSelfSignedCert": ".t--connection\\.ssl\\.authType",
"useCertInAuth": "[data-cy='authentication.useSelfSignedCert'] input",
"certificateDetails": "[data-cy='section-Certificate Details']",
"saveBtn": ".t--save-datasource" "saveBtn": ".t--save-datasource"
} }

View File

@ -15,7 +15,6 @@ const loginPage = require("../locators/LoginPage.json");
const signupPage = require("../locators/SignupPage.json"); const signupPage = require("../locators/SignupPage.json");
import homePage from "../locators/HomePage"; import homePage from "../locators/HomePage";
const pages = require("../locators/Pages.json"); const pages = require("../locators/Pages.json");
const datasourceEditor = require("../locators/DatasourcesEditor.json");
const datasourceFormData = require("../fixtures/datasources.json"); const datasourceFormData = require("../fixtures/datasources.json");
const commonlocators = require("../locators/commonlocators.json"); const commonlocators = require("../locators/commonlocators.json");
const queryEditor = require("../locators/QueryEditor.json"); const queryEditor = require("../locators/QueryEditor.json");
@ -144,6 +143,7 @@ Cypress.Commands.add(
); );
expect(firstTxt).to.equal(expectedvalue); expect(firstTxt).to.equal(expectedvalue);
}); });
cy.testSelfSignedCertificateSettingsInREST(true);
}, },
); );
@ -161,9 +161,25 @@ Cypress.Commands.add(
cy.xpath("//span[text()='Send client credentials in body']").should( cy.xpath("//span[text()='Send client credentials in body']").should(
"be.visible", "be.visible",
); );
cy.testSelfSignedCertificateSettingsInREST(true);
}, },
); );
Cypress.Commands.add("testSelfSignedCertificateSettingsInREST", (isOAuth2) => {
cy.get(datasource.advancedSettings).click();
cy.get(datasource.useCertInAuth).should("not.exist");
cy.get(datasource.certificateDetails).should("not.exist");
cy.TargetDropdownAndSelectOption(datasource.useSelfSignedCert, "Yes");
if (isOAuth2) {
cy.get(datasource.useCertInAuth).should("exist");
} else {
cy.get(datasource.useCertInAuth).should("not.exist");
}
cy.get(datasource.certificateDetails).should("exist");
cy.TargetDropdownAndSelectOption(datasource.useSelfSignedCert, "No");
cy.get(datasource.advancedSettings).click();
});
Cypress.Commands.add("addBasicProfileDetails", (username, password) => { Cypress.Commands.add("addBasicProfileDetails", (username, password) => {
cy.get(datasource.authType).click(); cy.get(datasource.authType).click();
cy.xpath(datasource.basic).click(); cy.xpath(datasource.basic).click();

View File

@ -39,6 +39,7 @@ function renderComponent(props: renderComponentProps) {
return ( return (
<StyledCheckbox <StyledCheckbox
cypressSelector={props?.input?.name}
isDefaultChecked={props?.input?.checked as boolean} isDefaultChecked={props?.input?.checked as boolean}
{...props} {...props}
info={undefined} info={undefined}

View File

@ -72,6 +72,7 @@ export interface Oauth2Common {
resource: string; resource: string;
sendScopeWithRefreshToken: string; sendScopeWithRefreshToken: string;
refreshTokenClientCredentialsLocation: string; refreshTokenClientCredentialsLocation: string;
useSelfSignedCert?: boolean;
} }
export interface ClientCredentials extends Oauth2Common { export interface ClientCredentials extends Oauth2Common {

View File

@ -845,10 +845,13 @@ class DatasourceRestAPIEditor extends React.Component<
}; };
renderOauth2AdvancedSettings = () => { renderOauth2AdvancedSettings = () => {
const { authentication, authType } = this.props.formData; const { authentication, authType, connection } = this.props.formData;
const isGrantTypeAuthorizationCode = const isGrantTypeAuthorizationCode =
_.get(authentication, "grantType") === GrantType.AuthorizationCode; _.get(authentication, "grantType") === GrantType.AuthorizationCode;
const isAuthenticationTypeOAuth2 = authType === AuthType.OAuth2; const isAuthenticationTypeOAuth2 = authType === AuthType.OAuth2;
const isConnectSelfSigned =
_.get(connection, "ssl.authType") === SSLType.SELF_SIGNED_CERTIFICATE;
return ( return (
<> <>
{isAuthenticationTypeOAuth2 && isGrantTypeAuthorizationCode && ( {isAuthenticationTypeOAuth2 && isGrantTypeAuthorizationCode && (
@ -919,6 +922,16 @@ class DatasourceRestAPIEditor extends React.Component<
"DEFAULT", "DEFAULT",
)} )}
</FormInputContainer> </FormInputContainer>
{isAuthenticationTypeOAuth2 && isConnectSelfSigned && (
<FormInputContainer data-replay-id={btoa("selfsignedcert")}>
{this.renderCheckboxViaFormControl(
"authentication.useSelfSignedCert",
"Use Self-Signed Certificate for Authorization requests",
"",
false,
)}
</FormInputContainer>
)}
</> </>
); );
}; };
@ -1127,6 +1140,30 @@ class DatasourceRestAPIEditor extends React.Component<
/> />
); );
} }
renderCheckboxViaFormControl(
configProperty: string,
label: string,
placeholderText: string,
isRequired: boolean,
) {
return (
<FormControl
config={{
id: "",
isValid: false,
isRequired: isRequired,
controlType: "CHECKBOX",
configProperty: configProperty,
label: label,
conditionals: {},
placeholderText: placeholderText,
formName: DATASOURCE_REST_API_FORM,
}}
formName={DATASOURCE_REST_API_FORM}
/>
);
}
} }
const mapStateToProps = (state: AppState, props: any) => { const mapStateToProps = (state: AppState, props: any) => {

View File

@ -104,6 +104,7 @@ const formToDatasourceAuthentication = (
sendScopeWithRefreshToken: authentication.sendScopeWithRefreshToken, sendScopeWithRefreshToken: authentication.sendScopeWithRefreshToken,
refreshTokenClientCredentialsLocation: refreshTokenClientCredentialsLocation:
authentication.refreshTokenClientCredentialsLocation, authentication.refreshTokenClientCredentialsLocation,
useSelfSignedCert: authentication.useSelfSignedCert,
}; };
if (isClientCredentials(authType, authentication)) { if (isClientCredentials(authType, authentication)) {
return { return {

View File

@ -129,6 +129,11 @@
<groupId>org.springframework</groupId> <groupId>org.springframework</groupId>
<artifactId>spring-webflux</artifactId> <artifactId>spring-webflux</artifactId>
</dependency> </dependency>
<dependency>
<groupId>io.projectreactor.netty</groupId>
<artifactId>reactor-netty-core</artifactId>
<scope>provided</scope>
</dependency>
</dependencies> </dependencies>
<build> <build>

View File

@ -1,6 +1,10 @@
package com.appsmith.external.helpers; package com.appsmith.external.helpers;
import com.appsmith.external.models.DatasourceConfiguration;
import com.appsmith.external.models.SSLDetails;
import com.appsmith.external.models.UploadedFile; import com.appsmith.external.models.UploadedFile;
import reactor.netty.tcp.DefaultSslContextSpec;
import reactor.netty.tcp.SslProvider;
import javax.net.ssl.SSLContext; import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManagerFactory; import javax.net.ssl.TrustManagerFactory;
@ -14,6 +18,7 @@ import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException; import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory; import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate; import java.security.cert.X509Certificate;
import java.util.function.Consumer;
public class SSLHelper { public class SSLHelper {
@ -50,4 +55,26 @@ public class SSLHelper {
return trustManagerFactory; return trustManagerFactory;
} }
public static Consumer<? super SslProvider.SslContextSpec> sslCheckForHttpClient(DatasourceConfiguration datasourceConfiguration) {
return (sslContextSpec) -> {
final DefaultSslContextSpec sslContextSpec1 = DefaultSslContextSpec.forClient();
if (datasourceConfiguration.getConnection() != null &&
datasourceConfiguration.getConnection().getSsl() != null &&
datasourceConfiguration.getConnection().getSsl().getAuthType() == SSLDetails.AuthType.SELF_SIGNED_CERTIFICATE) {
sslContextSpec1.configure(sslContextBuilder -> {
try {
final UploadedFile certificateFile = datasourceConfiguration.getConnection().getSsl().getCertificateFile();
sslContextBuilder.trustManager(SSLHelper.getSslTrustManagerFactory(certificateFile));
} catch (CertificateException | KeyStoreException | IOException | NoSuchAlgorithmException e) {
e.printStackTrace();
}
});
}
sslContextSpec.sslContext(sslContextSpec1);
};
}
} }

View File

@ -76,6 +76,8 @@ public class OAuth2 extends AuthenticationDTO {
String resource; String resource;
boolean useSelfSignedCert = false;
public String getScopeString() { public String getScopeString() {
if (scopeString != null && !scopeString.isBlank()) { if (scopeString != null && !scopeString.isBlank()) {
return scopeString; return scopeString;

View File

@ -1,7 +1,22 @@
package com.external.connections; package com.external.connections;
import com.appsmith.external.helpers.SSLHelper;
import com.appsmith.external.models.DatasourceConfiguration;
import com.appsmith.external.models.OAuth2;
import org.springframework.web.reactive.function.client.ExchangeFilterFunction; import org.springframework.web.reactive.function.client.ExchangeFilterFunction;
import reactor.netty.http.client.HttpClient;
// Parent type for all API connections that need to be created during datasource create method. // Parent type for all API connections that need to be created during datasource create method.
public abstract class APIConnection implements ExchangeFilterFunction { public abstract class APIConnection implements ExchangeFilterFunction {
HttpClient getSecuredHttpClient(DatasourceConfiguration datasourceConfiguration) {
final OAuth2 oAuth2 = (OAuth2) datasourceConfiguration.getAuthentication();
HttpClient httpClient = HttpClient.create();
if (oAuth2.isUseSelfSignedCert()) {
httpClient = httpClient.secure(SSLHelper.sslCheckForHttpClient(datasourceConfiguration));
}
return httpClient;
}
} }

View File

@ -4,6 +4,7 @@ import com.appsmith.external.exceptions.pluginExceptions.AppsmithPluginError;
import com.appsmith.external.exceptions.pluginExceptions.AppsmithPluginException; import com.appsmith.external.exceptions.pluginExceptions.AppsmithPluginException;
import com.appsmith.external.models.AuthenticationDTO; import com.appsmith.external.models.AuthenticationDTO;
import com.appsmith.external.models.BasicAuth; import com.appsmith.external.models.BasicAuth;
import com.appsmith.external.models.DatasourceConfiguration;
import com.appsmith.external.models.OAuth2; import com.appsmith.external.models.OAuth2;
import com.appsmith.external.models.ApiKeyAuth; import com.appsmith.external.models.ApiKeyAuth;
import com.appsmith.external.models.BearerTokenAuth; import com.appsmith.external.models.BearerTokenAuth;
@ -12,25 +13,26 @@ import reactor.core.publisher.Mono;
public class APIConnectionFactory { public class APIConnectionFactory {
public static Mono<APIConnection> createConnection(AuthenticationDTO authenticationType) { public static Mono<APIConnection> createConnection(DatasourceConfiguration datasourceConfiguration) {
if (authenticationType instanceof OAuth2) { final AuthenticationDTO authentication = datasourceConfiguration.getAuthentication();
if (OAuth2.Type.CLIENT_CREDENTIALS.equals(((OAuth2) authenticationType).getGrantType())) { if (authentication instanceof OAuth2) {
return Mono.from(OAuth2ClientCredentials.create((OAuth2) authenticationType)); if (OAuth2.Type.CLIENT_CREDENTIALS.equals(((OAuth2) authentication).getGrantType())) {
} else if (OAuth2.Type.AUTHORIZATION_CODE.equals(((OAuth2) authenticationType).getGrantType())) { return Mono.from(OAuth2ClientCredentials.create(datasourceConfiguration));
if (!Boolean.TRUE.equals(authenticationType.getIsAuthorized())) { } else if (OAuth2.Type.AUTHORIZATION_CODE.equals(((OAuth2) authentication).getGrantType())) {
if (!Boolean.TRUE.equals(authentication.getIsAuthorized())) {
return Mono.error(new AppsmithPluginException( return Mono.error(new AppsmithPluginException(
AppsmithPluginError.PLUGIN_DATASOURCE_ARGUMENT_ERROR, "Please authorize datasource")); AppsmithPluginError.PLUGIN_DATASOURCE_ARGUMENT_ERROR, "Please authorize datasource"));
} }
return Mono.from(OAuth2AuthorizationCode.create((OAuth2) authenticationType)); return Mono.from(OAuth2AuthorizationCode.create(datasourceConfiguration));
} else { } else {
return Mono.empty(); return Mono.empty();
} }
} else if (authenticationType instanceof BasicAuth) { } else if (authentication instanceof BasicAuth) {
return Mono.from(BasicAuthentication.create((BasicAuth) authenticationType)); return Mono.from(BasicAuthentication.create((BasicAuth) authentication));
} else if (authenticationType instanceof ApiKeyAuth) { } else if (authentication instanceof ApiKeyAuth) {
return Mono.from(ApiKeyAuthentication.create((ApiKeyAuth) authenticationType)); return Mono.from(ApiKeyAuthentication.create((ApiKeyAuth) authentication));
} else if (authenticationType instanceof BearerTokenAuth) { } else if (authentication instanceof BearerTokenAuth) {
return Mono.from(BearerTokenAuthentication.create((BearerTokenAuth) authenticationType)); return Mono.from(BearerTokenAuthentication.create((BearerTokenAuth) authentication));
} else { } else {
return Mono.empty(); return Mono.empty();
} }

View File

@ -4,6 +4,7 @@ import com.appsmith.external.constants.Authentication;
import com.appsmith.external.exceptions.pluginExceptions.StaleConnectionException; import com.appsmith.external.exceptions.pluginExceptions.StaleConnectionException;
import com.appsmith.external.models.AuthenticationDTO; import com.appsmith.external.models.AuthenticationDTO;
import com.appsmith.external.models.AuthenticationResponse; import com.appsmith.external.models.AuthenticationResponse;
import com.appsmith.external.models.DatasourceConfiguration;
import com.appsmith.external.models.OAuth2; import com.appsmith.external.models.OAuth2;
import com.appsmith.external.models.UpdatableConnection; import com.appsmith.external.models.UpdatableConnection;
import lombok.AccessLevel; import lombok.AccessLevel;
@ -14,6 +15,7 @@ import org.bson.internal.Base64;
import org.springframework.http.HttpHeaders; import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod; import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
import org.springframework.util.CollectionUtils; import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
import org.springframework.web.reactive.function.BodyExtractors; import org.springframework.web.reactive.function.BodyExtractors;
@ -25,6 +27,7 @@ import org.springframework.web.reactive.function.client.ExchangeStrategies;
import org.springframework.web.reactive.function.client.WebClient; import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.web.util.UriComponentsBuilder; import org.springframework.web.util.UriComponentsBuilder;
import reactor.core.publisher.Mono; import reactor.core.publisher.Mono;
import reactor.netty.http.client.HttpClient;
import java.net.URI; import java.net.URI;
import java.time.Clock; import java.time.Clock;
@ -69,15 +72,16 @@ public class OAuth2AuthorizationCode extends APIConnection implements UpdatableC
return true; return true;
} }
public static Mono<OAuth2AuthorizationCode> create(OAuth2 oAuth2) { public static Mono<OAuth2AuthorizationCode> create(DatasourceConfiguration datasourceConfiguration) {
if (oAuth2 == null) { if (datasourceConfiguration == null || datasourceConfiguration.getAuthentication() == null) {
return Mono.empty(); return Mono.empty();
} }
final OAuth2 oAuth2 = (OAuth2) datasourceConfiguration.getAuthentication();
// Create OAuth2Connection // Create OAuth2Connection
OAuth2AuthorizationCode connection = new OAuth2AuthorizationCode(); OAuth2AuthorizationCode connection = new OAuth2AuthorizationCode();
if (!isAuthenticationResponseValid(oAuth2)) { if (!isAuthenticationResponseValid(oAuth2)) {
return connection.generateOAuth2Token(oAuth2) return connection.generateOAuth2Token(datasourceConfiguration)
.flatMap(token -> { .flatMap(token -> {
updateConnection(connection, token); updateConnection(connection, token);
return Mono.just(connection); return Mono.just(connection);
@ -100,8 +104,13 @@ public class OAuth2AuthorizationCode extends APIConnection implements UpdatableC
return now.isAfter(expiresAt.minus(Duration.ofMinutes(1))); return now.isAfter(expiresAt.minus(Duration.ofMinutes(1)));
} }
private Mono<OAuth2> generateOAuth2Token(OAuth2 oAuth2) { private Mono<OAuth2> generateOAuth2Token(DatasourceConfiguration datasourceConfiguration) {
final OAuth2 oAuth2 = (OAuth2) datasourceConfiguration.getAuthentication();
final HttpClient securedHttpClient = this.getSecuredHttpClient(datasourceConfiguration);
// Webclient
WebClient.Builder webClientBuilder = WebClient.builder() WebClient.Builder webClientBuilder = WebClient.builder()
.clientConnector(new ReactorClientHttpConnector(securedHttpClient))
.defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE) .defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE)
.exchangeStrategies(ExchangeStrategies .exchangeStrategies(ExchangeStrategies
.builder() .builder()

View File

@ -4,6 +4,7 @@ import com.appsmith.external.constants.Authentication;
import com.appsmith.external.exceptions.pluginExceptions.StaleConnectionException; import com.appsmith.external.exceptions.pluginExceptions.StaleConnectionException;
import com.appsmith.external.models.AuthenticationDTO; import com.appsmith.external.models.AuthenticationDTO;
import com.appsmith.external.models.AuthenticationResponse; import com.appsmith.external.models.AuthenticationResponse;
import com.appsmith.external.models.DatasourceConfiguration;
import com.appsmith.external.models.OAuth2; import com.appsmith.external.models.OAuth2;
import com.appsmith.external.models.UpdatableConnection; import com.appsmith.external.models.UpdatableConnection;
import lombok.AccessLevel; import lombok.AccessLevel;
@ -14,6 +15,7 @@ import org.bson.internal.Base64;
import org.springframework.http.HttpHeaders; import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod; import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
import org.springframework.util.CollectionUtils; import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
import org.springframework.web.reactive.function.BodyExtractors; import org.springframework.web.reactive.function.BodyExtractors;
@ -25,6 +27,7 @@ import org.springframework.web.reactive.function.client.ExchangeStrategies;
import org.springframework.web.reactive.function.client.WebClient; import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.web.util.UriComponentsBuilder; import org.springframework.web.util.UriComponentsBuilder;
import reactor.core.publisher.Mono; import reactor.core.publisher.Mono;
import reactor.netty.http.client.HttpClient;
import java.net.URI; import java.net.URI;
import java.time.Clock; import java.time.Clock;
@ -45,10 +48,11 @@ public class OAuth2ClientCredentials extends APIConnection implements UpdatableC
private Object tokenResponse; private Object tokenResponse;
private static final int MAX_IN_MEMORY_SIZE = 10 * 1024 * 1024; // 10 MB private static final int MAX_IN_MEMORY_SIZE = 10 * 1024 * 1024; // 10 MB
public static Mono<OAuth2ClientCredentials> create(OAuth2 oAuth2) { public static Mono<OAuth2ClientCredentials> create(DatasourceConfiguration datasourceConfiguration) {
if (oAuth2 == null) { if (datasourceConfiguration == null) {
return Mono.empty(); return Mono.empty();
} }
final OAuth2 oAuth2 = (OAuth2) datasourceConfiguration.getAuthentication();
// Create OAuth2Connection // Create OAuth2Connection
OAuth2ClientCredentials connection = new OAuth2ClientCredentials(); OAuth2ClientCredentials connection = new OAuth2ClientCredentials();
@ -65,7 +69,7 @@ public class OAuth2ClientCredentials extends APIConnection implements UpdatableC
return now.isBefore(expiresAt.minus(Duration.ofMinutes(1))); return now.isBefore(expiresAt.minus(Duration.ofMinutes(1)));
}) })
// If invalid, regenerate token // If invalid, regenerate token
.switchIfEmpty(connection.generateOAuth2Token(oAuth2)) .switchIfEmpty(connection.generateOAuth2Token(datasourceConfiguration))
// Store valid token // Store valid token
.flatMap(token -> { .flatMap(token -> {
connection.setToken(token.getAuthenticationResponse().getToken()); connection.setToken(token.getAuthenticationResponse().getToken());
@ -77,9 +81,13 @@ public class OAuth2ClientCredentials extends APIConnection implements UpdatableC
}); });
} }
private Mono<OAuth2> generateOAuth2Token(OAuth2 oAuth2) { private Mono<OAuth2> generateOAuth2Token(DatasourceConfiguration datasourceConfiguration) {
final OAuth2 oAuth2 = (OAuth2) datasourceConfiguration.getAuthentication();
final HttpClient securedHttpClient = this.getSecuredHttpClient(datasourceConfiguration);
// Webclient // Webclient
WebClient.Builder webClientBuilder = WebClient.builder() final WebClient.Builder webClientBuilder = WebClient.builder()
.clientConnector(new ReactorClientHttpConnector(securedHttpClient))
.defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE) .defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE)
.exchangeStrategies(ExchangeStrategies .exchangeStrategies(ExchangeStrategies
.builder() .builder()

View File

@ -16,8 +16,6 @@ import com.appsmith.external.models.DatasourceTestResult;
import com.appsmith.external.models.PaginationField; import com.appsmith.external.models.PaginationField;
import com.appsmith.external.models.PaginationType; import com.appsmith.external.models.PaginationType;
import com.appsmith.external.models.Property; import com.appsmith.external.models.Property;
import com.appsmith.external.models.SSLDetails;
import com.appsmith.external.models.UploadedFile;
import com.appsmith.external.plugins.BasePlugin; import com.appsmith.external.plugins.BasePlugin;
import com.appsmith.external.plugins.PluginExecutor; import com.appsmith.external.plugins.PluginExecutor;
import com.appsmith.external.plugins.SmartSubstitutionInterface; import com.appsmith.external.plugins.SmartSubstitutionInterface;
@ -56,7 +54,6 @@ import reactor.core.Exceptions;
import reactor.core.publisher.Mono; import reactor.core.publisher.Mono;
import reactor.netty.http.client.HttpClient; import reactor.netty.http.client.HttpClient;
import reactor.netty.resources.ConnectionProvider; import reactor.netty.resources.ConnectionProvider;
import reactor.netty.tcp.DefaultSslContextSpec;
import reactor.util.function.Tuple2; import reactor.util.function.Tuple2;
import javax.crypto.SecretKey; import javax.crypto.SecretKey;
@ -68,9 +65,6 @@ import java.net.URLDecoder;
import java.net.URLEncoder; import java.net.URLEncoder;
import java.net.UnknownHostException; import java.net.UnknownHostException;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.time.Duration; import java.time.Duration;
import java.time.Instant; import java.time.Instant;
import java.util.ArrayList; import java.util.ArrayList;
@ -302,25 +296,8 @@ public class RestApiPlugin extends BasePlugin {
.build(); .build();
HttpClient httpClient = HttpClient.create(provider) HttpClient httpClient = HttpClient.create(provider)
.secure(sslContextSpec -> { .secure(SSLHelper.sslCheckForHttpClient(datasourceConfiguration))
.compress(true);
final DefaultSslContextSpec sslContextSpec1 = DefaultSslContextSpec.forClient();
if (datasourceConfiguration.getConnection() != null &&
datasourceConfiguration.getConnection().getSsl() != null &&
datasourceConfiguration.getConnection().getSsl().getAuthType() == SSLDetails.AuthType.SELF_SIGNED_CERTIFICATE) {
sslContextSpec1.configure(sslContextBuilder -> {
try {
final UploadedFile certificateFile = datasourceConfiguration.getConnection().getSsl().getCertificateFile();
sslContextBuilder.trustManager(SSLHelper.getSslTrustManagerFactory(certificateFile));
} catch (CertificateException | KeyStoreException | IOException | NoSuchAlgorithmException e) {
e.printStackTrace();
}
});
}
sslContextSpec.sslContext(sslContextSpec1);
}).compress(true);
if ("true".equals(System.getProperty("java.net.useSystemProxies")) if ("true".equals(System.getProperty("java.net.useSystemProxies"))
&& (!System.getProperty("http.proxyHost", "").isEmpty() || !System.getProperty("https.proxyHost", "").isEmpty())) { && (!System.getProperty("http.proxyHost", "").isEmpty() || !System.getProperty("https.proxyHost", "").isEmpty())) {
@ -620,7 +597,7 @@ public class RestApiPlugin extends BasePlugin {
@Override @Override
public Mono<APIConnection> datasourceCreate(DatasourceConfiguration datasourceConfiguration) { public Mono<APIConnection> datasourceCreate(DatasourceConfiguration datasourceConfiguration) {
return APIConnectionFactory.createConnection(datasourceConfiguration.getAuthentication()); return APIConnectionFactory.createConnection(datasourceConfiguration);
} }
@Override @Override

View File

@ -2,6 +2,7 @@ package com.external.connections;
import com.appsmith.external.exceptions.pluginExceptions.StaleConnectionException; import com.appsmith.external.exceptions.pluginExceptions.StaleConnectionException;
import com.appsmith.external.models.AuthenticationResponse; import com.appsmith.external.models.AuthenticationResponse;
import com.appsmith.external.models.DatasourceConfiguration;
import com.appsmith.external.models.OAuth2; import com.appsmith.external.models.OAuth2;
import okhttp3.mockwebserver.MockResponse; import okhttp3.mockwebserver.MockResponse;
import okhttp3.mockwebserver.MockWebServer; import okhttp3.mockwebserver.MockWebServer;
@ -56,13 +57,15 @@ public class OAuth2ClientCredentialsTest {
@Test @Test
public void testValidConnection() { public void testValidConnection() {
DatasourceConfiguration datasourceConfiguration = new DatasourceConfiguration();
OAuth2 oAuth2 = new OAuth2(); OAuth2 oAuth2 = new OAuth2();
datasourceConfiguration.setAuthentication(oAuth2);
AuthenticationResponse authenticationResponse = new AuthenticationResponse(); AuthenticationResponse authenticationResponse = new AuthenticationResponse();
oAuth2.setIsTokenHeader(true); oAuth2.setIsTokenHeader(true);
authenticationResponse.setToken("SomeToken"); authenticationResponse.setToken("SomeToken");
authenticationResponse.setExpiresAt(Instant.now().plusSeconds(1200)); authenticationResponse.setExpiresAt(Instant.now().plusSeconds(1200));
oAuth2.setAuthenticationResponse(authenticationResponse); oAuth2.setAuthenticationResponse(authenticationResponse);
OAuth2ClientCredentials connection = OAuth2ClientCredentials.create(oAuth2).block(Duration.ofMillis(100)); OAuth2ClientCredentials connection = OAuth2ClientCredentials.create(datasourceConfiguration).block(Duration.ofMillis(100));
assertThat(connection).isNotNull(); assertThat(connection).isNotNull();
assertThat(connection.getExpiresAt()).isEqualTo(authenticationResponse.getExpiresAt()); assertThat(connection.getExpiresAt()).isEqualTo(authenticationResponse.getExpiresAt());
assertThat(connection.getToken()).isEqualTo("SomeToken"); assertThat(connection.getToken()).isEqualTo("SomeToken");
@ -70,13 +73,15 @@ public class OAuth2ClientCredentialsTest {
@Test @Test
public void testStaleFilter() { public void testStaleFilter() {
DatasourceConfiguration datasourceConfiguration = new DatasourceConfiguration();
OAuth2 oAuth2 = new OAuth2(); OAuth2 oAuth2 = new OAuth2();
datasourceConfiguration.setAuthentication(oAuth2);
AuthenticationResponse authenticationResponse = new AuthenticationResponse(); AuthenticationResponse authenticationResponse = new AuthenticationResponse();
oAuth2.setIsTokenHeader(true); oAuth2.setIsTokenHeader(true);
authenticationResponse.setToken("SomeToken"); authenticationResponse.setToken("SomeToken");
authenticationResponse.setExpiresAt(Instant.now().plusSeconds(1200)); authenticationResponse.setExpiresAt(Instant.now().plusSeconds(1200));
oAuth2.setAuthenticationResponse(authenticationResponse); oAuth2.setAuthenticationResponse(authenticationResponse);
OAuth2ClientCredentials connection = OAuth2ClientCredentials.create(oAuth2).block(Duration.ofMillis(100)); OAuth2ClientCredentials connection = OAuth2ClientCredentials.create(datasourceConfiguration).block(Duration.ofMillis(100));
connection.setExpiresAt(Instant.now()); connection.setExpiresAt(Instant.now());
Mono<ClientResponse> response = connection.filter(Mockito.mock(ClientRequest.class), Mockito.mock(ExchangeFunction.class)); Mono<ClientResponse> response = connection.filter(Mockito.mock(ClientRequest.class), Mockito.mock(ExchangeFunction.class));
@ -89,7 +94,9 @@ public class OAuth2ClientCredentialsTest {
public void testCreate_withIsAuthorizationHeaderTrue_sendsCredentialsInHeader() throws InterruptedException { public void testCreate_withIsAuthorizationHeaderTrue_sendsCredentialsInHeader() throws InterruptedException {
String baseUrl = String.format("http://localhost:%s", mockEndpoint.getPort()); String baseUrl = String.format("http://localhost:%s", mockEndpoint.getPort());
final DatasourceConfiguration datasourceConfiguration = new DatasourceConfiguration();
OAuth2 oAuth2 = new OAuth2(); OAuth2 oAuth2 = new OAuth2();
datasourceConfiguration.setAuthentication(oAuth2);
oAuth2.setIsAuthorizationHeader(true); oAuth2.setIsAuthorizationHeader(true);
oAuth2.setGrantType(OAuth2.Type.CLIENT_CREDENTIALS); oAuth2.setGrantType(OAuth2.Type.CLIENT_CREDENTIALS);
oAuth2.setAccessTokenUrl(baseUrl); oAuth2.setAccessTokenUrl(baseUrl);
@ -101,7 +108,7 @@ public class OAuth2ClientCredentialsTest {
.setBody("{}") .setBody("{}")
.addHeader("Content-Type", "application/json")); .addHeader("Content-Type", "application/json"));
final OAuth2ClientCredentials response = OAuth2ClientCredentials.create(oAuth2).block(); final OAuth2ClientCredentials response = OAuth2ClientCredentials.create(datasourceConfiguration).block();
final RecordedRequest recordedRequest = mockEndpoint.takeRequest(30, TimeUnit.SECONDS); final RecordedRequest recordedRequest = mockEndpoint.takeRequest(30, TimeUnit.SECONDS);
final String authorizationHeader = recordedRequest.getHeader("Authorization"); final String authorizationHeader = recordedRequest.getHeader("Authorization");
@ -113,7 +120,9 @@ public class OAuth2ClientCredentialsTest {
public void testCreate_withIsAuthorizationHeaderFalse_sendsCredentialsInBody() throws InterruptedException, EOFException { public void testCreate_withIsAuthorizationHeaderFalse_sendsCredentialsInBody() throws InterruptedException, EOFException {
String baseUrl = String.format("http://localhost:%s", mockEndpoint.getPort()); String baseUrl = String.format("http://localhost:%s", mockEndpoint.getPort());
final DatasourceConfiguration datasourceConfiguration = new DatasourceConfiguration();
OAuth2 oAuth2 = new OAuth2(); OAuth2 oAuth2 = new OAuth2();
datasourceConfiguration.setAuthentication(oAuth2);
oAuth2.setGrantType(OAuth2.Type.CLIENT_CREDENTIALS); oAuth2.setGrantType(OAuth2.Type.CLIENT_CREDENTIALS);
oAuth2.setAccessTokenUrl(baseUrl); oAuth2.setAccessTokenUrl(baseUrl);
oAuth2.setClientId("testId"); oAuth2.setClientId("testId");
@ -124,7 +133,7 @@ public class OAuth2ClientCredentialsTest {
.setBody("{}") .setBody("{}")
.addHeader("Content-Type", "application/json")); .addHeader("Content-Type", "application/json"));
final OAuth2ClientCredentials response = OAuth2ClientCredentials.create(oAuth2).block(); final OAuth2ClientCredentials response = OAuth2ClientCredentials.create(datasourceConfiguration).block();
final RecordedRequest recordedRequest = mockEndpoint.takeRequest(30, TimeUnit.SECONDS); final RecordedRequest recordedRequest = mockEndpoint.takeRequest(30, TimeUnit.SECONDS);
final String authorizationHeader = recordedRequest.getHeader("Authorization"); final String authorizationHeader = recordedRequest.getHeader("Authorization");

View File

@ -3,6 +3,7 @@ package com.appsmith.server.solutions.ce;
import com.appsmith.external.constants.Authentication; import com.appsmith.external.constants.Authentication;
import com.appsmith.external.exceptions.pluginExceptions.AppsmithPluginError; import com.appsmith.external.exceptions.pluginExceptions.AppsmithPluginError;
import com.appsmith.external.exceptions.pluginExceptions.AppsmithPluginException; import com.appsmith.external.exceptions.pluginExceptions.AppsmithPluginException;
import com.appsmith.external.helpers.SSLHelper;
import com.appsmith.external.models.AuthenticationDTO; import com.appsmith.external.models.AuthenticationDTO;
import com.appsmith.external.models.AuthenticationResponse; import com.appsmith.external.models.AuthenticationResponse;
import com.appsmith.external.models.Datasource; import com.appsmith.external.models.Datasource;
@ -30,6 +31,7 @@ import lombok.extern.slf4j.Slf4j;
import org.bson.internal.Base64; import org.bson.internal.Base64;
import org.springframework.http.HttpMethod; import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
import org.springframework.http.server.reactive.ServerHttpRequest; import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap; import org.springframework.util.MultiValueMap;
@ -38,6 +40,7 @@ import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.client.WebClient; import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.web.util.UriComponentsBuilder; import org.springframework.web.util.UriComponentsBuilder;
import reactor.core.publisher.Mono; import reactor.core.publisher.Mono;
import reactor.netty.http.client.HttpClient;
import java.net.ConnectException; import java.net.ConnectException;
import java.net.URI; import java.net.URI;
@ -164,7 +167,15 @@ public class AuthenticationServiceCEImpl implements AuthenticationServiceCE {
.flatMap(datasourceService::getById) .flatMap(datasourceService::getById)
.flatMap(datasource -> { .flatMap(datasource -> {
OAuth2 oAuth2 = (OAuth2) datasource.getDatasourceConfiguration().getAuthentication(); OAuth2 oAuth2 = (OAuth2) datasource.getDatasourceConfiguration().getAuthentication();
WebClient.Builder builder = WebClient.builder().baseUrl(oAuth2.getAccessTokenUrl()); final HttpClient httpClient = HttpClient.create();
if (oAuth2.isUseSelfSignedCert()) {
httpClient.secure(SSLHelper.sslCheckForHttpClient(datasource.getDatasourceConfiguration()));
}
WebClient.Builder builder = WebClient.builder()
.clientConnector(new ReactorClientHttpConnector(httpClient))
.baseUrl(oAuth2.getAccessTokenUrl());
MultiValueMap<String, String> map = new LinkedMultiValueMap<>(); MultiValueMap<String, String> map = new LinkedMultiValueMap<>();

View File

@ -854,7 +854,6 @@ public class ExamplesWorkspaceClonerTests {
DatasourceConfiguration dc2 = new DatasourceConfiguration(); DatasourceConfiguration dc2 = new DatasourceConfiguration();
ds2.setDatasourceConfiguration(dc2); ds2.setDatasourceConfiguration(dc2);
dc2.setAuthentication(new OAuth2( dc2.setAuthentication(new OAuth2(
OAuth2.Type.CLIENT_CREDENTIALS, OAuth2.Type.CLIENT_CREDENTIALS,
true, true,
true, true,
@ -872,7 +871,8 @@ public class ExamplesWorkspaceClonerTests {
new Property("custom token param 2", "custom token param value 2") new Property("custom token param 2", "custom token param value 2")
), ),
null, null,
null null,
false
)); ));
final Datasource ds3 = new Datasource(); final Datasource ds3 = new Datasource();