Skip to content

Commit 7f8e3d6

Browse files
Pipeline shows disabled endpoints fix (#2881) (#3282)
# Description of Changes Previously, the dropdown menu in the pipeline configuration displayed all endpoints, including disabled ones, and allowed API calls to them. Changes: - Updated EndpointInterceptor to correctly parse request URIs and match them to corresponding endpoint names in settings.yml, ensuring disabled endpoints are blocked. - Added a new API endpoint in SettingsController to expose the endpointStatus map, allowing the frontend to check which endpoints are disabled. - Updated pipeline.js to use this new API and hide disabled endpoints from the dropdown menu. Tests: - Created a new Docker Compose setup using a custom settings.yml where all endpoints are disabled. - Implemented a test script to run this setup, send API requests to disabled endpoints, and verify they are correctly blocked. [Bug Fix Video](https://youtu.be/L1z3jZh8z8E) Closes #2881 --- ## Checklist ### General - [x] I have read the [Contribution Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md) - [x] I have read the [Stirling-PDF Developer Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md) (if applicable) - [ ] I have read the [How to add new languages to Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/HowToAddNewLanguage.md) (if applicable) - [x] I have performed a self-review of my own code - [x] My changes generate no new warnings ### Documentation - [ ] I have updated relevant docs on [Stirling-PDF's doc repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/) (if functionality has heavily changed) - [ ] I have read the section [Add New Translation Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/HowToAddNewLanguage.md#add-new-translation-tags) (for new translation tags only) ### UI Changes (if applicable) - [ ] Screenshots or videos demonstrating the UI changes are attached (e.g., as comments or direct attachments in the PR) ### Testing (if applicable) - [x] I have tested my changes locally. Refer to the [Testing Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md#6-testing) for more details. --------- Co-authored-by: Anthony Stirling <[email protected]>
1 parent 4e63a68 commit 7f8e3d6

File tree

10 files changed

+501
-8
lines changed

10 files changed

+501
-8
lines changed

Diff for: .github/workflows/build.yml

+1
Original file line numberDiff line numberDiff line change
@@ -141,4 +141,5 @@ jobs:
141141
run: |
142142
chmod +x ./testing/test_webpages.sh
143143
chmod +x ./testing/test.sh
144+
chmod +x ./testing/test_disabledEndpoints.sh
144145
./testing/test.sh
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
2+
services:
3+
stirling-pdf:
4+
container_name: Stirling-PDF-Fat-Disable-Endpoints
5+
image: docker.stirlingpdf.com/stirlingtools/stirling-pdf:latest-fat
6+
deploy:
7+
resources:
8+
limits:
9+
memory: 4G
10+
healthcheck:
11+
test: ["CMD-SHELL", "curl -f http://localhost:8080/api/v1/info/status | grep -q 'UP'"]
12+
interval: 5s
13+
timeout: 10s
14+
retries: 16
15+
ports:
16+
- 8080:8080
17+
volumes:
18+
- ./stirling/latest/data:/usr/share/tessdata:rw
19+
- ./stirling/latest/config:/configs:rw
20+
- ./stirling/latest/logs:/logs:rw
21+
- ../testing/allEndpointsRemovedSettings.yml:/configs/settings.yml:rw
22+
environment:
23+
DOCKER_ENABLE_SECURITY: "true"
24+
SECURITY_ENABLELOGIN: "false"
25+
PUID: 1002
26+
PGID: 1002
27+
UMASK: "022"
28+
SYSTEM_DEFAULTLOCALE: en-US
29+
UI_APPNAME: Stirling-PDF
30+
UI_HOMEDESCRIPTION: Demo site for Stirling-PDF Latest-fat with all Endpoints Disabled
31+
UI_APPNAMENAVBAR: Stirling-PDF Latest-fat
32+
SYSTEM_MAXFILESIZE: "100"
33+
METRICS_ENABLED: "true"
34+
SYSTEM_GOOGLEVISIBILITY: "true"
35+
restart: on-failure:5

Diff for: src/main/java/stirling/software/SPDF/config/EndpointConfiguration.java

+4
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,10 @@ public void disableEndpoint(String endpoint) {
4545
}
4646
}
4747

48+
public Map<String, Boolean> getEndpointStatuses() {
49+
return endpointStatuses;
50+
}
51+
4852
public boolean isEndpointEnabled(String endpoint) {
4953
if (endpoint.startsWith("/")) {
5054
endpoint = endpoint.substring(1);

Diff for: src/main/java/stirling/software/SPDF/config/EndpointInterceptor.java

+23-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,29 @@ public boolean preHandle(
2323
HttpServletRequest request, HttpServletResponse response, Object handler)
2424
throws Exception {
2525
String requestURI = request.getRequestURI();
26-
if (!endpointConfiguration.isEndpointEnabled(requestURI)) {
26+
boolean isEnabled;
27+
28+
// Extract the specific endpoint name (e.g: /api/v1/general/remove-pages -> remove-pages)
29+
if (requestURI.contains("/api/v1") && requestURI.split("/").length > 4) {
30+
31+
String[] requestURIParts = requestURI.split("/");
32+
String requestEndpoint;
33+
34+
// Endpoint: /api/v1/convert/pdf/img becomes pdf-to-img
35+
if ("convert".equals(requestURIParts[3]) && requestURIParts.length > 5) {
36+
requestEndpoint = requestURIParts[4] + "-to-" + requestURIParts[5];
37+
} else {
38+
requestEndpoint = requestURIParts[4];
39+
}
40+
41+
log.debug("Request endpoint: {}", requestEndpoint);
42+
isEnabled = endpointConfiguration.isEndpointEnabled(requestEndpoint);
43+
log.debug("Is endpoint enabled: {}", isEnabled);
44+
} else {
45+
isEnabled = endpointConfiguration.isEndpointEnabled(requestURI);
46+
}
47+
48+
if (!isEnabled) {
2749
response.sendError(HttpServletResponse.SC_FORBIDDEN, "This endpoint is disabled");
2850
return false;
2951
}

Diff for: src/main/java/stirling/software/SPDF/controller/api/SettingsController.java

+14-1
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,20 @@
11
package stirling.software.SPDF.controller.api;
22

33
import java.io.IOException;
4+
import java.util.Map;
45

56
import org.springframework.http.HttpStatus;
67
import org.springframework.http.ResponseEntity;
78
import org.springframework.stereotype.Controller;
9+
import org.springframework.web.bind.annotation.GetMapping;
810
import org.springframework.web.bind.annotation.PostMapping;
911
import org.springframework.web.bind.annotation.RequestBody;
1012
import org.springframework.web.bind.annotation.RequestMapping;
1113

1214
import io.swagger.v3.oas.annotations.Hidden;
1315
import io.swagger.v3.oas.annotations.tags.Tag;
1416

17+
import stirling.software.SPDF.config.EndpointConfiguration;
1518
import stirling.software.SPDF.config.InstallationPathConfig;
1619
import stirling.software.SPDF.model.ApplicationProperties;
1720
import stirling.software.SPDF.utils.GeneralUtils;
@@ -23,9 +26,13 @@
2326
public class SettingsController {
2427

2528
private final ApplicationProperties applicationProperties;
29+
private final EndpointConfiguration endpointConfiguration;
2630

27-
public SettingsController(ApplicationProperties applicationProperties) {
31+
public SettingsController(
32+
ApplicationProperties applicationProperties,
33+
EndpointConfiguration endpointConfiguration) {
2834
this.applicationProperties = applicationProperties;
35+
this.endpointConfiguration = endpointConfiguration;
2936
}
3037

3138
@PostMapping("/update-enable-analytics")
@@ -41,4 +48,10 @@ public ResponseEntity<String> updateApiKey(@RequestBody Boolean enabled) throws
4148
applicationProperties.getSystem().setEnableAnalytics(enabled);
4249
return ResponseEntity.ok("Updated");
4350
}
51+
52+
@GetMapping("/get-endpoints-status")
53+
@Hidden
54+
public ResponseEntity<Map<String, Boolean>> getDisabledEndpoints() {
55+
return ResponseEntity.ok(endpointConfiguration.getEndpointStatuses());
56+
}
4457
}

Diff for: src/main/resources/static/js/pipeline.js

+22-6
Original file line numberDiff line numberDiff line change
@@ -153,22 +153,32 @@ document.getElementById("submitConfigBtn").addEventListener("click", function ()
153153
let apiDocs = {};
154154
let apiSchemas = {};
155155
let operationSettings = {};
156+
let operationStatus = {};
156157

157158
fetchWithCsrf("v1/api-docs")
158159
.then((response) => response.json())
159160
.then((data) => {
160161
apiDocs = data.paths;
161162
apiSchemas = data.components.schemas;
162-
let operationsDropdown = document.getElementById("operationsDropdown");
163+
return fetchWithCsrf("api/v1/settings/get-endpoints-status")
164+
.then((response) => response.json())
165+
.then((data) => {
166+
operationStatus = data;
167+
})
168+
.catch((error) => {
169+
console.error("Error:", error);
170+
});
171+
})
172+
.then(() => {
163173
const ignoreOperations = ["/api/v1/pipeline/handleData", "/api/v1/pipeline/operationToIgnore"]; // Add the operations you want to ignore here
164-
174+
let operationsDropdown = document.getElementById("operationsDropdown");
165175
operationsDropdown.innerHTML = "";
166176

167177
let operationsByTag = {};
168178

169179
// Group operations by tags
170-
Object.keys(data.paths).forEach((operationPath) => {
171-
let operation = data.paths[operationPath].post;
180+
Object.keys(apiDocs).forEach((operationPath) => {
181+
let operation = apiDocs[operationPath].post;
172182
if (!operation || !operation.description) {
173183
console.log(operationPath);
174184
}
@@ -209,13 +219,19 @@ fetchWithCsrf("v1/api-docs")
209219
}
210220
operationPathDisplay = operationPathDisplay.replaceAll(" ", "-");
211221
option.textContent = operationPathDisplay;
212-
option.value = operationPath; // Keep the value with slashes for querying
213-
group.appendChild(option);
222+
223+
if (!(operationPathDisplay in operationStatus)) {
224+
option.value = operationPath; // Keep the value with slashes for querying
225+
group.appendChild(option);
226+
}
214227
});
215228

216229
operationsDropdown.appendChild(group);
217230
}
218231
});
232+
})
233+
.catch((error) => {
234+
console.error("Error:", error);
219235
});
220236

221237
document.getElementById('deletePipelineBtn').addEventListener('click', function(event) {

Diff for: testing/allEndpointsRemovedSettings.yml

+147
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
#############################################################################################################
2+
# Welcome to settings file from #
3+
# ____ _____ ___ ____ _ ___ _ _ ____ ____ ____ _____ #
4+
# / ___|_ _|_ _| _ \| | |_ _| \ | |/ ___| | _ \| _ \| ___| #
5+
# \___ \ | | | || |_) | | | || \| | | _ _____| |_) | | | | |_ #
6+
# ___) || | | || _ <| |___ | || |\ | |_| |_____| __/| |_| | _| #
7+
# |____/ |_| |___|_| \_\_____|___|_| \_|\____| |_| |____/|_| #
8+
# #
9+
# Custom setting.yml file with all endpoints disabled to only be used for testing purposes #
10+
# Do not comment out any entry, it will be removed on next startup #
11+
# If you want to override with environment parameter follow parameter naming SECURITY_INITIALLOGIN_USERNAME #
12+
#############################################################################################################
13+
14+
15+
security:
16+
enableLogin: false # set to 'true' to enable login
17+
csrfDisabled: false # set to 'true' to disable CSRF protection (not recommended for production)
18+
loginAttemptCount: 5 # lock user account after 5 tries; when using e.g. Fail2Ban you can deactivate the function with -1
19+
loginResetTimeMinutes: 120 # lock account for 2 hours after x attempts
20+
loginMethod: all # Accepts values like 'all' and 'normal'(only Login with Username/Password), 'oauth2'(only Login with OAuth2) or 'saml2'(only Login with SAML2)
21+
initialLogin:
22+
username: '' # initial username for the first login
23+
password: '' # initial password for the first login
24+
oauth2:
25+
enabled: false # set to 'true' to enable login (Note: enableLogin must also be 'true' for this to work)
26+
client:
27+
keycloak:
28+
issuer: '' # URL of the Keycloak realm's OpenID Connect Discovery endpoint
29+
clientId: '' # client ID for Keycloak OAuth2
30+
clientSecret: '' # client secret for Keycloak OAuth2
31+
scopes: openid, profile, email # scopes for Keycloak OAuth2
32+
useAsUsername: preferred_username # field to use as the username for Keycloak OAuth2. Available options are: [email | name | given_name | family_name | preferred_name]
33+
google:
34+
clientId: '' # client ID for Google OAuth2
35+
clientSecret: '' # client secret for Google OAuth2
36+
scopes: email, profile # scopes for Google OAuth2
37+
useAsUsername: email # field to use as the username for Google OAuth2. Available options are: [email | name | given_name | family_name]
38+
github:
39+
clientId: '' # client ID for GitHub OAuth2
40+
clientSecret: '' # client secret for GitHub OAuth2
41+
scopes: read:user # scope for GitHub OAuth2
42+
useAsUsername: login # field to use as the username for GitHub OAuth2. Available options are: [email | login | name]
43+
issuer: '' # set to any Provider that supports OpenID Connect Discovery (/.well-known/openid-configuration) endpoint
44+
clientId: '' # client ID from your Provider
45+
clientSecret: '' # client secret from your Provider
46+
autoCreateUser: true # set to 'true' to allow auto-creation of non-existing users
47+
blockRegistration: false # set to 'true' to deny login with SSO without prior registration by an admin
48+
useAsUsername: email # default is 'email'; custom fields can be used as the username
49+
scopes: openid, profile, email # specify the scopes for which the application will request permissions
50+
provider: google # set this to your OAuth Provider's name, e.g., 'google' or 'keycloak'
51+
saml2:
52+
enabled: false # Only enabled for paid enterprise clients (enterpriseEdition.enabled must be true)
53+
provider: '' # The name of your Provider
54+
autoCreateUser: true # set to 'true' to allow auto-creation of non-existing users
55+
blockRegistration: false # set to 'true' to deny login with SSO without prior registration by an admin
56+
registrationId: stirling # The name of your Service Provider (SP) app name. Should match the name in the path for your SSO & SLO URLs
57+
idpMetadataUri: https://dev-XXXXXXXX.okta.com/app/externalKey/sso/saml/metadata # The uri for your Provider's metadata
58+
idpSingleLoginUrl: https://dev-XXXXXXXX.okta.com/app/dev-XXXXXXXX_stirlingpdf_1/externalKey/sso/saml # The URL for initiating SSO. Provided by your Provider
59+
idpSingleLogoutUrl: https://dev-XXXXXXXX.okta.com/app/dev-XXXXXXXX_stirlingpdf_1/externalKey/slo/saml # The URL for initiating SLO. Provided by your Provider
60+
idpIssuer: '' # The ID of your Provider
61+
idpCert: classpath:okta.cert # The certificate your Provider will use to authenticate your app's SAML authentication requests. Provided by your Provider
62+
privateKey: classpath:saml-private-key.key # Your private key. Generated from your keypair
63+
spCert: classpath:saml-public-cert.crt # Your signing certificate. Generated from your keypair
64+
65+
enterpriseEdition:
66+
enabled: false # set to 'true' to enable enterprise edition
67+
key: 00000000-0000-0000-0000-000000000000
68+
SSOAutoLogin: false # Enable to auto login to first provided SSO
69+
CustomMetadata:
70+
autoUpdateMetadata: false # set to 'true' to automatically update metadata with below values
71+
author: username # supports text such as 'John Doe' or types such as username to autopopulate with user's username
72+
creator: Stirling-PDF # supports text such as 'Company-PDF'
73+
producer: Stirling-PDF # supports text such as 'Company-PDF'
74+
75+
legal:
76+
termsAndConditions: https://www.stirlingpdf.com/terms-and-conditions # URL to the terms and conditions of your application (e.g. https://example.com/terms). Empty string to disable or filename to load from local file in static folder
77+
privacyPolicy: https://www.stirlingpdf.com/privacy-policy # URL to the privacy policy of your application (e.g. https://example.com/privacy). Empty string to disable or filename to load from local file in static folder
78+
accessibilityStatement: '' # URL to the accessibility statement of your application (e.g. https://example.com/accessibility). Empty string to disable or filename to load from local file in static folder
79+
cookiePolicy: '' # URL to the cookie policy of your application (e.g. https://example.com/cookie). Empty string to disable or filename to load from local file in static folder
80+
impressum: '' # URL to the impressum of your application (e.g. https://example.com/impressum). Empty string to disable or filename to load from local file in static folder
81+
82+
system:
83+
defaultLocale: en-US # set the default language (e.g. 'de-DE', 'fr-FR', etc)
84+
googlevisibility: false # 'true' to allow Google visibility (via robots.txt), 'false' to disallow
85+
enableAlphaFunctionality: false # set to enable functionality which might need more testing before it fully goes live (this feature might make no changes)
86+
showUpdate: false # see when a new update is available
87+
showUpdateOnlyAdmin: false # only admins can see when a new update is available, depending on showUpdate it must be set to 'true'
88+
customHTMLFiles: false # enable to have files placed in /customFiles/templates override the existing template HTML files
89+
tessdataDir: /usr/share/tessdata # path to the directory containing the Tessdata files. This setting is relevant for Windows systems. For Windows users, this path should be adjusted to point to the appropriate directory where the Tessdata files are stored.
90+
enableAnalytics: true # set to 'true' to enable analytics, set to 'false' to disable analytics; for enterprise users, this is set to true
91+
disableSanitize: false # set to true to disable Sanitize HTML; (can lead to injections in HTML)
92+
datasource:
93+
enableCustomDatabase: false # Enterprise users ONLY, set this property to 'true' if you would like to use your own custom database configuration
94+
customDatabaseUrl: '' # eg jdbc:postgresql://localhost:5432/postgres, set the url for your own custom database connection. If provided, the type, hostName, port and name are not necessary and will not be used
95+
username: postgres # set the database username
96+
password: postgres # set the database password
97+
type: postgresql # the type of the database to set (e.g. 'h2', 'postgresql')
98+
hostName: localhost # the host name to use for the database url. Set to 'localhost' when running the app locally. Set to match the name of the container name of your database container when running the app on a server (Docker configuration)
99+
port: 5432 # set the port number of the database. Ensure this matches the port the database is listening to
100+
name: postgres # set the name of your database. Should match the name of the database you create
101+
customPaths:
102+
pipeline:
103+
watchedFoldersDir: "" #Defaults to /pipeline/watchedFolders
104+
finishedFoldersDir: "" #Defaults to /pipeline/finishedFolders
105+
operations:
106+
weasyprint: "" #Defaults to /opt/venv/bin/weasyprint
107+
unoconvert: "" #Defaults to /opt/venv/bin/unoconvert
108+
109+
110+
111+
ui:
112+
appName: '' # application's visible name
113+
homeDescription: '' # short description or tagline shown on the homepage
114+
appNameNavbar: '' # name displayed on the navigation bar
115+
languages: [] # If empty, all languages are enabled. To display only German and Polish ["de_DE", "pl_PL"]. British English is always enabled.
116+
117+
endpoints: # All the possible endpoints are disabled
118+
toRemove: [crop, merge-pdfs, multi-page-layout, overlay-pdfs, pdf-to-single-page, rearrange-pages, remove-image-pdf, remove-pages, rotate-pdf, scale-pages, split-by-size-or-count, split-pages, split-pdf-by-chapters, split-pdf-by-sections, add-password, add-watermark, auto-redact, cert-sign, get-info-on-pdf, redact, remove-cert-sign, remove-password, sanitize-pdf, validate-signature, file-to-pdf, html-to-pdf, img-to-pdf, markdown-to-pdf, pdf-to-csv, pdf-to-html, pdf-to-img, pdf-to-markdown, pdf-to-pdfa, pdf-to-presentation, pdf-to-text, pdf-to-word, pdf-to-xml, url-to-pdf, add-image, add-page-numbers, add-stamp, auto-rename, auto-split-pdf, compress-pdf, decompress-pdf, extract-image-scans, extract-images, flatten, ocr-pdf, remove-blanks, repair, replace-invert-pdf, show-javascript, update-metadata, filter-contains-image, filter-contains-text, filter-file-size, filter-page-count, filter-page-rotation, filter-page-size] # list endpoints to disable (e.g. ['img-to-pdf', 'remove-pages'])
119+
groupsToRemove: [] # list groups to disable (e.g. ['LibreOffice'])
120+
121+
metrics:
122+
enabled: true # 'true' to enable Info APIs (`/api/*`) endpoints, 'false' to disable
123+
124+
# Automatically Generated Settings (Do Not Edit Directly)
125+
AutomaticallyGenerated:
126+
key: cbb81c0f-50b1-450c-a2b5-89ae527776eb
127+
UUID: 10dd4fba-01fa-4717-9b78-3dc4f54e398a
128+
appVersion: 0.44.3
129+
130+
processExecutor:
131+
sessionLimit: # Process executor instances limits
132+
libreOfficeSessionLimit: 1
133+
pdfToHtmlSessionLimit: 1
134+
qpdfSessionLimit: 4
135+
tesseractSessionLimit: 1
136+
pythonOpenCvSessionLimit: 8
137+
weasyPrintSessionLimit: 16
138+
installAppSessionLimit: 1
139+
calibreSessionLimit: 1
140+
timeoutMinutes: # Process executor timeout in minutes
141+
libreOfficetimeoutMinutes: 30
142+
pdfToHtmltimeoutMinutes: 20
143+
pythonOpenCvtimeoutMinutes: 30
144+
weasyPrinttimeoutMinutes: 30
145+
installApptimeoutMinutes: 60
146+
calibretimeoutMinutes: 30
147+
tesseractTimeoutMinutes: 30

0 commit comments

Comments
 (0)