Skip to content

Commit 0dc3ce4

Browse files
authored
Merge pull request #41522 from appsmithorg/release
20/01/2026 - Daily Promotion
2 parents c9ef50c + fc5e4a2 commit 0dc3ce4

File tree

65 files changed

+1751
-1261
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

65 files changed

+1751
-1261
lines changed

.github/workflows/docker-base-image.yml

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ on:
1414
jobs:
1515
build-docker:
1616
runs-on: ubuntu-latest
17+
permissions:
18+
contents: read
19+
id-token: write
1720

1821
defaults:
1922
run:
@@ -29,12 +32,6 @@ jobs:
2932
username: ${{ secrets.DOCKER_HUB_USERNAME }}
3033
password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }}
3134

32-
- name: Set up QEMU
33-
uses: docker/setup-qemu-action@v3
34-
35-
- name: Set up Docker Buildx
36-
uses: docker/setup-buildx-action@v3
37-
3835
- name: Get tag
3936
id: tag
4037
run: |
@@ -60,5 +57,3 @@ jobs:
6057
platforms: linux/arm64,linux/amd64
6158
tags: |
6259
${{ vars.DOCKER_HUB_ORGANIZATION }}/base-${{ vars.EDITION }}:${{ steps.tag.outputs.tag }}
63-
env:
64-
DEPOT_TOKEN: ${{ secrets.DEPOT_TOKEN }}

app/client/cypress/locators/commonlocators.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -246,5 +246,8 @@
246246
"tostifyIcon": ".Toastify--animate-icon > span",
247247
"downloadFileType": "button[class*='t--open-dropdown-Select-file-type'] > span:first-of-type",
248248
"listToggle": "[data-testid='t--list-toggle']",
249-
"showBindingsMenu": "//*[@id='entity-properties-container']"
249+
"showBindingsMenu": "//*[@id='entity-properties-container']",
250+
"confirmationModal": "[data-testid='t--confirmation-modal']",
251+
"confirmationModalConfirm": "[data-testid='t--confirmation-modal-confirm']",
252+
"confirmationModalCancel": "[data-testid='t--confirmation-modal-cancel']"
250253
}

app/client/cypress/support/Pages/DataSources.ts

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import { anvilLocators } from "./Anvil/Locators";
1414
import { PluginActionForm } from "./PluginActionForm";
1515
import ApiEditor from "../../locators/ApiEditor";
1616
import BottomTabs from "./IDE/BottomTabs";
17+
import commonlocators from "../../locators/commonlocators.json";
1718

1819
export const DataSourceKVP = {
1920
Postgres: "PostgreSQL",
@@ -912,7 +913,7 @@ export class DataSources {
912913

913914
public DeleteDatasourceFromWithinDS(
914915
datasourceName: string,
915-
expectedRes: number | number[] = 200 || 409 || [200, 409],
916+
expectedRes: number | number[] = [200, 409],
916917
) {
917918
EditorNavigation.SelectEntityByName(datasourceName, EntityType.Datasource);
918919
this.agHelper.Sleep(); //for the Datasource page to open
@@ -935,7 +936,7 @@ export class DataSources {
935936
}
936937

937938
public DeleteDSDirectly(
938-
expectedRes: number | number[] = 200 || 409 || [200, 409],
939+
expectedRes: number | number[] = [200, 409],
939940
toNavigateToDSInfoPage = true,
940941
) {
941942
toNavigateToDSInfoPage &&
@@ -1187,8 +1188,32 @@ export class DataSources {
11871188

11881189
ToggleUsePreparedStatement(enable = true || false) {
11891190
this.pluginActionForm.toolbar.toggleSettings();
1190-
if (enable) this.agHelper.CheckUncheck(this._usePreparedStatement, true);
1191-
else this.agHelper.CheckUncheck(this._usePreparedStatement, false);
1191+
if (enable) {
1192+
this.agHelper.CheckUncheck(this._usePreparedStatement, true);
1193+
} else {
1194+
// When disabling, click the switch which will trigger the modal
1195+
this.agHelper
1196+
.GetElement(this._usePreparedStatement)
1197+
.click({ force: true });
1198+
1199+
// Wait for and handle the confirmation modal
1200+
this.agHelper.AssertElementVisibility(
1201+
commonlocators.confirmationModal,
1202+
true,
1203+
0,
1204+
5000,
1205+
);
1206+
this.agHelper.GetNClick(commonlocators.confirmationModalConfirm);
1207+
this.agHelper.AssertElementAbsence(
1208+
commonlocators.confirmationModal,
1209+
5000,
1210+
);
1211+
1212+
// Now verify the switch is actually unchecked
1213+
this.agHelper
1214+
.GetElement(this._usePreparedStatement)
1215+
.should("not.be.checked");
1216+
}
11921217
}
11931218

11941219
public EnterQuery(query: string, sleep = 500, toVerifySave = true) {

app/client/src/ce/constants/DeploymentConstants.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,8 @@ export const REDEPLOY_WARNING_MESSAGE: Record<
1313
> = {
1414
[REDEPLOY_TRIGGERS.PendingDeployment]: REDEPLOY_APP_WARNING,
1515
};
16+
17+
export const REDEPLOY_DOC_URL: Record<RedeployTriggerValue, string> = {
18+
[REDEPLOY_TRIGGERS.PendingDeployment]:
19+
"https://docs.appsmith.com/help-and-support/troubleshooting-guide/git-errors#edit-and-view-mode-out-of-sync",
20+
};

app/client/src/ce/constants/messages.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1630,6 +1630,36 @@ export const WELCOME_FORM_PASSWORDS_NOT_MATCHING_ERROR_MESSAGE = () =>
16301630

16311631
export const QUERY_CONFIRMATION_MODAL_MESSAGE = () =>
16321632
`Are you sure you want to run `;
1633+
1634+
// Security feature disable confirmation modal
1635+
export const DISABLE_PREPARED_STATEMENT_CONFIRMATION_HEADING = () =>
1636+
"Security Warning: Disable Prepared Statements?";
1637+
export const DISABLE_PREPARED_STATEMENT_CONFIRMATION_DESCRIPTION = () =>
1638+
"Disabling prepared statements exposes your application to SQL injection attacks. This setting will be saved and apply to production queries. Only disable if you absolutely need dynamic SQL patterns that cannot be parameterized.";
1639+
1640+
export const DISABLE_PREPARED_STATEMENT_CONFIRMATION_LEARN_MORE = {
1641+
TEXT: () => "Learn more",
1642+
URL: "https://docs.appsmith.com/connect-data/concepts/how-to-use-prepared-statements",
1643+
};
1644+
1645+
export const DISABLE_SMART_SUBSTITUTION_CONFIRMATION_HEADING = () =>
1646+
"Security Warning: Disable Smart Substitution?";
1647+
export const DISABLE_SMART_SUBSTITUTION_CONFIRMATION_DESCRIPTION = () =>
1648+
"Disabling smart substitution removes Appsmith's automatic escaping and validation. This increases the risk of malformed queries and injection vulnerabilities. Only disable if you fully understand the security implications.";
1649+
export const DISABLE_SMART_SUBSTITUTION_CONFIRMATION_LEARN_MORE = {
1650+
TEXT: () => "Learn more",
1651+
URL: "https://docs.appsmith.com/connect-data/reference/query-settings#smart-json-substitution",
1652+
};
1653+
1654+
export const DISABLE_BSON_SUBSTITUTION_CONFIRMATION_HEADING = () =>
1655+
"Security Warning: Disable Smart BSON Substitution?";
1656+
export const DISABLE_BSON_SUBSTITUTION_CONFIRMATION_DESCRIPTION = () =>
1657+
"Disabling smart BSON substitution removes Appsmith's automatic escaping and validation before sending commands to MongoDB. This increases the risk of malformed queries and injection vulnerabilities. Only disable if you fully understand the security implications.";
1658+
export const DISABLE_BSON_SUBSTITUTION_CONFIRMATION_LEARN_MORE = {
1659+
TEXT: () => "Learn more",
1660+
URL: "https://docs.appsmith.com/connect-data/reference/query-settings#smart-bson-substitution",
1661+
};
1662+
16331663
export const ENTITY_EXPLORER_TITLE = () => "NAVIGATION";
16341664
export const MULTI_SELECT_PROPERTY_PANE_MESSAGE = () =>
16351665
`Select a widget to see it's properties`;
Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
1+
import React from "react";
2+
import type { ControlProps } from "./BaseControl";
3+
import BaseControl from "./BaseControl";
4+
import {
5+
Switch,
6+
Link,
7+
Modal,
8+
ModalContent,
9+
ModalHeader,
10+
ModalBody,
11+
ModalFooter,
12+
Button,
13+
Text,
14+
} from "@appsmith/ads";
15+
import type { ControlType } from "constants/PropertyControlConstants";
16+
import type { WrappedFieldProps } from "redux-form";
17+
import { Field } from "redux-form";
18+
import styled from "styled-components";
19+
import {
20+
SECURITY_MODAL_CONTENT,
21+
type SecurityModalType,
22+
} from "constants/SecurityModalConstants";
23+
import { createMessage } from "ee/constants/messages";
24+
25+
type SwitchFieldProps = WrappedFieldProps & {
26+
label: string;
27+
isRequired: boolean;
28+
info: string;
29+
disabled?: boolean;
30+
modalType?: SecurityModalType;
31+
};
32+
33+
const SwitchWrapped = styled.div`
34+
flex-direction: row;
35+
display: flex;
36+
align-items: center;
37+
justify-content: end;
38+
position: relative;
39+
max-width: 60vw;
40+
`;
41+
42+
const StyledModalContent = styled(ModalContent)`
43+
&&& {
44+
width: 600px;
45+
}
46+
`;
47+
48+
export class SwitchWithConfirmationField extends React.Component<
49+
SwitchFieldProps,
50+
{ isModalOpen: boolean; pendingValue: boolean | null }
51+
> {
52+
constructor(props: SwitchFieldProps) {
53+
super(props);
54+
this.state = {
55+
isModalOpen: false,
56+
pendingValue: null,
57+
};
58+
}
59+
60+
/**
61+
* `redux-form`'s `input.value` can sometimes be a string (e.g. "false").
62+
* Normalize it to a boolean so the switch + confirmation logic behaves
63+
* consistently regardless of the serialized value type.
64+
*/
65+
private getBooleanValue(value: unknown): boolean {
66+
if (typeof value !== "string") return !!value;
67+
68+
if (value.toLocaleLowerCase() === "false") return false;
69+
70+
return !!value;
71+
}
72+
73+
handleSwitchChange = (isSelected: boolean) => {
74+
const currentValue = this.getBooleanValue(this.props.input.value);
75+
76+
// Only show modal when toggling from ON (true) to OFF (false)
77+
if (currentValue === true && isSelected === false) {
78+
this.setState({
79+
isModalOpen: true,
80+
pendingValue: isSelected,
81+
});
82+
} else {
83+
// Allow toggling from OFF to ON without confirmation
84+
this.props.input.onChange(isSelected);
85+
}
86+
};
87+
88+
handleConfirm = () => {
89+
const { pendingValue } = this.state;
90+
91+
if (pendingValue !== null) {
92+
this.props.input.onChange(pendingValue);
93+
}
94+
95+
this.setState({
96+
isModalOpen: false,
97+
pendingValue: null,
98+
});
99+
};
100+
101+
handleCancel = () => {
102+
this.setState({
103+
isModalOpen: false,
104+
pendingValue: null,
105+
});
106+
};
107+
108+
handleModalOpenChange = (isOpen: boolean) => {
109+
if (!isOpen) {
110+
this.handleCancel();
111+
}
112+
};
113+
114+
render() {
115+
const { disabled, modalType } = this.props;
116+
const { isModalOpen } = this.state;
117+
const isSelected = this.getBooleanValue(this.props.input.value);
118+
119+
// Get modal content based on modalType
120+
const modalContent = modalType ? SECURITY_MODAL_CONTENT[modalType] : null;
121+
122+
return (
123+
<>
124+
<SwitchWrapped data-testid={this.props.input.name}>
125+
<Switch
126+
className="switch-control"
127+
isDisabled={disabled}
128+
isSelected={isSelected}
129+
name={this.props.input.name}
130+
onChange={this.handleSwitchChange}
131+
/>
132+
</SwitchWrapped>
133+
134+
{modalContent && (
135+
<Modal onOpenChange={this.handleModalOpenChange} open={isModalOpen}>
136+
<StyledModalContent data-testid="t--confirmation-modal">
137+
<ModalHeader>{createMessage(modalContent.heading)}</ModalHeader>
138+
<ModalBody>
139+
<Text kind="body-m">
140+
{createMessage(modalContent.description)}
141+
{modalContent.learnMore ? (
142+
<>
143+
{" "}
144+
<Link target="_blank" to={modalContent.learnMore.url}>
145+
{createMessage(modalContent.learnMore.text)}
146+
</Link>
147+
.
148+
</>
149+
) : null}
150+
</Text>
151+
</ModalBody>
152+
<ModalFooter>
153+
<Button
154+
data-testid="t--confirmation-modal-cancel"
155+
kind="secondary"
156+
onClick={this.handleCancel}
157+
size="md"
158+
>
159+
Cancel
160+
</Button>
161+
<Button
162+
data-testid="t--confirmation-modal-confirm"
163+
kind="error"
164+
onClick={this.handleConfirm}
165+
size="md"
166+
>
167+
Disable
168+
</Button>
169+
</ModalFooter>
170+
</StyledModalContent>
171+
</Modal>
172+
)}
173+
</>
174+
);
175+
}
176+
}
177+
178+
class SwitchWithConfirmationControl extends BaseControl<SwitchWithConfirmationControlProps> {
179+
render() {
180+
const { configProperty, disabled, info, isRequired, label, modalType } =
181+
this.props;
182+
183+
return (
184+
<Field
185+
component={SwitchWithConfirmationField}
186+
disabled={disabled}
187+
info={info}
188+
isRequired={isRequired}
189+
label={label}
190+
modalType={modalType}
191+
name={configProperty}
192+
/>
193+
);
194+
}
195+
196+
getControlType(): ControlType {
197+
return "SWITCH_WITH_CONFIRMATION";
198+
}
199+
}
200+
201+
export interface SwitchWithConfirmationControlProps extends ControlProps {
202+
info?: string;
203+
modalType?: SecurityModalType;
204+
}
205+
206+
export default SwitchWithConfirmationControl;

app/client/src/constants/AppsmithActionConstants/formConfig/ApiSettingsConfig.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,10 +41,11 @@ export default [
4141
{
4242
label: "Smart JSON substitution",
4343
configProperty: "actionConfiguration.pluginSpecifiedTemplates[0].value",
44-
controlType: "SWITCH",
44+
controlType: "SWITCH_WITH_CONFIRMATION",
4545
tooltipText:
4646
"Turning on this property fixes the JSON substitution of bindings in API body by adding/removing quotes intelligently and reduces developer errors",
4747
initialValue: true,
48+
modalType: "SMART_SUBSTITUTION",
4849
},
4950
{
5051
label: "Protocol",

0 commit comments

Comments
 (0)