Compare commits
7 Commits
sso-refact
...
fixPipelin
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ac1b1916d2 | ||
|
|
a36c0415b7 | ||
|
|
dc986b5213 | ||
|
|
efb3b1f234 | ||
|
|
590a3b6cbb | ||
|
|
5deb27cc12 | ||
|
|
f51c3c870a |
2
.github/workflows/PR-Demo-Comment.yml
vendored
2
.github/workflows/PR-Demo-Comment.yml
vendored
@@ -121,7 +121,7 @@ jobs:
|
|||||||
password: ${{ secrets.DOCKER_HUB_API }}
|
password: ${{ secrets.DOCKER_HUB_API }}
|
||||||
|
|
||||||
- name: Build and push PR-specific image
|
- name: Build and push PR-specific image
|
||||||
uses: docker/build-push-action@0adf9959216b96bec444f325f1e493d4aa344497 # v6.14.0
|
uses: docker/build-push-action@ca877d9245402d1537745e0e356eab47c3520991 # v6.13.0
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
file: ./Dockerfile
|
file: ./Dockerfile
|
||||||
|
|||||||
4
.github/workflows/build.yml
vendored
4
.github/workflows/build.yml
vendored
@@ -49,7 +49,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Upload Test Reports
|
- name: Upload Test Reports
|
||||||
if: always()
|
if: always()
|
||||||
uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4.6.1
|
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
|
||||||
with:
|
with:
|
||||||
name: test-reports-jdk-${{ matrix.jdk-version }}
|
name: test-reports-jdk-${{ matrix.jdk-version }}
|
||||||
path: |
|
path: |
|
||||||
@@ -80,7 +80,7 @@ jobs:
|
|||||||
|
|
||||||
- name: FAILED - check the licenses for compatibility
|
- name: FAILED - check the licenses for compatibility
|
||||||
if: failure()
|
if: failure()
|
||||||
uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4.6.1
|
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
|
||||||
with:
|
with:
|
||||||
name: dependencies-without-allowed-license.json
|
name: dependencies-without-allowed-license.json
|
||||||
path: |
|
path: |
|
||||||
|
|||||||
4
.github/workflows/licenses-update.yml
vendored
4
.github/workflows/licenses-update.yml
vendored
@@ -24,7 +24,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Generate GitHub App Token
|
- name: Generate GitHub App Token
|
||||||
id: generate-token
|
id: generate-token
|
||||||
uses: actions/create-github-app-token@0d564482f06ca65fa9e77e2510873638c82206f2 # v1.11.5
|
uses: actions/create-github-app-token@67e27a7eb7db372a1c61a7f9bdab8699e9ee57f7 # v1.11.3
|
||||||
with:
|
with:
|
||||||
app-id: ${{ secrets.GH_APP_ID }}
|
app-id: ${{ secrets.GH_APP_ID }}
|
||||||
private-key: ${{ secrets.GH_APP_PRIVATE_KEY }}
|
private-key: ${{ secrets.GH_APP_PRIVATE_KEY }}
|
||||||
@@ -45,7 +45,7 @@ jobs:
|
|||||||
|
|
||||||
- name: FAILED - check the licenses for compatibility
|
- name: FAILED - check the licenses for compatibility
|
||||||
if: failure()
|
if: failure()
|
||||||
uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4.6.1
|
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
|
||||||
with:
|
with:
|
||||||
name: dependencies-without-allowed-license.json
|
name: dependencies-without-allowed-license.json
|
||||||
path: |
|
path: |
|
||||||
|
|||||||
10
.github/workflows/multiOSReleases.yml
vendored
10
.github/workflows/multiOSReleases.yml
vendored
@@ -80,7 +80,7 @@ jobs:
|
|||||||
mv ./build/libs/Stirling-PDF-${{ needs.read_versions.outputs.version }}.jar ./binaries/Stirling-PDF${{ matrix.file_suffix }}.jar
|
mv ./build/libs/Stirling-PDF-${{ needs.read_versions.outputs.version }}.jar ./binaries/Stirling-PDF${{ matrix.file_suffix }}.jar
|
||||||
|
|
||||||
- name: Upload build artifacts
|
- name: Upload build artifacts
|
||||||
uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4.6.1
|
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
|
||||||
with:
|
with:
|
||||||
retention-days: 1
|
retention-days: 1
|
||||||
if-no-files-found: error
|
if-no-files-found: error
|
||||||
@@ -114,7 +114,7 @@ jobs:
|
|||||||
run: ls -R
|
run: ls -R
|
||||||
|
|
||||||
- name: Upload signed artifacts
|
- name: Upload signed artifacts
|
||||||
uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4.6.1
|
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
|
||||||
with:
|
with:
|
||||||
retention-days: 1
|
retention-days: 1
|
||||||
if-no-files-found: error
|
if-no-files-found: error
|
||||||
@@ -188,7 +188,7 @@ jobs:
|
|||||||
run: ls -R ./binaries
|
run: ls -R ./binaries
|
||||||
|
|
||||||
- name: Upload build artifacts
|
- name: Upload build artifacts
|
||||||
uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4.6.1
|
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
|
||||||
with:
|
with:
|
||||||
retention-days: 1
|
retention-days: 1
|
||||||
if-no-files-found: error
|
if-no-files-found: error
|
||||||
@@ -224,7 +224,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Install Cosign
|
- name: Install Cosign
|
||||||
if: matrix.os == 'windows-latest'
|
if: matrix.os == 'windows-latest'
|
||||||
uses: sigstore/cosign-installer@d7d6bc7722e3daa8354c50bcb52f4837da5e9b6a # v3.8.1
|
uses: sigstore/cosign-installer@c56c2d3e59e4281cc41dea2217323ba5694b171e # v3.8.0
|
||||||
|
|
||||||
- name: Generate key pair
|
- name: Generate key pair
|
||||||
if: matrix.os == 'windows-latest'
|
if: matrix.os == 'windows-latest'
|
||||||
@@ -255,7 +255,7 @@ jobs:
|
|||||||
run: ls -R
|
run: ls -R
|
||||||
|
|
||||||
- name: Upload signed artifacts
|
- name: Upload signed artifacts
|
||||||
uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4.6.1
|
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
|
||||||
with:
|
with:
|
||||||
retention-days: 1
|
retention-days: 1
|
||||||
if-no-files-found: error
|
if-no-files-found: error
|
||||||
|
|||||||
2
.github/workflows/pre_commit.yml
vendored
2
.github/workflows/pre_commit.yml
vendored
@@ -22,7 +22,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Generate GitHub App Token
|
- name: Generate GitHub App Token
|
||||||
id: generate-token
|
id: generate-token
|
||||||
uses: actions/create-github-app-token@0d564482f06ca65fa9e77e2510873638c82206f2 # v1.11.5
|
uses: actions/create-github-app-token@67e27a7eb7db372a1c61a7f9bdab8699e9ee57f7 # v1.11.3
|
||||||
with:
|
with:
|
||||||
app-id: ${{ secrets.GH_APP_ID }}
|
app-id: ${{ secrets.GH_APP_ID }}
|
||||||
private-key: ${{ secrets.GH_APP_PRIVATE_KEY }}
|
private-key: ${{ secrets.GH_APP_PRIVATE_KEY }}
|
||||||
|
|||||||
8
.github/workflows/push-docker.yml
vendored
8
.github/workflows/push-docker.yml
vendored
@@ -42,7 +42,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Install cosign
|
- name: Install cosign
|
||||||
if: github.ref == 'refs/heads/master'
|
if: github.ref == 'refs/heads/master'
|
||||||
uses: sigstore/cosign-installer@d7d6bc7722e3daa8354c50bcb52f4837da5e9b6a # v3.8.1
|
uses: sigstore/cosign-installer@c56c2d3e59e4281cc41dea2217323ba5694b171e # v3.8.0
|
||||||
with:
|
with:
|
||||||
cosign-release: "v2.4.1"
|
cosign-release: "v2.4.1"
|
||||||
|
|
||||||
@@ -90,7 +90,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Build and push main Dockerfile
|
- name: Build and push main Dockerfile
|
||||||
id: build-push-regular
|
id: build-push-regular
|
||||||
uses: docker/build-push-action@0adf9959216b96bec444f325f1e493d4aa344497 # v6.14.0
|
uses: docker/build-push-action@ca877d9245402d1537745e0e356eab47c3520991 # v6.13.0
|
||||||
with:
|
with:
|
||||||
builder: ${{ steps.buildx.outputs.name }}
|
builder: ${{ steps.buildx.outputs.name }}
|
||||||
context: .
|
context: .
|
||||||
@@ -135,7 +135,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Build and push Dockerfile-ultra-lite
|
- name: Build and push Dockerfile-ultra-lite
|
||||||
id: build-push-lite
|
id: build-push-lite
|
||||||
uses: docker/build-push-action@0adf9959216b96bec444f325f1e493d4aa344497 # v6.14.0
|
uses: docker/build-push-action@ca877d9245402d1537745e0e356eab47c3520991 # v6.13.0
|
||||||
if: github.ref != 'refs/heads/main'
|
if: github.ref != 'refs/heads/main'
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
@@ -166,7 +166,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Build and push main Dockerfile fat
|
- name: Build and push main Dockerfile fat
|
||||||
id: build-push-fat
|
id: build-push-fat
|
||||||
uses: docker/build-push-action@0adf9959216b96bec444f325f1e493d4aa344497 # v6.14.0
|
uses: docker/build-push-action@ca877d9245402d1537745e0e356eab47c3520991 # v6.13.0
|
||||||
if: github.ref != 'refs/heads/main'
|
if: github.ref != 'refs/heads/main'
|
||||||
with:
|
with:
|
||||||
builder: ${{ steps.buildx.outputs.name }}
|
builder: ${{ steps.buildx.outputs.name }}
|
||||||
|
|||||||
6
.github/workflows/releaseArtifacts.yml
vendored
6
.github/workflows/releaseArtifacts.yml
vendored
@@ -63,7 +63,7 @@ jobs:
|
|||||||
ls -R ./build/launch4j
|
ls -R ./build/launch4j
|
||||||
|
|
||||||
- name: Upload build artifacts
|
- name: Upload build artifacts
|
||||||
uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4.6.1
|
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
|
||||||
with:
|
with:
|
||||||
name: binaries${{ matrix.file_suffix }}
|
name: binaries${{ matrix.file_suffix }}
|
||||||
path: |
|
path: |
|
||||||
@@ -95,7 +95,7 @@ jobs:
|
|||||||
run: ls -R
|
run: ls -R
|
||||||
|
|
||||||
- name: Install Cosign
|
- name: Install Cosign
|
||||||
uses: sigstore/cosign-installer@d7d6bc7722e3daa8354c50bcb52f4837da5e9b6a # v3.8.1
|
uses: sigstore/cosign-installer@c56c2d3e59e4281cc41dea2217323ba5694b171e # v3.8.0
|
||||||
|
|
||||||
- name: Generate key pair
|
- name: Generate key pair
|
||||||
run: cosign generate-key-pair
|
run: cosign generate-key-pair
|
||||||
@@ -139,7 +139,7 @@ jobs:
|
|||||||
./launch4j/Stirling-PDF-Server${{ matrix.file_suffix }}.exe
|
./launch4j/Stirling-PDF-Server${{ matrix.file_suffix }}.exe
|
||||||
|
|
||||||
- name: Upload signed artifacts
|
- name: Upload signed artifacts
|
||||||
uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4.6.1
|
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
|
||||||
with:
|
with:
|
||||||
name: signed${{ matrix.file_suffix }}
|
name: signed${{ matrix.file_suffix }}
|
||||||
path: |
|
path: |
|
||||||
|
|||||||
6
.github/workflows/scorecards.yml
vendored
6
.github/workflows/scorecards.yml
vendored
@@ -44,7 +44,7 @@ jobs:
|
|||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
|
||||||
- name: "Run analysis"
|
- name: "Run analysis"
|
||||||
uses: ossf/scorecard-action@f49aabe0b5af0936a0987cfb85d86b75731b0186 # v2.4.1
|
uses: ossf/scorecard-action@62b2cac7ed8198b15735ed49ab1e5cf35480ba46 # v2.4.0
|
||||||
with:
|
with:
|
||||||
results_file: results.sarif
|
results_file: results.sarif
|
||||||
results_format: sarif
|
results_format: sarif
|
||||||
@@ -66,7 +66,7 @@ jobs:
|
|||||||
# Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF
|
# Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF
|
||||||
# format to the repository Actions tab.
|
# format to the repository Actions tab.
|
||||||
- name: "Upload artifact"
|
- name: "Upload artifact"
|
||||||
uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4.6.1
|
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
|
||||||
with:
|
with:
|
||||||
name: SARIF file
|
name: SARIF file
|
||||||
path: results.sarif
|
path: results.sarif
|
||||||
@@ -74,6 +74,6 @@ jobs:
|
|||||||
|
|
||||||
# Upload the results to GitHub's code scanning dashboard.
|
# Upload the results to GitHub's code scanning dashboard.
|
||||||
- name: "Upload to code-scanning"
|
- name: "Upload to code-scanning"
|
||||||
uses: github/codeql-action/upload-sarif@b56ba49b26e50535fa1e7f7db0f4f7b4bf65d80d # v3.28.10
|
uses: github/codeql-action/upload-sarif@9e8d0789d4a0fa9ceb6b1738f7e269594bdd67f0 # v3.28.9
|
||||||
with:
|
with:
|
||||||
sarif_file: results.sarif
|
sarif_file: results.sarif
|
||||||
|
|||||||
4
.github/workflows/sonarqube.yml
vendored
4
.github/workflows/sonarqube.yml
vendored
@@ -46,7 +46,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Upload Problems Report on Failure
|
- name: Upload Problems Report on Failure
|
||||||
if: failure()
|
if: failure()
|
||||||
uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4.6.1
|
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
|
||||||
with:
|
with:
|
||||||
name: gradle-problems-report
|
name: gradle-problems-report
|
||||||
path: build/reports/problems/problems-report.html
|
path: build/reports/problems/problems-report.html
|
||||||
@@ -54,7 +54,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Upload Sonar Logs on Failure
|
- name: Upload Sonar Logs on Failure
|
||||||
if: failure()
|
if: failure()
|
||||||
uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4.6.1
|
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
|
||||||
with:
|
with:
|
||||||
name: sonar-logs
|
name: sonar-logs
|
||||||
path: |
|
path: |
|
||||||
|
|||||||
4
.github/workflows/sync_files.yml
vendored
4
.github/workflows/sync_files.yml
vendored
@@ -30,7 +30,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Generate GitHub App Token
|
- name: Generate GitHub App Token
|
||||||
id: generate-token
|
id: generate-token
|
||||||
uses: actions/create-github-app-token@0d564482f06ca65fa9e77e2510873638c82206f2 # v1.11.5
|
uses: actions/create-github-app-token@67e27a7eb7db372a1c61a7f9bdab8699e9ee57f7 # v1.11.3
|
||||||
with:
|
with:
|
||||||
app-id: ${{ secrets.GH_APP_ID }}
|
app-id: ${{ secrets.GH_APP_ID }}
|
||||||
private-key: ${{ secrets.GH_APP_PRIVATE_KEY }}
|
private-key: ${{ secrets.GH_APP_PRIVATE_KEY }}
|
||||||
@@ -63,7 +63,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Generate GitHub App Token
|
- name: Generate GitHub App Token
|
||||||
id: generate-token
|
id: generate-token
|
||||||
uses: actions/create-github-app-token@0d564482f06ca65fa9e77e2510873638c82206f2 # v1.11.5
|
uses: actions/create-github-app-token@67e27a7eb7db372a1c61a7f9bdab8699e9ee57f7 # v1.11.3
|
||||||
with:
|
with:
|
||||||
app-id: ${{ vars.GH_APP_ID }}
|
app-id: ${{ vars.GH_APP_ID }}
|
||||||
private-key: ${{ secrets.GH_APP_PRIVATE_KEY }}
|
private-key: ${{ secrets.GH_APP_PRIVATE_KEY }}
|
||||||
|
|||||||
2
.github/workflows/testdriver.yml
vendored
2
.github/workflows/testdriver.yml
vendored
@@ -46,7 +46,7 @@ jobs:
|
|||||||
password: ${{ secrets.DOCKER_HUB_API }}
|
password: ${{ secrets.DOCKER_HUB_API }}
|
||||||
|
|
||||||
- name: Build and push test image
|
- name: Build and push test image
|
||||||
uses: docker/build-push-action@0adf9959216b96bec444f325f1e493d4aa344497 # v6.14.0
|
uses: docker/build-push-action@ca877d9245402d1537745e0e356eab47c3520991 # v6.13.0
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
file: ./Dockerfile
|
file: ./Dockerfile
|
||||||
|
|||||||
@@ -9,8 +9,8 @@ COPY . .
|
|||||||
|
|
||||||
# Build the application with DOCKER_ENABLE_SECURITY=false
|
# Build the application with DOCKER_ENABLE_SECURITY=false
|
||||||
RUN DOCKER_ENABLE_SECURITY=true \
|
RUN DOCKER_ENABLE_SECURITY=true \
|
||||||
STIRLING_PDF_DESKTOP_UI=false \
|
STIRLING_PDF_DESKTOP_UI=false \
|
||||||
./gradlew clean build
|
./gradlew clean build
|
||||||
|
|
||||||
# Main stage
|
# Main stage
|
||||||
FROM alpine:3.21.3@sha256:a8560b36e8b8210634f77d9f7f9efd7ffa463e380b75e2e74aff4511df3ef88c
|
FROM alpine:3.21.3@sha256:a8560b36e8b8210634f77d9f7f9efd7ffa463e380b75e2e74aff4511df3ef88c
|
||||||
@@ -50,29 +50,29 @@ RUN echo "@main https://dl-cdn.alpinelinux.org/alpine/edge/main" | tee -a /etc/a
|
|||||||
echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/testing" | tee -a /etc/apk/repositories && \
|
echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/testing" | tee -a /etc/apk/repositories && \
|
||||||
apk upgrade --no-cache -a && \
|
apk upgrade --no-cache -a && \
|
||||||
apk add --no-cache \
|
apk add --no-cache \
|
||||||
ca-certificates \
|
ca-certificates \
|
||||||
tzdata \
|
tzdata \
|
||||||
tini \
|
tini \
|
||||||
bash \
|
bash \
|
||||||
curl \
|
curl \
|
||||||
shadow \
|
shadow \
|
||||||
su-exec \
|
su-exec \
|
||||||
openssl \
|
openssl \
|
||||||
openssl-dev \
|
openssl-dev \
|
||||||
openjdk21-jre \
|
openjdk21-jre \
|
||||||
# Doc conversion
|
# Doc conversion
|
||||||
gcompat \
|
gcompat \
|
||||||
libc6-compat \
|
libc6-compat \
|
||||||
libreoffice \
|
libreoffice \
|
||||||
# pdftohtml
|
# pdftohtml
|
||||||
poppler-utils \
|
poppler-utils \
|
||||||
# OCR MY PDF (unpaper for descew and other advanced featues)
|
# OCR MY PDF (unpaper for descew and other advanced featues)
|
||||||
qpdf \
|
qpdf \
|
||||||
tesseract-ocr-data-eng \
|
tesseract-ocr-data-eng \
|
||||||
|
|
||||||
font-terminus font-dejavu font-noto font-noto-cjk font-awesome font-noto-extra font-liberation font-linux-libertine \
|
font-terminus font-dejavu font-noto font-noto-cjk font-awesome font-noto-extra \
|
||||||
# CV
|
# CV
|
||||||
py3-opencv \
|
py3-opencv \
|
||||||
python3 \
|
python3 \
|
||||||
py3-pip \
|
py3-pip \
|
||||||
py3-pillow@testing \
|
py3-pillow@testing \
|
||||||
@@ -89,7 +89,7 @@ RUN echo "@main https://dl-cdn.alpinelinux.org/alpine/edge/main" | tee -a /etc/a
|
|||||||
fc-cache -f -v && \
|
fc-cache -f -v && \
|
||||||
chmod +x /scripts/* && \
|
chmod +x /scripts/* && \
|
||||||
chmod +x /scripts/init.sh && \
|
chmod +x /scripts/init.sh && \
|
||||||
# User permissions
|
# User permissions
|
||||||
addgroup -S stirlingpdfgroup && adduser -S stirlingpdfuser -G stirlingpdfgroup && \
|
addgroup -S stirlingpdfgroup && adduser -S stirlingpdfuser -G stirlingpdfgroup && \
|
||||||
chown -R stirlingpdfuser:stirlingpdfgroup $HOME /scripts /usr/share/fonts/opentype/noto /configs /customFiles /pipeline && \
|
chown -R stirlingpdfuser:stirlingpdfgroup $HOME /scripts /usr/share/fonts/opentype/noto /configs /customFiles /pipeline && \
|
||||||
chown stirlingpdfuser:stirlingpdfgroup /app.jar
|
chown stirlingpdfuser:stirlingpdfgroup /app.jar
|
||||||
|
|||||||
@@ -148,11 +148,11 @@ Stirling-PDF currently supports 39 languages!
|
|||||||
| Simplified Chinese (简体中文) (zh_CN) |  |
|
| Simplified Chinese (简体中文) (zh_CN) |  |
|
||||||
| Slovakian (Slovensky) (sk_SK) |  |
|
| Slovakian (Slovensky) (sk_SK) |  |
|
||||||
| Slovenian (Slovenščina) (sl_SI) |  |
|
| Slovenian (Slovenščina) (sl_SI) |  |
|
||||||
| Spanish (Español) (es_ES) |  |
|
| Spanish (Español) (es_ES) |  |
|
||||||
| Swedish (Svenska) (sv_SE) |  |
|
| Swedish (Svenska) (sv_SE) |  |
|
||||||
| Thai (ไทย) (th_TH) |  |
|
| Thai (ไทย) (th_TH) |  |
|
||||||
| Tibetan (བོད་ཡིག་) (zh_BO) |  |
|
| Tibetan (བོད་ཡིག་) (zh_BO) |  |
|
||||||
| Traditional Chinese (繁體中文) (zh_TW) |  |
|
| Traditional Chinese (繁體中文) (zh_TW) |  |
|
||||||
| Turkish (Türkçe) (tr_TR) |  |
|
| Turkish (Türkçe) (tr_TR) |  |
|
||||||
| Ukrainian (Українська) (uk_UA) |  |
|
| Ukrainian (Українська) (uk_UA) |  |
|
||||||
| Vietnamese (Tiếng Việt) (vi_VN) |  |
|
| Vietnamese (Tiếng Việt) (vi_VN) |  |
|
||||||
|
|||||||
15
build.gradle
15
build.gradle
@@ -1,6 +1,6 @@
|
|||||||
plugins {
|
plugins {
|
||||||
id "java"
|
id "java"
|
||||||
id "org.springframework.boot" version "3.4.3"
|
id "org.springframework.boot" version "3.4.1"
|
||||||
id "io.spring.dependency-management" version "1.1.7"
|
id "io.spring.dependency-management" version "1.1.7"
|
||||||
id "org.springdoc.openapi-gradle-plugin" version "1.8.0"
|
id "org.springdoc.openapi-gradle-plugin" version "1.8.0"
|
||||||
id "io.swagger.swaggerhub" version "1.3.2"
|
id "io.swagger.swaggerhub" version "1.3.2"
|
||||||
@@ -15,12 +15,12 @@ plugins {
|
|||||||
import com.github.jk1.license.render.*
|
import com.github.jk1.license.render.*
|
||||||
|
|
||||||
ext {
|
ext {
|
||||||
springBootVersion = "3.4.3"
|
springBootVersion = "3.4.2"
|
||||||
pdfboxVersion = "3.0.4"
|
pdfboxVersion = "3.0.4"
|
||||||
imageioVersion = "3.12.0"
|
imageioVersion = "3.12.0"
|
||||||
lombokVersion = "1.18.36"
|
lombokVersion = "1.18.36"
|
||||||
bouncycastleVersion = "1.80"
|
bouncycastleVersion = "1.80"
|
||||||
springSecuritySamlVersion = "6.4.3"
|
springSecuritySamlVersion = "6.4.2"
|
||||||
openSamlVersion = "4.3.2"
|
openSamlVersion = "4.3.2"
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -294,8 +294,8 @@ configurations.all {
|
|||||||
dependencies {
|
dependencies {
|
||||||
|
|
||||||
//tmp for security bumps
|
//tmp for security bumps
|
||||||
implementation 'ch.qos.logback:logback-core:1.5.16'
|
implementation 'ch.qos.logback:logback-core:1.5.15'
|
||||||
implementation 'ch.qos.logback:logback-classic:1.5.16'
|
implementation 'ch.qos.logback:logback-classic:1.5.15'
|
||||||
|
|
||||||
|
|
||||||
// Exclude vulnerable BouncyCastle version used in tableau
|
// Exclude vulnerable BouncyCastle version used in tableau
|
||||||
@@ -306,7 +306,8 @@ dependencies {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (System.getenv("STIRLING_PDF_DESKTOP_UI") != "false") {
|
if (System.getenv("STIRLING_PDF_DESKTOP_UI") != "false") {
|
||||||
implementation "me.friwi:jcefmaven:132.3.1"
|
implementation 'org.apache.commons:commons-compress:1.26.0'
|
||||||
|
implementation "me.friwi:jcefmaven:127.3.1"
|
||||||
implementation "org.openjfx:javafx-controls:21"
|
implementation "org.openjfx:javafx-controls:21"
|
||||||
implementation "org.openjfx:javafx-swing:21"
|
implementation "org.openjfx:javafx-swing:21"
|
||||||
}
|
}
|
||||||
@@ -331,7 +332,7 @@ dependencies {
|
|||||||
implementation "org.springframework.boot:spring-boot-starter-data-jpa:$springBootVersion"
|
implementation "org.springframework.boot:spring-boot-starter-data-jpa:$springBootVersion"
|
||||||
implementation "org.springframework.boot:spring-boot-starter-oauth2-client:$springBootVersion"
|
implementation "org.springframework.boot:spring-boot-starter-oauth2-client:$springBootVersion"
|
||||||
|
|
||||||
implementation "org.springframework.session:spring-session-core:3.4.2"
|
implementation "org.springframework.session:spring-session-core:3.4.1"
|
||||||
implementation "org.springframework:spring-jdbc:6.2.3"
|
implementation "org.springframework:spring-jdbc:6.2.3"
|
||||||
|
|
||||||
implementation 'com.unboundid.product.scim2:scim2-sdk-client:2.3.5'
|
implementation 'com.unboundid.product.scim2:scim2-sdk-client:2.3.5'
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ import stirling.software.SPDF.utils.GeneralUtils;
|
|||||||
@Service
|
@Service
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class KeygenLicenseVerifier {
|
public class KeygenLicenseVerifier {
|
||||||
// todo: place in config files?
|
|
||||||
private static final String ACCOUNT_ID = "e5430f69-e834-4ae4-befd-b602aae5f372";
|
private static final String ACCOUNT_ID = "e5430f69-e834-4ae4-befd-b602aae5f372";
|
||||||
private static final String BASE_URL = "https://api.keygen.sh/v1/accounts";
|
private static final String BASE_URL = "https://api.keygen.sh/v1/accounts";
|
||||||
private static final ObjectMapper objectMapper = new ObjectMapper();
|
private static final ObjectMapper objectMapper = new ObjectMapper();
|
||||||
@@ -69,7 +68,7 @@ public class KeygenLicenseVerifier {
|
|||||||
|
|
||||||
return false;
|
return false;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("Error verifying license: {}", e.getMessage());
|
log.error("Error verifying license: " + e.getMessage());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -96,9 +95,10 @@ public class KeygenLicenseVerifier {
|
|||||||
.build();
|
.build();
|
||||||
|
|
||||||
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
|
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
|
||||||
log.debug("ValidateLicenseResponse body: {}", response.body());
|
log.debug(" validateLicenseResponse body: " + response.body());
|
||||||
JsonNode jsonResponse = objectMapper.readTree(response.body());
|
JsonNode jsonResponse = objectMapper.readTree(response.body());
|
||||||
if (response.statusCode() == 200) {
|
if (response.statusCode() == 200) {
|
||||||
|
|
||||||
JsonNode metaNode = jsonResponse.path("meta");
|
JsonNode metaNode = jsonResponse.path("meta");
|
||||||
boolean isValid = metaNode.path("valid").asBoolean();
|
boolean isValid = metaNode.path("valid").asBoolean();
|
||||||
|
|
||||||
@@ -120,7 +120,7 @@ public class KeygenLicenseVerifier {
|
|||||||
log.info(applicationProperties.toString());
|
log.info(applicationProperties.toString());
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
log.error("Error validating license. Status code: {}", response.statusCode());
|
log.error("Error validating license. Status code: " + response.statusCode());
|
||||||
}
|
}
|
||||||
return jsonResponse;
|
return jsonResponse;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package stirling.software.SPDF;
|
package stirling.software.SPDF;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.net.ServerSocket;
|
||||||
import java.net.URISyntaxException;
|
import java.net.URISyntaxException;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
@@ -28,7 +29,6 @@ import stirling.software.SPDF.UI.WebBrowser;
|
|||||||
import stirling.software.SPDF.config.ConfigInitializer;
|
import stirling.software.SPDF.config.ConfigInitializer;
|
||||||
import stirling.software.SPDF.config.InstallationPathConfig;
|
import stirling.software.SPDF.config.InstallationPathConfig;
|
||||||
import stirling.software.SPDF.model.ApplicationProperties;
|
import stirling.software.SPDF.model.ApplicationProperties;
|
||||||
import stirling.software.SPDF.utils.UrlUtils;
|
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@EnableScheduling
|
@EnableScheduling
|
||||||
@@ -64,12 +64,6 @@ public class SPDFApplication {
|
|||||||
app.setHeadless(false);
|
app.setHeadless(false);
|
||||||
props.put("java.awt.headless", "false");
|
props.put("java.awt.headless", "false");
|
||||||
props.put("spring.main.web-application-type", "servlet");
|
props.put("spring.main.web-application-type", "servlet");
|
||||||
|
|
||||||
int desiredPort = 8080;
|
|
||||||
String port = UrlUtils.findAvailablePort(desiredPort);
|
|
||||||
props.put("server.port", port);
|
|
||||||
System.setProperty("server.port", port);
|
|
||||||
log.info("Desktop UI mode: Using port {}", port);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
app.setAdditionalProfiles(getActiveProfile(args));
|
app.setAdditionalProfiles(getActiveProfile(args));
|
||||||
@@ -166,17 +160,7 @@ public class SPDFApplication {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Value("${server.port:8080}")
|
@Value("${server.port:8080}")
|
||||||
public void setServerPort(String port) {
|
public void setServerPortStatic(String port) {
|
||||||
if ("auto".equalsIgnoreCase(port)) {
|
|
||||||
// Use Spring Boot's automatic port assignment (server.port=0)
|
|
||||||
SPDFApplication.serverPortStatic =
|
|
||||||
"0"; // This will let Spring Boot assign an available port
|
|
||||||
} else {
|
|
||||||
SPDFApplication.serverPortStatic = port;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void setServerPortStatic(String port) {
|
|
||||||
if ("auto".equalsIgnoreCase(port)) {
|
if ("auto".equalsIgnoreCase(port)) {
|
||||||
// Use Spring Boot's automatic port assignment (server.port=0)
|
// Use Spring Boot's automatic port assignment (server.port=0)
|
||||||
SPDFApplication.serverPortStatic =
|
SPDFApplication.serverPortStatic =
|
||||||
@@ -213,11 +197,36 @@ public class SPDFApplication {
|
|||||||
return new String[] {"default"};
|
return new String[] {"default"};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static boolean isPortAvailable(int port) {
|
||||||
|
try (ServerSocket socket = new ServerSocket(port)) {
|
||||||
|
return true;
|
||||||
|
} catch (IOException e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Optionally keep this method if you want to provide a manual port-incrementation fallback.
|
||||||
|
private static String findAvailablePort(int startPort) {
|
||||||
|
int port = startPort;
|
||||||
|
while (!isPortAvailable(port)) {
|
||||||
|
port++;
|
||||||
|
}
|
||||||
|
return String.valueOf(port);
|
||||||
|
}
|
||||||
|
|
||||||
public static String getStaticBaseUrl() {
|
public static String getStaticBaseUrl() {
|
||||||
return baseUrlStatic;
|
return baseUrlStatic;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getNonStaticBaseUrl() {
|
||||||
|
return baseUrlStatic;
|
||||||
|
}
|
||||||
|
|
||||||
public static String getStaticPort() {
|
public static String getStaticPort() {
|
||||||
return serverPortStatic;
|
return serverPortStatic;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getNonStaticPort() {
|
||||||
|
return serverPortStatic;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,7 +35,10 @@ public class AppConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
@ConditionalOnProperty(name = "system.customHTMLFiles", havingValue = "true")
|
@ConditionalOnProperty(
|
||||||
|
name = "system.customHTMLFiles",
|
||||||
|
havingValue = "true",
|
||||||
|
matchIfMissing = false)
|
||||||
public SpringTemplateEngine templateEngine(ResourceLoader resourceLoader) {
|
public SpringTemplateEngine templateEngine(ResourceLoader resourceLoader) {
|
||||||
SpringTemplateEngine templateEngine = new SpringTemplateEngine();
|
SpringTemplateEngine templateEngine = new SpringTemplateEngine();
|
||||||
templateEngine.addTemplateResolver(new FileFallbackTemplateResolver(resourceLoader));
|
templateEngine.addTemplateResolver(new FileFallbackTemplateResolver(resourceLoader));
|
||||||
@@ -126,8 +129,8 @@ public class AppConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@ConditionalOnMissingClass("stirling.software.SPDF.config.security.SecurityConfiguration")
|
@ConditionalOnMissingClass("stirling.software.SPDF.config.security.SecurityConfiguration")
|
||||||
@Bean(name = "activeSecurity")
|
@Bean(name = "activSecurity")
|
||||||
public boolean missingActiveSecurity() {
|
public boolean missingActivSecurity() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,13 +2,13 @@ package stirling.software.SPDF.config;
|
|||||||
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
|
||||||
import org.springframework.context.annotation.Scope;
|
import org.springframework.context.annotation.Scope;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
import stirling.software.SPDF.config.interfaces.ShowAdminInterface;
|
import stirling.software.SPDF.config.interfaces.ShowAdminInterface;
|
||||||
import stirling.software.SPDF.model.ApplicationProperties;
|
import stirling.software.SPDF.model.ApplicationProperties;
|
||||||
|
|
||||||
@Configuration
|
@Service
|
||||||
class AppUpdateService {
|
class AppUpdateService {
|
||||||
|
|
||||||
private final ApplicationProperties applicationProperties;
|
private final ApplicationProperties applicationProperties;
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ public class CleanUrlInterceptor implements HandlerInterceptor {
|
|||||||
"endpoints",
|
"endpoints",
|
||||||
"logout",
|
"logout",
|
||||||
"error",
|
"error",
|
||||||
"errorOAuth",
|
"erroroauth",
|
||||||
"file",
|
"file",
|
||||||
"messageType",
|
"messageType",
|
||||||
"infoMessage");
|
"infoMessage");
|
||||||
|
|||||||
@@ -62,6 +62,7 @@ public class RuntimePathConfig {
|
|||||||
String weasyPrintPath = isDocker ? "/opt/venv/bin/weasyprint" : "weasyprint";
|
String weasyPrintPath = isDocker ? "/opt/venv/bin/weasyprint" : "weasyprint";
|
||||||
String unoConvertPath = isDocker ? "/opt/venv/bin/unoconvert" : "unoconvert";
|
String unoConvertPath = isDocker ? "/opt/venv/bin/unoconvert" : "unoconvert";
|
||||||
|
|
||||||
|
|
||||||
// Check for custom operation paths
|
// Check for custom operation paths
|
||||||
Operations operations = properties.getSystem().getCustomPaths().getOperations();
|
Operations operations = properties.getSystem().getCustomPaths().getOperations();
|
||||||
if (operations != null) {
|
if (operations != null) {
|
||||||
@@ -81,4 +82,5 @@ public class RuntimePathConfig {
|
|||||||
private boolean isRunningInDocker() {
|
private boolean isRunningInDocker() {
|
||||||
return Files.exists(Paths.get("/.dockerenv"));
|
return Files.exists(Paths.get("/.dockerenv"));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ package stirling.software.SPDF.config.interfaces;
|
|||||||
import java.sql.SQLException;
|
import java.sql.SQLException;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import stirling.software.SPDF.model.exception.UnsupportedProviderException;
|
import stirling.software.SPDF.model.provider.UnsupportedProviderException;
|
||||||
import stirling.software.SPDF.utils.FileInfo;
|
import stirling.software.SPDF.utils.FileInfo;
|
||||||
|
|
||||||
public interface DatabaseInterface {
|
public interface DatabaseInterface {
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ public class CustomAuthenticationFailureHandler extends SimpleUrlAuthenticationF
|
|||||||
}
|
}
|
||||||
if (exception instanceof BadCredentialsException
|
if (exception instanceof BadCredentialsException
|
||||||
|| exception instanceof UsernameNotFoundException) {
|
|| exception instanceof UsernameNotFoundException) {
|
||||||
getRedirectStrategy().sendRedirect(request, response, "/login?error=badCredentials");
|
getRedirectStrategy().sendRedirect(request, response, "/login?error=badcredentials");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (exception instanceof InternalAuthenticationServiceException
|
if (exception instanceof InternalAuthenticationServiceException
|
||||||
|
|||||||
@@ -14,8 +14,8 @@ import org.springframework.security.saml2.provider.service.authentication.Saml2A
|
|||||||
import org.springframework.security.web.authentication.logout.SimpleUrlLogoutSuccessHandler;
|
import org.springframework.security.web.authentication.logout.SimpleUrlLogoutSuccessHandler;
|
||||||
|
|
||||||
import com.coveo.saml.SamlClient;
|
import com.coveo.saml.SamlClient;
|
||||||
import com.coveo.saml.SamlException;
|
|
||||||
|
|
||||||
|
import jakarta.servlet.ServletException;
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
import jakarta.servlet.http.HttpServletResponse;
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
@@ -28,43 +28,62 @@ import stirling.software.SPDF.config.security.saml2.CustomSaml2AuthenticatedPrin
|
|||||||
import stirling.software.SPDF.model.ApplicationProperties;
|
import stirling.software.SPDF.model.ApplicationProperties;
|
||||||
import stirling.software.SPDF.model.ApplicationProperties.Security.OAUTH2;
|
import stirling.software.SPDF.model.ApplicationProperties.Security.OAUTH2;
|
||||||
import stirling.software.SPDF.model.ApplicationProperties.Security.SAML2;
|
import stirling.software.SPDF.model.ApplicationProperties.Security.SAML2;
|
||||||
import stirling.software.SPDF.model.provider.KeycloakProvider;
|
import stirling.software.SPDF.model.Provider;
|
||||||
|
import stirling.software.SPDF.model.provider.UnsupportedProviderException;
|
||||||
import stirling.software.SPDF.utils.UrlUtils;
|
import stirling.software.SPDF.utils.UrlUtils;
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@AllArgsConstructor
|
@AllArgsConstructor
|
||||||
public class CustomLogoutSuccessHandler extends SimpleUrlLogoutSuccessHandler {
|
public class CustomLogoutSuccessHandler extends SimpleUrlLogoutSuccessHandler {
|
||||||
|
|
||||||
public static final String LOGOUT_PATH = "/login?logout=true";
|
|
||||||
|
|
||||||
private final ApplicationProperties applicationProperties;
|
private final ApplicationProperties applicationProperties;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onLogoutSuccess(
|
public void onLogoutSuccess(
|
||||||
HttpServletRequest request, HttpServletResponse response, Authentication authentication)
|
HttpServletRequest request, HttpServletResponse response, Authentication authentication)
|
||||||
throws IOException {
|
throws IOException, ServletException {
|
||||||
|
|
||||||
if (!response.isCommitted()) {
|
if (!response.isCommitted()) {
|
||||||
|
// Handle user logout due to disabled account
|
||||||
|
if (request.getParameter("userIsDisabled") != null) {
|
||||||
|
response.sendRedirect(
|
||||||
|
request.getContextPath() + "/login?erroroauth=userIsDisabled");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Handle OAuth2 authentication error
|
||||||
|
if (request.getParameter("oauth2AuthenticationErrorWeb") != null) {
|
||||||
|
response.sendRedirect(
|
||||||
|
request.getContextPath() + "/login?erroroauth=userAlreadyExistsWeb");
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (authentication != null) {
|
if (authentication != null) {
|
||||||
|
// Handle SAML2 logout redirection
|
||||||
if (authentication instanceof Saml2Authentication) {
|
if (authentication instanceof Saml2Authentication) {
|
||||||
// Handle SAML2 logout redirection
|
|
||||||
getRedirect_saml2(request, response, authentication);
|
getRedirect_saml2(request, response, authentication);
|
||||||
} else if (authentication instanceof OAuth2AuthenticationToken) {
|
return;
|
||||||
// Handle OAuth2 logout redirection
|
}
|
||||||
|
// Handle OAuth2 logout redirection
|
||||||
|
else if (authentication instanceof OAuth2AuthenticationToken) {
|
||||||
getRedirect_oauth2(request, response, authentication);
|
getRedirect_oauth2(request, response, authentication);
|
||||||
} else if (authentication instanceof UsernamePasswordAuthenticationToken) {
|
return;
|
||||||
// Handle Username/Password logout
|
}
|
||||||
getRedirectStrategy().sendRedirect(request, response, LOGOUT_PATH);
|
// Handle Username/Password logout
|
||||||
} else {
|
else if (authentication instanceof UsernamePasswordAuthenticationToken) {
|
||||||
// Handle unknown authentication types
|
getRedirectStrategy().sendRedirect(request, response, "/login?logout=true");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Handle unknown authentication types
|
||||||
|
else {
|
||||||
log.error(
|
log.error(
|
||||||
"Authentication class unknown: {}",
|
"authentication class unknown: "
|
||||||
authentication.getClass().getSimpleName());
|
+ authentication.getClass().getSimpleName());
|
||||||
getRedirectStrategy().sendRedirect(request, response, LOGOUT_PATH);
|
getRedirectStrategy().sendRedirect(request, response, "/login?logout=true");
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Redirect to login page after logout
|
// Redirect to login page after logout
|
||||||
String path = checkForErrors(request);
|
getRedirectStrategy().sendRedirect(request, response, "/login?logout=true");
|
||||||
getRedirectStrategy().sendRedirect(request, response, path);
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -81,7 +100,7 @@ public class CustomLogoutSuccessHandler extends SimpleUrlLogoutSuccessHandler {
|
|||||||
CustomSaml2AuthenticatedPrincipal principal =
|
CustomSaml2AuthenticatedPrincipal principal =
|
||||||
(CustomSaml2AuthenticatedPrincipal) samlAuthentication.getPrincipal();
|
(CustomSaml2AuthenticatedPrincipal) samlAuthentication.getPrincipal();
|
||||||
|
|
||||||
String nameIdValue = principal.name();
|
String nameIdValue = principal.getName();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Read certificate from the resource
|
// Read certificate from the resource
|
||||||
@@ -92,7 +111,27 @@ public class CustomLogoutSuccessHandler extends SimpleUrlLogoutSuccessHandler {
|
|||||||
certificates.add(certificate);
|
certificates.add(certificate);
|
||||||
|
|
||||||
// Construct URLs required for SAML configuration
|
// Construct URLs required for SAML configuration
|
||||||
SamlClient samlClient = getSamlClient(registrationId, samlConf, certificates);
|
String serverUrl =
|
||||||
|
SPDFApplication.getStaticBaseUrl() + ":" + SPDFApplication.getStaticPort();
|
||||||
|
|
||||||
|
String relyingPartyIdentifier =
|
||||||
|
serverUrl + "/saml2/service-provider-metadata/" + registrationId;
|
||||||
|
|
||||||
|
String assertionConsumerServiceUrl = serverUrl + "/login/saml2/sso/" + registrationId;
|
||||||
|
|
||||||
|
String idpUrl = samlConf.getIdpSingleLogoutUrl();
|
||||||
|
|
||||||
|
String idpIssuer = samlConf.getIdpIssuer();
|
||||||
|
|
||||||
|
// Create SamlClient instance for SAML logout
|
||||||
|
SamlClient samlClient =
|
||||||
|
new SamlClient(
|
||||||
|
relyingPartyIdentifier,
|
||||||
|
assertionConsumerServiceUrl,
|
||||||
|
idpUrl,
|
||||||
|
idpIssuer,
|
||||||
|
certificates,
|
||||||
|
SamlClient.SamlIdpBinding.POST);
|
||||||
|
|
||||||
// Read private key for service provider
|
// Read private key for service provider
|
||||||
Resource privateKeyResource = samlConf.getPrivateKey();
|
Resource privateKeyResource = samlConf.getPrivateKey();
|
||||||
@@ -104,12 +143,8 @@ public class CustomLogoutSuccessHandler extends SimpleUrlLogoutSuccessHandler {
|
|||||||
// Redirect to identity provider for logout
|
// Redirect to identity provider for logout
|
||||||
samlClient.redirectToIdentityProvider(response, null, nameIdValue);
|
samlClient.redirectToIdentityProvider(response, null, nameIdValue);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error(
|
log.error(nameIdValue, e);
|
||||||
"Error retrieving logout URL from Provider {} for user {}",
|
getRedirectStrategy().sendRedirect(request, response, "/login?logout=true");
|
||||||
samlConf.getProvider(),
|
|
||||||
nameIdValue,
|
|
||||||
e);
|
|
||||||
getRedirectStrategy().sendRedirect(request, response, LOGOUT_PATH);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -117,107 +152,87 @@ public class CustomLogoutSuccessHandler extends SimpleUrlLogoutSuccessHandler {
|
|||||||
private void getRedirect_oauth2(
|
private void getRedirect_oauth2(
|
||||||
HttpServletRequest request, HttpServletResponse response, Authentication authentication)
|
HttpServletRequest request, HttpServletResponse response, Authentication authentication)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
String registrationId;
|
String param = "logout=true";
|
||||||
|
String registrationId = null;
|
||||||
|
String issuer = null;
|
||||||
|
String clientId = null;
|
||||||
OAUTH2 oauth = applicationProperties.getSecurity().getOauth2();
|
OAUTH2 oauth = applicationProperties.getSecurity().getOauth2();
|
||||||
String path = checkForErrors(request);
|
|
||||||
|
|
||||||
if (authentication instanceof OAuth2AuthenticationToken oauthToken) {
|
if (authentication instanceof OAuth2AuthenticationToken) {
|
||||||
|
OAuth2AuthenticationToken oauthToken = (OAuth2AuthenticationToken) authentication;
|
||||||
registrationId = oauthToken.getAuthorizedClientRegistrationId();
|
registrationId = oauthToken.getAuthorizedClientRegistrationId();
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Get OAuth2 provider details from configuration
|
||||||
|
Provider provider = oauth.getClient().get(registrationId);
|
||||||
|
issuer = provider.getIssuer();
|
||||||
|
clientId = provider.getClientId();
|
||||||
|
} catch (UnsupportedProviderException e) {
|
||||||
|
log.error(e.getMessage());
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
registrationId = oauth.getProvider() != null ? oauth.getProvider() : "";
|
registrationId = oauth.getProvider() != null ? oauth.getProvider() : "";
|
||||||
|
issuer = oauth.getIssuer();
|
||||||
|
clientId = oauth.getClientId();
|
||||||
|
}
|
||||||
|
String errorMessage = "";
|
||||||
|
// Handle different error scenarios during logout
|
||||||
|
if (request.getParameter("oauth2AuthenticationErrorWeb") != null) {
|
||||||
|
param = "erroroauth=oauth2AuthenticationErrorWeb";
|
||||||
|
} else if ((errorMessage = request.getParameter("error")) != null) {
|
||||||
|
param = "error=" + sanitizeInput(errorMessage);
|
||||||
|
} else if ((errorMessage = request.getParameter("erroroauth")) != null) {
|
||||||
|
param = "erroroauth=" + sanitizeInput(errorMessage);
|
||||||
|
} else if (request.getParameter("oauth2AutoCreateDisabled") != null) {
|
||||||
|
param = "error=oauth2AutoCreateDisabled";
|
||||||
|
} else if (request.getParameter("oauth2_admin_blocked_user") != null) {
|
||||||
|
param = "erroroauth=oauth2_admin_blocked_user";
|
||||||
|
} else if (request.getParameter("userIsDisabled") != null) {
|
||||||
|
param = "erroroauth=userIsDisabled";
|
||||||
|
} else if (request.getParameter("badcredentials") != null) {
|
||||||
|
param = "error=badcredentials";
|
||||||
}
|
}
|
||||||
|
|
||||||
String redirectUrl = UrlUtils.getOrigin(request) + "/login?" + path;
|
String redirect_url = UrlUtils.getOrigin(request) + "/login?" + param;
|
||||||
|
|
||||||
// Redirect based on OAuth2 provider
|
// Redirect based on OAuth2 provider
|
||||||
switch (registrationId.toLowerCase()) {
|
switch (registrationId.toLowerCase()) {
|
||||||
case "keycloak" -> {
|
case "keycloak":
|
||||||
KeycloakProvider keycloak = oauth.getClient().getKeycloak();
|
// Add Keycloak specific logout URL if needed
|
||||||
String logoutUrl =
|
String logoutUrl =
|
||||||
keycloak.getIssuer()
|
issuer
|
||||||
+ "/protocol/openid-connect/logout"
|
+ "/protocol/openid-connect/logout"
|
||||||
+ "?client_id="
|
+ "?client_id="
|
||||||
+ keycloak.getClientId()
|
+ clientId
|
||||||
+ "&post_logout_redirect_uri="
|
+ "&post_logout_redirect_uri="
|
||||||
+ response.encodeRedirectURL(redirectUrl);
|
+ response.encodeRedirectURL(redirect_url);
|
||||||
log.info("Redirecting to Keycloak logout URL: {}", logoutUrl);
|
log.info("Redirecting to Keycloak logout URL: " + logoutUrl);
|
||||||
response.sendRedirect(logoutUrl);
|
response.sendRedirect(logoutUrl);
|
||||||
}
|
break;
|
||||||
case "github", "google" -> {
|
case "github":
|
||||||
log.info(
|
// Add GitHub specific logout URL if needed
|
||||||
"No redirect URL for {} available. Redirecting to default logout URL: {}",
|
String githubLogoutUrl = "https://github.com/logout";
|
||||||
registrationId,
|
log.info("Redirecting to GitHub logout URL: " + githubLogoutUrl);
|
||||||
redirectUrl);
|
response.sendRedirect(githubLogoutUrl);
|
||||||
response.sendRedirect(redirectUrl);
|
break;
|
||||||
}
|
case "google":
|
||||||
default -> {
|
// Add Google specific logout URL if needed
|
||||||
log.info("Redirecting to default logout URL: {}", redirectUrl);
|
// String googleLogoutUrl =
|
||||||
response.sendRedirect(redirectUrl);
|
// "https://accounts.google.com/Logout?continue=https://appengine.google.com/_ah/logout?continue="
|
||||||
}
|
// + response.encodeRedirectURL(redirect_url);
|
||||||
|
log.info("Google does not have a specific logout URL");
|
||||||
|
// log.info("Redirecting to Google logout URL: " + googleLogoutUrl);
|
||||||
|
// response.sendRedirect(googleLogoutUrl);
|
||||||
|
// break;
|
||||||
|
default:
|
||||||
|
String defaultRedirectUrl = request.getContextPath() + "/login?" + param;
|
||||||
|
log.info("Redirecting to default logout URL: " + defaultRedirectUrl);
|
||||||
|
response.sendRedirect(defaultRedirectUrl);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static SamlClient getSamlClient(
|
// Sanitize input to avoid potential security vulnerabilities
|
||||||
String registrationId, SAML2 samlConf, List<X509Certificate> certificates)
|
|
||||||
throws SamlException {
|
|
||||||
String serverUrl =
|
|
||||||
SPDFApplication.getStaticBaseUrl() + ":" + SPDFApplication.getStaticPort();
|
|
||||||
|
|
||||||
String relyingPartyIdentifier =
|
|
||||||
serverUrl + "/saml2/service-provider-metadata/" + registrationId;
|
|
||||||
|
|
||||||
String assertionConsumerServiceUrl = serverUrl + "/login/saml2/sso/" + registrationId;
|
|
||||||
|
|
||||||
String idpSLOUrl = samlConf.getIdpSingleLogoutUrl();
|
|
||||||
|
|
||||||
String idpIssuer = samlConf.getIdpIssuer();
|
|
||||||
|
|
||||||
// Create SamlClient instance for SAML logout
|
|
||||||
return new SamlClient(
|
|
||||||
relyingPartyIdentifier,
|
|
||||||
assertionConsumerServiceUrl,
|
|
||||||
idpSLOUrl,
|
|
||||||
idpIssuer,
|
|
||||||
certificates,
|
|
||||||
SamlClient.SamlIdpBinding.POST);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles different error scenarios during logout. Will return a <code>String</code> containing
|
|
||||||
* the error request parameter.
|
|
||||||
*
|
|
||||||
* @param request the user's <code>HttpServletRequest</code> request.
|
|
||||||
* @return a <code>String</code> containing the error request parameter.
|
|
||||||
*/
|
|
||||||
private String checkForErrors(HttpServletRequest request) {
|
|
||||||
String errorMessage;
|
|
||||||
String path = "logout=true";
|
|
||||||
|
|
||||||
if (request.getParameter("oAuth2AuthenticationErrorWeb") != null) {
|
|
||||||
path = "errorOAuth=userAlreadyExistsWeb";
|
|
||||||
} else if ((errorMessage = request.getParameter("errorOAuth")) != null) {
|
|
||||||
path = "errorOAuth=" + sanitizeInput(errorMessage);
|
|
||||||
} else if (request.getParameter("oAuth2AutoCreateDisabled") != null) {
|
|
||||||
path = "errorOAuth=oAuth2AutoCreateDisabled";
|
|
||||||
} else if (request.getParameter("oAuth2AdminBlockedUser") != null) {
|
|
||||||
path = "errorOAuth=oAuth2AdminBlockedUser";
|
|
||||||
} else if (request.getParameter("userIsDisabled") != null) {
|
|
||||||
path = "errorOAuth=userIsDisabled";
|
|
||||||
} else if ((errorMessage = request.getParameter("error")) != null) {
|
|
||||||
path = "errorOAuth=" + sanitizeInput(errorMessage);
|
|
||||||
} else if (request.getParameter("badCredentials") != null) {
|
|
||||||
path = "errorOAuth=badCredentials";
|
|
||||||
}
|
|
||||||
|
|
||||||
return path;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sanitize input to avoid potential security vulnerabilities. Will return a sanitised <code>
|
|
||||||
* String</code>.
|
|
||||||
*
|
|
||||||
* @return a sanitised <code>String</code>
|
|
||||||
*/
|
|
||||||
private String sanitizeInput(String input) {
|
private String sanitizeInput(String input) {
|
||||||
return input.replaceAll("[^a-zA-Z0-9 ]", "");
|
return input.replaceAll("[^a-zA-Z0-9 ]", "");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import lombok.extern.slf4j.Slf4j;
|
|||||||
import stirling.software.SPDF.config.interfaces.DatabaseInterface;
|
import stirling.software.SPDF.config.interfaces.DatabaseInterface;
|
||||||
import stirling.software.SPDF.model.ApplicationProperties;
|
import stirling.software.SPDF.model.ApplicationProperties;
|
||||||
import stirling.software.SPDF.model.Role;
|
import stirling.software.SPDF.model.Role;
|
||||||
import stirling.software.SPDF.model.exception.UnsupportedProviderException;
|
import stirling.software.SPDF.model.provider.UnsupportedProviderException;
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@Component
|
@Component
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
package stirling.software.SPDF.config.security;
|
package stirling.software.SPDF.config.security;
|
||||||
|
|
||||||
import java.util.Optional;
|
import java.util.*;
|
||||||
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.beans.factory.annotation.Qualifier;
|
import org.springframework.beans.factory.annotation.Qualifier;
|
||||||
@@ -51,7 +51,11 @@ public class SecurityConfiguration {
|
|||||||
|
|
||||||
private final CustomUserDetailsService userDetailsService;
|
private final CustomUserDetailsService userDetailsService;
|
||||||
private final UserService userService;
|
private final UserService userService;
|
||||||
|
|
||||||
|
@Qualifier("loginEnabled")
|
||||||
private final boolean loginEnabledValue;
|
private final boolean loginEnabledValue;
|
||||||
|
|
||||||
|
@Qualifier("runningEE")
|
||||||
private final boolean runningEE;
|
private final boolean runningEE;
|
||||||
|
|
||||||
private final ApplicationProperties applicationProperties;
|
private final ApplicationProperties applicationProperties;
|
||||||
@@ -105,7 +109,6 @@ public class SecurityConfiguration {
|
|||||||
if (applicationProperties.getSecurity().getCsrfDisabled() || !loginEnabledValue) {
|
if (applicationProperties.getSecurity().getCsrfDisabled() || !loginEnabledValue) {
|
||||||
http.csrf(csrf -> csrf.disable());
|
http.csrf(csrf -> csrf.disable());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (loginEnabledValue) {
|
if (loginEnabledValue) {
|
||||||
http.addFilterBefore(
|
http.addFilterBefore(
|
||||||
userAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
|
userAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
|
||||||
@@ -161,7 +164,8 @@ public class SecurityConfiguration {
|
|||||||
.logoutSuccessHandler(
|
.logoutSuccessHandler(
|
||||||
new CustomLogoutSuccessHandler(applicationProperties))
|
new CustomLogoutSuccessHandler(applicationProperties))
|
||||||
.clearAuthentication(true)
|
.clearAuthentication(true)
|
||||||
.invalidateHttpSession(true)
|
.invalidateHttpSession( // Invalidate session
|
||||||
|
true)
|
||||||
.deleteCookies("JSESSIONID", "remember-me"));
|
.deleteCookies("JSESSIONID", "remember-me"));
|
||||||
http.rememberMe(
|
http.rememberMe(
|
||||||
rememberMeConfigurer -> // Use the configurator directly
|
rememberMeConfigurer -> // Use the configurator directly
|
||||||
@@ -223,14 +227,14 @@ public class SecurityConfiguration {
|
|||||||
.permitAll());
|
.permitAll());
|
||||||
}
|
}
|
||||||
// Handle OAUTH2 Logins
|
// Handle OAUTH2 Logins
|
||||||
if (applicationProperties.getSecurity().isOauth2Active()) {
|
if (applicationProperties.getSecurity().isOauth2Activ()) {
|
||||||
http.oauth2Login(
|
http.oauth2Login(
|
||||||
oauth2 ->
|
oauth2 ->
|
||||||
oauth2.loginPage("/oauth2")
|
oauth2.loginPage("/oauth2")
|
||||||
.
|
.
|
||||||
/*
|
/*
|
||||||
This Custom handler is used to check if the OAUTH2 user trying to log in, already exists in the database.
|
This Custom handler is used to check if the OAUTH2 user trying to log in, already exists in the database.
|
||||||
If user exists, login proceeds as usual. If user does not exist, then it is auto-created but only if 'OAUTH2AutoCreateUser'
|
If user exists, login proceeds as usual. If user does not exist, then it is autocreated but only if 'OAUTH2AutoCreateUser'
|
||||||
is set as true, else login fails with an error message advising the same.
|
is set as true, else login fails with an error message advising the same.
|
||||||
*/
|
*/
|
||||||
successHandler(
|
successHandler(
|
||||||
@@ -254,7 +258,8 @@ public class SecurityConfiguration {
|
|||||||
.permitAll());
|
.permitAll());
|
||||||
}
|
}
|
||||||
// Handle SAML
|
// Handle SAML
|
||||||
if (applicationProperties.getSecurity().isSaml2Active() && runningEE) {
|
if (applicationProperties.getSecurity().isSaml2Activ()) {
|
||||||
|
// && runningEE
|
||||||
// Configure the authentication provider
|
// Configure the authentication provider
|
||||||
OpenSaml4AuthenticationProvider authenticationProvider =
|
OpenSaml4AuthenticationProvider authenticationProvider =
|
||||||
new OpenSaml4AuthenticationProvider();
|
new OpenSaml4AuthenticationProvider();
|
||||||
@@ -279,13 +284,12 @@ public class SecurityConfiguration {
|
|||||||
.authenticationRequestResolver(
|
.authenticationRequestResolver(
|
||||||
saml2AuthenticationRequestResolver);
|
saml2AuthenticationRequestResolver);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("Error configuring SAML 2 login", e);
|
log.error("Error configuring SAML2 login", e);
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
log.info("SAML 2 login is not enabled. Using default.");
|
|
||||||
http.authorizeHttpRequests(authz -> authz.anyRequest().permitAll());
|
http.authorizeHttpRequests(authz -> authz.anyRequest().permitAll());
|
||||||
}
|
}
|
||||||
return http.build();
|
return http.build();
|
||||||
@@ -311,7 +315,7 @@ public class SecurityConfiguration {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public boolean activeSecurity() {
|
public boolean activSecurity() {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -88,7 +88,7 @@ public class UserAuthenticationFilter extends OncePerRequestFilter {
|
|||||||
// Use API key to authenticate. This requires you to have an authentication
|
// Use API key to authenticate. This requires you to have an authentication
|
||||||
// provider for API keys.
|
// provider for API keys.
|
||||||
Optional<User> user = userService.getUserByApiKey(apiKey);
|
Optional<User> user = userService.getUserByApiKey(apiKey);
|
||||||
if (user.isEmpty()) {
|
if (!user.isPresent()) {
|
||||||
response.setStatus(HttpStatus.UNAUTHORIZED.value());
|
response.setStatus(HttpStatus.UNAUTHORIZED.value());
|
||||||
response.getWriter().write("Invalid API Key.");
|
response.getWriter().write("Invalid API Key.");
|
||||||
return;
|
return;
|
||||||
@@ -150,7 +150,7 @@ public class UserAuthenticationFilter extends OncePerRequestFilter {
|
|||||||
OAUTH2 oAuth = securityProp.getOauth2();
|
OAUTH2 oAuth = securityProp.getOauth2();
|
||||||
blockRegistration = oAuth != null && oAuth.getBlockRegistration();
|
blockRegistration = oAuth != null && oAuth.getBlockRegistration();
|
||||||
} else if (principal instanceof CustomSaml2AuthenticatedPrincipal) {
|
} else if (principal instanceof CustomSaml2AuthenticatedPrincipal) {
|
||||||
username = ((CustomSaml2AuthenticatedPrincipal) principal).name();
|
username = ((CustomSaml2AuthenticatedPrincipal) principal).getName();
|
||||||
loginMethod = LoginMethod.SAML2USER;
|
loginMethod = LoginMethod.SAML2USER;
|
||||||
SAML2 saml2 = securityProp.getSaml2();
|
SAML2 saml2 = securityProp.getSaml2();
|
||||||
blockRegistration = saml2 != null && saml2.getBlockRegistration();
|
blockRegistration = saml2 != null && saml2.getBlockRegistration();
|
||||||
@@ -177,7 +177,7 @@ public class UserAuthenticationFilter extends OncePerRequestFilter {
|
|||||||
if (blockRegistration && !isUserExists) {
|
if (blockRegistration && !isUserExists) {
|
||||||
log.warn("Blocked registration for OAuth2/SAML user: {}", username);
|
log.warn("Blocked registration for OAuth2/SAML user: {}", username);
|
||||||
response.sendRedirect(
|
response.sendRedirect(
|
||||||
request.getContextPath() + "/logout?oAuth2AdminBlockedUser=true");
|
request.getContextPath() + "/logout?oauth2_admin_blocked_user=true");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -193,7 +193,7 @@ public class UserAuthenticationFilter extends OncePerRequestFilter {
|
|||||||
|
|
||||||
// Redirect to logout if credentials are invalid
|
// Redirect to logout if credentials are invalid
|
||||||
if (!isUserExists && notSsoLogin) {
|
if (!isUserExists && notSsoLogin) {
|
||||||
response.sendRedirect(request.getContextPath() + "/logout?badCredentials=true");
|
response.sendRedirect(request.getContextPath() + "/logout?badcredentials=true");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (isUserDisabled) {
|
if (isUserDisabled) {
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ import stirling.software.SPDF.config.security.saml2.CustomSaml2AuthenticatedPrin
|
|||||||
import stirling.software.SPDF.config.security.session.SessionPersistentRegistry;
|
import stirling.software.SPDF.config.security.session.SessionPersistentRegistry;
|
||||||
import stirling.software.SPDF.controller.api.pipeline.UserServiceInterface;
|
import stirling.software.SPDF.controller.api.pipeline.UserServiceInterface;
|
||||||
import stirling.software.SPDF.model.*;
|
import stirling.software.SPDF.model.*;
|
||||||
import stirling.software.SPDF.model.exception.UnsupportedProviderException;
|
import stirling.software.SPDF.model.provider.UnsupportedProviderException;
|
||||||
import stirling.software.SPDF.repository.AuthorityRepository;
|
import stirling.software.SPDF.repository.AuthorityRepository;
|
||||||
import stirling.software.SPDF.repository.UserRepository;
|
import stirling.software.SPDF.repository.UserRepository;
|
||||||
|
|
||||||
@@ -78,18 +78,20 @@ public class UserService implements UserServiceInterface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Handle OAUTH2 login and user auto creation.
|
// Handle OAUTH2 login and user auto creation.
|
||||||
public void processSSOPostLogin(String username, boolean autoCreateUser)
|
public boolean processSSOPostLogin(String username, boolean autoCreateUser)
|
||||||
throws IllegalArgumentException, SQLException, UnsupportedProviderException {
|
throws IllegalArgumentException, SQLException, UnsupportedProviderException {
|
||||||
if (!isUsernameValid(username)) {
|
if (!isUsernameValid(username)) {
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
Optional<User> existingUser = findByUsernameIgnoreCase(username);
|
Optional<User> existingUser = findByUsernameIgnoreCase(username);
|
||||||
if (existingUser.isPresent()) {
|
if (existingUser.isPresent()) {
|
||||||
return;
|
return true;
|
||||||
}
|
}
|
||||||
if (autoCreateUser) {
|
if (autoCreateUser) {
|
||||||
saveUser(username, AuthenticationType.SSO);
|
saveUser(username, AuthenticationType.SSO);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Authentication getAuthentication(String apiKey) {
|
public Authentication getAuthentication(String apiKey) {
|
||||||
@@ -371,16 +373,19 @@ public class UserService implements UserServiceInterface {
|
|||||||
|
|
||||||
public void invalidateUserSessions(String username) {
|
public void invalidateUserSessions(String username) {
|
||||||
String usernameP = "";
|
String usernameP = "";
|
||||||
|
|
||||||
for (Object principal : sessionRegistry.getAllPrincipals()) {
|
for (Object principal : sessionRegistry.getAllPrincipals()) {
|
||||||
for (SessionInformation sessionsInformation :
|
for (SessionInformation sessionsInformation :
|
||||||
sessionRegistry.getAllSessions(principal, false)) {
|
sessionRegistry.getAllSessions(principal, false)) {
|
||||||
if (principal instanceof UserDetails userDetails) {
|
if (principal instanceof UserDetails) {
|
||||||
|
UserDetails userDetails = (UserDetails) principal;
|
||||||
usernameP = userDetails.getUsername();
|
usernameP = userDetails.getUsername();
|
||||||
} else if (principal instanceof OAuth2User oAuth2User) {
|
} else if (principal instanceof OAuth2User) {
|
||||||
|
OAuth2User oAuth2User = (OAuth2User) principal;
|
||||||
usernameP = oAuth2User.getName();
|
usernameP = oAuth2User.getName();
|
||||||
} else if (principal instanceof CustomSaml2AuthenticatedPrincipal saml2User) {
|
} else if (principal instanceof CustomSaml2AuthenticatedPrincipal) {
|
||||||
usernameP = saml2User.name();
|
CustomSaml2AuthenticatedPrincipal saml2User =
|
||||||
|
(CustomSaml2AuthenticatedPrincipal) principal;
|
||||||
|
usernameP = saml2User.getName();
|
||||||
} else if (principal instanceof String) {
|
} else if (principal instanceof String) {
|
||||||
usernameP = (String) principal;
|
usernameP = (String) principal;
|
||||||
}
|
}
|
||||||
@@ -393,7 +398,6 @@ public class UserService implements UserServiceInterface {
|
|||||||
|
|
||||||
public String getCurrentUsername() {
|
public String getCurrentUsername() {
|
||||||
Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
|
Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
|
||||||
|
|
||||||
if (principal instanceof UserDetails) {
|
if (principal instanceof UserDetails) {
|
||||||
return ((UserDetails) principal).getUsername();
|
return ((UserDetails) principal).getUsername();
|
||||||
} else if (principal instanceof OAuth2User) {
|
} else if (principal instanceof OAuth2User) {
|
||||||
@@ -401,48 +405,42 @@ public class UserService implements UserServiceInterface {
|
|||||||
.getAttribute(
|
.getAttribute(
|
||||||
applicationProperties.getSecurity().getOauth2().getUseAsUsername());
|
applicationProperties.getSecurity().getOauth2().getUseAsUsername());
|
||||||
} else if (principal instanceof CustomSaml2AuthenticatedPrincipal) {
|
} else if (principal instanceof CustomSaml2AuthenticatedPrincipal) {
|
||||||
return ((CustomSaml2AuthenticatedPrincipal) principal).name();
|
return ((CustomSaml2AuthenticatedPrincipal) principal).getName();
|
||||||
|
} else if (principal instanceof String) {
|
||||||
|
return (String) principal;
|
||||||
} else {
|
} else {
|
||||||
return principal.toString();
|
return principal.toString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
public void syncCustomApiUser(String customApiKey) {
|
public void syncCustomApiUser(String customApiKey)
|
||||||
if (customApiKey == null || customApiKey.trim().isBlank()) {
|
throws SQLException, UnsupportedProviderException {
|
||||||
|
if (customApiKey == null || customApiKey.trim().length() == 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
String username = "CUSTOM_API_USER";
|
String username = "CUSTOM_API_USER";
|
||||||
Optional<User> existingUser = findByUsernameIgnoreCase(username);
|
Optional<User> existingUser = findByUsernameIgnoreCase(username);
|
||||||
|
if (!existingUser.isPresent()) {
|
||||||
existingUser.ifPresentOrElse(
|
// Create new user with API role
|
||||||
user -> {
|
User user = new User();
|
||||||
// Update API key if it has changed
|
user.setUsername(username);
|
||||||
User updatedUser = existingUser.get();
|
user.setPassword(UUID.randomUUID().toString());
|
||||||
|
user.setEnabled(true);
|
||||||
if (!customApiKey.equals(updatedUser.getApiKey())) {
|
user.setFirstLogin(false);
|
||||||
updatedUser.setApiKey(customApiKey);
|
user.setAuthenticationType(AuthenticationType.WEB);
|
||||||
userRepository.save(updatedUser);
|
user.setApiKey(customApiKey);
|
||||||
}
|
user.addAuthority(new Authority(Role.INTERNAL_API_USER.getRoleId(), user));
|
||||||
},
|
userRepository.save(user);
|
||||||
() -> {
|
|
||||||
// Create new user with API role
|
|
||||||
User user = new User();
|
|
||||||
user.setUsername(username);
|
|
||||||
user.setPassword(UUID.randomUUID().toString());
|
|
||||||
user.setEnabled(true);
|
|
||||||
user.setFirstLogin(false);
|
|
||||||
user.setAuthenticationType(AuthenticationType.WEB);
|
|
||||||
user.setApiKey(customApiKey);
|
|
||||||
user.addAuthority(new Authority(Role.INTERNAL_API_USER.getRoleId(), user));
|
|
||||||
userRepository.save(user);
|
|
||||||
});
|
|
||||||
|
|
||||||
try {
|
|
||||||
databaseService.exportDatabase();
|
databaseService.exportDatabase();
|
||||||
} catch (SQLException | UnsupportedProviderException e) {
|
} else {
|
||||||
log.error("Error exporting database after synchronising custom API user", e);
|
// Update API key if it has changed
|
||||||
|
User user = existingUser.get();
|
||||||
|
if (!customApiKey.equals(user.getApiKey())) {
|
||||||
|
user.setApiKey(customApiKey);
|
||||||
|
userRepository.save(user);
|
||||||
|
databaseService.exportDatabase();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import lombok.extern.slf4j.Slf4j;
|
|||||||
|
|
||||||
import stirling.software.SPDF.config.InstallationPathConfig;
|
import stirling.software.SPDF.config.InstallationPathConfig;
|
||||||
import stirling.software.SPDF.model.ApplicationProperties;
|
import stirling.software.SPDF.model.ApplicationProperties;
|
||||||
import stirling.software.SPDF.model.exception.UnsupportedProviderException;
|
import stirling.software.SPDF.model.provider.UnsupportedProviderException;
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@Getter
|
@Getter
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import org.springframework.stereotype.Component;
|
|||||||
|
|
||||||
import stirling.software.SPDF.config.interfaces.DatabaseInterface;
|
import stirling.software.SPDF.config.interfaces.DatabaseInterface;
|
||||||
import stirling.software.SPDF.controller.api.H2SQLCondition;
|
import stirling.software.SPDF.controller.api.H2SQLCondition;
|
||||||
import stirling.software.SPDF.model.exception.UnsupportedProviderException;
|
import stirling.software.SPDF.model.provider.UnsupportedProviderException;
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
@Conditional(H2SQLCondition.class)
|
@Conditional(H2SQLCondition.class)
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ public class CustomOAuth2AuthenticationFailureHandler
|
|||||||
|
|
||||||
if (exception instanceof BadCredentialsException) {
|
if (exception instanceof BadCredentialsException) {
|
||||||
log.error("BadCredentialsException", exception);
|
log.error("BadCredentialsException", exception);
|
||||||
getRedirectStrategy().sendRedirect(request, response, "/login?error=badCredentials");
|
getRedirectStrategy().sendRedirect(request, response, "/login?error=badcredentials");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (exception instanceof DisabledException) {
|
if (exception instanceof DisabledException) {
|
||||||
@@ -50,12 +50,10 @@ public class CustomOAuth2AuthenticationFailureHandler
|
|||||||
if (error.getErrorCode().equals("Password must not be null")) {
|
if (error.getErrorCode().equals("Password must not be null")) {
|
||||||
errorCode = "userAlreadyExistsWeb";
|
errorCode = "userAlreadyExistsWeb";
|
||||||
}
|
}
|
||||||
|
log.error("OAuth2 Authentication error: " + errorCode);
|
||||||
log.error(
|
log.error("OAuth2AuthenticationException", exception);
|
||||||
"OAuth2 Authentication error: {}",
|
getRedirectStrategy().sendRedirect(request, response, "/login?erroroauth=" + errorCode);
|
||||||
errorCode != null ? errorCode : exception.getMessage(),
|
return;
|
||||||
exception);
|
|
||||||
getRedirectStrategy().sendRedirect(request, response, "/login?errorOAuth=" + errorCode);
|
|
||||||
}
|
}
|
||||||
log.error("Unhandled authentication exception", exception);
|
log.error("Unhandled authentication exception", exception);
|
||||||
super.onAuthenticationFailure(request, response, exception);
|
super.onAuthenticationFailure(request, response, exception);
|
||||||
|
|||||||
@@ -20,18 +20,19 @@ import stirling.software.SPDF.config.security.UserService;
|
|||||||
import stirling.software.SPDF.model.ApplicationProperties;
|
import stirling.software.SPDF.model.ApplicationProperties;
|
||||||
import stirling.software.SPDF.model.ApplicationProperties.Security.OAUTH2;
|
import stirling.software.SPDF.model.ApplicationProperties.Security.OAUTH2;
|
||||||
import stirling.software.SPDF.model.AuthenticationType;
|
import stirling.software.SPDF.model.AuthenticationType;
|
||||||
import stirling.software.SPDF.model.exception.UnsupportedProviderException;
|
import stirling.software.SPDF.model.provider.UnsupportedProviderException;
|
||||||
import stirling.software.SPDF.utils.RequestUriUtils;
|
import stirling.software.SPDF.utils.RequestUriUtils;
|
||||||
|
|
||||||
public class CustomOAuth2AuthenticationSuccessHandler
|
public class CustomOAuth2AuthenticationSuccessHandler
|
||||||
extends SavedRequestAwareAuthenticationSuccessHandler {
|
extends SavedRequestAwareAuthenticationSuccessHandler {
|
||||||
|
|
||||||
private final LoginAttemptService loginAttemptService;
|
private LoginAttemptService loginAttemptService;
|
||||||
private final ApplicationProperties applicationProperties;
|
|
||||||
private final UserService userService;
|
private ApplicationProperties applicationProperties;
|
||||||
|
private UserService userService;
|
||||||
|
|
||||||
public CustomOAuth2AuthenticationSuccessHandler(
|
public CustomOAuth2AuthenticationSuccessHandler(
|
||||||
LoginAttemptService loginAttemptService,
|
final LoginAttemptService loginAttemptService,
|
||||||
ApplicationProperties applicationProperties,
|
ApplicationProperties applicationProperties,
|
||||||
UserService userService) {
|
UserService userService) {
|
||||||
this.applicationProperties = applicationProperties;
|
this.applicationProperties = applicationProperties;
|
||||||
@@ -47,9 +48,11 @@ public class CustomOAuth2AuthenticationSuccessHandler
|
|||||||
Object principal = authentication.getPrincipal();
|
Object principal = authentication.getPrincipal();
|
||||||
String username = "";
|
String username = "";
|
||||||
|
|
||||||
if (principal instanceof OAuth2User oauthUser) {
|
if (principal instanceof OAuth2User) {
|
||||||
|
OAuth2User oauthUser = (OAuth2User) principal;
|
||||||
username = oauthUser.getName();
|
username = oauthUser.getName();
|
||||||
} else if (principal instanceof UserDetails oauthUser) {
|
} else if (principal instanceof UserDetails) {
|
||||||
|
UserDetails oauthUser = (UserDetails) principal;
|
||||||
username = oauthUser.getUsername();
|
username = oauthUser.getUsername();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -75,7 +78,6 @@ public class CustomOAuth2AuthenticationSuccessHandler
|
|||||||
throw new LockedException(
|
throw new LockedException(
|
||||||
"Your account has been locked due to too many failed login attempts.");
|
"Your account has been locked due to too many failed login attempts.");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (userService.isUserDisabled(username)) {
|
if (userService.isUserDisabled(username)) {
|
||||||
getRedirectStrategy()
|
getRedirectStrategy()
|
||||||
.sendRedirect(request, response, "/logout?userIsDisabled=true");
|
.sendRedirect(request, response, "/logout?userIsDisabled=true");
|
||||||
@@ -85,14 +87,13 @@ public class CustomOAuth2AuthenticationSuccessHandler
|
|||||||
&& userService.hasPassword(username)
|
&& userService.hasPassword(username)
|
||||||
&& !userService.isAuthenticationTypeByUsername(username, AuthenticationType.SSO)
|
&& !userService.isAuthenticationTypeByUsername(username, AuthenticationType.SSO)
|
||||||
&& oAuth.getAutoCreateUser()) {
|
&& oAuth.getAutoCreateUser()) {
|
||||||
response.sendRedirect(contextPath + "/logout?oAuth2AuthenticationErrorWeb=true");
|
response.sendRedirect(contextPath + "/logout?oauth2AuthenticationErrorWeb=true");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (oAuth.getBlockRegistration()
|
if (oAuth.getBlockRegistration()
|
||||||
&& !userService.usernameExistsIgnoreCase(username)) {
|
&& !userService.usernameExistsIgnoreCase(username)) {
|
||||||
response.sendRedirect(contextPath + "/logout?oAuth2AdminBlockedUser=true");
|
response.sendRedirect(contextPath + "/logout?oauth2_admin_blocked_user=true");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (principal instanceof OAuth2User) {
|
if (principal instanceof OAuth2User) {
|
||||||
|
|||||||
@@ -17,19 +17,19 @@ import stirling.software.SPDF.config.security.LoginAttemptService;
|
|||||||
import stirling.software.SPDF.config.security.UserService;
|
import stirling.software.SPDF.config.security.UserService;
|
||||||
import stirling.software.SPDF.model.ApplicationProperties;
|
import stirling.software.SPDF.model.ApplicationProperties;
|
||||||
import stirling.software.SPDF.model.ApplicationProperties.Security.OAUTH2;
|
import stirling.software.SPDF.model.ApplicationProperties.Security.OAUTH2;
|
||||||
|
import stirling.software.SPDF.model.ApplicationProperties.Security.OAUTH2.Client;
|
||||||
import stirling.software.SPDF.model.User;
|
import stirling.software.SPDF.model.User;
|
||||||
import stirling.software.SPDF.model.UsernameAttribute;
|
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class CustomOAuth2UserService implements OAuth2UserService<OidcUserRequest, OidcUser> {
|
public class CustomOAuth2UserService implements OAuth2UserService<OidcUserRequest, OidcUser> {
|
||||||
|
|
||||||
private final OidcUserService delegate = new OidcUserService();
|
private final OidcUserService delegate = new OidcUserService();
|
||||||
|
|
||||||
private final UserService userService;
|
private UserService userService;
|
||||||
|
|
||||||
private final LoginAttemptService loginAttemptService;
|
private LoginAttemptService loginAttemptService;
|
||||||
|
|
||||||
private final ApplicationProperties applicationProperties;
|
private ApplicationProperties applicationProperties;
|
||||||
|
|
||||||
public CustomOAuth2UserService(
|
public CustomOAuth2UserService(
|
||||||
ApplicationProperties applicationProperties,
|
ApplicationProperties applicationProperties,
|
||||||
@@ -42,26 +42,34 @@ public class CustomOAuth2UserService implements OAuth2UserService<OidcUserReques
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public OidcUser loadUser(OidcUserRequest userRequest) throws OAuth2AuthenticationException {
|
public OidcUser loadUser(OidcUserRequest userRequest) throws OAuth2AuthenticationException {
|
||||||
|
OAUTH2 oauth2 = applicationProperties.getSecurity().getOauth2();
|
||||||
|
String usernameAttribute = oauth2.getUseAsUsername();
|
||||||
|
if (usernameAttribute == null || usernameAttribute.trim().isEmpty()) {
|
||||||
|
Client client = oauth2.getClient();
|
||||||
|
if (client != null && client.getKeycloak() != null) {
|
||||||
|
usernameAttribute = client.getKeycloak().getUseAsUsername();
|
||||||
|
} else {
|
||||||
|
usernameAttribute = "email";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
OidcUser user = delegate.loadUser(userRequest);
|
OidcUser user = delegate.loadUser(userRequest);
|
||||||
OAUTH2 oauth2 = applicationProperties.getSecurity().getOauth2();
|
String username = user.getUserInfo().getClaimAsString(usernameAttribute);
|
||||||
UsernameAttribute usernameAttribute =
|
|
||||||
UsernameAttribute.valueOf(oauth2.getUseAsUsername().toUpperCase());
|
|
||||||
String usernameAttributeKey = usernameAttribute.getName();
|
|
||||||
|
|
||||||
// todo: save user by OIDC ID instead of username
|
// Check if the username claim is null or empty
|
||||||
Optional<User> internalUser =
|
if (username == null || username.trim().isEmpty()) {
|
||||||
userService.findByUsernameIgnoreCase(user.getAttribute(usernameAttributeKey));
|
throw new IllegalArgumentException(
|
||||||
|
"Claim '" + usernameAttribute + "' cannot be null or empty");
|
||||||
|
}
|
||||||
|
|
||||||
if (internalUser.isPresent()) {
|
Optional<User> duser = userService.findByUsernameIgnoreCase(username);
|
||||||
String internalUsername = internalUser.get().getUsername();
|
if (duser.isPresent()) {
|
||||||
if (loginAttemptService.isBlocked(internalUsername)) {
|
if (loginAttemptService.isBlocked(username)) {
|
||||||
throw new LockedException(
|
throw new LockedException(
|
||||||
"The account "
|
"Your account has been locked due to too many failed login attempts.");
|
||||||
+ internalUsername
|
|
||||||
+ " has been locked due to too many failed login attempts.");
|
|
||||||
}
|
}
|
||||||
if (userService.hasPassword(usernameAttributeKey)) {
|
if (userService.hasPassword(username)) {
|
||||||
throw new IllegalArgumentException("Password must not be null");
|
throw new IllegalArgumentException("Password must not be null");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -71,7 +79,7 @@ public class CustomOAuth2UserService implements OAuth2UserService<OidcUserReques
|
|||||||
user.getAuthorities(),
|
user.getAuthorities(),
|
||||||
userRequest.getIdToken(),
|
userRequest.getIdToken(),
|
||||||
user.getUserInfo(),
|
user.getUserInfo(),
|
||||||
usernameAttributeKey);
|
usernameAttribute);
|
||||||
} catch (IllegalArgumentException e) {
|
} catch (IllegalArgumentException e) {
|
||||||
log.error("Error loading OIDC user: {}", e.getMessage());
|
log.error("Error loading OIDC user: {}", e.getMessage());
|
||||||
throw new OAuth2AuthenticationException(new OAuth2Error(e.getMessage()), e);
|
throw new OAuth2AuthenticationException(new OAuth2Error(e.getMessage()), e);
|
||||||
|
|||||||
@@ -1,8 +1,5 @@
|
|||||||
package stirling.software.SPDF.config.security.oauth2;
|
package stirling.software.SPDF.config.security.oauth2;
|
||||||
|
|
||||||
import static org.springframework.security.oauth2.core.AuthorizationGrantType.AUTHORIZATION_CODE;
|
|
||||||
import static stirling.software.SPDF.utils.validation.Validator.*;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -29,20 +26,18 @@ import stirling.software.SPDF.model.ApplicationProperties;
|
|||||||
import stirling.software.SPDF.model.ApplicationProperties.Security.OAUTH2;
|
import stirling.software.SPDF.model.ApplicationProperties.Security.OAUTH2;
|
||||||
import stirling.software.SPDF.model.ApplicationProperties.Security.OAUTH2.Client;
|
import stirling.software.SPDF.model.ApplicationProperties.Security.OAUTH2.Client;
|
||||||
import stirling.software.SPDF.model.User;
|
import stirling.software.SPDF.model.User;
|
||||||
import stirling.software.SPDF.model.UsernameAttribute;
|
import stirling.software.SPDF.model.provider.GithubProvider;
|
||||||
import stirling.software.SPDF.model.exception.NoProviderFoundException;
|
|
||||||
import stirling.software.SPDF.model.provider.GitHubProvider;
|
|
||||||
import stirling.software.SPDF.model.provider.GoogleProvider;
|
import stirling.software.SPDF.model.provider.GoogleProvider;
|
||||||
import stirling.software.SPDF.model.provider.KeycloakProvider;
|
import stirling.software.SPDF.model.provider.KeycloakProvider;
|
||||||
import stirling.software.SPDF.model.provider.Provider;
|
|
||||||
|
|
||||||
@Slf4j
|
|
||||||
@Configuration
|
@Configuration
|
||||||
@ConditionalOnProperty(value = "security.oauth2.enabled", havingValue = "true")
|
@Slf4j
|
||||||
|
@ConditionalOnProperty(
|
||||||
|
value = "security.oauth2.enabled",
|
||||||
|
havingValue = "true",
|
||||||
|
matchIfMissing = false)
|
||||||
public class OAuth2Configuration {
|
public class OAuth2Configuration {
|
||||||
|
|
||||||
public static final String REDIRECT_URI_PATH = "{baseUrl}/login/oauth2/code/";
|
|
||||||
|
|
||||||
private final ApplicationProperties applicationProperties;
|
private final ApplicationProperties applicationProperties;
|
||||||
@Lazy private final UserService userService;
|
@Lazy private final UserService userService;
|
||||||
|
|
||||||
@@ -53,175 +48,139 @@ public class OAuth2Configuration {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
@ConditionalOnProperty(value = "security.oauth2.enabled", havingValue = "true")
|
@ConditionalOnProperty(
|
||||||
public ClientRegistrationRepository clientRegistrationRepository()
|
value = "security.oauth2.enabled",
|
||||||
throws NoProviderFoundException {
|
havingValue = "true",
|
||||||
|
matchIfMissing = false)
|
||||||
|
public ClientRegistrationRepository clientRegistrationRepository() {
|
||||||
List<ClientRegistration> registrations = new ArrayList<>();
|
List<ClientRegistration> registrations = new ArrayList<>();
|
||||||
githubClientRegistration().ifPresent(registrations::add);
|
githubClientRegistration().ifPresent(registrations::add);
|
||||||
oidcClientRegistration().ifPresent(registrations::add);
|
oidcClientRegistration().ifPresent(registrations::add);
|
||||||
googleClientRegistration().ifPresent(registrations::add);
|
googleClientRegistration().ifPresent(registrations::add);
|
||||||
keycloakClientRegistration().ifPresent(registrations::add);
|
keycloakClientRegistration().ifPresent(registrations::add);
|
||||||
|
|
||||||
if (registrations.isEmpty()) {
|
if (registrations.isEmpty()) {
|
||||||
log.error("No OAuth2 provider registered");
|
log.error("At least one OAuth2 provider must be configured");
|
||||||
throw new NoProviderFoundException("At least one OAuth2 provider must be configured.");
|
System.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new InMemoryClientRegistrationRepository(registrations);
|
return new InMemoryClientRegistrationRepository(registrations);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Optional<ClientRegistration> keycloakClientRegistration() {
|
private Optional<ClientRegistration> googleClientRegistration() {
|
||||||
OAUTH2 oauth2 = applicationProperties.getSecurity().getOauth2();
|
OAUTH2 oauth = applicationProperties.getSecurity().getOauth2();
|
||||||
|
if (oauth == null || !oauth.getEnabled()) {
|
||||||
if (isOAuth2Enabled(oauth2) || isClientInitialised(oauth2)) {
|
|
||||||
return Optional.empty();
|
return Optional.empty();
|
||||||
}
|
}
|
||||||
|
Client client = oauth.getClient();
|
||||||
|
if (client == null) {
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
GoogleProvider google = client.getGoogle();
|
||||||
|
return google != null && google.isSettingsValid()
|
||||||
|
? Optional.of(
|
||||||
|
ClientRegistration.withRegistrationId(google.getName())
|
||||||
|
.clientId(google.getClientId())
|
||||||
|
.clientSecret(google.getClientSecret())
|
||||||
|
.scope(google.getScopes())
|
||||||
|
.authorizationUri(google.getAuthorizationuri())
|
||||||
|
.tokenUri(google.getTokenuri())
|
||||||
|
.userInfoUri(google.getUserinfouri())
|
||||||
|
.userNameAttributeName(google.getUseAsUsername())
|
||||||
|
.clientName(google.getClientName())
|
||||||
|
.redirectUri("{baseUrl}/login/oauth2/code/" + google.getName())
|
||||||
|
.authorizationGrantType(
|
||||||
|
org.springframework.security.oauth2.core
|
||||||
|
.AuthorizationGrantType.AUTHORIZATION_CODE)
|
||||||
|
.build())
|
||||||
|
: Optional.empty();
|
||||||
|
}
|
||||||
|
|
||||||
Client client = oauth2.getClient();
|
private Optional<ClientRegistration> keycloakClientRegistration() {
|
||||||
KeycloakProvider keycloakClient = client.getKeycloak();
|
OAUTH2 oauth = applicationProperties.getSecurity().getOauth2();
|
||||||
Provider keycloak =
|
if (oauth == null || !oauth.getEnabled()) {
|
||||||
new KeycloakProvider(
|
return Optional.empty();
|
||||||
keycloakClient.getIssuer(),
|
}
|
||||||
keycloakClient.getClientId(),
|
Client client = oauth.getClient();
|
||||||
keycloakClient.getClientSecret(),
|
if (client == null) {
|
||||||
keycloakClient.getScopes(),
|
return Optional.empty();
|
||||||
keycloakClient.getUseAsUsername());
|
}
|
||||||
|
KeycloakProvider keycloak = client.getKeycloak();
|
||||||
return validateProvider(keycloak)
|
return keycloak != null && keycloak.isSettingsValid()
|
||||||
? Optional.of(
|
? Optional.of(
|
||||||
ClientRegistrations.fromIssuerLocation(keycloak.getIssuer())
|
ClientRegistrations.fromIssuerLocation(keycloak.getIssuer())
|
||||||
.registrationId(keycloak.getName())
|
.registrationId(keycloak.getName())
|
||||||
.clientId(keycloak.getClientId())
|
.clientId(keycloak.getClientId())
|
||||||
.clientSecret(keycloak.getClientSecret())
|
.clientSecret(keycloak.getClientSecret())
|
||||||
.scope(keycloak.getScopes())
|
.scope(keycloak.getScopes())
|
||||||
.userNameAttributeName(keycloak.getUseAsUsername().getName())
|
.userNameAttributeName(keycloak.getUseAsUsername())
|
||||||
.clientName(keycloak.getClientName())
|
.clientName(keycloak.getClientName())
|
||||||
.build())
|
.build())
|
||||||
: Optional.empty();
|
: Optional.empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
private Optional<ClientRegistration> googleClientRegistration() {
|
|
||||||
OAUTH2 oAuth2 = applicationProperties.getSecurity().getOauth2();
|
|
||||||
|
|
||||||
if (isOAuth2Enabled(oAuth2) || isClientInitialised(oAuth2)) {
|
|
||||||
return Optional.empty();
|
|
||||||
}
|
|
||||||
|
|
||||||
Client client = oAuth2.getClient();
|
|
||||||
GoogleProvider googleClient = client.getGoogle();
|
|
||||||
Provider google =
|
|
||||||
new GoogleProvider(
|
|
||||||
googleClient.getClientId(),
|
|
||||||
googleClient.getClientSecret(),
|
|
||||||
googleClient.getScopes(),
|
|
||||||
googleClient.getUseAsUsername());
|
|
||||||
|
|
||||||
return validateProvider(google)
|
|
||||||
? Optional.of(
|
|
||||||
ClientRegistration.withRegistrationId(google.getName())
|
|
||||||
.clientId(google.getClientId())
|
|
||||||
.clientSecret(google.getClientSecret())
|
|
||||||
.scope(google.getScopes())
|
|
||||||
.authorizationUri(google.getAuthorizationUri())
|
|
||||||
.tokenUri(google.getTokenUri())
|
|
||||||
.userInfoUri(google.getUserInfoUri())
|
|
||||||
.userNameAttributeName(google.getUseAsUsername().getName())
|
|
||||||
.clientName(google.getClientName())
|
|
||||||
.redirectUri(REDIRECT_URI_PATH + google.getName())
|
|
||||||
.authorizationGrantType(AUTHORIZATION_CODE)
|
|
||||||
.build())
|
|
||||||
: Optional.empty();
|
|
||||||
}
|
|
||||||
|
|
||||||
private Optional<ClientRegistration> githubClientRegistration() {
|
private Optional<ClientRegistration> githubClientRegistration() {
|
||||||
OAUTH2 oAuth2 = applicationProperties.getSecurity().getOauth2();
|
OAUTH2 oauth = applicationProperties.getSecurity().getOauth2();
|
||||||
|
if (oauth == null || !oauth.getEnabled()) {
|
||||||
if (isOAuth2Enabled(oAuth2)) {
|
|
||||||
return Optional.empty();
|
return Optional.empty();
|
||||||
}
|
}
|
||||||
|
Client client = oauth.getClient();
|
||||||
Client client = oAuth2.getClient();
|
if (client == null) {
|
||||||
GitHubProvider githubClient = client.getGithub();
|
return Optional.empty();
|
||||||
Provider github =
|
}
|
||||||
new GitHubProvider(
|
GithubProvider github = client.getGithub();
|
||||||
githubClient.getClientId(),
|
return github != null && github.isSettingsValid()
|
||||||
githubClient.getClientSecret(),
|
|
||||||
githubClient.getScopes(),
|
|
||||||
githubClient.getUseAsUsername());
|
|
||||||
|
|
||||||
return validateProvider(github)
|
|
||||||
? Optional.of(
|
? Optional.of(
|
||||||
ClientRegistration.withRegistrationId(github.getName())
|
ClientRegistration.withRegistrationId(github.getName())
|
||||||
.clientId(github.getClientId())
|
.clientId(github.getClientId())
|
||||||
.clientSecret(github.getClientSecret())
|
.clientSecret(github.getClientSecret())
|
||||||
.scope(github.getScopes())
|
.scope(github.getScopes())
|
||||||
.authorizationUri(github.getAuthorizationUri())
|
.authorizationUri(github.getAuthorizationuri())
|
||||||
.tokenUri(github.getTokenUri())
|
.tokenUri(github.getTokenuri())
|
||||||
.userInfoUri(github.getUserInfoUri())
|
.userInfoUri(github.getUserinfouri())
|
||||||
.userNameAttributeName(github.getUseAsUsername().getName())
|
.userNameAttributeName(github.getUseAsUsername())
|
||||||
.clientName(github.getClientName())
|
.clientName(github.getClientName())
|
||||||
.redirectUri(REDIRECT_URI_PATH + github.getName())
|
.redirectUri("{baseUrl}/login/oauth2/code/" + github.getName())
|
||||||
.authorizationGrantType(AUTHORIZATION_CODE)
|
.authorizationGrantType(
|
||||||
|
org.springframework.security.oauth2.core
|
||||||
|
.AuthorizationGrantType.AUTHORIZATION_CODE)
|
||||||
.build())
|
.build())
|
||||||
: Optional.empty();
|
: Optional.empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
private Optional<ClientRegistration> oidcClientRegistration() {
|
private Optional<ClientRegistration> oidcClientRegistration() {
|
||||||
OAUTH2 oauth = applicationProperties.getSecurity().getOauth2();
|
OAUTH2 oauth = applicationProperties.getSecurity().getOauth2();
|
||||||
|
if (oauth == null
|
||||||
if (isOAuth2Enabled(oauth) || isClientInitialised(oauth)) {
|
|| oauth.getIssuer() == null
|
||||||
|
|| oauth.getIssuer().isEmpty()
|
||||||
|
|| oauth.getClientId() == null
|
||||||
|
|| oauth.getClientId().isEmpty()
|
||||||
|
|| oauth.getClientSecret() == null
|
||||||
|
|| oauth.getClientSecret().isEmpty()
|
||||||
|
|| oauth.getScopes() == null
|
||||||
|
|| oauth.getScopes().isEmpty()
|
||||||
|
|| oauth.getUseAsUsername() == null
|
||||||
|
|| oauth.getUseAsUsername().isEmpty()) {
|
||||||
return Optional.empty();
|
return Optional.empty();
|
||||||
}
|
}
|
||||||
|
return Optional.of(
|
||||||
String name = oauth.getProvider();
|
ClientRegistrations.fromIssuerLocation(oauth.getIssuer())
|
||||||
String firstChar = String.valueOf(name.charAt(0));
|
.registrationId("oidc")
|
||||||
String clientName = name.replaceFirst(firstChar, firstChar.toUpperCase());
|
.clientId(oauth.getClientId())
|
||||||
|
.clientSecret(oauth.getClientSecret())
|
||||||
Provider oidcProvider =
|
.scope(oauth.getScopes())
|
||||||
new Provider(
|
.userNameAttributeName(oauth.getUseAsUsername())
|
||||||
oauth.getIssuer(),
|
.clientName("OIDC")
|
||||||
name,
|
.build());
|
||||||
clientName,
|
|
||||||
oauth.getClientId(),
|
|
||||||
oauth.getClientSecret(),
|
|
||||||
oauth.getScopes(),
|
|
||||||
UsernameAttribute.valueOf(oauth.getUseAsUsername().toUpperCase()),
|
|
||||||
oauth.getLogoutUrl(),
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null);
|
|
||||||
|
|
||||||
return !isStringEmpty(oidcProvider.getIssuer()) || validateProvider(oidcProvider)
|
|
||||||
? Optional.of(
|
|
||||||
ClientRegistrations.fromIssuerLocation(oauth.getIssuer())
|
|
||||||
.registrationId(name)
|
|
||||||
.clientId(oidcProvider.getClientId())
|
|
||||||
.clientSecret(oidcProvider.getClientSecret())
|
|
||||||
.scope(oidcProvider.getScopes())
|
|
||||||
.userNameAttributeName(oidcProvider.getUseAsUsername().getName())
|
|
||||||
.clientName(clientName)
|
|
||||||
.redirectUri(REDIRECT_URI_PATH + name)
|
|
||||||
.authorizationGrantType(AUTHORIZATION_CODE)
|
|
||||||
.build())
|
|
||||||
: Optional.empty();
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isOAuth2Enabled(OAUTH2 oAuth2) {
|
|
||||||
return oAuth2 == null || !oAuth2.getEnabled();
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isClientInitialised(OAUTH2 oauth2) {
|
|
||||||
Client client = oauth2.getClient();
|
|
||||||
return client == null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
This following function is to grant Authorities to the OAUTH2 user from the values stored in the database.
|
This following function is to grant Authorities to the OAUTH2 user from the values stored in the database.
|
||||||
This is required for the internal; 'hasRole()' function to give out the correct role.
|
This is required for the internal; 'hasRole()' function to give out the correct role.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
@ConditionalOnProperty(value = "security.oauth2.enabled", havingValue = "true")
|
@ConditionalOnProperty(
|
||||||
|
value = "security.oauth2.enabled",
|
||||||
|
havingValue = "true",
|
||||||
|
matchIfMissing = false)
|
||||||
GrantedAuthoritiesMapper userAuthoritiesMapper() {
|
GrantedAuthoritiesMapper userAuthoritiesMapper() {
|
||||||
return (authorities) -> {
|
return (authorities) -> {
|
||||||
Set<GrantedAuthority> mappedAuthorities = new HashSet<>();
|
Set<GrantedAuthority> mappedAuthorities = new HashSet<>();
|
||||||
@@ -241,9 +200,11 @@ public class OAuth2Configuration {
|
|||||||
(String) oauth2Auth.getAttributes().get(useAsUsername));
|
(String) oauth2Auth.getAttributes().get(useAsUsername));
|
||||||
if (userOpt.isPresent()) {
|
if (userOpt.isPresent()) {
|
||||||
User user = userOpt.get();
|
User user = userOpt.get();
|
||||||
mappedAuthorities.add(
|
if (user != null) {
|
||||||
new SimpleGrantedAuthority(
|
mappedAuthorities.add(
|
||||||
userService.findRole(user).getAuthority()));
|
new SimpleGrantedAuthority(
|
||||||
|
userService.findRole(user).getAuthority()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -13,10 +13,8 @@ import org.bouncycastle.openssl.PEMParser;
|
|||||||
import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;
|
import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;
|
||||||
import org.bouncycastle.util.io.pem.PemObject;
|
import org.bouncycastle.util.io.pem.PemObject;
|
||||||
import org.bouncycastle.util.io.pem.PemReader;
|
import org.bouncycastle.util.io.pem.PemReader;
|
||||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
|
||||||
import org.springframework.core.io.Resource;
|
import org.springframework.core.io.Resource;
|
||||||
|
|
||||||
@ConditionalOnProperty(name = "security.saml2.enabled", havingValue = "true")
|
|
||||||
public class CertificateUtils {
|
public class CertificateUtils {
|
||||||
|
|
||||||
public static X509Certificate readCertificate(Resource certificateResource) throws Exception {
|
public static X509Certificate readCertificate(Resource certificateResource) throws Exception {
|
||||||
|
|||||||
@@ -4,13 +4,27 @@ import java.io.Serializable;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
|
||||||
import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticatedPrincipal;
|
import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticatedPrincipal;
|
||||||
|
|
||||||
@ConditionalOnProperty(name = "security.saml2.enabled", havingValue = "true")
|
public class CustomSaml2AuthenticatedPrincipal
|
||||||
public record CustomSaml2AuthenticatedPrincipal(String name, Map<String, List<Object>> attributes, String nameId, List<String> sessionIndexes)
|
|
||||||
implements Saml2AuthenticatedPrincipal, Serializable {
|
implements Saml2AuthenticatedPrincipal, Serializable {
|
||||||
|
|
||||||
|
private final String name;
|
||||||
|
private final Map<String, List<Object>> attributes;
|
||||||
|
private final String nameId;
|
||||||
|
private final List<String> sessionIndexes;
|
||||||
|
|
||||||
|
public CustomSaml2AuthenticatedPrincipal(
|
||||||
|
String name,
|
||||||
|
Map<String, List<Object>> attributes,
|
||||||
|
String nameId,
|
||||||
|
List<String> sessionIndexes) {
|
||||||
|
this.name = name;
|
||||||
|
this.attributes = attributes;
|
||||||
|
this.nameId = nameId;
|
||||||
|
this.sessionIndexes = sessionIndexes;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getName() {
|
public String getName() {
|
||||||
return this.name;
|
return this.name;
|
||||||
@@ -21,4 +35,11 @@ public record CustomSaml2AuthenticatedPrincipal(String name, Map<String, List<Ob
|
|||||||
return this.attributes;
|
return this.attributes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getNameId() {
|
||||||
|
return this.nameId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> getSessionIndexes() {
|
||||||
|
return this.sessionIndexes;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,20 +2,19 @@ package stirling.software.SPDF.config.security.saml2;
|
|||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
|
||||||
import org.springframework.security.authentication.ProviderNotFoundException;
|
import org.springframework.security.authentication.ProviderNotFoundException;
|
||||||
import org.springframework.security.core.AuthenticationException;
|
import org.springframework.security.core.AuthenticationException;
|
||||||
import org.springframework.security.saml2.core.Saml2Error;
|
import org.springframework.security.saml2.core.Saml2Error;
|
||||||
import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationException;
|
import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationException;
|
||||||
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
|
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
|
||||||
|
|
||||||
|
import jakarta.servlet.ServletException;
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
import jakarta.servlet.http.HttpServletResponse;
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@ConditionalOnProperty(name = "security.saml2.enabled", havingValue = "true")
|
|
||||||
public class CustomSaml2AuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler {
|
public class CustomSaml2AuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -23,19 +22,18 @@ public class CustomSaml2AuthenticationFailureHandler extends SimpleUrlAuthentica
|
|||||||
HttpServletRequest request,
|
HttpServletRequest request,
|
||||||
HttpServletResponse response,
|
HttpServletResponse response,
|
||||||
AuthenticationException exception)
|
AuthenticationException exception)
|
||||||
throws IOException {
|
throws IOException, ServletException {
|
||||||
log.error("Authentication error", exception);
|
|
||||||
|
|
||||||
if (exception instanceof Saml2AuthenticationException) {
|
if (exception instanceof Saml2AuthenticationException) {
|
||||||
Saml2Error error = ((Saml2AuthenticationException) exception).getSaml2Error();
|
Saml2Error error = ((Saml2AuthenticationException) exception).getSaml2Error();
|
||||||
getRedirectStrategy()
|
getRedirectStrategy()
|
||||||
.sendRedirect(request, response, "/login?errorOAuth=" + error.getErrorCode());
|
.sendRedirect(request, response, "/login?erroroauth=" + error.getErrorCode());
|
||||||
} else if (exception instanceof ProviderNotFoundException) {
|
} else if (exception instanceof ProviderNotFoundException) {
|
||||||
getRedirectStrategy()
|
getRedirectStrategy()
|
||||||
.sendRedirect(
|
.sendRedirect(
|
||||||
request,
|
request,
|
||||||
response,
|
response,
|
||||||
"/login?errorOAuth=not_authentication_provider_found");
|
"/login?erroroauth=not_authentication_provider_found");
|
||||||
}
|
}
|
||||||
|
log.error("AuthenticationException: " + exception);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ import stirling.software.SPDF.config.security.UserService;
|
|||||||
import stirling.software.SPDF.model.ApplicationProperties;
|
import stirling.software.SPDF.model.ApplicationProperties;
|
||||||
import stirling.software.SPDF.model.ApplicationProperties.Security.SAML2;
|
import stirling.software.SPDF.model.ApplicationProperties.Security.SAML2;
|
||||||
import stirling.software.SPDF.model.AuthenticationType;
|
import stirling.software.SPDF.model.AuthenticationType;
|
||||||
import stirling.software.SPDF.model.exception.UnsupportedProviderException;
|
import stirling.software.SPDF.model.provider.UnsupportedProviderException;
|
||||||
import stirling.software.SPDF.utils.RequestUriUtils;
|
import stirling.software.SPDF.utils.RequestUriUtils;
|
||||||
|
|
||||||
@AllArgsConstructor
|
@AllArgsConstructor
|
||||||
@@ -42,7 +42,7 @@ public class CustomSaml2AuthenticationSuccessHandler
|
|||||||
log.debug("Starting SAML2 authentication success handling");
|
log.debug("Starting SAML2 authentication success handling");
|
||||||
|
|
||||||
if (principal instanceof CustomSaml2AuthenticatedPrincipal) {
|
if (principal instanceof CustomSaml2AuthenticatedPrincipal) {
|
||||||
String username = ((CustomSaml2AuthenticatedPrincipal) principal).name();
|
String username = ((CustomSaml2AuthenticatedPrincipal) principal).getName();
|
||||||
log.debug("Authenticated principal found for user: {}", username);
|
log.debug("Authenticated principal found for user: {}", username);
|
||||||
|
|
||||||
HttpSession session = request.getSession(false);
|
HttpSession session = request.getSession(false);
|
||||||
@@ -97,7 +97,7 @@ public class CustomSaml2AuthenticationSuccessHandler
|
|||||||
"User {} exists with password but is not SSO user, redirecting to logout",
|
"User {} exists with password but is not SSO user, redirecting to logout",
|
||||||
username);
|
username);
|
||||||
response.sendRedirect(
|
response.sendRedirect(
|
||||||
contextPath + "/logout?oAuth2AuthenticationErrorWeb=true");
|
contextPath + "/logout?oauth2AuthenticationErrorWeb=true");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -105,18 +105,20 @@ public class CustomSaml2AuthenticationSuccessHandler
|
|||||||
if (saml2.getBlockRegistration() && !userExists) {
|
if (saml2.getBlockRegistration() && !userExists) {
|
||||||
log.debug("Registration blocked for new user: {}", username);
|
log.debug("Registration blocked for new user: {}", username);
|
||||||
response.sendRedirect(
|
response.sendRedirect(
|
||||||
contextPath + "/login?errorOAuth=oAuth2AdminBlockedUser");
|
contextPath + "/login?erroroauth=oauth2_admin_blocked_user");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
log.debug("Processing SSO post-login for user: {}", username);
|
log.debug("Processing SSO post-login for user: {}", username);
|
||||||
userService.processSSOPostLogin(username, saml2.getAutoCreateUser());
|
userService.processSSOPostLogin(username, saml2.getAutoCreateUser());
|
||||||
log.debug("Successfully processed authentication for user: {}", username);
|
log.debug("Successfully processed authentication for user: {}", username);
|
||||||
response.sendRedirect(contextPath + "/");
|
response.sendRedirect(contextPath + "/");
|
||||||
|
return;
|
||||||
} catch (IllegalArgumentException | SQLException | UnsupportedProviderException e) {
|
} catch (IllegalArgumentException | SQLException | UnsupportedProviderException e) {
|
||||||
log.debug(
|
log.debug(
|
||||||
"Invalid username detected for user: {}, redirecting to logout",
|
"Invalid username detected for user: {}, redirecting to logout",
|
||||||
username);
|
username);
|
||||||
response.sendRedirect(contextPath + "/logout?invalidUsername=true");
|
response.sendRedirect(contextPath + "/logout?invalidUsername=true");
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import org.opensaml.saml.saml2.core.Assertion;
|
|||||||
import org.opensaml.saml.saml2.core.Attribute;
|
import org.opensaml.saml.saml2.core.Attribute;
|
||||||
import org.opensaml.saml.saml2.core.AttributeStatement;
|
import org.opensaml.saml.saml2.core.AttributeStatement;
|
||||||
import org.opensaml.saml.saml2.core.AuthnStatement;
|
import org.opensaml.saml.saml2.core.AuthnStatement;
|
||||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
|
||||||
import org.springframework.core.convert.converter.Converter;
|
import org.springframework.core.convert.converter.Converter;
|
||||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||||
import org.springframework.security.saml2.provider.service.authentication.OpenSaml4AuthenticationProvider.ResponseToken;
|
import org.springframework.security.saml2.provider.service.authentication.OpenSaml4AuthenticationProvider.ResponseToken;
|
||||||
@@ -19,11 +18,10 @@ import stirling.software.SPDF.config.security.UserService;
|
|||||||
import stirling.software.SPDF.model.User;
|
import stirling.software.SPDF.model.User;
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@ConditionalOnProperty(name = "security.saml2.enabled", havingValue = "true")
|
|
||||||
public class CustomSaml2ResponseAuthenticationConverter
|
public class CustomSaml2ResponseAuthenticationConverter
|
||||||
implements Converter<ResponseToken, Saml2Authentication> {
|
implements Converter<ResponseToken, Saml2Authentication> {
|
||||||
|
|
||||||
private final UserService userService;
|
private UserService userService;
|
||||||
|
|
||||||
public CustomSaml2ResponseAuthenticationConverter(UserService userService) {
|
public CustomSaml2ResponseAuthenticationConverter(UserService userService) {
|
||||||
this.userService = userService;
|
this.userService = userService;
|
||||||
@@ -63,10 +61,10 @@ public class CustomSaml2ResponseAuthenticationConverter
|
|||||||
Map<String, List<Object>> attributes = extractAttributes(assertion);
|
Map<String, List<Object>> attributes = extractAttributes(assertion);
|
||||||
|
|
||||||
// Debug log with actual values
|
// Debug log with actual values
|
||||||
log.debug("Extracted SAML Attributes: {}", attributes);
|
log.debug("Extracted SAML Attributes: " + attributes);
|
||||||
|
|
||||||
// Try to get username/identifier in order of preference
|
// Try to get username/identifier in order of preference
|
||||||
String userIdentifier;
|
String userIdentifier = null;
|
||||||
if (hasAttribute(attributes, "username")) {
|
if (hasAttribute(attributes, "username")) {
|
||||||
userIdentifier = getFirstAttributeValue(attributes, "username");
|
userIdentifier = getFirstAttributeValue(attributes, "username");
|
||||||
} else if (hasAttribute(attributes, "emailaddress")) {
|
} else if (hasAttribute(attributes, "emailaddress")) {
|
||||||
@@ -86,8 +84,10 @@ public class CustomSaml2ResponseAuthenticationConverter
|
|||||||
SimpleGrantedAuthority simpleGrantedAuthority = new SimpleGrantedAuthority("ROLE_USER");
|
SimpleGrantedAuthority simpleGrantedAuthority = new SimpleGrantedAuthority("ROLE_USER");
|
||||||
if (userOpt.isPresent()) {
|
if (userOpt.isPresent()) {
|
||||||
User user = userOpt.get();
|
User user = userOpt.get();
|
||||||
simpleGrantedAuthority =
|
if (user != null) {
|
||||||
new SimpleGrantedAuthority(userService.findRole(user).getAuthority());
|
simpleGrantedAuthority =
|
||||||
|
new SimpleGrantedAuthority(userService.findRole(user).getAuthority());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
List<String> sessionIndexes = new ArrayList<>();
|
List<String> sessionIndexes = new ArrayList<>();
|
||||||
@@ -102,7 +102,7 @@ public class CustomSaml2ResponseAuthenticationConverter
|
|||||||
return new Saml2Authentication(
|
return new Saml2Authentication(
|
||||||
principal,
|
principal,
|
||||||
responseToken.getToken().getSaml2Response(),
|
responseToken.getToken().getSaml2Response(),
|
||||||
List.of(simpleGrantedAuthority));
|
Collections.singletonList(simpleGrantedAuthority));
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean hasAttribute(Map<String, List<Object>> attributes, String name) {
|
private boolean hasAttribute(Map<String, List<Object>> attributes, String name) {
|
||||||
|
|||||||
@@ -11,12 +11,10 @@ import org.springframework.context.annotation.Configuration;
|
|||||||
import org.springframework.core.io.Resource;
|
import org.springframework.core.io.Resource;
|
||||||
import org.springframework.security.saml2.core.Saml2X509Credential;
|
import org.springframework.security.saml2.core.Saml2X509Credential;
|
||||||
import org.springframework.security.saml2.core.Saml2X509Credential.Saml2X509CredentialType;
|
import org.springframework.security.saml2.core.Saml2X509Credential.Saml2X509CredentialType;
|
||||||
import org.springframework.security.saml2.provider.service.authentication.AbstractSaml2AuthenticationRequest;
|
|
||||||
import org.springframework.security.saml2.provider.service.registration.InMemoryRelyingPartyRegistrationRepository;
|
import org.springframework.security.saml2.provider.service.registration.InMemoryRelyingPartyRegistrationRepository;
|
||||||
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration;
|
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration;
|
||||||
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository;
|
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository;
|
||||||
import org.springframework.security.saml2.provider.service.registration.Saml2MessageBinding;
|
import org.springframework.security.saml2.provider.service.registration.Saml2MessageBinding;
|
||||||
import org.springframework.security.saml2.provider.service.web.HttpSessionSaml2AuthenticationRequestRepository;
|
|
||||||
import org.springframework.security.saml2.provider.service.web.authentication.OpenSaml4AuthenticationRequestResolver;
|
import org.springframework.security.saml2.provider.service.web.authentication.OpenSaml4AuthenticationRequestResolver;
|
||||||
|
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
@@ -28,20 +26,27 @@ import stirling.software.SPDF.model.ApplicationProperties.Security.SAML2;
|
|||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@ConditionalOnProperty(value = "security.saml2.enabled", havingValue = "true")
|
@ConditionalOnProperty(
|
||||||
|
value = "security.saml2.enabled",
|
||||||
|
havingValue = "true",
|
||||||
|
matchIfMissing = false)
|
||||||
public class SAML2Configuration {
|
public class SAML2Configuration {
|
||||||
|
|
||||||
private final ApplicationProperties applicationProperties;
|
private final ApplicationProperties applicationProperties;
|
||||||
|
|
||||||
public SAML2Configuration(ApplicationProperties applicationProperties) {
|
public SAML2Configuration(ApplicationProperties applicationProperties) {
|
||||||
|
|
||||||
this.applicationProperties = applicationProperties;
|
this.applicationProperties = applicationProperties;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
@ConditionalOnProperty(name = "security.saml2.enabled", havingValue = "true")
|
@ConditionalOnProperty(
|
||||||
|
name = "security.saml2.enabled",
|
||||||
|
havingValue = "true",
|
||||||
|
matchIfMissing = false)
|
||||||
public RelyingPartyRegistrationRepository relyingPartyRegistrations() throws Exception {
|
public RelyingPartyRegistrationRepository relyingPartyRegistrations() throws Exception {
|
||||||
SAML2 samlConf = applicationProperties.getSecurity().getSaml2();
|
SAML2 samlConf = applicationProperties.getSecurity().getSaml2();
|
||||||
X509Certificate idpCert = CertificateUtils.readCertificate(samlConf.getIdpCert());
|
X509Certificate idpCert = CertificateUtils.readCertificate(samlConf.getidpCert());
|
||||||
Saml2X509Credential verificationCredential = Saml2X509Credential.verification(idpCert);
|
Saml2X509Credential verificationCredential = Saml2X509Credential.verification(idpCert);
|
||||||
Resource privateKeyResource = samlConf.getPrivateKey();
|
Resource privateKeyResource = samlConf.getPrivateKey();
|
||||||
Resource certificateResource = samlConf.getSpCert();
|
Resource certificateResource = samlConf.getSpCert();
|
||||||
@@ -53,124 +58,81 @@ public class SAML2Configuration {
|
|||||||
RelyingPartyRegistration rp =
|
RelyingPartyRegistration rp =
|
||||||
RelyingPartyRegistration.withRegistrationId(samlConf.getRegistrationId())
|
RelyingPartyRegistration.withRegistrationId(samlConf.getRegistrationId())
|
||||||
.signingX509Credentials(c -> c.add(signingCredential))
|
.signingX509Credentials(c -> c.add(signingCredential))
|
||||||
.entityId(samlConf.getIdpIssuer())
|
|
||||||
.singleLogoutServiceBinding(Saml2MessageBinding.POST)
|
|
||||||
.singleLogoutServiceLocation(samlConf.getIdpSingleLogoutUrl())
|
|
||||||
.singleLogoutServiceResponseLocation("http://localhost:8080/login")
|
|
||||||
.assertionConsumerServiceBinding(Saml2MessageBinding.POST)
|
|
||||||
.assertionConsumerServiceLocation(
|
|
||||||
"{baseUrl}/login/saml2/sso/{registrationId}")
|
|
||||||
.assertingPartyMetadata(
|
.assertingPartyMetadata(
|
||||||
metadata ->
|
metadata ->
|
||||||
metadata.entityId(samlConf.getIdpIssuer())
|
metadata.entityId(samlConf.getIdpIssuer())
|
||||||
|
.singleSignOnServiceLocation(
|
||||||
|
samlConf.getIdpSingleLoginUrl())
|
||||||
.verificationX509Credentials(
|
.verificationX509Credentials(
|
||||||
c -> c.add(verificationCredential))
|
c -> c.add(verificationCredential))
|
||||||
.singleSignOnServiceBinding(
|
.singleSignOnServiceBinding(
|
||||||
Saml2MessageBinding.POST)
|
Saml2MessageBinding.POST)
|
||||||
.singleSignOnServiceLocation(
|
|
||||||
samlConf.getIdpSingleLoginUrl())
|
|
||||||
.singleLogoutServiceBinding(
|
|
||||||
Saml2MessageBinding.POST)
|
|
||||||
.singleLogoutServiceLocation(
|
|
||||||
samlConf.getIdpSingleLogoutUrl())
|
|
||||||
.wantAuthnRequestsSigned(true))
|
.wantAuthnRequestsSigned(true))
|
||||||
.build();
|
.build();
|
||||||
return new InMemoryRelyingPartyRegistrationRepository(rp);
|
return new InMemoryRelyingPartyRegistrationRepository(rp);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
@ConditionalOnProperty(name = "security.saml2.enabled", havingValue = "true")
|
@ConditionalOnProperty(
|
||||||
|
name = "security.saml2.enabled",
|
||||||
|
havingValue = "true",
|
||||||
|
matchIfMissing = false)
|
||||||
public OpenSaml4AuthenticationRequestResolver authenticationRequestResolver(
|
public OpenSaml4AuthenticationRequestResolver authenticationRequestResolver(
|
||||||
RelyingPartyRegistrationRepository relyingPartyRegistrationRepository) {
|
RelyingPartyRegistrationRepository relyingPartyRegistrationRepository) {
|
||||||
OpenSaml4AuthenticationRequestResolver resolver =
|
OpenSaml4AuthenticationRequestResolver resolver =
|
||||||
new OpenSaml4AuthenticationRequestResolver(relyingPartyRegistrationRepository);
|
new OpenSaml4AuthenticationRequestResolver(relyingPartyRegistrationRepository);
|
||||||
|
|
||||||
resolver.setAuthnRequestCustomizer(
|
resolver.setAuthnRequestCustomizer(
|
||||||
customizer -> {
|
customizer -> {
|
||||||
HttpServletRequest request = customizer.getRequest();
|
log.debug("Customizing SAML Authentication request");
|
||||||
AuthnRequest authnRequest = customizer.getAuthnRequest();
|
AuthnRequest authnRequest = customizer.getAuthnRequest();
|
||||||
HttpSessionSaml2AuthenticationRequestRepository requestRepository =
|
log.debug("AuthnRequest ID: {}", authnRequest.getID());
|
||||||
new HttpSessionSaml2AuthenticationRequestRepository();
|
if (authnRequest.getID() == null) {
|
||||||
AbstractSaml2AuthenticationRequest saml2AuthenticationRequest =
|
authnRequest.setID("ARQ" + UUID.randomUUID().toString());
|
||||||
requestRepository.loadAuthenticationRequest(request);
|
}
|
||||||
|
log.debug("AuthnRequest new ID after set: {}", authnRequest.getID());
|
||||||
if (saml2AuthenticationRequest != null) {
|
log.debug("AuthnRequest IssueInstant: {}", authnRequest.getIssueInstant());
|
||||||
String sessionId = request.getSession(false).getId();
|
log.debug(
|
||||||
|
"AuthnRequest Issuer: {}",
|
||||||
log.debug(
|
authnRequest.getIssuer() != null
|
||||||
"Retrieving SAML 2 authentication request ID from the current HTTP session {}",
|
? authnRequest.getIssuer().getValue()
|
||||||
sessionId);
|
: "null");
|
||||||
|
HttpServletRequest request = customizer.getRequest();
|
||||||
String authenticationRequestId = saml2AuthenticationRequest.getId();
|
// Log HTTP request details
|
||||||
|
log.debug("HTTP Request Method: {}", request.getMethod());
|
||||||
if (!authenticationRequestId.isBlank()) {
|
log.debug("Request URI: {}", request.getRequestURI());
|
||||||
authnRequest.setID(authenticationRequestId);
|
log.debug("Request URL: {}", request.getRequestURL().toString());
|
||||||
} else {
|
log.debug("Query String: {}", request.getQueryString());
|
||||||
log.warn(
|
log.debug("Remote Address: {}", request.getRemoteAddr());
|
||||||
"No authentication request found for HTTP session {}. Generating new ID",
|
// Log headers
|
||||||
sessionId);
|
Collections.list(request.getHeaderNames())
|
||||||
authnRequest.setID("ARQ" + UUID.randomUUID().toString().substring(1));
|
.forEach(
|
||||||
}
|
headerName -> {
|
||||||
} else {
|
log.debug(
|
||||||
log.debug("Generating new authentication request ID");
|
"Header - {}: {}",
|
||||||
authnRequest.setID("ARQ" + UUID.randomUUID().toString().substring(1));
|
headerName,
|
||||||
|
request.getHeader(headerName));
|
||||||
|
});
|
||||||
|
// Log SAML specific parameters
|
||||||
|
log.debug("SAML Request Parameters:");
|
||||||
|
log.debug("SAMLRequest: {}", request.getParameter("SAMLRequest"));
|
||||||
|
log.debug("RelayState: {}", request.getParameter("RelayState"));
|
||||||
|
// Log session debugrmation if exists
|
||||||
|
if (request.getSession(false) != null) {
|
||||||
|
log.debug("Session ID: {}", request.getSession().getId());
|
||||||
|
}
|
||||||
|
// Log any assertions consumer service details if present
|
||||||
|
if (authnRequest.getAssertionConsumerServiceURL() != null) {
|
||||||
|
log.debug(
|
||||||
|
"AssertionConsumerServiceURL: {}",
|
||||||
|
authnRequest.getAssertionConsumerServiceURL());
|
||||||
|
}
|
||||||
|
// Log NameID policy if present
|
||||||
|
if (authnRequest.getNameIDPolicy() != null) {
|
||||||
|
log.debug(
|
||||||
|
"NameIDPolicy Format: {}",
|
||||||
|
authnRequest.getNameIDPolicy().getFormat());
|
||||||
}
|
}
|
||||||
|
|
||||||
logAuthnRequestDetails(authnRequest);
|
|
||||||
logHttpRequestDetails(request);
|
|
||||||
});
|
});
|
||||||
return resolver;
|
return resolver;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void logAuthnRequestDetails(AuthnRequest authnRequest) {
|
|
||||||
String message =
|
|
||||||
"""
|
|
||||||
AuthnRequest:
|
|
||||||
|
|
||||||
ID: {}
|
|
||||||
Issuer: {}
|
|
||||||
IssueInstant: {}
|
|
||||||
AssertionConsumerService (ACS) URL: {}
|
|
||||||
""";
|
|
||||||
log.debug(
|
|
||||||
message,
|
|
||||||
authnRequest.getID(),
|
|
||||||
authnRequest.getIssuer() != null ? authnRequest.getIssuer().getValue() : null,
|
|
||||||
authnRequest.getIssueInstant(),
|
|
||||||
authnRequest.getAssertionConsumerServiceURL());
|
|
||||||
|
|
||||||
if (authnRequest.getNameIDPolicy() != null) {
|
|
||||||
log.debug("NameIDPolicy Format: {}", authnRequest.getNameIDPolicy().getFormat());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void logHttpRequestDetails(HttpServletRequest request) {
|
|
||||||
log.debug("HTTP Headers: ");
|
|
||||||
Collections.list(request.getHeaderNames())
|
|
||||||
.forEach(
|
|
||||||
headerName ->
|
|
||||||
log.debug("{}: {}", headerName, request.getHeader(headerName)));
|
|
||||||
String message =
|
|
||||||
"""
|
|
||||||
HTTP Request Method: {}
|
|
||||||
Session ID: {}
|
|
||||||
Request Path: {}
|
|
||||||
Query String: {}
|
|
||||||
Remote Address: {}
|
|
||||||
|
|
||||||
SAML Request Parameters:
|
|
||||||
|
|
||||||
SAMLRequest: {}
|
|
||||||
RelayState: {}
|
|
||||||
""";
|
|
||||||
log.debug(
|
|
||||||
message,
|
|
||||||
request.getMethod(),
|
|
||||||
request.getSession().getId(),
|
|
||||||
request.getRequestURI(),
|
|
||||||
request.getQueryString(),
|
|
||||||
request.getRemoteAddr(),
|
|
||||||
request.getParameter("SAMLRequest"),
|
|
||||||
request.getParameter("RelayState"));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ public class SessionPersistentRegistry implements SessionRegistry {
|
|||||||
} else if (principal instanceof OAuth2User) {
|
} else if (principal instanceof OAuth2User) {
|
||||||
principalName = ((OAuth2User) principal).getName();
|
principalName = ((OAuth2User) principal).getName();
|
||||||
} else if (principal instanceof CustomSaml2AuthenticatedPrincipal) {
|
} else if (principal instanceof CustomSaml2AuthenticatedPrincipal) {
|
||||||
principalName = ((CustomSaml2AuthenticatedPrincipal) principal).name();
|
principalName = ((CustomSaml2AuthenticatedPrincipal) principal).getName();
|
||||||
} else if (principal instanceof String) {
|
} else if (principal instanceof String) {
|
||||||
principalName = (String) principal;
|
principalName = (String) principal;
|
||||||
}
|
}
|
||||||
@@ -79,7 +79,7 @@ public class SessionPersistentRegistry implements SessionRegistry {
|
|||||||
} else if (principal instanceof OAuth2User) {
|
} else if (principal instanceof OAuth2User) {
|
||||||
principalName = ((OAuth2User) principal).getName();
|
principalName = ((OAuth2User) principal).getName();
|
||||||
} else if (principal instanceof CustomSaml2AuthenticatedPrincipal) {
|
} else if (principal instanceof CustomSaml2AuthenticatedPrincipal) {
|
||||||
principalName = ((CustomSaml2AuthenticatedPrincipal) principal).name();
|
principalName = ((CustomSaml2AuthenticatedPrincipal) principal).getName();
|
||||||
} else if (principal instanceof String) {
|
} else if (principal instanceof String) {
|
||||||
principalName = (String) principal;
|
principalName = (String) principal;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ import stirling.software.SPDF.model.AuthenticationType;
|
|||||||
import stirling.software.SPDF.model.Role;
|
import stirling.software.SPDF.model.Role;
|
||||||
import stirling.software.SPDF.model.User;
|
import stirling.software.SPDF.model.User;
|
||||||
import stirling.software.SPDF.model.api.user.UsernameAndPass;
|
import stirling.software.SPDF.model.api.user.UsernameAndPass;
|
||||||
import stirling.software.SPDF.model.exception.UnsupportedProviderException;
|
import stirling.software.SPDF.model.provider.UnsupportedProviderException;
|
||||||
|
|
||||||
@Controller
|
@Controller
|
||||||
@Tag(name = "User", description = "User APIs")
|
@Tag(name = "User", description = "User APIs")
|
||||||
@@ -126,7 +126,7 @@ public class UserController {
|
|||||||
return new RedirectView("/change-creds?messageType=notAuthenticated", true);
|
return new RedirectView("/change-creds?messageType=notAuthenticated", true);
|
||||||
}
|
}
|
||||||
Optional<User> userOpt = userService.findByUsernameIgnoreCase(principal.getName());
|
Optional<User> userOpt = userService.findByUsernameIgnoreCase(principal.getName());
|
||||||
if (userOpt.isEmpty()) {
|
if (userOpt == null || userOpt.isEmpty()) {
|
||||||
return new RedirectView("/change-creds?messageType=userNotFound", true);
|
return new RedirectView("/change-creds?messageType=userNotFound", true);
|
||||||
}
|
}
|
||||||
User user = userOpt.get();
|
User user = userOpt.get();
|
||||||
@@ -154,7 +154,7 @@ public class UserController {
|
|||||||
return new RedirectView("/account?messageType=notAuthenticated", true);
|
return new RedirectView("/account?messageType=notAuthenticated", true);
|
||||||
}
|
}
|
||||||
Optional<User> userOpt = userService.findByUsernameIgnoreCase(principal.getName());
|
Optional<User> userOpt = userService.findByUsernameIgnoreCase(principal.getName());
|
||||||
if (userOpt.isEmpty()) {
|
if (userOpt == null || userOpt.isEmpty()) {
|
||||||
return new RedirectView("/account?messageType=userNotFound", true);
|
return new RedirectView("/account?messageType=userNotFound", true);
|
||||||
}
|
}
|
||||||
User user = userOpt.get();
|
User user = userOpt.get();
|
||||||
@@ -176,7 +176,7 @@ public class UserController {
|
|||||||
for (Map.Entry<String, String[]> entry : paramMap.entrySet()) {
|
for (Map.Entry<String, String[]> entry : paramMap.entrySet()) {
|
||||||
updates.put(entry.getKey(), entry.getValue()[0]);
|
updates.put(entry.getKey(), entry.getValue()[0]);
|
||||||
}
|
}
|
||||||
log.debug("Processed updates: {}", updates);
|
log.debug("Processed updates: " + updates);
|
||||||
// Assuming you have a method in userService to update the settings for a user
|
// Assuming you have a method in userService to update the settings for a user
|
||||||
userService.updateUserSettings(principal.getName(), updates);
|
userService.updateUserSettings(principal.getName(), updates);
|
||||||
// Redirect to a page of your choice after updating
|
// Redirect to a page of your choice after updating
|
||||||
@@ -199,7 +199,7 @@ public class UserController {
|
|||||||
Optional<User> userOpt = userService.findByUsernameIgnoreCase(username);
|
Optional<User> userOpt = userService.findByUsernameIgnoreCase(username);
|
||||||
if (userOpt.isPresent()) {
|
if (userOpt.isPresent()) {
|
||||||
User user = userOpt.get();
|
User user = userOpt.get();
|
||||||
if (user.getUsername().equalsIgnoreCase(username)) {
|
if (user != null && user.getUsername().equalsIgnoreCase(username)) {
|
||||||
return new RedirectView("/addUsers?messageType=usernameExists", true);
|
return new RedirectView("/addUsers?messageType=usernameExists", true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -276,7 +276,7 @@ public class UserController {
|
|||||||
Authentication authentication)
|
Authentication authentication)
|
||||||
throws SQLException, UnsupportedProviderException {
|
throws SQLException, UnsupportedProviderException {
|
||||||
Optional<User> userOpt = userService.findByUsernameIgnoreCase(username);
|
Optional<User> userOpt = userService.findByUsernameIgnoreCase(username);
|
||||||
if (userOpt.isEmpty()) {
|
if (!userOpt.isPresent()) {
|
||||||
return new RedirectView("/addUsers?messageType=userNotFound", true);
|
return new RedirectView("/addUsers?messageType=userNotFound", true);
|
||||||
}
|
}
|
||||||
if (!userService.usernameExistsIgnoreCase(username)) {
|
if (!userService.usernameExistsIgnoreCase(username)) {
|
||||||
@@ -295,20 +295,20 @@ public class UserController {
|
|||||||
List<Object> principals = sessionRegistry.getAllPrincipals();
|
List<Object> principals = sessionRegistry.getAllPrincipals();
|
||||||
String userNameP = "";
|
String userNameP = "";
|
||||||
for (Object principal : principals) {
|
for (Object principal : principals) {
|
||||||
List<SessionInformation> sessionsInformation =
|
List<SessionInformation> sessionsInformations =
|
||||||
sessionRegistry.getAllSessions(principal, false);
|
sessionRegistry.getAllSessions(principal, false);
|
||||||
if (principal instanceof UserDetails) {
|
if (principal instanceof UserDetails) {
|
||||||
userNameP = ((UserDetails) principal).getUsername();
|
userNameP = ((UserDetails) principal).getUsername();
|
||||||
} else if (principal instanceof OAuth2User) {
|
} else if (principal instanceof OAuth2User) {
|
||||||
userNameP = ((OAuth2User) principal).getName();
|
userNameP = ((OAuth2User) principal).getName();
|
||||||
} else if (principal instanceof CustomSaml2AuthenticatedPrincipal) {
|
} else if (principal instanceof CustomSaml2AuthenticatedPrincipal) {
|
||||||
userNameP = ((CustomSaml2AuthenticatedPrincipal) principal).name();
|
userNameP = ((CustomSaml2AuthenticatedPrincipal) principal).getName();
|
||||||
} else if (principal instanceof String) {
|
} else if (principal instanceof String) {
|
||||||
userNameP = (String) principal;
|
userNameP = (String) principal;
|
||||||
}
|
}
|
||||||
if (userNameP.equalsIgnoreCase(username)) {
|
if (userNameP.equalsIgnoreCase(username)) {
|
||||||
for (SessionInformation sessionInfo : sessionsInformation) {
|
for (SessionInformation sessionsInformation : sessionsInformations) {
|
||||||
sessionRegistry.expireSession(sessionInfo.getSessionId());
|
sessionRegistry.expireSession(sessionsInformation.getSessionId());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,8 +52,7 @@ public class CompressController {
|
|||||||
this.pdfDocumentFactory = pdfDocumentFactory;
|
this.pdfDocumentFactory = pdfDocumentFactory;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void compressImagesInPDF(Path pdfFile, double initialScaleFactor, boolean grayScale)
|
private void compressImagesInPDF(Path pdfFile, double initialScaleFactor) throws Exception {
|
||||||
throws Exception {
|
|
||||||
byte[] fileBytes = Files.readAllBytes(pdfFile);
|
byte[] fileBytes = Files.readAllBytes(pdfFile);
|
||||||
try (PDDocument doc = Loader.loadPDF(fileBytes)) {
|
try (PDDocument doc = Loader.loadPDF(fileBytes)) {
|
||||||
double scaleFactor = initialScaleFactor;
|
double scaleFactor = initialScaleFactor;
|
||||||
@@ -78,23 +77,11 @@ public class CompressController {
|
|||||||
bufferedImage.getScaledInstance(
|
bufferedImage.getScaledInstance(
|
||||||
newWidth, newHeight, Image.SCALE_SMOOTH);
|
newWidth, newHeight, Image.SCALE_SMOOTH);
|
||||||
|
|
||||||
BufferedImage scaledBufferedImage;
|
BufferedImage scaledBufferedImage =
|
||||||
if (grayScale
|
new BufferedImage(
|
||||||
|| bufferedImage.getType() == BufferedImage.TYPE_BYTE_GRAY) {
|
newWidth, newHeight, BufferedImage.TYPE_INT_RGB);
|
||||||
scaledBufferedImage =
|
scaledBufferedImage.getGraphics().drawImage(scaledImage, 0, 0, null);
|
||||||
new BufferedImage(
|
|
||||||
newWidth, newHeight, BufferedImage.TYPE_BYTE_GRAY);
|
|
||||||
scaledBufferedImage
|
|
||||||
.getGraphics()
|
|
||||||
.drawImage(scaledImage, 0, 0, null);
|
|
||||||
} else {
|
|
||||||
scaledBufferedImage =
|
|
||||||
new BufferedImage(
|
|
||||||
newWidth, newHeight, BufferedImage.TYPE_INT_RGB);
|
|
||||||
scaledBufferedImage
|
|
||||||
.getGraphics()
|
|
||||||
.drawImage(scaledImage, 0, 0, null);
|
|
||||||
}
|
|
||||||
ByteArrayOutputStream compressedImageStream =
|
ByteArrayOutputStream compressedImageStream =
|
||||||
new ByteArrayOutputStream();
|
new ByteArrayOutputStream();
|
||||||
ImageIO.write(scaledBufferedImage, "jpeg", compressedImageStream);
|
ImageIO.write(scaledBufferedImage, "jpeg", compressedImageStream);
|
||||||
@@ -153,7 +140,6 @@ public class CompressController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
boolean sizeMet = false;
|
boolean sizeMet = false;
|
||||||
boolean grayscaleEnabled = Boolean.TRUE.equals(request.getGrayscale());
|
|
||||||
while (!sizeMet && optimizeLevel <= 9) {
|
while (!sizeMet && optimizeLevel <= 9) {
|
||||||
|
|
||||||
// Apply additional image compression for levels 6-9
|
// Apply additional image compression for levels 6-9
|
||||||
@@ -167,7 +153,7 @@ public class CompressController {
|
|||||||
case 9 -> 0.5; // 60% of original size
|
case 9 -> 0.5; // 60% of original size
|
||||||
default -> 1.0;
|
default -> 1.0;
|
||||||
};
|
};
|
||||||
compressImagesInPDF(tempInputFile, scaleFactor, grayscaleEnabled);
|
compressImagesInPDF(tempInputFile, scaleFactor);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run QPDF optimization
|
// Run QPDF optimization
|
||||||
@@ -184,7 +170,6 @@ public class CompressController {
|
|||||||
command.add("--compression-level=" + optimizeLevel);
|
command.add("--compression-level=" + optimizeLevel);
|
||||||
command.add("--compress-streams=y");
|
command.add("--compress-streams=y");
|
||||||
command.add("--object-streams=generate");
|
command.add("--object-streams=generate");
|
||||||
command.add("--no-warn");
|
|
||||||
command.add(tempInputFile.toString());
|
command.add(tempInputFile.toString());
|
||||||
command.add(tempOutputFile.toString());
|
command.add(tempOutputFile.toString());
|
||||||
|
|
||||||
|
|||||||
@@ -1,15 +1,8 @@
|
|||||||
package stirling.software.SPDF.controller.web;
|
package stirling.software.SPDF.controller.web;
|
||||||
|
|
||||||
import static stirling.software.SPDF.utils.validation.Validator.validateProvider;
|
|
||||||
|
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.time.temporal.ChronoUnit;
|
import java.time.temporal.ChronoUnit;
|
||||||
import java.util.Date;
|
import java.util.*;
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import org.springframework.security.access.prepost.PreAuthorize;
|
import org.springframework.security.access.prepost.PreAuthorize;
|
||||||
@@ -31,16 +24,12 @@ import lombok.extern.slf4j.Slf4j;
|
|||||||
|
|
||||||
import stirling.software.SPDF.config.security.saml2.CustomSaml2AuthenticatedPrincipal;
|
import stirling.software.SPDF.config.security.saml2.CustomSaml2AuthenticatedPrincipal;
|
||||||
import stirling.software.SPDF.config.security.session.SessionPersistentRegistry;
|
import stirling.software.SPDF.config.security.session.SessionPersistentRegistry;
|
||||||
import stirling.software.SPDF.model.ApplicationProperties;
|
import stirling.software.SPDF.model.*;
|
||||||
import stirling.software.SPDF.model.ApplicationProperties.Security;
|
import stirling.software.SPDF.model.ApplicationProperties.Security;
|
||||||
import stirling.software.SPDF.model.ApplicationProperties.Security.OAUTH2;
|
import stirling.software.SPDF.model.ApplicationProperties.Security.OAUTH2;
|
||||||
import stirling.software.SPDF.model.ApplicationProperties.Security.OAUTH2.Client;
|
import stirling.software.SPDF.model.ApplicationProperties.Security.OAUTH2.Client;
|
||||||
import stirling.software.SPDF.model.ApplicationProperties.Security.SAML2;
|
import stirling.software.SPDF.model.ApplicationProperties.Security.SAML2;
|
||||||
import stirling.software.SPDF.model.Authority;
|
import stirling.software.SPDF.model.provider.GithubProvider;
|
||||||
import stirling.software.SPDF.model.Role;
|
|
||||||
import stirling.software.SPDF.model.SessionEntity;
|
|
||||||
import stirling.software.SPDF.model.User;
|
|
||||||
import stirling.software.SPDF.model.provider.GitHubProvider;
|
|
||||||
import stirling.software.SPDF.model.provider.GoogleProvider;
|
import stirling.software.SPDF.model.provider.GoogleProvider;
|
||||||
import stirling.software.SPDF.model.provider.KeycloakProvider;
|
import stirling.software.SPDF.model.provider.KeycloakProvider;
|
||||||
import stirling.software.SPDF.repository.UserRepository;
|
import stirling.software.SPDF.repository.UserRepository;
|
||||||
@@ -50,12 +39,12 @@ import stirling.software.SPDF.repository.UserRepository;
|
|||||||
@Tag(name = "Account Security", description = "Account Security APIs")
|
@Tag(name = "Account Security", description = "Account Security APIs")
|
||||||
public class AccountWebController {
|
public class AccountWebController {
|
||||||
|
|
||||||
public static final String OAUTH_2_AUTHORIZATION = "/oauth2/authorization/";
|
|
||||||
|
|
||||||
private final ApplicationProperties applicationProperties;
|
private final ApplicationProperties applicationProperties;
|
||||||
|
|
||||||
private final SessionPersistentRegistry sessionPersistentRegistry;
|
private final SessionPersistentRegistry sessionPersistentRegistry;
|
||||||
// Assuming you have a repository for user operations
|
|
||||||
private final UserRepository userRepository;
|
private final UserRepository // Assuming you have a repository for user operations
|
||||||
|
userRepository;
|
||||||
|
|
||||||
public AccountWebController(
|
public AccountWebController(
|
||||||
ApplicationProperties applicationProperties,
|
ApplicationProperties applicationProperties,
|
||||||
@@ -72,127 +61,132 @@ public class AccountWebController {
|
|||||||
if (authentication != null && authentication.isAuthenticated()) {
|
if (authentication != null && authentication.isAuthenticated()) {
|
||||||
return "redirect:/";
|
return "redirect:/";
|
||||||
}
|
}
|
||||||
|
|
||||||
Map<String, String> providerList = new HashMap<>();
|
Map<String, String> providerList = new HashMap<>();
|
||||||
Security securityProps = applicationProperties.getSecurity();
|
Security securityProps = applicationProperties.getSecurity();
|
||||||
OAUTH2 oauth = securityProps.getOauth2();
|
OAUTH2 oauth = securityProps.getOauth2();
|
||||||
|
|
||||||
if (oauth != null) {
|
if (oauth != null) {
|
||||||
if (oauth.getEnabled()) {
|
if (oauth.getEnabled()) {
|
||||||
if (oauth.isSettingsValid()) {
|
if (oauth.isSettingsValid()) {
|
||||||
String firstChar = String.valueOf(oauth.getProvider().charAt(0));
|
providerList.put("/oauth2/authorization/oidc", oauth.getProvider());
|
||||||
String clientName =
|
|
||||||
oauth.getProvider().replaceFirst(firstChar, firstChar.toUpperCase());
|
|
||||||
providerList.put(OAUTH_2_AUTHORIZATION + oauth.getProvider(), clientName);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Client client = oauth.getClient();
|
Client client = oauth.getClient();
|
||||||
|
|
||||||
if (client != null) {
|
if (client != null) {
|
||||||
GoogleProvider google = client.getGoogle();
|
GoogleProvider google = client.getGoogle();
|
||||||
|
if (google.isSettingsValid()) {
|
||||||
if (validateProvider(google)) {
|
|
||||||
providerList.put(
|
providerList.put(
|
||||||
OAUTH_2_AUTHORIZATION + google.getName(), google.getClientName());
|
"/oauth2/authorization/" + google.getName(),
|
||||||
|
google.getClientName());
|
||||||
}
|
}
|
||||||
|
GithubProvider github = client.getGithub();
|
||||||
GitHubProvider github = client.getGithub();
|
if (github.isSettingsValid()) {
|
||||||
|
|
||||||
if (validateProvider(github)) {
|
|
||||||
providerList.put(
|
providerList.put(
|
||||||
OAUTH_2_AUTHORIZATION + github.getName(), github.getClientName());
|
"/oauth2/authorization/" + github.getName(),
|
||||||
|
github.getClientName());
|
||||||
}
|
}
|
||||||
|
|
||||||
KeycloakProvider keycloak = client.getKeycloak();
|
KeycloakProvider keycloak = client.getKeycloak();
|
||||||
|
if (keycloak.isSettingsValid()) {
|
||||||
if (validateProvider(keycloak)) {
|
|
||||||
providerList.put(
|
providerList.put(
|
||||||
OAUTH_2_AUTHORIZATION + keycloak.getName(),
|
"/oauth2/authorization/" + keycloak.getName(),
|
||||||
keycloak.getClientName());
|
keycloak.getClientName());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
SAML2 saml2 = securityProps.getSaml2();
|
SAML2 saml2 = securityProps.getSaml2();
|
||||||
|
if (securityProps.isSaml2Activ()
|
||||||
if (securityProps.isSaml2Active()
|
&& applicationProperties.getSystem().getEnableAlphaFunctionality()) {
|
||||||
&& applicationProperties.getSystem().getEnableAlphaFunctionality()
|
providerList.put("/saml2/authenticate/" + saml2.getRegistrationId(), "SAML 2");
|
||||||
&& applicationProperties.getEnterpriseEdition().isEnabled()) {
|
|
||||||
String samlIdp = saml2.getProvider();
|
|
||||||
String saml2AuthenticationPath = "/saml2/authenticate/" + saml2.getRegistrationId();
|
|
||||||
|
|
||||||
if (applicationProperties.getEnterpriseEdition().isSsoAutoLogin()) {
|
|
||||||
return "redirect:"
|
|
||||||
+ request.getRequestURL()
|
|
||||||
+ saml2AuthenticationPath;
|
|
||||||
} else {
|
|
||||||
providerList.put(saml2AuthenticationPath, samlIdp + " (SAML 2)");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove any null keys/values from the providerList
|
// Remove any null keys/values from the providerList
|
||||||
providerList
|
providerList
|
||||||
.entrySet()
|
.entrySet()
|
||||||
.removeIf(entry -> entry.getKey() == null || entry.getValue() == null);
|
.removeIf(entry -> entry.getKey() == null || entry.getValue() == null);
|
||||||
model.addAttribute("providerList", providerList);
|
model.addAttribute("providerlist", providerList);
|
||||||
model.addAttribute("loginMethod", securityProps.getLoginMethod());
|
model.addAttribute("loginMethod", securityProps.getLoginMethod());
|
||||||
|
boolean altLogin = providerList.size() > 0 ? securityProps.isAltLogin() : false;
|
||||||
boolean altLogin = !providerList.isEmpty() ? securityProps.isAltLogin() : false;
|
|
||||||
|
|
||||||
model.addAttribute("altLogin", altLogin);
|
model.addAttribute("altLogin", altLogin);
|
||||||
model.addAttribute("currentPage", "login");
|
model.addAttribute("currentPage", "login");
|
||||||
String error = request.getParameter("error");
|
String error = request.getParameter("error");
|
||||||
|
|
||||||
if (error != null) {
|
if (error != null) {
|
||||||
switch (error) {
|
switch (error) {
|
||||||
case "badCredentials" -> error = "login.invalid";
|
case "badcredentials":
|
||||||
case "locked" -> error = "login.locked";
|
error = "login.invalid";
|
||||||
case "oauth2AuthenticationError" -> error = "userAlreadyExistsOAuthMessage";
|
break;
|
||||||
|
case "locked":
|
||||||
|
error = "login.locked";
|
||||||
|
break;
|
||||||
|
case "oauth2AuthenticationError":
|
||||||
|
error = "userAlreadyExistsOAuthMessage";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
model.addAttribute("error", error);
|
model.addAttribute("error", error);
|
||||||
}
|
}
|
||||||
|
String erroroauth = request.getParameter("erroroauth");
|
||||||
String errorOAuth = request.getParameter("errorOAuth");
|
if (erroroauth != null) {
|
||||||
|
switch (erroroauth) {
|
||||||
if (errorOAuth != null) {
|
case "oauth2AutoCreateDisabled":
|
||||||
switch (errorOAuth) {
|
erroroauth = "login.oauth2AutoCreateDisabled";
|
||||||
case "oAuth2AutoCreateDisabled" -> errorOAuth = "login.oAuth2AutoCreateDisabled";
|
break;
|
||||||
case "invalidUsername" -> errorOAuth = "login.invalid";
|
case "invalidUsername":
|
||||||
case "userAlreadyExistsWeb" -> errorOAuth = "userAlreadyExistsWebMessage";
|
erroroauth = "login.invalid";
|
||||||
case "oAuth2AuthenticationErrorWeb" -> errorOAuth = "login.oauth2InvalidUserType";
|
break;
|
||||||
case "invalid_token_response" -> errorOAuth = "login.oauth2InvalidTokenResponse";
|
case "userAlreadyExistsWeb":
|
||||||
case "authorization_request_not_found" ->
|
erroroauth = "userAlreadyExistsWebMessage";
|
||||||
errorOAuth = "login.oauth2RequestNotFound";
|
break;
|
||||||
case "access_denied" -> errorOAuth = "login.oauth2AccessDenied";
|
case "oauth2AuthenticationErrorWeb":
|
||||||
case "invalid_user_info_response" ->
|
erroroauth = "login.oauth2InvalidUserType";
|
||||||
errorOAuth = "login.oauth2InvalidUserInfoResponse";
|
break;
|
||||||
case "invalid_request" -> errorOAuth = "login.oauth2invalidRequest";
|
case "invalid_token_response":
|
||||||
case "invalid_id_token" -> errorOAuth = "login.oauth2InvalidIdToken";
|
erroroauth = "login.oauth2InvalidTokenResponse";
|
||||||
case "oAuth2AdminBlockedUser" -> errorOAuth = "login.oAuth2AdminBlockedUser";
|
break;
|
||||||
case "userIsDisabled" -> errorOAuth = "login.userIsDisabled";
|
case "authorization_request_not_found":
|
||||||
case "invalid_destination" -> errorOAuth = "login.invalid_destination";
|
erroroauth = "login.oauth2RequestNotFound";
|
||||||
case "relying_party_registration_not_found" ->
|
break;
|
||||||
errorOAuth = "login.relyingPartyRegistrationNotFound";
|
case "access_denied":
|
||||||
|
erroroauth = "login.oauth2AccessDenied";
|
||||||
|
break;
|
||||||
|
case "invalid_user_info_response":
|
||||||
|
erroroauth = "login.oauth2InvalidUserInfoResponse";
|
||||||
|
break;
|
||||||
|
case "invalid_request":
|
||||||
|
erroroauth = "login.oauth2invalidRequest";
|
||||||
|
break;
|
||||||
|
case "invalid_id_token":
|
||||||
|
erroroauth = "login.oauth2InvalidIdToken";
|
||||||
|
break;
|
||||||
|
case "oauth2_admin_blocked_user":
|
||||||
|
erroroauth = "login.oauth2AdminBlockedUser";
|
||||||
|
break;
|
||||||
|
case "userIsDisabled":
|
||||||
|
erroroauth = "login.userIsDisabled";
|
||||||
|
break;
|
||||||
|
case "invalid_destination":
|
||||||
|
erroroauth = "login.invalid_destination";
|
||||||
|
break;
|
||||||
|
case "relying_party_registration_not_found":
|
||||||
|
erroroauth = "login.relyingPartyRegistrationNotFound";
|
||||||
|
break;
|
||||||
// Valid InResponseTo was not available from the validation context, unable to
|
// Valid InResponseTo was not available from the validation context, unable to
|
||||||
// evaluate
|
// evaluate
|
||||||
case "invalid_in_response_to" -> errorOAuth = "login.invalid_in_response_to";
|
case "invalid_in_response_to":
|
||||||
case "not_authentication_provider_found" ->
|
erroroauth = "login.invalid_in_response_to";
|
||||||
errorOAuth = "login.not_authentication_provider_found";
|
break;
|
||||||
|
case "not_authentication_provider_found":
|
||||||
|
erroroauth = "login.not_authentication_provider_found";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
model.addAttribute("erroroauth", erroroauth);
|
||||||
model.addAttribute("errorOAuth", errorOAuth);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (request.getParameter("messageType") != null) {
|
if (request.getParameter("messageType") != null) {
|
||||||
model.addAttribute("messageType", "changedCredsMessage");
|
model.addAttribute("messageType", "changedCredsMessage");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (request.getParameter("logout") != null) {
|
if (request.getParameter("logout") != null) {
|
||||||
model.addAttribute("logoutMessage", "You have been logged out.");
|
model.addAttribute("logoutMessage", "You have been logged out.");
|
||||||
}
|
}
|
||||||
|
|
||||||
return "login";
|
return "login";
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -236,11 +230,13 @@ public class AccountWebController {
|
|||||||
.plus(maxInactiveInterval, ChronoUnit.SECONDS);
|
.plus(maxInactiveInterval, ChronoUnit.SECONDS);
|
||||||
if (now.isAfter(expirationTime)) {
|
if (now.isAfter(expirationTime)) {
|
||||||
sessionPersistentRegistry.expireSession(sessionEntity.getSessionId());
|
sessionPersistentRegistry.expireSession(sessionEntity.getSessionId());
|
||||||
|
hasActiveSession = false;
|
||||||
} else {
|
} else {
|
||||||
hasActiveSession = !sessionEntity.isExpired();
|
hasActiveSession = !sessionEntity.isExpired();
|
||||||
}
|
}
|
||||||
lastRequest = sessionEntity.getLastRequest();
|
lastRequest = sessionEntity.getLastRequest();
|
||||||
} else {
|
} else {
|
||||||
|
hasActiveSession = false;
|
||||||
// No session, set default last request time
|
// No session, set default last request time
|
||||||
lastRequest = new Date(0);
|
lastRequest = new Date(0);
|
||||||
}
|
}
|
||||||
@@ -277,41 +273,53 @@ public class AccountWebController {
|
|||||||
})
|
})
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
String messageType = request.getParameter("messageType");
|
String messageType = request.getParameter("messageType");
|
||||||
|
String deleteMessage = null;
|
||||||
String deleteMessage;
|
|
||||||
if (messageType != null) {
|
if (messageType != null) {
|
||||||
deleteMessage =
|
switch (messageType) {
|
||||||
switch (messageType) {
|
case "deleteCurrentUser":
|
||||||
case "deleteCurrentUser" -> "deleteCurrentUserMessage";
|
deleteMessage = "deleteCurrentUserMessage";
|
||||||
case "deleteUsernameExists" -> "deleteUsernameExistsMessage";
|
break;
|
||||||
default -> null;
|
case "deleteUsernameExists":
|
||||||
};
|
deleteMessage = "deleteUsernameExistsMessage";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
model.addAttribute("deleteMessage", deleteMessage);
|
model.addAttribute("deleteMessage", deleteMessage);
|
||||||
|
String addMessage = null;
|
||||||
String addMessage;
|
switch (messageType) {
|
||||||
addMessage =
|
case "usernameExists":
|
||||||
switch (messageType) {
|
addMessage = "usernameExistsMessage";
|
||||||
case "usernameExists" -> "usernameExistsMessage";
|
break;
|
||||||
case "invalidUsername" -> "invalidUsernameMessage";
|
case "invalidUsername":
|
||||||
case "invalidPassword" -> "invalidPasswordMessage";
|
addMessage = "invalidUsernameMessage";
|
||||||
default -> null;
|
break;
|
||||||
};
|
case "invalidPassword":
|
||||||
|
addMessage = "invalidPasswordMessage";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
model.addAttribute("addMessage", addMessage);
|
model.addAttribute("addMessage", addMessage);
|
||||||
}
|
}
|
||||||
|
String changeMessage = null;
|
||||||
String changeMessage;
|
|
||||||
if (messageType != null) {
|
if (messageType != null) {
|
||||||
changeMessage =
|
switch (messageType) {
|
||||||
switch (messageType) {
|
case "userNotFound":
|
||||||
case "userNotFound" -> "userNotFoundMessage";
|
changeMessage = "userNotFoundMessage";
|
||||||
case "downgradeCurrentUser" -> "downgradeCurrentUserMessage";
|
break;
|
||||||
case "disabledCurrentUser" -> "disabledCurrentUserMessage";
|
case "downgradeCurrentUser":
|
||||||
default -> messageType;
|
changeMessage = "downgradeCurrentUserMessage";
|
||||||
};
|
break;
|
||||||
|
case "disabledCurrentUser":
|
||||||
|
changeMessage = "disabledCurrentUserMessage";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
changeMessage = messageType;
|
||||||
|
break;
|
||||||
|
}
|
||||||
model.addAttribute("changeMessage", changeMessage);
|
model.addAttribute("changeMessage", changeMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
model.addAttribute("users", sortedUsers);
|
model.addAttribute("users", sortedUsers);
|
||||||
model.addAttribute("currentUsername", authentication.getName());
|
model.addAttribute("currentUsername", authentication.getName());
|
||||||
model.addAttribute("roleDetails", roleDetails);
|
model.addAttribute("roleDetails", roleDetails);
|
||||||
@@ -332,51 +340,78 @@ public class AccountWebController {
|
|||||||
if (authentication != null && authentication.isAuthenticated()) {
|
if (authentication != null && authentication.isAuthenticated()) {
|
||||||
Object principal = authentication.getPrincipal();
|
Object principal = authentication.getPrincipal();
|
||||||
String username = null;
|
String username = null;
|
||||||
|
if (principal instanceof UserDetails) {
|
||||||
// Retrieve username and other attributes and add login attributes to the model
|
// Cast the principal object to UserDetails
|
||||||
if (principal instanceof UserDetails userDetails) {
|
UserDetails userDetails = (UserDetails) principal;
|
||||||
|
// Retrieve username and other attributes
|
||||||
username = userDetails.getUsername();
|
username = userDetails.getUsername();
|
||||||
|
// Add oAuth2 Login attributes to the model
|
||||||
model.addAttribute("oAuth2Login", false);
|
model.addAttribute("oAuth2Login", false);
|
||||||
}
|
}
|
||||||
if (principal instanceof OAuth2User userDetails) {
|
if (principal instanceof OAuth2User) {
|
||||||
username = userDetails.getName();
|
// Cast the principal object to OAuth2User
|
||||||
|
OAuth2User userDetails = (OAuth2User) principal;
|
||||||
|
// Retrieve username and other attributes
|
||||||
|
username =
|
||||||
|
userDetails.getAttribute(
|
||||||
|
applicationProperties.getSecurity().getOauth2().getUseAsUsername());
|
||||||
|
// Add oAuth2 Login attributes to the model
|
||||||
model.addAttribute("oAuth2Login", true);
|
model.addAttribute("oAuth2Login", true);
|
||||||
}
|
}
|
||||||
if (principal instanceof CustomSaml2AuthenticatedPrincipal userDetails) {
|
if (principal instanceof CustomSaml2AuthenticatedPrincipal) {
|
||||||
username = userDetails.name();
|
// Cast the principal object to OAuth2User
|
||||||
model.addAttribute("saml2Login", true);
|
CustomSaml2AuthenticatedPrincipal userDetails =
|
||||||
|
(CustomSaml2AuthenticatedPrincipal) principal;
|
||||||
|
// Retrieve username and other attributes
|
||||||
|
username = userDetails.getName();
|
||||||
|
// Add oAuth2 Login attributes to the model
|
||||||
|
model.addAttribute("oAuth2Login", true);
|
||||||
}
|
}
|
||||||
if (username != null) {
|
if (username != null) {
|
||||||
// Fetch user details from the database
|
// Fetch user details from the database
|
||||||
Optional<User> user = userRepository.findByUsernameIgnoreCaseWithSettings(username);
|
Optional<User> user =
|
||||||
|
userRepository
|
||||||
if (user.isEmpty()) {
|
.findByUsernameIgnoreCaseWithSettings( // Assuming findByUsername
|
||||||
|
// method exists
|
||||||
|
username);
|
||||||
|
if (!user.isPresent()) {
|
||||||
return "redirect:/error";
|
return "redirect:/error";
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert settings map to JSON string
|
// Convert settings map to JSON string
|
||||||
ObjectMapper objectMapper = new ObjectMapper();
|
ObjectMapper objectMapper = new ObjectMapper();
|
||||||
String settingsJson;
|
String settingsJson;
|
||||||
try {
|
try {
|
||||||
settingsJson = objectMapper.writeValueAsString(user.get().getSettings());
|
settingsJson = objectMapper.writeValueAsString(user.get().getSettings());
|
||||||
} catch (JsonProcessingException e) {
|
} catch (JsonProcessingException e) {
|
||||||
log.error("Error converting settings map", e);
|
// Handle JSON conversion error
|
||||||
|
log.error("exception", e);
|
||||||
return "redirect:/error";
|
return "redirect:/error";
|
||||||
}
|
}
|
||||||
|
|
||||||
String messageType = request.getParameter("messageType");
|
String messageType = request.getParameter("messageType");
|
||||||
if (messageType != null) {
|
if (messageType != null) {
|
||||||
switch (messageType) {
|
switch (messageType) {
|
||||||
case "notAuthenticated" -> messageType = "notAuthenticatedMessage";
|
case "notAuthenticated":
|
||||||
case "userNotFound" -> messageType = "userNotFoundMessage";
|
messageType = "notAuthenticatedMessage";
|
||||||
case "incorrectPassword" -> messageType = "incorrectPasswordMessage";
|
break;
|
||||||
case "usernameExists" -> messageType = "usernameExistsMessage";
|
case "userNotFound":
|
||||||
case "invalidUsername" -> messageType = "invalidUsernameMessage";
|
messageType = "userNotFoundMessage";
|
||||||
|
break;
|
||||||
|
case "incorrectPassword":
|
||||||
|
messageType = "incorrectPasswordMessage";
|
||||||
|
break;
|
||||||
|
case "usernameExists":
|
||||||
|
messageType = "usernameExistsMessage";
|
||||||
|
break;
|
||||||
|
case "invalidUsername":
|
||||||
|
messageType = "invalidUsernameMessage";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
model.addAttribute("messageType", messageType);
|
||||||
}
|
}
|
||||||
|
// Add attributes to the model
|
||||||
model.addAttribute("username", username);
|
model.addAttribute("username", username);
|
||||||
model.addAttribute("messageType", messageType);
|
|
||||||
model.addAttribute("role", user.get().getRolesAsString());
|
model.addAttribute("role", user.get().getRolesAsString());
|
||||||
model.addAttribute("settings", settingsJson);
|
model.addAttribute("settings", settingsJson);
|
||||||
model.addAttribute("changeCredsFlag", user.get().isFirstLogin());
|
model.addAttribute("changeCredsFlag", user.get().isFirstLogin());
|
||||||
@@ -397,12 +432,19 @@ public class AccountWebController {
|
|||||||
}
|
}
|
||||||
if (authentication != null && authentication.isAuthenticated()) {
|
if (authentication != null && authentication.isAuthenticated()) {
|
||||||
Object principal = authentication.getPrincipal();
|
Object principal = authentication.getPrincipal();
|
||||||
if (principal instanceof UserDetails userDetails) {
|
if (principal instanceof UserDetails) {
|
||||||
|
// Cast the principal object to UserDetails
|
||||||
|
UserDetails userDetails = (UserDetails) principal;
|
||||||
|
// Retrieve username and other attributes
|
||||||
String username = userDetails.getUsername();
|
String username = userDetails.getUsername();
|
||||||
// Fetch user details from the database
|
// Fetch user details from the database
|
||||||
Optional<User> user = userRepository.findByUsernameIgnoreCase(username);
|
Optional<User> user =
|
||||||
if (user.isEmpty()) {
|
userRepository
|
||||||
// Handle error appropriately, example redirection in case of error
|
.findByUsernameIgnoreCase( // Assuming findByUsername method exists
|
||||||
|
username);
|
||||||
|
if (!user.isPresent()) {
|
||||||
|
// Handle error appropriately
|
||||||
|
// Example redirection in case of error
|
||||||
return "redirect:/error";
|
return "redirect:/error";
|
||||||
}
|
}
|
||||||
String messageType = request.getParameter("messageType");
|
String messageType = request.getParameter("messageType");
|
||||||
@@ -425,7 +467,7 @@ public class AccountWebController {
|
|||||||
}
|
}
|
||||||
model.addAttribute("messageType", messageType);
|
model.addAttribute("messageType", messageType);
|
||||||
}
|
}
|
||||||
|
// Add attributes to the model
|
||||||
model.addAttribute("username", username);
|
model.addAttribute("username", username);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
package stirling.software.SPDF.model;
|
package stirling.software.SPDF.model;
|
||||||
|
|
||||||
import static stirling.software.SPDF.utils.validation.Validator.*;
|
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileNotFoundException;
|
import java.io.FileNotFoundException;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@@ -14,6 +12,7 @@ import java.util.ArrayList;
|
|||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
@@ -35,11 +34,10 @@ import lombok.extern.slf4j.Slf4j;
|
|||||||
|
|
||||||
import stirling.software.SPDF.config.InstallationPathConfig;
|
import stirling.software.SPDF.config.InstallationPathConfig;
|
||||||
import stirling.software.SPDF.config.YamlPropertySourceFactory;
|
import stirling.software.SPDF.config.YamlPropertySourceFactory;
|
||||||
import stirling.software.SPDF.model.exception.UnsupportedProviderException;
|
import stirling.software.SPDF.model.provider.GithubProvider;
|
||||||
import stirling.software.SPDF.model.provider.GitHubProvider;
|
|
||||||
import stirling.software.SPDF.model.provider.GoogleProvider;
|
import stirling.software.SPDF.model.provider.GoogleProvider;
|
||||||
import stirling.software.SPDF.model.provider.KeycloakProvider;
|
import stirling.software.SPDF.model.provider.KeycloakProvider;
|
||||||
import stirling.software.SPDF.model.provider.Provider;
|
import stirling.software.SPDF.model.provider.UnsupportedProviderException;
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
@ConfigurationProperties(prefix = "")
|
@ConfigurationProperties(prefix = "")
|
||||||
@@ -138,13 +136,13 @@ public class ApplicationProperties {
|
|||||||
|| loginMethod.equalsIgnoreCase(LoginMethods.ALL.toString()));
|
|| loginMethod.equalsIgnoreCase(LoginMethods.ALL.toString()));
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isOauth2Active() {
|
public boolean isOauth2Activ() {
|
||||||
return (oauth2 != null
|
return (oauth2 != null
|
||||||
&& oauth2.getEnabled()
|
&& oauth2.getEnabled()
|
||||||
&& !loginMethod.equalsIgnoreCase(LoginMethods.NORMAL.toString()));
|
&& !loginMethod.equalsIgnoreCase(LoginMethods.NORMAL.toString()));
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isSaml2Active() {
|
public boolean isSaml2Activ() {
|
||||||
return (saml2 != null
|
return (saml2 != null
|
||||||
&& saml2.getEnabled()
|
&& saml2.getEnabled()
|
||||||
&& !loginMethod.equalsIgnoreCase(LoginMethods.NORMAL.toString()));
|
&& !loginMethod.equalsIgnoreCase(LoginMethods.NORMAL.toString()));
|
||||||
@@ -160,7 +158,6 @@ public class ApplicationProperties {
|
|||||||
@Setter
|
@Setter
|
||||||
@ToString
|
@ToString
|
||||||
public static class SAML2 {
|
public static class SAML2 {
|
||||||
private String provider;
|
|
||||||
private Boolean enabled = false;
|
private Boolean enabled = false;
|
||||||
private Boolean autoCreateUser = false;
|
private Boolean autoCreateUser = false;
|
||||||
private Boolean blockRegistration = false;
|
private Boolean blockRegistration = false;
|
||||||
@@ -198,7 +195,7 @@ public class ApplicationProperties {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public Resource getIdpCert() {
|
public Resource getidpCert() {
|
||||||
if (idpCert == null) return null;
|
if (idpCert == null) return null;
|
||||||
if (idpCert.startsWith("classpath:")) {
|
if (idpCert.startsWith("classpath:")) {
|
||||||
return new ClassPathResource(idpCert.substring("classpath:".length()));
|
return new ClassPathResource(idpCert.substring("classpath:".length()));
|
||||||
@@ -228,11 +225,12 @@ public class ApplicationProperties {
|
|||||||
private Collection<String> scopes = new ArrayList<>();
|
private Collection<String> scopes = new ArrayList<>();
|
||||||
private String provider;
|
private String provider;
|
||||||
private Client client = new Client();
|
private Client client = new Client();
|
||||||
private String logoutUrl;
|
|
||||||
|
|
||||||
public void setScopes(String scopes) {
|
public void setScopes(String scopes) {
|
||||||
List<String> scopesList =
|
List<String> scopesList =
|
||||||
Arrays.stream(scopes.split(",")).map(String::trim).toList();
|
Arrays.stream(scopes.split(","))
|
||||||
|
.map(String::trim)
|
||||||
|
.collect(Collectors.toList());
|
||||||
this.scopes.addAll(scopesList);
|
this.scopes.addAll(scopesList);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -245,31 +243,32 @@ public class ApplicationProperties {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public boolean isSettingsValid() {
|
public boolean isSettingsValid() {
|
||||||
return !isStringEmpty(this.getIssuer())
|
return isValid(this.getIssuer(), "issuer")
|
||||||
&& !isStringEmpty(this.getClientId())
|
&& isValid(this.getClientId(), "clientId")
|
||||||
&& !isStringEmpty(this.getClientSecret())
|
&& isValid(this.getClientSecret(), "clientSecret")
|
||||||
&& !isCollectionEmpty(this.getScopes())
|
&& isValid(this.getScopes(), "scopes")
|
||||||
&& !isStringEmpty(this.getUseAsUsername());
|
&& isValid(this.getUseAsUsername(), "useAsUsername");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Data
|
@Data
|
||||||
public static class Client {
|
public static class Client {
|
||||||
private GoogleProvider google = new GoogleProvider();
|
private GoogleProvider google = new GoogleProvider();
|
||||||
private GitHubProvider github = new GitHubProvider();
|
private GithubProvider github = new GithubProvider();
|
||||||
private KeycloakProvider keycloak = new KeycloakProvider();
|
private KeycloakProvider keycloak = new KeycloakProvider();
|
||||||
|
|
||||||
public Provider get(String registrationId) throws UnsupportedProviderException {
|
public Provider get(String registrationId) throws UnsupportedProviderException {
|
||||||
return switch (registrationId.toLowerCase()) {
|
switch (registrationId.toLowerCase()) {
|
||||||
case "google" -> getGoogle();
|
case "google":
|
||||||
case "github" -> getGithub();
|
return getGoogle();
|
||||||
case "keycloak" -> getKeycloak();
|
case "github":
|
||||||
default ->
|
return getGithub();
|
||||||
throw new UnsupportedProviderException(
|
case "keycloak":
|
||||||
"Logout from the provider "
|
return getKeycloak();
|
||||||
+ registrationId
|
default:
|
||||||
+ " is not supported. "
|
throw new UnsupportedProviderException(
|
||||||
+ "Report it at https://github.com/Stirling-Tools/Stirling-PDF/issues");
|
"Logout from the provider is not supported? Report it at"
|
||||||
};
|
+ " https://github.com/Stirling-Tools/Stirling-PDF/issues");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -336,10 +335,10 @@ public class ApplicationProperties {
|
|||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return """
|
return """
|
||||||
Driver {
|
Driver {
|
||||||
driverName='%s'
|
driverName='%s'
|
||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
.formatted(driverName);
|
.formatted(driverName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
80
src/main/java/stirling/software/SPDF/model/Provider.java
Normal file
80
src/main/java/stirling/software/SPDF/model/Provider.java
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
package stirling.software.SPDF.model;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
|
||||||
|
public class Provider implements ProviderInterface {
|
||||||
|
private String name;
|
||||||
|
private String clientName;
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getClientName() {
|
||||||
|
return clientName;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected boolean isValid(String value, String name) {
|
||||||
|
if (value != null && !value.trim().isEmpty()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected boolean isValid(Collection<String> value, String name) {
|
||||||
|
if (value != null && !value.isEmpty()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Collection<String> getScopes() {
|
||||||
|
throw new UnsupportedOperationException("Unimplemented method 'getScope'");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setScopes(String scopes) {
|
||||||
|
throw new UnsupportedOperationException("Unimplemented method 'setScope'");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getUseAsUsername() {
|
||||||
|
throw new UnsupportedOperationException("Unimplemented method 'getUseAsUsername'");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setUseAsUsername(String useAsUsername) {
|
||||||
|
throw new UnsupportedOperationException("Unimplemented method 'setUseAsUsername'");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getIssuer() {
|
||||||
|
throw new UnsupportedOperationException("Unimplemented method 'getIssuer'");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setIssuer(String issuer) {
|
||||||
|
throw new UnsupportedOperationException("Unimplemented method 'setIssuer'");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getClientSecret() {
|
||||||
|
throw new UnsupportedOperationException("Unimplemented method 'getClientSecret'");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setClientSecret(String clientSecret) {
|
||||||
|
throw new UnsupportedOperationException("Unimplemented method 'setClientSecret'");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getClientId() {
|
||||||
|
throw new UnsupportedOperationException("Unimplemented method 'getClientId'");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setClientId(String clientId) {
|
||||||
|
throw new UnsupportedOperationException("Unimplemented method 'setClientId'");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
package stirling.software.SPDF.model;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
|
||||||
|
public interface ProviderInterface {
|
||||||
|
|
||||||
|
public Collection<String> getScopes();
|
||||||
|
|
||||||
|
public void setScopes(String scopes);
|
||||||
|
|
||||||
|
public String getUseAsUsername();
|
||||||
|
|
||||||
|
public void setUseAsUsername(String useAsUsername);
|
||||||
|
|
||||||
|
public String getIssuer();
|
||||||
|
|
||||||
|
public void setIssuer(String issuer);
|
||||||
|
|
||||||
|
public String getClientSecret();
|
||||||
|
|
||||||
|
public void setClientSecret(String clientSecret);
|
||||||
|
|
||||||
|
public String getClientId();
|
||||||
|
|
||||||
|
public void setClientId(String clientId);
|
||||||
|
}
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
package stirling.software.SPDF.model;
|
|
||||||
|
|
||||||
import lombok.Getter;
|
|
||||||
|
|
||||||
@Getter
|
|
||||||
public enum UsernameAttribute {
|
|
||||||
EMAIL("email"),
|
|
||||||
LOGIN("login"),
|
|
||||||
PROFILE("profile"),
|
|
||||||
NAME("name"),
|
|
||||||
USERNAME("username"),
|
|
||||||
NICKNAME("nickname"),
|
|
||||||
GIVEN_NAME("given_name"),
|
|
||||||
MIDDLE_NAME("middle_name"),
|
|
||||||
FAMILY_NAME("family_name"),
|
|
||||||
PREFERRED_NAME("preferred_name"),
|
|
||||||
PREFERRED_USERNAME("preferred_username");
|
|
||||||
|
|
||||||
private final String name;
|
|
||||||
|
|
||||||
UsernameAttribute(final String name) {
|
|
||||||
this.name = name;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -30,13 +30,4 @@ public class OptimizePdfRequest extends PDFFile {
|
|||||||
"Whether to normalize the PDF content for better compatibility. Default is false.",
|
"Whether to normalize the PDF content for better compatibility. Default is false.",
|
||||||
defaultValue = "false")
|
defaultValue = "false")
|
||||||
private Boolean normalize = false;
|
private Boolean normalize = false;
|
||||||
|
|
||||||
@Schema(
|
|
||||||
description = "Whether to convert the PDF to grayscale. Default is false.",
|
|
||||||
defaultValue = "false")
|
|
||||||
private Boolean grayscale = false;
|
|
||||||
|
|
||||||
public Boolean getGrayscale() {
|
|
||||||
return grayscale;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +0,0 @@
|
|||||||
package stirling.software.SPDF.model.exception;
|
|
||||||
|
|
||||||
public class NoProviderFoundException extends Exception {
|
|
||||||
public NoProviderFoundException(String message) {
|
|
||||||
super(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
public NoProviderFoundException(String message, Throwable cause) {
|
|
||||||
super(message, cause);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
package stirling.software.SPDF.model.exception;
|
|
||||||
|
|
||||||
public class UnsupportedUsernameAttribute extends RuntimeException {
|
|
||||||
public UnsupportedUsernameAttribute(String message) {
|
|
||||||
super(message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,86 +0,0 @@
|
|||||||
package stirling.software.SPDF.model.provider;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collection;
|
|
||||||
|
|
||||||
import lombok.NoArgsConstructor;
|
|
||||||
import stirling.software.SPDF.model.UsernameAttribute;
|
|
||||||
|
|
||||||
@NoArgsConstructor
|
|
||||||
public class GitHubProvider extends Provider {
|
|
||||||
|
|
||||||
private static final String NAME = "github";
|
|
||||||
private static final String CLIENT_NAME = "GitHub";
|
|
||||||
private static final String AUTHORIZATION_URI = "https://github.com/login/oauth/authorize";
|
|
||||||
private static final String TOKEN_URI = "https://github.com/login/oauth/access_token";
|
|
||||||
private static final String USER_INFO_URI = "https://api.github.com/user";
|
|
||||||
|
|
||||||
public GitHubProvider(
|
|
||||||
String clientId,
|
|
||||||
String clientSecret,
|
|
||||||
Collection<String> scopes,
|
|
||||||
UsernameAttribute useAsUsername) {
|
|
||||||
super(
|
|
||||||
null,
|
|
||||||
NAME,
|
|
||||||
CLIENT_NAME,
|
|
||||||
clientId,
|
|
||||||
clientSecret,
|
|
||||||
scopes,
|
|
||||||
useAsUsername != null ? useAsUsername : UsernameAttribute.LOGIN,
|
|
||||||
null,
|
|
||||||
AUTHORIZATION_URI,
|
|
||||||
TOKEN_URI,
|
|
||||||
USER_INFO_URI);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getAuthorizationUri() {
|
|
||||||
return AUTHORIZATION_URI;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getTokenUri() {
|
|
||||||
return TOKEN_URI;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getUserInfoUri() {
|
|
||||||
return USER_INFO_URI;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getName() {
|
|
||||||
return NAME;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getClientName() {
|
|
||||||
return CLIENT_NAME;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Collection<String> getScopes() {
|
|
||||||
Collection<String> scopes = super.getScopes();
|
|
||||||
|
|
||||||
if (scopes == null || scopes.isEmpty()) {
|
|
||||||
scopes = new ArrayList<>();
|
|
||||||
scopes.add("read:user");
|
|
||||||
}
|
|
||||||
|
|
||||||
return scopes;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return "GitHub [clientId="
|
|
||||||
+ getClientId()
|
|
||||||
+ ", clientSecret="
|
|
||||||
+ (getClientSecret() != null && !getClientSecret().isEmpty() ? "*****" : "NULL")
|
|
||||||
+ ", scopes="
|
|
||||||
+ getScopes()
|
|
||||||
+ ", useAsUsername="
|
|
||||||
+ getUseAsUsername()
|
|
||||||
+ "]";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,114 @@
|
|||||||
|
package stirling.software.SPDF.model.provider;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import stirling.software.SPDF.model.Provider;
|
||||||
|
|
||||||
|
public class GithubProvider extends Provider {
|
||||||
|
|
||||||
|
private static final String authorizationUri = "https://github.com/login/oauth/authorize";
|
||||||
|
private static final String tokenUri = "https://github.com/login/oauth/access_token";
|
||||||
|
private static final String userInfoUri = "https://api.github.com/user";
|
||||||
|
private String clientId;
|
||||||
|
private String clientSecret;
|
||||||
|
private Collection<String> scopes = new ArrayList<>();
|
||||||
|
private String useAsUsername = "login";
|
||||||
|
|
||||||
|
public String getAuthorizationuri() {
|
||||||
|
return authorizationUri;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getTokenuri() {
|
||||||
|
return tokenUri;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getUserinfouri() {
|
||||||
|
return userInfoUri;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getIssuer() {
|
||||||
|
return new String();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setIssuer(String issuer) {}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getClientId() {
|
||||||
|
return this.clientId;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setClientId(String clientId) {
|
||||||
|
this.clientId = clientId;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getClientSecret() {
|
||||||
|
return this.clientSecret;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setClientSecret(String clientSecret) {
|
||||||
|
this.clientSecret = clientSecret;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Collection<String> getScopes() {
|
||||||
|
if (scopes == null || scopes.isEmpty()) {
|
||||||
|
scopes = new ArrayList<>();
|
||||||
|
scopes.add("read:user");
|
||||||
|
}
|
||||||
|
return scopes;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setScopes(String scopes) {
|
||||||
|
this.scopes =
|
||||||
|
Arrays.stream(scopes.split(",")).map(String::trim).collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getUseAsUsername() {
|
||||||
|
return this.useAsUsername;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setUseAsUsername(String useAsUsername) {
|
||||||
|
this.useAsUsername = useAsUsername;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "GitHub [clientId="
|
||||||
|
+ clientId
|
||||||
|
+ ", clientSecret="
|
||||||
|
+ (clientSecret != null && !clientSecret.isEmpty() ? "MASKED" : "NULL")
|
||||||
|
+ ", scopes="
|
||||||
|
+ scopes
|
||||||
|
+ ", useAsUsername="
|
||||||
|
+ useAsUsername
|
||||||
|
+ "]";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return "github";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getClientName() {
|
||||||
|
return "GitHub";
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isSettingsValid() {
|
||||||
|
return super.isValid(this.getClientId(), "clientId")
|
||||||
|
&& super.isValid(this.getClientSecret(), "clientSecret")
|
||||||
|
&& super.isValid(this.getScopes(), "scopes")
|
||||||
|
&& isValid(this.getUseAsUsername(), "useAsUsername");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,85 +1,116 @@
|
|||||||
package stirling.software.SPDF.model.provider;
|
package stirling.software.SPDF.model.provider;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import lombok.NoArgsConstructor;
|
import stirling.software.SPDF.model.Provider;
|
||||||
import stirling.software.SPDF.model.UsernameAttribute;
|
|
||||||
|
|
||||||
@NoArgsConstructor
|
|
||||||
public class GoogleProvider extends Provider {
|
public class GoogleProvider extends Provider {
|
||||||
|
|
||||||
private static final String NAME = "google";
|
private static final String authorizationUri = "https://accounts.google.com/o/oauth2/v2/auth";
|
||||||
private static final String CLIENT_NAME = "Google";
|
private static final String tokenUri = "https://www.googleapis.com/oauth2/v4/token";
|
||||||
private static final String AUTHORIZATION_URI = "https://accounts.google.com/o/oauth2/v2/auth";
|
private static final String userInfoUri =
|
||||||
private static final String TOKEN_URI = "https://www.googleapis.com/oauth2/v4/token";
|
|
||||||
private static final String USER_INFO_URI =
|
|
||||||
"https://www.googleapis.com/oauth2/v3/userinfo?alt=json";
|
"https://www.googleapis.com/oauth2/v3/userinfo?alt=json";
|
||||||
|
private String clientId;
|
||||||
|
private String clientSecret;
|
||||||
|
private Collection<String> scopes = new ArrayList<>();
|
||||||
|
private String useAsUsername = "email";
|
||||||
|
|
||||||
public GoogleProvider(
|
public String getAuthorizationuri() {
|
||||||
String clientId,
|
return authorizationUri;
|
||||||
String clientSecret,
|
|
||||||
Collection<String> scopes,
|
|
||||||
UsernameAttribute useAsUsername) {
|
|
||||||
super(
|
|
||||||
null,
|
|
||||||
NAME,
|
|
||||||
CLIENT_NAME,
|
|
||||||
clientId,
|
|
||||||
clientSecret,
|
|
||||||
scopes,
|
|
||||||
useAsUsername,
|
|
||||||
null,
|
|
||||||
AUTHORIZATION_URI,
|
|
||||||
TOKEN_URI,
|
|
||||||
USER_INFO_URI);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getAuthorizationUri() {
|
public String getTokenuri() {
|
||||||
return AUTHORIZATION_URI;
|
return tokenUri;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getTokenUri() {
|
public String getUserinfouri() {
|
||||||
return TOKEN_URI;
|
return userInfoUri;
|
||||||
}
|
|
||||||
|
|
||||||
public String getUserinfoUri() {
|
|
||||||
return USER_INFO_URI;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getName() {
|
public String getIssuer() {
|
||||||
return NAME;
|
return new String();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getClientName() {
|
public void setIssuer(String issuer) {}
|
||||||
return CLIENT_NAME;
|
|
||||||
|
@Override
|
||||||
|
public String getClientId() {
|
||||||
|
return this.clientId;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setClientId(String clientId) {
|
||||||
|
this.clientId = clientId;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getClientSecret() {
|
||||||
|
return this.clientSecret;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setClientSecret(String clientSecret) {
|
||||||
|
this.clientSecret = clientSecret;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Collection<String> getScopes() {
|
public Collection<String> getScopes() {
|
||||||
Collection<String> scopes = super.getScopes();
|
|
||||||
|
|
||||||
if (scopes == null || scopes.isEmpty()) {
|
if (scopes == null || scopes.isEmpty()) {
|
||||||
scopes = new ArrayList<>();
|
scopes = new ArrayList<>();
|
||||||
scopes.add("https://www.googleapis.com/auth/userinfo.email");
|
scopes.add("https://www.googleapis.com/auth/userinfo.email");
|
||||||
scopes.add("https://www.googleapis.com/auth/userinfo.profile");
|
scopes.add("https://www.googleapis.com/auth/userinfo.profile");
|
||||||
}
|
}
|
||||||
|
|
||||||
return scopes;
|
return scopes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setScopes(String scopes) {
|
||||||
|
this.scopes =
|
||||||
|
Arrays.stream(scopes.split(",")).map(String::trim).collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getUseAsUsername() {
|
||||||
|
return this.useAsUsername;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setUseAsUsername(String useAsUsername) {
|
||||||
|
this.useAsUsername = useAsUsername;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "Google [clientId="
|
return "Google [clientId="
|
||||||
+ getClientId()
|
+ clientId
|
||||||
+ ", clientSecret="
|
+ ", clientSecret="
|
||||||
+ (getClientSecret() != null && !getClientSecret().isEmpty() ? "*****" : "NULL")
|
+ (clientSecret != null && !clientSecret.isEmpty() ? "MASKED" : "NULL")
|
||||||
+ ", scopes="
|
+ ", scopes="
|
||||||
+ getScopes()
|
+ scopes
|
||||||
+ ", useAsUsername="
|
+ ", useAsUsername="
|
||||||
+ getUseAsUsername()
|
+ useAsUsername
|
||||||
+ "]";
|
+ "]";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return "google";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getClientName() {
|
||||||
|
return "Google";
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isSettingsValid() {
|
||||||
|
return super.isValid(this.getClientId(), "clientId")
|
||||||
|
&& super.isValid(this.getClientSecret(), "clientSecret")
|
||||||
|
&& super.isValid(this.getScopes(), "scopes")
|
||||||
|
&& isValid(this.getUseAsUsername(), "useAsUsername");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,72 +1,106 @@
|
|||||||
package stirling.software.SPDF.model.provider;
|
package stirling.software.SPDF.model.provider;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import lombok.NoArgsConstructor;
|
import stirling.software.SPDF.model.Provider;
|
||||||
import stirling.software.SPDF.model.UsernameAttribute;
|
|
||||||
|
|
||||||
@NoArgsConstructor
|
|
||||||
public class KeycloakProvider extends Provider {
|
public class KeycloakProvider extends Provider {
|
||||||
|
|
||||||
private static final String NAME = "keycloak";
|
private String issuer;
|
||||||
private static final String CLIENT_NAME = "Keycloak";
|
private String clientId;
|
||||||
|
private String clientSecret;
|
||||||
|
private Collection<String> scopes = new ArrayList<>();
|
||||||
|
private String useAsUsername = "email";
|
||||||
|
|
||||||
public KeycloakProvider(
|
@Override
|
||||||
String issuer,
|
public String getIssuer() {
|
||||||
String clientId,
|
return this.issuer;
|
||||||
String clientSecret,
|
|
||||||
Collection<String> scopes,
|
|
||||||
UsernameAttribute useAsUsername) {
|
|
||||||
super(
|
|
||||||
issuer,
|
|
||||||
NAME,
|
|
||||||
CLIENT_NAME,
|
|
||||||
clientId,
|
|
||||||
clientSecret,
|
|
||||||
scopes,
|
|
||||||
useAsUsername,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getName() {
|
public void setIssuer(String issuer) {
|
||||||
return NAME;
|
this.issuer = issuer;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getClientName() {
|
public String getClientId() {
|
||||||
return CLIENT_NAME;
|
return this.clientId;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setClientId(String clientId) {
|
||||||
|
this.clientId = clientId;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getClientSecret() {
|
||||||
|
return this.clientSecret;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setClientSecret(String clientSecret) {
|
||||||
|
this.clientSecret = clientSecret;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Collection<String> getScopes() {
|
public Collection<String> getScopes() {
|
||||||
Collection<String> scopes = super.getScopes();
|
|
||||||
|
|
||||||
if (scopes == null || scopes.isEmpty()) {
|
if (scopes == null || scopes.isEmpty()) {
|
||||||
scopes = new ArrayList<>();
|
scopes = new ArrayList<>();
|
||||||
scopes.add("profile");
|
scopes.add("profile");
|
||||||
scopes.add("email");
|
scopes.add("email");
|
||||||
}
|
}
|
||||||
|
|
||||||
return scopes;
|
return scopes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setScopes(String scopes) {
|
||||||
|
this.scopes =
|
||||||
|
Arrays.stream(scopes.split(",")).map(String::trim).collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getUseAsUsername() {
|
||||||
|
return this.useAsUsername;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setUseAsUsername(String useAsUsername) {
|
||||||
|
this.useAsUsername = useAsUsername;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "Keycloak [issuer="
|
return "Keycloak [issuer="
|
||||||
+ getIssuer()
|
+ issuer
|
||||||
+ ", clientId="
|
+ ", clientId="
|
||||||
+ getClientId()
|
+ clientId
|
||||||
+ ", clientSecret="
|
+ ", clientSecret="
|
||||||
+ (getClientSecret() != null && !getClientSecret().isBlank() ? "*****" : "NULL")
|
+ (clientSecret != null && !clientSecret.isEmpty() ? "MASKED" : "NULL")
|
||||||
+ ", scopes="
|
+ ", scopes="
|
||||||
+ getScopes()
|
+ scopes
|
||||||
+ ", useAsUsername="
|
+ ", useAsUsername="
|
||||||
+ getUseAsUsername()
|
+ useAsUsername
|
||||||
+ "]";
|
+ "]";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return "keycloak";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getClientName() {
|
||||||
|
return "Keycloak";
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isSettingsValid() {
|
||||||
|
return isValid(this.getIssuer(), "issuer")
|
||||||
|
&& isValid(this.getClientId(), "clientId")
|
||||||
|
&& isValid(this.getClientSecret(), "clientSecret")
|
||||||
|
&& isValid(this.getScopes(), "scopes")
|
||||||
|
&& isValid(this.getUseAsUsername(), "useAsUsername");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,134 +0,0 @@
|
|||||||
package stirling.software.SPDF.model.provider;
|
|
||||||
|
|
||||||
import static stirling.software.SPDF.model.UsernameAttribute.EMAIL;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
import lombok.Data;
|
|
||||||
import lombok.NoArgsConstructor;
|
|
||||||
|
|
||||||
import stirling.software.SPDF.model.UsernameAttribute;
|
|
||||||
import stirling.software.SPDF.model.exception.UnsupportedUsernameAttribute;
|
|
||||||
|
|
||||||
@Data
|
|
||||||
@NoArgsConstructor
|
|
||||||
public class Provider {
|
|
||||||
|
|
||||||
public static final String EXCEPTION_MESSAGE = "The attribute %s is not supported for %s.";
|
|
||||||
|
|
||||||
private String issuer;
|
|
||||||
private String name;
|
|
||||||
private String clientName;
|
|
||||||
private String clientId;
|
|
||||||
private String clientSecret;
|
|
||||||
private Collection<String> scopes;
|
|
||||||
private UsernameAttribute useAsUsername;
|
|
||||||
private String logoutUrl;
|
|
||||||
private String authorizationUri;
|
|
||||||
private String tokenUri;
|
|
||||||
private String userInfoUri;
|
|
||||||
|
|
||||||
public Provider(
|
|
||||||
String issuer,
|
|
||||||
String name,
|
|
||||||
String clientName,
|
|
||||||
String clientId,
|
|
||||||
String clientSecret,
|
|
||||||
Collection<String> scopes,
|
|
||||||
UsernameAttribute useAsUsername,
|
|
||||||
String logoutUrl,
|
|
||||||
String authorizationUri,
|
|
||||||
String tokenUri,
|
|
||||||
String userInfoUri) {
|
|
||||||
this.issuer = issuer;
|
|
||||||
this.name = name;
|
|
||||||
this.clientName = clientName;
|
|
||||||
this.clientId = clientId;
|
|
||||||
this.clientSecret = clientSecret;
|
|
||||||
this.scopes = scopes == null ? new ArrayList<>() : scopes;
|
|
||||||
this.useAsUsername =
|
|
||||||
useAsUsername != null ? validateUsernameAttribute(useAsUsername) : EMAIL;
|
|
||||||
this.logoutUrl = logoutUrl;
|
|
||||||
this.authorizationUri = authorizationUri;
|
|
||||||
this.tokenUri = tokenUri;
|
|
||||||
this.userInfoUri = userInfoUri;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setScopes(String scopes) {
|
|
||||||
if (scopes != null && !scopes.isBlank()) {
|
|
||||||
this.scopes =
|
|
||||||
Arrays.stream(scopes.split(",")).map(String::trim).collect(Collectors.toList());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private UsernameAttribute validateUsernameAttribute(UsernameAttribute usernameAttribute) {
|
|
||||||
switch (name) {
|
|
||||||
case "google" -> {
|
|
||||||
return validateGoogleUsernameAttribute(usernameAttribute);
|
|
||||||
}
|
|
||||||
case "github" -> {
|
|
||||||
return validateGitHubUsernameAttribute(usernameAttribute);
|
|
||||||
}
|
|
||||||
case "keycloak" -> {
|
|
||||||
return validateKeycloakUsernameAttribute(usernameAttribute);
|
|
||||||
}
|
|
||||||
default -> {
|
|
||||||
return usernameAttribute;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private UsernameAttribute validateKeycloakUsernameAttribute(
|
|
||||||
UsernameAttribute usernameAttribute) {
|
|
||||||
switch (usernameAttribute) {
|
|
||||||
case EMAIL, NAME, GIVEN_NAME, FAMILY_NAME, PREFERRED_USERNAME -> {
|
|
||||||
return usernameAttribute;
|
|
||||||
}
|
|
||||||
default ->
|
|
||||||
throw new UnsupportedUsernameAttribute(
|
|
||||||
String.format(EXCEPTION_MESSAGE, usernameAttribute, clientName));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private UsernameAttribute validateGoogleUsernameAttribute(UsernameAttribute usernameAttribute) {
|
|
||||||
switch (usernameAttribute) {
|
|
||||||
case EMAIL, NAME, GIVEN_NAME, FAMILY_NAME -> {
|
|
||||||
return usernameAttribute;
|
|
||||||
}
|
|
||||||
default ->
|
|
||||||
throw new UnsupportedUsernameAttribute(
|
|
||||||
String.format(EXCEPTION_MESSAGE, usernameAttribute, clientName));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private UsernameAttribute validateGitHubUsernameAttribute(UsernameAttribute usernameAttribute) {
|
|
||||||
switch (usernameAttribute) {
|
|
||||||
case LOGIN, EMAIL, NAME -> {
|
|
||||||
return usernameAttribute;
|
|
||||||
}
|
|
||||||
default ->
|
|
||||||
throw new UnsupportedUsernameAttribute(
|
|
||||||
String.format(EXCEPTION_MESSAGE, usernameAttribute, clientName));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return "Provider [name="
|
|
||||||
+ getName()
|
|
||||||
+ ", clientName="
|
|
||||||
+ getClientName()
|
|
||||||
+ ", clientId="
|
|
||||||
+ getClientId()
|
|
||||||
+ ", clientSecret="
|
|
||||||
+ (getClientSecret() != null && !getClientSecret().isEmpty() ? "*****" : "NULL")
|
|
||||||
+ ", scopes="
|
|
||||||
+ getScopes()
|
|
||||||
+ ", useAsUsername="
|
|
||||||
+ getUseAsUsername()
|
|
||||||
+ "]";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package stirling.software.SPDF.model.exception;
|
package stirling.software.SPDF.model.provider;
|
||||||
|
|
||||||
public class UnsupportedProviderException extends Exception {
|
public class UnsupportedProviderException extends Exception {
|
||||||
public UnsupportedProviderException(String message) {
|
public UnsupportedProviderException(String message) {
|
||||||
@@ -207,7 +207,8 @@ public class PostHogService {
|
|||||||
|
|
||||||
private void addIfNotEmpty(Map<String, Object> map, String key, Object value) {
|
private void addIfNotEmpty(Map<String, Object> map, String key, Object value) {
|
||||||
if (value != null) {
|
if (value != null) {
|
||||||
if (value instanceof String strValue) {
|
if (value instanceof String) {
|
||||||
|
String strValue = (String) value;
|
||||||
if (!StringUtils.isBlank(strValue)) {
|
if (!StringUtils.isBlank(strValue)) {
|
||||||
map.put(key, strValue.trim());
|
map.put(key, strValue.trim());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,11 @@
|
|||||||
package stirling.software.SPDF.utils;
|
package stirling.software.SPDF.utils;
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.net.ServerSocket;
|
|
||||||
|
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
|
||||||
public class UrlUtils {
|
public class UrlUtils {
|
||||||
|
|
||||||
|
private UrlUtils() {}
|
||||||
|
|
||||||
public static String getOrigin(HttpServletRequest request) {
|
public static String getOrigin(HttpServletRequest request) {
|
||||||
String scheme = request.getScheme(); // http or https
|
String scheme = request.getScheme(); // http or https
|
||||||
String serverName = request.getServerName(); // localhost
|
String serverName = request.getServerName(); // localhost
|
||||||
@@ -15,20 +14,4 @@ public class UrlUtils {
|
|||||||
|
|
||||||
return scheme + "://" + serverName + ":" + serverPort + contextPath;
|
return scheme + "://" + serverName + ":" + serverPort + contextPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean isPortAvailable(int port) {
|
|
||||||
try (ServerSocket socket = new ServerSocket(port)) {
|
|
||||||
return true;
|
|
||||||
} catch (IOException e) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String findAvailablePort(int startPort) {
|
|
||||||
int port = startPort;
|
|
||||||
while (!isPortAvailable(port)) {
|
|
||||||
port++;
|
|
||||||
}
|
|
||||||
return String.valueOf(port);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,36 +0,0 @@
|
|||||||
package stirling.software.SPDF.utils.validation;
|
|
||||||
|
|
||||||
import java.util.Collection;
|
|
||||||
|
|
||||||
import stirling.software.SPDF.model.provider.Provider;
|
|
||||||
|
|
||||||
public class Validator {
|
|
||||||
|
|
||||||
public static boolean validateProvider(Provider provider) {
|
|
||||||
if (provider == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isStringEmpty(provider.getClientId())) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isStringEmpty(provider.getClientSecret())) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isCollectionEmpty(provider.getScopes())) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean isStringEmpty(String input) {
|
|
||||||
return input == null || input.isBlank();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean isCollectionEmpty(Collection<String> input) {
|
|
||||||
return input == null || input.isEmpty();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -572,8 +572,8 @@ login.invalid=اسم المستخدم أو كلمة المرور غير صالح
|
|||||||
login.locked=تم قفل حسابك.
|
login.locked=تم قفل حسابك.
|
||||||
login.signinTitle=الرجاء تسجيل الدخول
|
login.signinTitle=الرجاء تسجيل الدخول
|
||||||
login.ssoSignIn=تسجيل الدخول عبر تسجيل الدخول الأحادي
|
login.ssoSignIn=تسجيل الدخول عبر تسجيل الدخول الأحادي
|
||||||
login.oAuth2AutoCreateDisabled=تم تعطيل الإنشاء التلقائي لمستخدم OAuth2
|
login.oauth2AutoCreateDisabled=تم تعطيل الإنشاء التلقائي لمستخدم OAuth2
|
||||||
login.oAuth2AdminBlockedUser=تم حظر تسجيل أو تسجيل دخول المستخدمين غير المسجلين حاليًا. يرجى الاتصال بالمسؤول.
|
login.oauth2AdminBlockedUser=تم حظر تسجيل أو تسجيل دخول المستخدمين غير المسجلين حاليًا. يرجى الاتصال بالمسؤول.
|
||||||
login.oauth2RequestNotFound=لم يتم العثور على طلب التفويض
|
login.oauth2RequestNotFound=لم يتم العثور على طلب التفويض
|
||||||
login.oauth2InvalidUserInfoResponse=استجابة معلومات المستخدم غير صالحة
|
login.oauth2InvalidUserInfoResponse=استجابة معلومات المستخدم غير صالحة
|
||||||
login.oauth2invalidRequest=طلب غير صالح
|
login.oauth2invalidRequest=طلب غير صالح
|
||||||
@@ -1384,4 +1384,3 @@ validateSignature.cert.version=Version
|
|||||||
validateSignature.cert.keyUsage=Key Usage
|
validateSignature.cert.keyUsage=Key Usage
|
||||||
validateSignature.cert.selfSigned=Self-Signed
|
validateSignature.cert.selfSigned=Self-Signed
|
||||||
validateSignature.cert.bits=bits
|
validateSignature.cert.bits=bits
|
||||||
compress.grayscale.label=تطبيق التدرج الرمادي للضغط
|
|
||||||
|
|||||||
@@ -572,8 +572,8 @@ login.invalid=Etibarsız istifadəçi adı və ya şifr.
|
|||||||
login.locked=Sizin hesabınız kilidlənmişdir.
|
login.locked=Sizin hesabınız kilidlənmişdir.
|
||||||
login.signinTitle=Zəhmət olmasa, daxil olun
|
login.signinTitle=Zəhmət olmasa, daxil olun
|
||||||
login.ssoSignIn=Single Sign-on vasitəsilə daxil olun
|
login.ssoSignIn=Single Sign-on vasitəsilə daxil olun
|
||||||
login.oAuth2AutoCreateDisabled=OAUTH2 Auto-Create İstifadəçisi Deaktivləşdirilmişdir
|
login.oauth2AutoCreateDisabled=OAUTH2 Auto-Create İstifadəçisi Deaktivləşdirilmişdir
|
||||||
login.oAuth2AdminBlockedUser=Qeydiyyatdan keçməmiş istifadəçilərin qeydiyyatı və daxil olması hal-hazırda bloklanmışdır. Zəhmət olmasa, administratorla əlaqə saxlayın.
|
login.oauth2AdminBlockedUser=Qeydiyyatdan keçməmiş istifadəçilərin qeydiyyatı və daxil olması hal-hazırda bloklanmışdır. Zəhmət olmasa, administratorla əlaqə saxlayın.
|
||||||
login.oauth2RequestNotFound=Təsdiqlənmə sorğusu tapılmadı
|
login.oauth2RequestNotFound=Təsdiqlənmə sorğusu tapılmadı
|
||||||
login.oauth2InvalidUserInfoResponse=Yanlış İstifadəçi Məlumatı Cavabı
|
login.oauth2InvalidUserInfoResponse=Yanlış İstifadəçi Məlumatı Cavabı
|
||||||
login.oauth2invalidRequest=Etibarsız Sorğu
|
login.oauth2invalidRequest=Etibarsız Sorğu
|
||||||
@@ -1384,4 +1384,3 @@ validateSignature.cert.version=Version
|
|||||||
validateSignature.cert.keyUsage=Key Usage
|
validateSignature.cert.keyUsage=Key Usage
|
||||||
validateSignature.cert.selfSigned=Self-Signed
|
validateSignature.cert.selfSigned=Self-Signed
|
||||||
validateSignature.cert.bits=bits
|
validateSignature.cert.bits=bits
|
||||||
compress.grayscale.label=Sıxma üçün Boz Rəng Tətbiq Edin
|
|
||||||
|
|||||||
@@ -572,8 +572,8 @@ login.invalid=Невалидно потребителско име или пар
|
|||||||
login.locked=Вашият акаунт е заключен.
|
login.locked=Вашият акаунт е заключен.
|
||||||
login.signinTitle=Моля впишете се
|
login.signinTitle=Моля впишете се
|
||||||
login.ssoSignIn=Влизане чрез еднократно влизане
|
login.ssoSignIn=Влизане чрез еднократно влизане
|
||||||
login.oAuth2AutoCreateDisabled=OAUTH2 Автоматично създаване на потребител е деактивирано
|
login.oauth2AutoCreateDisabled=OAUTH2 Автоматично създаване на потребител е деактивирано
|
||||||
login.oAuth2AdminBlockedUser=Регистрацията или влизането на нерегистрирани потребители в момента е блокирано. Моля, свържете се с администратора.
|
login.oauth2AdminBlockedUser=Регистрацията или влизането на нерегистрирани потребители в момента е блокирано. Моля, свържете се с администратора.
|
||||||
login.oauth2RequestNotFound=Заявката за оторизация не е намерена
|
login.oauth2RequestNotFound=Заявката за оторизация не е намерена
|
||||||
login.oauth2InvalidUserInfoResponse=Невалидна информация за потребителя
|
login.oauth2InvalidUserInfoResponse=Невалидна информация за потребителя
|
||||||
login.oauth2invalidRequest=Невалидна заявка
|
login.oauth2invalidRequest=Невалидна заявка
|
||||||
@@ -1384,4 +1384,3 @@ validateSignature.cert.version=Версия
|
|||||||
validateSignature.cert.keyUsage=Предназначение на ключа за използване
|
validateSignature.cert.keyUsage=Предназначение на ключа за използване
|
||||||
validateSignature.cert.selfSigned=Самостоятелно подписан
|
validateSignature.cert.selfSigned=Самостоятелно подписан
|
||||||
validateSignature.cert.bits=битове
|
validateSignature.cert.bits=битове
|
||||||
compress.grayscale.label=Приложи сива скала за компресиране
|
|
||||||
|
|||||||
@@ -572,8 +572,8 @@ login.invalid=Nom d'usuari/contrasenya no vàlid
|
|||||||
login.locked=Compte bloquejat
|
login.locked=Compte bloquejat
|
||||||
login.signinTitle=Autenticat
|
login.signinTitle=Autenticat
|
||||||
login.ssoSignIn=Inicia sessió mitjançant inici de sessió únic
|
login.ssoSignIn=Inicia sessió mitjançant inici de sessió únic
|
||||||
login.oAuth2AutoCreateDisabled=La creació automàtica d'usuaris OAUTH2 està desactivada
|
login.oauth2AutoCreateDisabled=La creació automàtica d'usuaris OAUTH2 està desactivada
|
||||||
login.oAuth2AdminBlockedUser=El registre o inici de sessió d'usuaris no registrats està actualment bloquejat. Si us plau, contacta amb l'administrador.
|
login.oauth2AdminBlockedUser=El registre o inici de sessió d'usuaris no registrats està actualment bloquejat. Si us plau, contacta amb l'administrador.
|
||||||
login.oauth2RequestNotFound=Sol·licitud d'autorització no trobada
|
login.oauth2RequestNotFound=Sol·licitud d'autorització no trobada
|
||||||
login.oauth2InvalidUserInfoResponse=Resposta d'informació d'usuari no vàlida
|
login.oauth2InvalidUserInfoResponse=Resposta d'informació d'usuari no vàlida
|
||||||
login.oauth2invalidRequest=Sol·licitud no vàlida
|
login.oauth2invalidRequest=Sol·licitud no vàlida
|
||||||
@@ -1384,4 +1384,3 @@ validateSignature.cert.version=Version
|
|||||||
validateSignature.cert.keyUsage=Key Usage
|
validateSignature.cert.keyUsage=Key Usage
|
||||||
validateSignature.cert.selfSigned=Self-Signed
|
validateSignature.cert.selfSigned=Self-Signed
|
||||||
validateSignature.cert.bits=bits
|
validateSignature.cert.bits=bits
|
||||||
compress.grayscale.label=Aplicar escala de grisos per a la compressió
|
|
||||||
|
|||||||
@@ -572,8 +572,8 @@ login.invalid=Neplatné uživatelské jméno nebo heslo.
|
|||||||
login.locked=Váš účet byl uzamčen.
|
login.locked=Váš účet byl uzamčen.
|
||||||
login.signinTitle=Prosím přihlaste se
|
login.signinTitle=Prosím přihlaste se
|
||||||
login.ssoSignIn=Přihlásit se přes Single Sign-on
|
login.ssoSignIn=Přihlásit se přes Single Sign-on
|
||||||
login.oAuth2AutoCreateDisabled=Automatické vytváření OAUTH2 uživatelů je zakázáno
|
login.oauth2AutoCreateDisabled=Automatické vytváření OAUTH2 uživatelů je zakázáno
|
||||||
login.oAuth2AdminBlockedUser=Registrace nebo přihlášení neregistrovaných uživatelů je momentálně blokováno. Kontaktujte prosím správce.
|
login.oauth2AdminBlockedUser=Registrace nebo přihlášení neregistrovaných uživatelů je momentálně blokováno. Kontaktujte prosím správce.
|
||||||
login.oauth2RequestNotFound=Požadavek na autorizaci nebyl nalezen
|
login.oauth2RequestNotFound=Požadavek na autorizaci nebyl nalezen
|
||||||
login.oauth2InvalidUserInfoResponse=Neplatná odpověď s informacemi o uživateli
|
login.oauth2InvalidUserInfoResponse=Neplatná odpověď s informacemi o uživateli
|
||||||
login.oauth2invalidRequest=Neplatný požadavek
|
login.oauth2invalidRequest=Neplatný požadavek
|
||||||
@@ -1384,4 +1384,3 @@ validateSignature.cert.version=Verze
|
|||||||
validateSignature.cert.keyUsage=Použití klíče
|
validateSignature.cert.keyUsage=Použití klíče
|
||||||
validateSignature.cert.selfSigned=Podepsaný sám sebou
|
validateSignature.cert.selfSigned=Podepsaný sám sebou
|
||||||
validateSignature.cert.bits=bitů
|
validateSignature.cert.bits=bitů
|
||||||
compress.grayscale.label=Použít stupnici šedi pro kompresi
|
|
||||||
|
|||||||
@@ -572,8 +572,8 @@ login.invalid=Ugyldigt brugernavn eller adgangskode.
|
|||||||
login.locked=Din konto er blevet låst.
|
login.locked=Din konto er blevet låst.
|
||||||
login.signinTitle=Log venligst ind
|
login.signinTitle=Log venligst ind
|
||||||
login.ssoSignIn=Log ind via Single Sign-on
|
login.ssoSignIn=Log ind via Single Sign-on
|
||||||
login.oAuth2AutoCreateDisabled=OAUTH2 Auto-Opret Bruger Deaktiveret
|
login.oauth2AutoCreateDisabled=OAUTH2 Auto-Opret Bruger Deaktiveret
|
||||||
login.oAuth2AdminBlockedUser=Registrering eller login af ikke-registrerede brugere er i øjeblikket blokeret. Kontakt venligst administratoren.
|
login.oauth2AdminBlockedUser=Registrering eller login af ikke-registrerede brugere er i øjeblikket blokeret. Kontakt venligst administratoren.
|
||||||
login.oauth2RequestNotFound=Autorisationsanmodning ikke fundet
|
login.oauth2RequestNotFound=Autorisationsanmodning ikke fundet
|
||||||
login.oauth2InvalidUserInfoResponse=Ugyldigt Brugerinfo Svar
|
login.oauth2InvalidUserInfoResponse=Ugyldigt Brugerinfo Svar
|
||||||
login.oauth2invalidRequest=Ugyldig Anmodning
|
login.oauth2invalidRequest=Ugyldig Anmodning
|
||||||
@@ -1384,4 +1384,3 @@ validateSignature.cert.version=Version
|
|||||||
validateSignature.cert.keyUsage=Key Usage
|
validateSignature.cert.keyUsage=Key Usage
|
||||||
validateSignature.cert.selfSigned=Self-Signed
|
validateSignature.cert.selfSigned=Self-Signed
|
||||||
validateSignature.cert.bits=bits
|
validateSignature.cert.bits=bits
|
||||||
compress.grayscale.label=Anvend gråskala til komprimering
|
|
||||||
|
|||||||
@@ -572,8 +572,8 @@ login.invalid=Benutzername oder Passwort ungültig.
|
|||||||
login.locked=Ihr Konto wurde gesperrt.
|
login.locked=Ihr Konto wurde gesperrt.
|
||||||
login.signinTitle=Bitte melden Sie sich an.
|
login.signinTitle=Bitte melden Sie sich an.
|
||||||
login.ssoSignIn=Anmeldung per Single Sign-On
|
login.ssoSignIn=Anmeldung per Single Sign-On
|
||||||
login.oAuth2AutoCreateDisabled=OAUTH2 Benutzer automatisch erstellen deaktiviert
|
login.oauth2AutoCreateDisabled=OAUTH2 Benutzer automatisch erstellen deaktiviert
|
||||||
login.oAuth2AdminBlockedUser=Die Registrierung bzw. das anmelden von nicht registrierten Benutzern ist derzeit gesperrt. Bitte wenden Sie sich an den Administrator.
|
login.oauth2AdminBlockedUser=Die Registrierung bzw. das anmelden von nicht registrierten Benutzern ist derzeit gesperrt. Bitte wenden Sie sich an den Administrator.
|
||||||
login.oauth2RequestNotFound=Autorisierungsanfrage nicht gefunden
|
login.oauth2RequestNotFound=Autorisierungsanfrage nicht gefunden
|
||||||
login.oauth2InvalidUserInfoResponse=Ungültige Benutzerinformationsantwort
|
login.oauth2InvalidUserInfoResponse=Ungültige Benutzerinformationsantwort
|
||||||
login.oauth2invalidRequest=ungültige Anfrage
|
login.oauth2invalidRequest=ungültige Anfrage
|
||||||
@@ -1384,4 +1384,3 @@ validateSignature.cert.version=Version
|
|||||||
validateSignature.cert.keyUsage=Schlüsselverwendung
|
validateSignature.cert.keyUsage=Schlüsselverwendung
|
||||||
validateSignature.cert.selfSigned=Selbstsigniert
|
validateSignature.cert.selfSigned=Selbstsigniert
|
||||||
validateSignature.cert.bits=bits
|
validateSignature.cert.bits=bits
|
||||||
compress.grayscale.label=Graustufen für Komprimierung anwenden
|
|
||||||
|
|||||||
@@ -572,8 +572,8 @@ login.invalid=Μη έγκυρο όνομα χρήστη ή κωδικός.
|
|||||||
login.locked=Ο λογαριασμός σας έχει κλειδωθεί.
|
login.locked=Ο λογαριασμός σας έχει κλειδωθεί.
|
||||||
login.signinTitle=Παρακαλώ συνδεθείτε
|
login.signinTitle=Παρακαλώ συνδεθείτε
|
||||||
login.ssoSignIn=Σύνδεση μέσω Single Sign-on
|
login.ssoSignIn=Σύνδεση μέσω Single Sign-on
|
||||||
login.oAuth2AutoCreateDisabled=Η αυτόματη δημιουργία χρήστη OAUTH2 είναι απενεργοποιημένη
|
login.oauth2AutoCreateDisabled=Η αυτόματη δημιουργία χρήστη OAUTH2 είναι απενεργοποιημένη
|
||||||
login.oAuth2AdminBlockedUser=Η εγγραφή ή σύνδεση μη εγγεγραμμένων χρηστών είναι προς το παρόν αποκλεισμένη. Παρακαλώ επικοινωνήστε με τον διαχειριστή.
|
login.oauth2AdminBlockedUser=Η εγγραφή ή σύνδεση μη εγγεγραμμένων χρηστών είναι προς το παρόν αποκλεισμένη. Παρακαλώ επικοινωνήστε με τον διαχειριστή.
|
||||||
login.oauth2RequestNotFound=Το αίτημα εξουσιοδότησης δεν βρέθηκε
|
login.oauth2RequestNotFound=Το αίτημα εξουσιοδότησης δεν βρέθηκε
|
||||||
login.oauth2InvalidUserInfoResponse=Μη έγκυρη απόκριση πληροφοριών χρήστη
|
login.oauth2InvalidUserInfoResponse=Μη έγκυρη απόκριση πληροφοριών χρήστη
|
||||||
login.oauth2invalidRequest=Μη έγκυρο αίτημα
|
login.oauth2invalidRequest=Μη έγκυρο αίτημα
|
||||||
@@ -1384,4 +1384,3 @@ validateSignature.cert.version=Έκδοση
|
|||||||
validateSignature.cert.keyUsage=Χρήση κλειδιού
|
validateSignature.cert.keyUsage=Χρήση κλειδιού
|
||||||
validateSignature.cert.selfSigned=Αυτο-υπογεγραμμένο
|
validateSignature.cert.selfSigned=Αυτο-υπογεγραμμένο
|
||||||
validateSignature.cert.bits=bits
|
validateSignature.cert.bits=bits
|
||||||
compress.grayscale.label=Εφαρμογή κλίμακας του γκρι για συμπίεση
|
|
||||||
|
|||||||
@@ -572,8 +572,8 @@ login.invalid=Invalid username or password.
|
|||||||
login.locked=Your account has been locked.
|
login.locked=Your account has been locked.
|
||||||
login.signinTitle=Please sign in
|
login.signinTitle=Please sign in
|
||||||
login.ssoSignIn=Login via Single Sign-on
|
login.ssoSignIn=Login via Single Sign-on
|
||||||
login.oAuth2AutoCreateDisabled=OAUTH2 Auto-Create User Disabled
|
login.oauth2AutoCreateDisabled=OAUTH2 Auto-Create User Disabled
|
||||||
login.oAuth2AdminBlockedUser=Registration or logging in of non-registered users is currently blocked. Please contact the administrator.
|
login.oauth2AdminBlockedUser=Registration or logging in of non-registered users is currently blocked. Please contact the administrator.
|
||||||
login.oauth2RequestNotFound=Authorization request not found
|
login.oauth2RequestNotFound=Authorization request not found
|
||||||
login.oauth2InvalidUserInfoResponse=Invalid User Info Response
|
login.oauth2InvalidUserInfoResponse=Invalid User Info Response
|
||||||
login.oauth2invalidRequest=Invalid Request
|
login.oauth2invalidRequest=Invalid Request
|
||||||
@@ -1384,4 +1384,3 @@ validateSignature.cert.version=Version
|
|||||||
validateSignature.cert.keyUsage=Key Usage
|
validateSignature.cert.keyUsage=Key Usage
|
||||||
validateSignature.cert.selfSigned=Self-Signed
|
validateSignature.cert.selfSigned=Self-Signed
|
||||||
validateSignature.cert.bits=bits
|
validateSignature.cert.bits=bits
|
||||||
compress.grayscale.label=Apply Grayscale for Compression
|
|
||||||
|
|||||||
@@ -572,8 +572,8 @@ login.invalid=Invalid username or password.
|
|||||||
login.locked=Your account has been locked.
|
login.locked=Your account has been locked.
|
||||||
login.signinTitle=Please sign in
|
login.signinTitle=Please sign in
|
||||||
login.ssoSignIn=Login via Single Sign-on
|
login.ssoSignIn=Login via Single Sign-on
|
||||||
login.oAuth2AutoCreateDisabled=OAUTH2 Auto-Create User Disabled
|
login.oauth2AutoCreateDisabled=OAUTH2 Auto-Create User Disabled
|
||||||
login.oAuth2AdminBlockedUser=Registration or logging in of non-registered users is currently blocked. Please contact the administrator.
|
login.oauth2AdminBlockedUser=Registration or logging in of non-registered users is currently blocked. Please contact the administrator.
|
||||||
login.oauth2RequestNotFound=Authorization request not found
|
login.oauth2RequestNotFound=Authorization request not found
|
||||||
login.oauth2InvalidUserInfoResponse=Invalid User Info Response
|
login.oauth2InvalidUserInfoResponse=Invalid User Info Response
|
||||||
login.oauth2invalidRequest=Invalid Request
|
login.oauth2invalidRequest=Invalid Request
|
||||||
@@ -1384,4 +1384,3 @@ validateSignature.cert.version=Version
|
|||||||
validateSignature.cert.keyUsage=Key Usage
|
validateSignature.cert.keyUsage=Key Usage
|
||||||
validateSignature.cert.selfSigned=Self-Signed
|
validateSignature.cert.selfSigned=Self-Signed
|
||||||
validateSignature.cert.bits=bits
|
validateSignature.cert.bits=bits
|
||||||
compress.grayscale.label=Apply Grayscale for Compression
|
|
||||||
|
|||||||
@@ -138,13 +138,13 @@ analytics.settings=Puede cambiar la configuración de analíticas en el archivo
|
|||||||
# NAVBAR #
|
# NAVBAR #
|
||||||
#############
|
#############
|
||||||
navbar.favorite=Favoritos
|
navbar.favorite=Favoritos
|
||||||
navbar.recent=Nuevo y recientemente actualizado
|
navbar.recent=New and recently updated
|
||||||
navbar.darkmode=Modo oscuro
|
navbar.darkmode=Modo oscuro
|
||||||
navbar.language=Idiomas
|
navbar.language=Idiomas
|
||||||
navbar.settings=Configuración
|
navbar.settings=Configuración
|
||||||
navbar.allTools=Herramientas
|
navbar.allTools=Herramientas
|
||||||
navbar.multiTool=Multi herramientas
|
navbar.multiTool=Multi herramientas
|
||||||
navbar.search=Buscar
|
navbar.search=Search
|
||||||
navbar.sections.organize=Organizar
|
navbar.sections.organize=Organizar
|
||||||
navbar.sections.convertTo=Convertir a PDF
|
navbar.sections.convertTo=Convertir a PDF
|
||||||
navbar.sections.convertFrom=Convertir desde PDF
|
navbar.sections.convertFrom=Convertir desde PDF
|
||||||
@@ -246,14 +246,14 @@ database.info_1=Al importar datos, es fundamental garantizar la estructura corre
|
|||||||
database.info_2=El nombre del archivo no importa al cargarlo. Posteriormente se le cambiará el nombre para que siga el formato backup_user_yyyyMMddHHmm.sql, lo que garantiza una convención de nomenclatura coherente.
|
database.info_2=El nombre del archivo no importa al cargarlo. Posteriormente se le cambiará el nombre para que siga el formato backup_user_yyyyMMddHHmm.sql, lo que garantiza una convención de nomenclatura coherente.
|
||||||
database.submit=Importar Copia de Seguridad
|
database.submit=Importar Copia de Seguridad
|
||||||
database.importIntoDatabaseSuccessed=Importación a la base de datos ha sido exitosa
|
database.importIntoDatabaseSuccessed=Importación a la base de datos ha sido exitosa
|
||||||
database.backupCreated=Respaldo de la Base de Datos exitoso
|
database.backupCreated=Database backup successful
|
||||||
database.fileNotFound=Archivo no encontrado
|
database.fileNotFound=Archivo no encontrado
|
||||||
database.fileNullOrEmpty=El archivo no debe ser nulo o vacío.
|
database.fileNullOrEmpty=El archivo no debe ser nulo o vacío.
|
||||||
database.failedImportFile=Archivo de importación fallido
|
database.failedImportFile=Archivo de importación fallido
|
||||||
database.notSupported=Esta función no esta disponible para su conexión de Base de Datos
|
database.notSupported=This function is not available for your database connection.
|
||||||
|
|
||||||
session.expired=Tu sesión ha caducado. Actualice la página e inténtelo de nuevo.
|
session.expired=Tu sesión ha caducado. Actualice la página e inténtelo de nuevo.
|
||||||
session.refreshPage=Refrescar Página
|
session.refreshPage=Refresh Page
|
||||||
|
|
||||||
#############
|
#############
|
||||||
# HOME-PAGE #
|
# HOME-PAGE #
|
||||||
@@ -262,18 +262,18 @@ home.desc=Su ventanilla única autohospedada para todas sus necesidades PDF
|
|||||||
home.searchBar=Buscar características...
|
home.searchBar=Buscar características...
|
||||||
|
|
||||||
|
|
||||||
home.viewPdf.title=Ver/Editar PDF
|
home.viewPdf.title=View/Edit PDF
|
||||||
home.viewPdf.desc=Ver, anotar, añadir texto o imágenes
|
home.viewPdf.desc=Ver, anotar, añadir texto o imágenes
|
||||||
viewPdf.tags=ver,leer,anotar,texto,imagen
|
viewPdf.tags=ver,leer,anotar,texto,imagen
|
||||||
|
|
||||||
home.setFavorites=Agregar Favoritos
|
home.setFavorites=Set Favourites
|
||||||
home.hideFavorites=Ocultar Favoritos
|
home.hideFavorites=Hide Favourites
|
||||||
home.showFavorites=Mostrar Favoritos
|
home.showFavorites=Show Favourites
|
||||||
home.legacyHomepage=Página de inicio anterior
|
home.legacyHomepage=Old homepage
|
||||||
home.newHomePage=¡Prueba nuestra nueva página de inicio!
|
home.newHomePage=Try our new homepage!
|
||||||
home.alphabetical=Alfabetico
|
home.alphabetical=Alphabetical
|
||||||
home.globalPopularity=Popularidad Global
|
home.globalPopularity=Global Popularity
|
||||||
home.sortBy=Ordenado por:
|
home.sortBy=Sort by:
|
||||||
|
|
||||||
home.multiTool.title=Multi-herramienta PDF
|
home.multiTool.title=Multi-herramienta PDF
|
||||||
home.multiTool.desc=Combinar, rotar, reorganizar y eliminar páginas
|
home.multiTool.desc=Combinar, rotar, reorganizar y eliminar páginas
|
||||||
@@ -462,9 +462,9 @@ home.MarkdownToPDF.title=Markdown a PDF
|
|||||||
home.MarkdownToPDF.desc=Convierte cualquier archivo Markdown a PDF
|
home.MarkdownToPDF.desc=Convierte cualquier archivo Markdown a PDF
|
||||||
MarkdownToPDF.tags=margen,contenido web,transformación,convertir
|
MarkdownToPDF.tags=margen,contenido web,transformación,convertir
|
||||||
|
|
||||||
home.PDFToMarkdown.title=PDF a Markdown
|
home.PDFToMarkdown.title=PDF to Markdown
|
||||||
home.PDFToMarkdown.desc=Convierte cualquier PDF a Markdown
|
home.PDFToMarkdown.desc=Converts any PDF to Markdown
|
||||||
PDFToMarkdown.tags=markup,contenido web,transformación,convertir,md
|
PDFToMarkdown.tags=markup,web-content,transformation,convert,md
|
||||||
|
|
||||||
home.getPdfInfo.title=Obtener toda la información en PDF
|
home.getPdfInfo.title=Obtener toda la información en PDF
|
||||||
home.getPdfInfo.desc=Obtiene toda la información posible de archivos PDF
|
home.getPdfInfo.desc=Obtiene toda la información posible de archivos PDF
|
||||||
@@ -489,9 +489,9 @@ home.autoRedact.title=Auto Redactar
|
|||||||
home.autoRedact.desc=Redactar automáticamente (ocultar) texto en un PDF según el texto introducido
|
home.autoRedact.desc=Redactar automáticamente (ocultar) texto en un PDF según el texto introducido
|
||||||
autoRedact.tags=Redactar,Ocultar,ocultar,negro,subrayador,oculto
|
autoRedact.tags=Redactar,Ocultar,ocultar,negro,subrayador,oculto
|
||||||
|
|
||||||
home.redact.title=Redacción Manual
|
home.redact.title=Manual Redaction
|
||||||
home.redact.desc=Redacta un PDF basado en el texto seleccionado, dibuja formas y/o página(s) selecionada(s)
|
home.redact.desc=Redacts a PDF based on selected text, drawn shapes and/or selected page(s)
|
||||||
redact.tags=Redactar,Ocultar,oscurece,negro,marcador,oculto,manual
|
redact.tags=Redact,Hide,black out,black,marker,hidden,manual
|
||||||
|
|
||||||
home.tableExtraxt.title=PDF a CSV
|
home.tableExtraxt.title=PDF a CSV
|
||||||
home.tableExtraxt.desc=Extraer Tablas de un PDF convirtiéndolas a CSV
|
home.tableExtraxt.desc=Extraer Tablas de un PDF convirtiéndolas a CSV
|
||||||
@@ -533,9 +533,9 @@ home.splitPdfByChapters.title=Dividir PDF por capítulos
|
|||||||
home.splitPdfByChapters.desc=Divida un PDF en varios archivos según su estructura de capítulos.
|
home.splitPdfByChapters.desc=Divida un PDF en varios archivos según su estructura de capítulos.
|
||||||
splitPdfByChapters.tags=dividir,capítulos,marcadores,organizar
|
splitPdfByChapters.tags=dividir,capítulos,marcadores,organizar
|
||||||
|
|
||||||
home.validateSignature.title=Validar firma del PDF
|
home.validateSignature.title=Validate PDF Signature
|
||||||
home.validateSignature.desc=Verifica firmas digitales y certificados en los documentos PDF
|
home.validateSignature.desc=Verify digital signatures and certificates in PDF documents
|
||||||
validateSignature.tags=firma,verificar,validar,pdf,certificado,firma digital,validar firma,validar certificado
|
validateSignature.tags=signature,verify,validate,pdf,certificate,digital signature,Validate Signature,Validate certificate
|
||||||
|
|
||||||
#replace-invert-color
|
#replace-invert-color
|
||||||
replace-color.title=Reemplazar-Invertir-Color
|
replace-color.title=Reemplazar-Invertir-Color
|
||||||
@@ -572,58 +572,58 @@ login.invalid=Nombre de usuario o contraseña erróneos.
|
|||||||
login.locked=Su cuenta se ha bloqueado.
|
login.locked=Su cuenta se ha bloqueado.
|
||||||
login.signinTitle=Por favor, inicie sesión
|
login.signinTitle=Por favor, inicie sesión
|
||||||
login.ssoSignIn=Iniciar sesión a través del inicio de sesión único
|
login.ssoSignIn=Iniciar sesión a través del inicio de sesión único
|
||||||
login.oAuth2AutoCreateDisabled=Usuario de creación automática de OAUTH2 DESACTIVADO
|
login.oauth2AutoCreateDisabled=Usuario de creación automática de OAUTH2 DESACTIVADO
|
||||||
login.oAuth2AdminBlockedUser=El registro o inicio de sesión de usuarios no registrados está actualmente bloqueado. Por favor, contáctese con el administrador.
|
login.oauth2AdminBlockedUser=El registro o inicio de sesión de usuarios no registrados está actualmente bloqueado. Por favor, contáctese con el administrador.
|
||||||
login.oauth2RequestNotFound=Solicitud de autorización no encontrada
|
login.oauth2RequestNotFound=Solicitud de autorización no encontrada
|
||||||
login.oauth2InvalidUserInfoResponse=Respuesta de información de usuario no válida
|
login.oauth2InvalidUserInfoResponse=Respuesta de información de usuario no válida
|
||||||
login.oauth2invalidRequest=Solicitud no válida
|
login.oauth2invalidRequest=Solicitud no válida
|
||||||
login.oauth2AccessDenied=Acceso denegado
|
login.oauth2AccessDenied=Acceso denegado
|
||||||
login.oauth2InvalidTokenResponse=Respuesta de token no válida
|
login.oauth2InvalidTokenResponse=Respuesta de token no válida
|
||||||
login.oauth2InvalidIdToken=Token de identificación no válido
|
login.oauth2InvalidIdToken=Token de identificación no válido
|
||||||
login.relyingPartyRegistrationNotFound=No hay registro de terceros confiables
|
login.relyingPartyRegistrationNotFound=No relying party registration found
|
||||||
login.userIsDisabled=El usuario está desactivado, actualmente el acceso está bloqueado para ese nombre de usuario. Por favor, póngase en contacto con el administrador.
|
login.userIsDisabled=El usuario está desactivado, actualmente el acceso está bloqueado para ese nombre de usuario. Por favor, póngase en contacto con el administrador.
|
||||||
login.alreadyLoggedIn=Ya has iniciado sesión en
|
login.alreadyLoggedIn=Ya has iniciado sesión en
|
||||||
login.alreadyLoggedIn2=dispositivos. Cierra sesión en los dispositivos y vuelve a intentarlo.
|
login.alreadyLoggedIn2=dispositivos. Cierra sesión en los dispositivos y vuelve a intentarlo.
|
||||||
login.toManySessions=Tienes demasiadas sesiones activas
|
login.toManySessions=Tienes demasiadas sesiones activas
|
||||||
|
|
||||||
#auto-redact
|
#auto-redact
|
||||||
autoRedact.title=Auto Censurar Texto
|
autoRedact.title=Auto Redactar
|
||||||
autoRedact.header=Auto Censurar Texto
|
autoRedact.header=Auto Redactar
|
||||||
autoRedact.colorLabel=Color
|
autoRedact.colorLabel=Color
|
||||||
autoRedact.textsToRedactLabel=Texto para Censurar (separado por líneas)
|
autoRedact.textsToRedactLabel=Texto para Redactar (separado por líneas)
|
||||||
autoRedact.textsToRedactPlaceholder=por ej. \nConfidencial \nAlto-Secreto
|
autoRedact.textsToRedactPlaceholder=por ej. \nConfidencial \nAlto-Secreto
|
||||||
autoRedact.useRegexLabel=Usar Regex
|
autoRedact.useRegexLabel=Usar Regex
|
||||||
autoRedact.wholeWordSearchLabel=Búsqueda por palabra completa
|
autoRedact.wholeWordSearchLabel=Búsqueda por palabra completa
|
||||||
autoRedact.customPaddingLabel=Extra Padding personalizado
|
autoRedact.customPaddingLabel=Extra Padding personalizado
|
||||||
autoRedact.convertPDFToImageLabel=Convertir PDF a imagen PDF (Utilizado para eliminar el texto detrás del cajetín de censura)
|
autoRedact.convertPDFToImageLabel=Convertir PDF a imagen PDF (Utilizado para quitar el texto detrás del cajetín)
|
||||||
autoRedact.submitButton=Enviar
|
autoRedact.submitButton=Enviar
|
||||||
|
|
||||||
#redact
|
#redact
|
||||||
redact.title=Censurar texto Manualmente
|
redact.title=Manual Redaction
|
||||||
redact.header=Censurar texto Manualmente
|
redact.header=Manual Redaction
|
||||||
redact.submit=Enviar
|
redact.submit=Redact
|
||||||
redact.textBasedRedaction=Censura basada en texto
|
redact.textBasedRedaction=Text based Redaction
|
||||||
redact.pageBasedRedaction=Censura basada en la página
|
redact.pageBasedRedaction=Page-based Redaction
|
||||||
redact.convertPDFToImageLabel=Convertir PDF a PDF-Image (Utilizado para eliminar el texto detrás del cajetín de censura)
|
redact.convertPDFToImageLabel=Convert PDF to PDF-Image (Used to remove text behind the box)
|
||||||
redact.pageRedactionNumbers.title=Páginas
|
redact.pageRedactionNumbers.title=Pages
|
||||||
redact.pageRedactionNumbers.placeholder=(e.g. 1,2,8 or 4,7,12-16 or 2n-1)
|
redact.pageRedactionNumbers.placeholder=(e.g. 1,2,8 or 4,7,12-16 or 2n-1)
|
||||||
redact.redactionColor.title=Color del cajetín
|
redact.redactionColor.title=Redaction Color
|
||||||
redact.export=Exportar
|
redact.export=Export
|
||||||
redact.upload=Cargar
|
redact.upload=Upload
|
||||||
redact.boxRedaction=Dibujar cajetín de censura
|
redact.boxRedaction=Box draw redaction
|
||||||
redact.zoom=Zoom
|
redact.zoom=Zoom
|
||||||
redact.zoomIn=Zoom in
|
redact.zoomIn=Zoom in
|
||||||
redact.zoomOut=Zoom out
|
redact.zoomOut=Zoom out
|
||||||
redact.nextPage=Siguiente página
|
redact.nextPage=Next Page
|
||||||
redact.previousPage=Pagina Anterior
|
redact.previousPage=Previous Page
|
||||||
redact.toggleSidebar=Activar/desactivar barra lateral
|
redact.toggleSidebar=Toggle Sidebar
|
||||||
redact.showThumbnails=Mostrar Miniaturas
|
redact.showThumbnails=Show Thumbnails
|
||||||
redact.showDocumentOutline=Mostra Esquema del documento (doble-click para expandir/colapsar elementos)
|
redact.showDocumentOutline=Show Document Outline (double-click to expand/collapse all items)
|
||||||
redact.showAttatchments=Mostrar Adjuntos
|
redact.showAttatchments=Show Attachments
|
||||||
redact.showLayers=Mostrar Capas (doble-click para reiniciar las capas a su estado inicial)
|
redact.showLayers=Show Layers (double-click to reset all layers to the default state)
|
||||||
redact.colourPicker=Seleccionador de Color
|
redact.colourPicker=Colour Picker
|
||||||
redact.findCurrentOutlineItem=Buscar elemento actual de esquema
|
redact.findCurrentOutlineItem=Find current outline item
|
||||||
redact.applyChanges=Aplicar Cambios
|
redact.applyChanges=Apply Changes
|
||||||
|
|
||||||
#showJS
|
#showJS
|
||||||
showJS.title=Mostrar Javascript
|
showJS.title=Mostrar Javascript
|
||||||
@@ -661,9 +661,9 @@ MarkdownToPDF.credit=Usa WeasyPrint
|
|||||||
|
|
||||||
|
|
||||||
#pdf-to-markdown
|
#pdf-to-markdown
|
||||||
PDFToMarkdown.title=PDF a Markdown
|
PDFToMarkdown.title=PDF To Markdown
|
||||||
PDFToMarkdown.header=PDF a Markdown
|
PDFToMarkdown.header=PDF To Markdown
|
||||||
PDFToMarkdown.submit=Convertir
|
PDFToMarkdown.submit=Convert
|
||||||
|
|
||||||
|
|
||||||
#url-to-pdf
|
#url-to-pdf
|
||||||
@@ -713,14 +713,14 @@ AddStampRequest.submit=Enviar
|
|||||||
|
|
||||||
|
|
||||||
#sanitizePDF
|
#sanitizePDF
|
||||||
sanitizePDF.title=Limpiar archivo PDF
|
sanitizePDF.title=Desinfectar archivo PDF
|
||||||
sanitizePDF.header=Limpiar un archivo PDF
|
sanitizePDF.header=Desinfectar un archivo PDF
|
||||||
sanitizePDF.selectText.1=Eliminar código JavaScript
|
sanitizePDF.selectText.1=Eliminar acciones JavaScript
|
||||||
sanitizePDF.selectText.2=Eliminar archivos incrustados
|
sanitizePDF.selectText.2=Eliminar archivos incrustados
|
||||||
sanitizePDF.selectText.3=Eliminar metadatos
|
sanitizePDF.selectText.3=Eliminar metadatos
|
||||||
sanitizePDF.selectText.4=Eliminar enlaces
|
sanitizePDF.selectText.4=Eliminar enlaces
|
||||||
sanitizePDF.selectText.5=Eliminar fuentes
|
sanitizePDF.selectText.5=Eliminar fuentes
|
||||||
sanitizePDF.submit=Limpiar PDF
|
sanitizePDF.submit=Desinfectar PDF
|
||||||
|
|
||||||
|
|
||||||
#addPageNumbers
|
#addPageNumbers
|
||||||
@@ -874,13 +874,13 @@ sign.save=Guardar Firma
|
|||||||
sign.personalSigs=Firmas Personales
|
sign.personalSigs=Firmas Personales
|
||||||
sign.sharedSigs=Firmas compartidas
|
sign.sharedSigs=Firmas compartidas
|
||||||
sign.noSavedSigs=No se encontraron firmas guardadas
|
sign.noSavedSigs=No se encontraron firmas guardadas
|
||||||
sign.addToAll=Agregar a todas las páginas
|
sign.addToAll=Add to all pages
|
||||||
sign.delete=Eliminar
|
sign.delete=Delete
|
||||||
sign.first=Primera página
|
sign.first=First page
|
||||||
sign.last=Última página
|
sign.last=Last page
|
||||||
sign.next=Siguiente página
|
sign.next=Next page
|
||||||
sign.previous=Página anterior
|
sign.previous=Previous page
|
||||||
sign.maintainRatio=Activar/desactivar la relación de aspecto
|
sign.maintainRatio=Toggle maintain aspect ratio
|
||||||
|
|
||||||
|
|
||||||
#repair
|
#repair
|
||||||
@@ -952,7 +952,7 @@ compress.title=Comprimir
|
|||||||
compress.header=Comprimir PDF
|
compress.header=Comprimir PDF
|
||||||
compress.credit=Este servicio utiliza qpdf para compresión/optimización de PDF
|
compress.credit=Este servicio utiliza qpdf para compresión/optimización de PDF
|
||||||
compress.selectText.1=Modo manual - De 1 a 5
|
compress.selectText.1=Modo manual - De 1 a 5
|
||||||
compress.selectText.1.1=En los niveles de optimización 6 a 9, además de la compresión general de PDF, se reduce la resolución de la imagen para reducir aún más el tamaño del archivo. Los niveles más altos dan como resultado una mayor compresión de la imagen (hasta el 50 % del tamaño original), lo que permite lograr una mayor reducción del tamaño, pero con una posible pérdida de calidad en las imágenes.
|
compress.selectText.1.1=In optimization levels 6 to 9, in addition to general PDF compression, image resolution is scaled down to further reduce file size. Higher levels result in stronger image compression (up to 50% of the original size), achieving greater size reduction but with potential quality loss in images.
|
||||||
compress.selectText.2=Nivel de optimización:
|
compress.selectText.2=Nivel de optimización:
|
||||||
compress.selectText.3=4 (Terrible para imágenes de texto)
|
compress.selectText.3=4 (Terrible para imágenes de texto)
|
||||||
compress.selectText.4=Modo automático: ajusta automáticamente la calidad para que el PDF tenga el tamaño exacto
|
compress.selectText.4=Modo automático: ajusta automáticamente la calidad para que el PDF tenga el tamaño exacto
|
||||||
@@ -1000,42 +1000,42 @@ pdfOrganiser.placeholder=(por ej., 1,3,2 o 4-8,2,10-12 o 2n-1)
|
|||||||
multiTool.title=Multi-herramienta PDF
|
multiTool.title=Multi-herramienta PDF
|
||||||
multiTool.header=Multi-herramienta PDF
|
multiTool.header=Multi-herramienta PDF
|
||||||
multiTool.uploadPrompts=Nombre del archivo
|
multiTool.uploadPrompts=Nombre del archivo
|
||||||
multiTool.selectAll=Seleccionar Todo
|
multiTool.selectAll=Select All
|
||||||
multiTool.deselectAll=Deseleccionar Todo
|
multiTool.deselectAll=Deselect All
|
||||||
multiTool.selectPages=Seleccionar página(s)
|
multiTool.selectPages=Page Select
|
||||||
multiTool.selectedPages=Página(s) Selecccionada(s)
|
multiTool.selectedPages=Selected Pages
|
||||||
multiTool.page=Página
|
multiTool.page=Page
|
||||||
multiTool.deleteSelected=Borrar selecionado(s)
|
multiTool.deleteSelected=Delete Selected
|
||||||
multiTool.downloadAll=Exportar
|
multiTool.downloadAll=Export
|
||||||
multiTool.downloadSelected=Exportar selecionado(s)
|
multiTool.downloadSelected=Export Selected
|
||||||
|
|
||||||
multiTool.insertPageBreak=Insertar salto página
|
multiTool.insertPageBreak=Insert Page Break
|
||||||
multiTool.addFile=Agregar Archivo
|
multiTool.addFile=Add File
|
||||||
multiTool.rotateLeft=Rotar a la izquierda
|
multiTool.rotateLeft=Rotate Left
|
||||||
multiTool.rotateRight=Rotar a la derecha
|
multiTool.rotateRight=Rotate Right
|
||||||
multiTool.split=Dividir
|
multiTool.split=Split
|
||||||
multiTool.moveLeft=Mover a la izquierda
|
multiTool.moveLeft=Move Left
|
||||||
multiTool.moveRight=Mover a la derecha
|
multiTool.moveRight=Move Right
|
||||||
multiTool.delete=Borrar
|
multiTool.delete=Delete
|
||||||
multiTool.dragDropMessage=Página(s) Selecccionada(s)
|
multiTool.dragDropMessage=Page(s) Selected
|
||||||
multiTool.undo=Deshacer
|
multiTool.undo=Undo
|
||||||
multiTool.redo=Rehacer
|
multiTool.redo=Redo
|
||||||
|
|
||||||
#decrypt
|
#decrypt
|
||||||
decrypt.passwordPrompt=Este archivo está protegido con contraseña. Introdúzca la contraseña:
|
decrypt.passwordPrompt=This file is password-protected. Please enter the password:
|
||||||
decrypt.cancelled=Operación cancelada para el PDF: {0}
|
decrypt.cancelled=Operation cancelled for PDF: {0}
|
||||||
decrypt.noPassword=No se proporcionó contraseña para PDF cifrado: {0}
|
decrypt.noPassword=No password provided for encrypted PDF: {0}
|
||||||
decrypt.invalidPassword=Por favor, inténtelo de nuevo con la contraseña correcta.
|
decrypt.invalidPassword=Please try again with the correct password.
|
||||||
decrypt.invalidPasswordHeader=Contraseña incorrecta o cifrado no compatible para PDF: {0}
|
decrypt.invalidPasswordHeader=Incorrect password or unsupported encryption for PDF: {0}
|
||||||
decrypt.unexpectedError=Se produjo un error al procesar el archivo. Inténtalo nuevamente.
|
decrypt.unexpectedError=There was an error processing the file. Please try again.
|
||||||
decrypt.serverError=Error del servidor al descifrar: {0}
|
decrypt.serverError=Server error while decrypting: {0}
|
||||||
decrypt.success=Archivo descifrado exitosamente.
|
decrypt.success=File decrypted successfully.
|
||||||
|
|
||||||
#multiTool-advert
|
#multiTool-advert
|
||||||
multiTool-advert.message=Esta función también está disponible en nuestra <a href="{0}">página de herramientas múltiples</a>. ¡Échale un vistazo para ver una interfaz de usuario página por página mejorada y funciones adicionales!
|
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
|
||||||
|
|
||||||
#view pdf
|
#view pdf
|
||||||
viewPdf.title=Ver/Editar PDF
|
viewPdf.title=View/Edit PDF
|
||||||
viewPdf.header=Ver PDF
|
viewPdf.header=Ver PDF
|
||||||
|
|
||||||
#pageRemover
|
#pageRemover
|
||||||
@@ -1337,51 +1337,50 @@ splitByChapters.submit=Dividir PDF
|
|||||||
|
|
||||||
#File Chooser
|
#File Chooser
|
||||||
fileChooser.click=Click
|
fileChooser.click=Click
|
||||||
fileChooser.or=o
|
fileChooser.or=or
|
||||||
fileChooser.dragAndDrop=Arrastrar & Soltar
|
fileChooser.dragAndDrop=Drag & Drop
|
||||||
fileChooser.dragAndDropPDF=Arrastrar & Soltar archivo PDF
|
fileChooser.dragAndDropPDF=Drag & Drop PDF file
|
||||||
fileChooser.dragAndDropImage=Arrastrar & Soltar archivo de Imagen
|
fileChooser.dragAndDropImage=Drag & Drop Image file
|
||||||
fileChooser.hoveredDragAndDrop=Arrastrar & Soltar archivos(s) aquí
|
fileChooser.hoveredDragAndDrop=Drag & Drop file(s) here
|
||||||
|
|
||||||
#release notes
|
#release notes
|
||||||
releases.footer=Versiones
|
releases.footer=Releases
|
||||||
releases.title=Notas de la versión
|
releases.title=Release Notes
|
||||||
releases.header=Notas de la versión
|
releases.header=Release Notes
|
||||||
releases.current.version=versión Actual
|
releases.current.version=Current Release
|
||||||
releases.note=Las notas de la versión solo están disponibles en Inglés
|
releases.note=Release notes are only available in English
|
||||||
|
|
||||||
#Validate Signature
|
#Validate Signature
|
||||||
validateSignature.title=Validar firmas del PDF
|
validateSignature.title=Validate PDF Signatures
|
||||||
validateSignature.header=Validar firmas del PDF
|
validateSignature.header=Validate Digital Signatures
|
||||||
validateSignature.selectPDF=Seleccione el archivo PDF firmado
|
validateSignature.selectPDF=Select signed PDF file
|
||||||
validateSignature.submit=Validar firmas
|
validateSignature.submit=Validate Signatures
|
||||||
validateSignature.results=Resultados de la Validación
|
validateSignature.results=Validation Results
|
||||||
validateSignature.status=Estador
|
validateSignature.status=Status
|
||||||
validateSignature.signer=Firmante
|
validateSignature.signer=Signer
|
||||||
validateSignature.date=Fecha
|
validateSignature.date=Date
|
||||||
validateSignature.reason=Razón
|
validateSignature.reason=Reason
|
||||||
validateSignature.location=Ubicación
|
validateSignature.location=Location
|
||||||
validateSignature.noSignatures=No se encontrario firmas digiales en este documento
|
validateSignature.noSignatures=No digital signatures found in this document
|
||||||
validateSignature.status.valid=Válido
|
validateSignature.status.valid=Valid
|
||||||
validateSignature.status.invalid=Inválido
|
validateSignature.status.invalid=Invalid
|
||||||
validateSignature.chain.invalid=Error en la validación de la cadena de certificados: no se puede verificar la identidad del firmante
|
validateSignature.chain.invalid=Certificate chain validation failed - cannot verify signer's identity
|
||||||
validateSignature.trust.invalid=El certificado no se encuentra en los almacenes de confianza: no se puede verificar la fuente
|
validateSignature.trust.invalid=Certificate not in trust store - source cannot be verified
|
||||||
validateSignature.cert.expired=Certificado expiredo
|
validateSignature.cert.expired=Certificate has expired
|
||||||
validateSignature.cert.revoked=Certificado fue revocado
|
validateSignature.cert.revoked=Certificate has been revoked
|
||||||
validateSignature.signature.info=Información de la firma
|
validateSignature.signature.info=Signature Information
|
||||||
validateSignature.signature=Firma
|
validateSignature.signature=Signature
|
||||||
validateSignature.signature.mathValid=La firma es matemáticamente válida aunque:
|
validateSignature.signature.mathValid=Signature is mathematically valid BUT:
|
||||||
validateSignature.selectCustomCert=Archivo de certificado personalizado X.509 (opcional)
|
validateSignature.selectCustomCert=Custom Certificate File X.509 (Optional)
|
||||||
validateSignature.cert.info=Detalles Certificado
|
validateSignature.cert.info=Certificate Details
|
||||||
validateSignature.cert.issuer=Emisor
|
validateSignature.cert.issuer=Issuer
|
||||||
validateSignature.cert.subject=Subject
|
validateSignature.cert.subject=Subject
|
||||||
validateSignature.cert.serialNumber=Número Serie
|
validateSignature.cert.serialNumber=Serial Number
|
||||||
validateSignature.cert.validFrom=Válido desde
|
validateSignature.cert.validFrom=Valid From
|
||||||
validateSignature.cert.validUntil=Válido hasta
|
validateSignature.cert.validUntil=Valid Until
|
||||||
validateSignature.cert.algorithm=Algoritmo
|
validateSignature.cert.algorithm=Algorithm
|
||||||
validateSignature.cert.keySize=Tamaño llave
|
validateSignature.cert.keySize=Key Size
|
||||||
validateSignature.cert.version=Versión
|
validateSignature.cert.version=Version
|
||||||
validateSignature.cert.keyUsage=Uso de la llave
|
validateSignature.cert.keyUsage=Key Usage
|
||||||
validateSignature.cert.selfSigned=Autofirmado
|
validateSignature.cert.selfSigned=Self-Signed
|
||||||
validateSignature.cert.bits=bits
|
validateSignature.cert.bits=bits
|
||||||
compress.grayscale.label=Aplicar escala de grises para compresión
|
|
||||||
|
|||||||
@@ -572,8 +572,8 @@ login.invalid=Okerreko erabiltzaile izena edo pasahitza.
|
|||||||
login.locked=Zure kontua blokeatu egin da.
|
login.locked=Zure kontua blokeatu egin da.
|
||||||
login.signinTitle=Mesedez, hasi saioa
|
login.signinTitle=Mesedez, hasi saioa
|
||||||
login.ssoSignIn=Hasi saioa Saioa hasteko modu bakarraren bidez
|
login.ssoSignIn=Hasi saioa Saioa hasteko modu bakarraren bidez
|
||||||
login.oAuth2AutoCreateDisabled=OAUTH2 Sortu automatikoki erabiltzailea desgaituta dago
|
login.oauth2AutoCreateDisabled=OAUTH2 Sortu automatikoki erabiltzailea desgaituta dago
|
||||||
login.oAuth2AdminBlockedUser=Registration or logging in of non-registered users is currently blocked. Please contact the administrator.
|
login.oauth2AdminBlockedUser=Registration or logging in of non-registered users is currently blocked. Please contact the administrator.
|
||||||
login.oauth2RequestNotFound=Authorization request not found
|
login.oauth2RequestNotFound=Authorization request not found
|
||||||
login.oauth2InvalidUserInfoResponse=Invalid User Info Response
|
login.oauth2InvalidUserInfoResponse=Invalid User Info Response
|
||||||
login.oauth2invalidRequest=Invalid Request
|
login.oauth2invalidRequest=Invalid Request
|
||||||
@@ -1384,4 +1384,3 @@ validateSignature.cert.version=Version
|
|||||||
validateSignature.cert.keyUsage=Key Usage
|
validateSignature.cert.keyUsage=Key Usage
|
||||||
validateSignature.cert.selfSigned=Self-Signed
|
validateSignature.cert.selfSigned=Self-Signed
|
||||||
validateSignature.cert.bits=bits
|
validateSignature.cert.bits=bits
|
||||||
compress.grayscale.label=Aplikatu grisezko eskala konpresiorako
|
|
||||||
|
|||||||
@@ -572,8 +572,8 @@ login.invalid=نام کاربری یا رمز عبور اشتباه است.
|
|||||||
login.locked=حساب شما قفل شده است.
|
login.locked=حساب شما قفل شده است.
|
||||||
login.signinTitle=لطفاً وارد شوید
|
login.signinTitle=لطفاً وارد شوید
|
||||||
login.ssoSignIn=ورود از طریق Single Sign-on
|
login.ssoSignIn=ورود از طریق Single Sign-on
|
||||||
login.oAuth2AutoCreateDisabled=ایجاد خودکار کاربر با OAUTH2 غیرفعال است
|
login.oauth2AutoCreateDisabled=ایجاد خودکار کاربر با OAUTH2 غیرفعال است
|
||||||
login.oAuth2AdminBlockedUser=ثبتنام یا ورود کاربران ثبتنشده در حال حاضر مسدود است. لطفاً با مدیر تماس بگیرید.
|
login.oauth2AdminBlockedUser=ثبتنام یا ورود کاربران ثبتنشده در حال حاضر مسدود است. لطفاً با مدیر تماس بگیرید.
|
||||||
login.oauth2RequestNotFound=درخواست احراز هویت پیدا نشد
|
login.oauth2RequestNotFound=درخواست احراز هویت پیدا نشد
|
||||||
login.oauth2InvalidUserInfoResponse=پاسخ اطلاعات کاربری نامعتبر است
|
login.oauth2InvalidUserInfoResponse=پاسخ اطلاعات کاربری نامعتبر است
|
||||||
login.oauth2invalidRequest=درخواست نامعتبر
|
login.oauth2invalidRequest=درخواست نامعتبر
|
||||||
@@ -1384,4 +1384,3 @@ validateSignature.cert.version=نسخه
|
|||||||
validateSignature.cert.keyUsage=کاربرد کلید
|
validateSignature.cert.keyUsage=کاربرد کلید
|
||||||
validateSignature.cert.selfSigned=با امضای خود
|
validateSignature.cert.selfSigned=با امضای خود
|
||||||
validateSignature.cert.bits=بیتها
|
validateSignature.cert.bits=بیتها
|
||||||
compress.grayscale.label=اعمال مقیاس خاکستری برای فشردهسازی
|
|
||||||
|
|||||||
@@ -572,8 +572,8 @@ login.invalid=Nom d'utilisateur ou mot de passe invalide.
|
|||||||
login.locked=Votre compte a été verrouillé.
|
login.locked=Votre compte a été verrouillé.
|
||||||
login.signinTitle=Veuillez vous connecter
|
login.signinTitle=Veuillez vous connecter
|
||||||
login.ssoSignIn=Se connecter via l'authentification unique
|
login.ssoSignIn=Se connecter via l'authentification unique
|
||||||
login.oAuth2AutoCreateDisabled=OAUTH2 Création automatique d'utilisateur désactivée
|
login.oauth2AutoCreateDisabled=OAUTH2 Création automatique d'utilisateur désactivée
|
||||||
login.oAuth2AdminBlockedUser=La création ou l'authentification d'utilisateurs non enregistrés est actuellement bloquée. Veuillez contacter l'administrateur.
|
login.oauth2AdminBlockedUser=La création ou l'authentification d'utilisateurs non enregistrés est actuellement bloquée. Veuillez contacter l'administrateur.
|
||||||
login.oauth2RequestNotFound=Demande d'autorisation introuvable
|
login.oauth2RequestNotFound=Demande d'autorisation introuvable
|
||||||
login.oauth2InvalidUserInfoResponse=Réponse contenant les informations de l'utilisateur est invalide
|
login.oauth2InvalidUserInfoResponse=Réponse contenant les informations de l'utilisateur est invalide
|
||||||
login.oauth2invalidRequest=Requête invalide
|
login.oauth2invalidRequest=Requête invalide
|
||||||
@@ -1384,4 +1384,3 @@ validateSignature.cert.version=Version
|
|||||||
validateSignature.cert.keyUsage=Usage de la clé
|
validateSignature.cert.keyUsage=Usage de la clé
|
||||||
validateSignature.cert.selfSigned=Auto-signé
|
validateSignature.cert.selfSigned=Auto-signé
|
||||||
validateSignature.cert.bits=bits
|
validateSignature.cert.bits=bits
|
||||||
compress.grayscale.label=Appliquer l'échelle de gris pour la compression
|
|
||||||
|
|||||||
@@ -1384,4 +1384,3 @@ validateSignature.cert.version=Leagan
|
|||||||
validateSignature.cert.keyUsage=Úsáid Eochrach
|
validateSignature.cert.keyUsage=Úsáid Eochrach
|
||||||
validateSignature.cert.selfSigned=Féin-Sínithe
|
validateSignature.cert.selfSigned=Féin-Sínithe
|
||||||
validateSignature.cert.bits=giotáin
|
validateSignature.cert.bits=giotáin
|
||||||
compress.grayscale.label=Cuir Scála Liath i bhFeidhm le Comhbhrú
|
|
||||||
|
|||||||
@@ -572,8 +572,8 @@ login.invalid=अमान्य उपयोगकर्ता नाम या
|
|||||||
login.locked=आपका खाता लॉक कर दिया गया है।
|
login.locked=आपका खाता लॉक कर दिया गया है।
|
||||||
login.signinTitle=कृपया साइन इन करें
|
login.signinTitle=कृपया साइन इन करें
|
||||||
login.ssoSignIn=सिंगल साइन-ऑन के माध्यम से लॉगिन करें
|
login.ssoSignIn=सिंगल साइन-ऑन के माध्यम से लॉगिन करें
|
||||||
login.oAuth2AutoCreateDisabled=OAUTH2 स्वतः उपयोगकर्ता निर्माण अक्षम है
|
login.oauth2AutoCreateDisabled=OAUTH2 स्वतः उपयोगकर्ता निर्माण अक्षम है
|
||||||
login.oAuth2AdminBlockedUser=गैर-पंजीकृत उपयोगकर्ताओं का पंजीकरण या लॉगिन वर्तमान में अवरुद्ध है। कृपया व्यवस्थापक से संपर्क करें।
|
login.oauth2AdminBlockedUser=गैर-पंजीकृत उपयोगकर्ताओं का पंजीकरण या लॉगिन वर्तमान में अवरुद्ध है। कृपया व्यवस्थापक से संपर्क करें।
|
||||||
login.oauth2RequestNotFound=प्राधिकरण अनुरोध नहीं मिला
|
login.oauth2RequestNotFound=प्राधिकरण अनुरोध नहीं मिला
|
||||||
login.oauth2InvalidUserInfoResponse=अमान्य उपयोगकर्ता जानकारी प्रतिक्रिया
|
login.oauth2InvalidUserInfoResponse=अमान्य उपयोगकर्ता जानकारी प्रतिक्रिया
|
||||||
login.oauth2invalidRequest=अमान्य अनुरोध
|
login.oauth2invalidRequest=अमान्य अनुरोध
|
||||||
@@ -1384,4 +1384,3 @@ validateSignature.cert.version=संस्करण
|
|||||||
validateSignature.cert.keyUsage=कुंजी उपयोग
|
validateSignature.cert.keyUsage=कुंजी उपयोग
|
||||||
validateSignature.cert.selfSigned=स्व-हस्ताक्षरित
|
validateSignature.cert.selfSigned=स्व-हस्ताक्षरित
|
||||||
validateSignature.cert.bits=बिट्स
|
validateSignature.cert.bits=बिट्स
|
||||||
compress.grayscale.label=संपीड़न के लिए ग्रेस्केल लागू करें
|
|
||||||
|
|||||||
@@ -572,8 +572,8 @@ login.invalid=Neispravno korisničko ime ili zaporka.
|
|||||||
login.locked=Vaš račun je zaključan.
|
login.locked=Vaš račun je zaključan.
|
||||||
login.signinTitle=Molimo vas da se prijavite
|
login.signinTitle=Molimo vas da se prijavite
|
||||||
login.ssoSignIn=Prijavite se putem jedinstvene prijave
|
login.ssoSignIn=Prijavite se putem jedinstvene prijave
|
||||||
login.oAuth2AutoCreateDisabled=OAUTH2 automatsko kreiranje korisnika je onemogućeno
|
login.oauth2AutoCreateDisabled=OAUTH2 automatsko kreiranje korisnika je onemogućeno
|
||||||
login.oAuth2AdminBlockedUser=Registracija ili prijava nekadreguiranih korisnika trenutno su blokirane. Molimo Vas da kontaktirate administratora.
|
login.oauth2AdminBlockedUser=Registracija ili prijava nekadreguiranih korisnika trenutno su blokirane. Molimo Vas da kontaktirate administratora.
|
||||||
login.oauth2RequestNotFound=Zahtjev za autorizaciju nije pronađen
|
login.oauth2RequestNotFound=Zahtjev za autorizaciju nije pronađen
|
||||||
login.oauth2InvalidUserInfoResponse=Nevažeće informacije o korisniku
|
login.oauth2InvalidUserInfoResponse=Nevažeće informacije o korisniku
|
||||||
login.oauth2invalidRequest=Neispravan zahtjev
|
login.oauth2invalidRequest=Neispravan zahtjev
|
||||||
@@ -1384,4 +1384,3 @@ validateSignature.cert.version=Version
|
|||||||
validateSignature.cert.keyUsage=Key Usage
|
validateSignature.cert.keyUsage=Key Usage
|
||||||
validateSignature.cert.selfSigned=Self-Signed
|
validateSignature.cert.selfSigned=Self-Signed
|
||||||
validateSignature.cert.bits=bits
|
validateSignature.cert.bits=bits
|
||||||
compress.grayscale.label=Primijeni sivinu za kompresiju
|
|
||||||
|
|||||||
@@ -572,8 +572,8 @@ login.invalid=Érvénytelen felhasználónév vagy jelszó.
|
|||||||
login.locked=A fiókja zárolva van.
|
login.locked=A fiókja zárolva van.
|
||||||
login.signinTitle=Kérjük, jelentkezzen be
|
login.signinTitle=Kérjük, jelentkezzen be
|
||||||
login.ssoSignIn=Bejelentkezés egyszeri bejelentkezéssel
|
login.ssoSignIn=Bejelentkezés egyszeri bejelentkezéssel
|
||||||
login.oAuth2AutoCreateDisabled=OAuth2 automatikus felhasználólétrehozás letiltva
|
login.oauth2AutoCreateDisabled=OAuth2 automatikus felhasználólétrehozás letiltva
|
||||||
login.oAuth2AdminBlockedUser=A nem regisztrált felhasználók regisztrációja vagy bejelentkezése jelenleg le van tiltva. Kérjük, forduljon a rendszergazdához.
|
login.oauth2AdminBlockedUser=A nem regisztrált felhasználók regisztrációja vagy bejelentkezése jelenleg le van tiltva. Kérjük, forduljon a rendszergazdához.
|
||||||
login.oauth2RequestNotFound=A hitelesítési kérés nem található
|
login.oauth2RequestNotFound=A hitelesítési kérés nem található
|
||||||
login.oauth2InvalidUserInfoResponse=Érvénytelen felhasználói információ válasz
|
login.oauth2InvalidUserInfoResponse=Érvénytelen felhasználói információ válasz
|
||||||
login.oauth2invalidRequest=Érvénytelen kérés
|
login.oauth2invalidRequest=Érvénytelen kérés
|
||||||
@@ -1384,4 +1384,3 @@ validateSignature.cert.version=Verzió
|
|||||||
validateSignature.cert.keyUsage=Kulcshasználat
|
validateSignature.cert.keyUsage=Kulcshasználat
|
||||||
validateSignature.cert.selfSigned=Önaláírt
|
validateSignature.cert.selfSigned=Önaláírt
|
||||||
validateSignature.cert.bits=bit
|
validateSignature.cert.bits=bit
|
||||||
compress.grayscale.label=Szürkeárnyalatok alkalmazása tömörítéshez
|
|
||||||
|
|||||||
@@ -572,8 +572,8 @@ login.invalid=Nama pengguna atau kata sandi tidak valid.
|
|||||||
login.locked=Akun Anda telah dikunci.
|
login.locked=Akun Anda telah dikunci.
|
||||||
login.signinTitle=Silakan masuk
|
login.signinTitle=Silakan masuk
|
||||||
login.ssoSignIn=Masuk melalui Single Sign - on
|
login.ssoSignIn=Masuk melalui Single Sign - on
|
||||||
login.oAuth2AutoCreateDisabled=OAUTH2 Buat Otomatis Pengguna Dinonaktifkan
|
login.oauth2AutoCreateDisabled=OAUTH2 Buat Otomatis Pengguna Dinonaktifkan
|
||||||
login.oAuth2AdminBlockedUser=Registrasi atau login pengguna yang tidak terdaftar saat ini diblokir. Silakan hubungi administrator.
|
login.oauth2AdminBlockedUser=Registrasi atau login pengguna yang tidak terdaftar saat ini diblokir. Silakan hubungi administrator.
|
||||||
login.oauth2RequestNotFound=Permintaan otorisasi tidak ditemukan
|
login.oauth2RequestNotFound=Permintaan otorisasi tidak ditemukan
|
||||||
login.oauth2InvalidUserInfoResponse=Respons Info Pengguna Tidak Valid
|
login.oauth2InvalidUserInfoResponse=Respons Info Pengguna Tidak Valid
|
||||||
login.oauth2invalidRequest=Permintaan Tidak Valid
|
login.oauth2invalidRequest=Permintaan Tidak Valid
|
||||||
@@ -1384,4 +1384,3 @@ validateSignature.cert.version=Version
|
|||||||
validateSignature.cert.keyUsage=Key Usage
|
validateSignature.cert.keyUsage=Key Usage
|
||||||
validateSignature.cert.selfSigned=Self-Signed
|
validateSignature.cert.selfSigned=Self-Signed
|
||||||
validateSignature.cert.bits=bits
|
validateSignature.cert.bits=bits
|
||||||
compress.grayscale.label=Terapkan Skala Abu-Abu untuk Kompresi
|
|
||||||
|
|||||||
@@ -572,8 +572,8 @@ login.invalid=Nome utente o password errati.
|
|||||||
login.locked=Il tuo account è stato bloccato.
|
login.locked=Il tuo account è stato bloccato.
|
||||||
login.signinTitle=Per favore accedi
|
login.signinTitle=Per favore accedi
|
||||||
login.ssoSignIn=Accedi tramite Single Sign-on
|
login.ssoSignIn=Accedi tramite Single Sign-on
|
||||||
login.oAuth2AutoCreateDisabled=Creazione automatica utente OAUTH2 DISABILITATA
|
login.oauth2AutoCreateDisabled=Creazione automatica utente OAUTH2 DISABILITATA
|
||||||
login.oAuth2AdminBlockedUser=La registrazione o l'accesso degli utenti non registrati è attualmente bloccata. Si prega di contattare l'amministratore.
|
login.oauth2AdminBlockedUser=La registrazione o l'accesso degli utenti non registrati è attualmente bloccata. Si prega di contattare l'amministratore.
|
||||||
login.oauth2RequestNotFound=Richiesta di autorizzazione non trovata
|
login.oauth2RequestNotFound=Richiesta di autorizzazione non trovata
|
||||||
login.oauth2InvalidUserInfoResponse=Risposta relativa alle informazioni utente non valida
|
login.oauth2InvalidUserInfoResponse=Risposta relativa alle informazioni utente non valida
|
||||||
login.oauth2invalidRequest=Richiesta non valida
|
login.oauth2invalidRequest=Richiesta non valida
|
||||||
@@ -1384,4 +1384,3 @@ validateSignature.cert.version=Versione
|
|||||||
validateSignature.cert.keyUsage=Utilizzo della chiave
|
validateSignature.cert.keyUsage=Utilizzo della chiave
|
||||||
validateSignature.cert.selfSigned=Autofirmato
|
validateSignature.cert.selfSigned=Autofirmato
|
||||||
validateSignature.cert.bits=bit
|
validateSignature.cert.bits=bit
|
||||||
compress.grayscale.label=Applica scala di grigio per la compressione
|
|
||||||
|
|||||||
@@ -572,8 +572,8 @@ login.invalid=ユーザー名かパスワードが無効です。
|
|||||||
login.locked=あなたのアカウントはロックされています。
|
login.locked=あなたのアカウントはロックされています。
|
||||||
login.signinTitle=サインインしてください
|
login.signinTitle=サインインしてください
|
||||||
login.ssoSignIn=シングルサインオンでログイン
|
login.ssoSignIn=シングルサインオンでログイン
|
||||||
login.oAuth2AutoCreateDisabled=OAuth 2自動作成ユーザーが無効
|
login.oauth2AutoCreateDisabled=OAuth 2自動作成ユーザーが無効
|
||||||
login.oAuth2AdminBlockedUser=現在、未登録ユーザーの登録またはログインはブロックされています。管理者にお問い合わせください。
|
login.oauth2AdminBlockedUser=現在、未登録ユーザーの登録またはログインはブロックされています。管理者にお問い合わせください。
|
||||||
login.oauth2RequestNotFound=認証リクエストが見つかりません
|
login.oauth2RequestNotFound=認証リクエストが見つかりません
|
||||||
login.oauth2InvalidUserInfoResponse=無効なユーザー情報の応答
|
login.oauth2InvalidUserInfoResponse=無効なユーザー情報の応答
|
||||||
login.oauth2invalidRequest=無効なリクエスト
|
login.oauth2invalidRequest=無効なリクエスト
|
||||||
@@ -1384,4 +1384,3 @@ validateSignature.cert.version=バージョン
|
|||||||
validateSignature.cert.keyUsage=キーの使用法
|
validateSignature.cert.keyUsage=キーの使用法
|
||||||
validateSignature.cert.selfSigned=自己署名
|
validateSignature.cert.selfSigned=自己署名
|
||||||
validateSignature.cert.bits=ビット
|
validateSignature.cert.bits=ビット
|
||||||
compress.grayscale.label=圧縮にグレースケールを適用する
|
|
||||||
|
|||||||
@@ -572,8 +572,8 @@ login.invalid=사용자 이름 또는 비밀번호가 잘못되었습니다.
|
|||||||
login.locked=계정이 잠겼습니다.
|
login.locked=계정이 잠겼습니다.
|
||||||
login.signinTitle=로그인해 주세요
|
login.signinTitle=로그인해 주세요
|
||||||
login.ssoSignIn=단일 로그인으로 로그인
|
login.ssoSignIn=단일 로그인으로 로그인
|
||||||
login.oAuth2AutoCreateDisabled=OAuth2 사용자 자동 생성이 비활성화되었습니다
|
login.oauth2AutoCreateDisabled=OAuth2 사용자 자동 생성이 비활성화되었습니다
|
||||||
login.oAuth2AdminBlockedUser=현재 미등록 사용자의 등록 또는 로그인이 차단되어 있습니다. 관리자에게 문의하세요.
|
login.oauth2AdminBlockedUser=현재 미등록 사용자의 등록 또는 로그인이 차단되어 있습니다. 관리자에게 문의하세요.
|
||||||
login.oauth2RequestNotFound=인증 요청을 찾을 수 없습니다
|
login.oauth2RequestNotFound=인증 요청을 찾을 수 없습니다
|
||||||
login.oauth2InvalidUserInfoResponse=잘못된 사용자 정보 응답
|
login.oauth2InvalidUserInfoResponse=잘못된 사용자 정보 응답
|
||||||
login.oauth2invalidRequest=잘못된 요청
|
login.oauth2invalidRequest=잘못된 요청
|
||||||
@@ -1384,4 +1384,3 @@ validateSignature.cert.version=버전
|
|||||||
validateSignature.cert.keyUsage=키 용도
|
validateSignature.cert.keyUsage=키 용도
|
||||||
validateSignature.cert.selfSigned=자체 서명
|
validateSignature.cert.selfSigned=자체 서명
|
||||||
validateSignature.cert.bits=비트
|
validateSignature.cert.bits=비트
|
||||||
compress.grayscale.label=압축을 위해 그레이스케일 적용
|
|
||||||
|
|||||||
@@ -572,8 +572,8 @@ login.invalid=Ongeldige gebruikersnaam of wachtwoord.
|
|||||||
login.locked=Je account is geblokkeerd.
|
login.locked=Je account is geblokkeerd.
|
||||||
login.signinTitle=Gelieve in te loggen
|
login.signinTitle=Gelieve in te loggen
|
||||||
login.ssoSignIn=Inloggen via Single Sign-on
|
login.ssoSignIn=Inloggen via Single Sign-on
|
||||||
login.oAuth2AutoCreateDisabled=OAUTH2 Automatisch aanmaken gebruiker uitgeschakeld
|
login.oauth2AutoCreateDisabled=OAUTH2 Automatisch aanmaken gebruiker uitgeschakeld
|
||||||
login.oAuth2AdminBlockedUser=Registratie of inloggen van niet-registreerde gebruikers is helaas momenteel geblokkeerd. Neem contact op met de beheerder.
|
login.oauth2AdminBlockedUser=Registratie of inloggen van niet-registreerde gebruikers is helaas momenteel geblokkeerd. Neem contact op met de beheerder.
|
||||||
login.oauth2RequestNotFound=Autorisatieverzoek niet gevonden
|
login.oauth2RequestNotFound=Autorisatieverzoek niet gevonden
|
||||||
login.oauth2InvalidUserInfoResponse=Ongeldige reactie op gebruikersinfo
|
login.oauth2InvalidUserInfoResponse=Ongeldige reactie op gebruikersinfo
|
||||||
login.oauth2invalidRequest=Ongeldig verzoek
|
login.oauth2invalidRequest=Ongeldig verzoek
|
||||||
@@ -1384,4 +1384,3 @@ validateSignature.cert.version=Version
|
|||||||
validateSignature.cert.keyUsage=Key Usage
|
validateSignature.cert.keyUsage=Key Usage
|
||||||
validateSignature.cert.selfSigned=Self-Signed
|
validateSignature.cert.selfSigned=Self-Signed
|
||||||
validateSignature.cert.bits=bits
|
validateSignature.cert.bits=bits
|
||||||
compress.grayscale.label=Grijsschaal toepassen voor compressie
|
|
||||||
|
|||||||
@@ -572,8 +572,8 @@ login.invalid=Ugyldig brukernavn eller passord.
|
|||||||
login.locked=Kontoen din har blitt låst.
|
login.locked=Kontoen din har blitt låst.
|
||||||
login.signinTitle=Vennligst logg inn
|
login.signinTitle=Vennligst logg inn
|
||||||
login.ssoSignIn=Logg inn via Enkel Pålogging
|
login.ssoSignIn=Logg inn via Enkel Pålogging
|
||||||
login.oAuth2AutoCreateDisabled=OAUTH2 Auto-Opretting av bruker deaktivert
|
login.oauth2AutoCreateDisabled=OAUTH2 Auto-Opretting av bruker deaktivert
|
||||||
login.oAuth2AdminBlockedUser=Registration or logging in of non-registered users is currently blocked. Please contact the administrator.
|
login.oauth2AdminBlockedUser=Registration or logging in of non-registered users is currently blocked. Please contact the administrator.
|
||||||
login.oauth2RequestNotFound=Autentiseringsforespørsel ikke funnet
|
login.oauth2RequestNotFound=Autentiseringsforespørsel ikke funnet
|
||||||
login.oauth2InvalidUserInfoResponse=Ugyldig brukerinforespons
|
login.oauth2InvalidUserInfoResponse=Ugyldig brukerinforespons
|
||||||
login.oauth2invalidRequest=Ugyldig forespørsel
|
login.oauth2invalidRequest=Ugyldig forespørsel
|
||||||
@@ -1384,4 +1384,3 @@ validateSignature.cert.version=Version
|
|||||||
validateSignature.cert.keyUsage=Key Usage
|
validateSignature.cert.keyUsage=Key Usage
|
||||||
validateSignature.cert.selfSigned=Self-Signed
|
validateSignature.cert.selfSigned=Self-Signed
|
||||||
validateSignature.cert.bits=bits
|
validateSignature.cert.bits=bits
|
||||||
compress.grayscale.label=Bruk gråskala for komprimering
|
|
||||||
|
|||||||
@@ -572,8 +572,8 @@ login.invalid=Nieprawidłowe dane logowania
|
|||||||
login.locked=Konto jest zablokowane
|
login.locked=Konto jest zablokowane
|
||||||
login.signinTitle=Zaloguj się
|
login.signinTitle=Zaloguj się
|
||||||
login.ssoSignIn=Zaloguj się za pomocą logowania jednokrotnego
|
login.ssoSignIn=Zaloguj się za pomocą logowania jednokrotnego
|
||||||
login.oAuth2AutoCreateDisabled=Wyłączono automatyczne tworzenie użytkownika OAUTH2
|
login.oauth2AutoCreateDisabled=Wyłączono automatyczne tworzenie użytkownika OAUTH2
|
||||||
login.oAuth2AdminBlockedUser=Rejestracja lub logowanie niezarejestrowanych użytkowników jest obecnie zablokowane. Prosimy o kontakt z administratorem.
|
login.oauth2AdminBlockedUser=Rejestracja lub logowanie niezarejestrowanych użytkowników jest obecnie zablokowane. Prosimy o kontakt z administratorem.
|
||||||
login.oauth2RequestNotFound=Błąd logowania OAuth2
|
login.oauth2RequestNotFound=Błąd logowania OAuth2
|
||||||
login.oauth2InvalidUserInfoResponse=Niewłaściwe dane logowania
|
login.oauth2InvalidUserInfoResponse=Niewłaściwe dane logowania
|
||||||
login.oauth2invalidRequest=Nieprawidłowe żądanie
|
login.oauth2invalidRequest=Nieprawidłowe żądanie
|
||||||
@@ -1384,4 +1384,3 @@ validateSignature.cert.version=Version
|
|||||||
validateSignature.cert.keyUsage=Key Usage
|
validateSignature.cert.keyUsage=Key Usage
|
||||||
validateSignature.cert.selfSigned=Self-Signed
|
validateSignature.cert.selfSigned=Self-Signed
|
||||||
validateSignature.cert.bits=bits
|
validateSignature.cert.bits=bits
|
||||||
compress.grayscale.label=Zastosuj skalę szarości do kompresji
|
|
||||||
|
|||||||
@@ -572,8 +572,8 @@ login.invalid=Usuário ou senha inválidos.
|
|||||||
login.locked=Sua conta foi bloqueada.
|
login.locked=Sua conta foi bloqueada.
|
||||||
login.signinTitle=Por favor, inicie a sessão
|
login.signinTitle=Por favor, inicie a sessão
|
||||||
login.ssoSignIn=Iniciar sessão através de login único (SSO)
|
login.ssoSignIn=Iniciar sessão através de login único (SSO)
|
||||||
login.oAuth2AutoCreateDisabled=Auto-Criar Usuário OAUTH2 Desativado
|
login.oauth2AutoCreateDisabled=Auto-Criar Usuário OAUTH2 Desativado
|
||||||
login.oAuth2AdminBlockedUser=O registro ou login de usuários não registrados está atualmente bloqueado. Entre em contato com o administrador.
|
login.oauth2AdminBlockedUser=O registro ou login de usuários não registrados está atualmente bloqueado. Entre em contato com o administrador.
|
||||||
login.oauth2RequestNotFound=Solicitação de autorização não encontrada
|
login.oauth2RequestNotFound=Solicitação de autorização não encontrada
|
||||||
login.oauth2InvalidUserInfoResponse=Resposta de informação de usuário inválida
|
login.oauth2InvalidUserInfoResponse=Resposta de informação de usuário inválida
|
||||||
login.oauth2invalidRequest=Requisição Inválida
|
login.oauth2invalidRequest=Requisição Inválida
|
||||||
@@ -1384,4 +1384,3 @@ validateSignature.cert.version=Versão
|
|||||||
validateSignature.cert.keyUsage=Uso da chave
|
validateSignature.cert.keyUsage=Uso da chave
|
||||||
validateSignature.cert.selfSigned=Autoassinados
|
validateSignature.cert.selfSigned=Autoassinados
|
||||||
validateSignature.cert.bits=bits
|
validateSignature.cert.bits=bits
|
||||||
compress.grayscale.label=Aplicar escala de cinza para compressão
|
|
||||||
|
|||||||
@@ -572,8 +572,8 @@ login.invalid=Nome de utilizador ou palavra-passe inválidos.
|
|||||||
login.locked=A sua conta foi bloqueada.
|
login.locked=A sua conta foi bloqueada.
|
||||||
login.signinTitle=Por favor inicie sessão
|
login.signinTitle=Por favor inicie sessão
|
||||||
login.ssoSignIn=Login via Single Sign-on
|
login.ssoSignIn=Login via Single Sign-on
|
||||||
login.oAuth2AutoCreateDisabled=Criação Automática de Utilizador OAUTH2 Desativada
|
login.oauth2AutoCreateDisabled=Criação Automática de Utilizador OAUTH2 Desativada
|
||||||
login.oAuth2AdminBlockedUser=O registo ou login de utilizadores não registados está atualmente bloqueado. Por favor contacte o administrador.
|
login.oauth2AdminBlockedUser=O registo ou login de utilizadores não registados está atualmente bloqueado. Por favor contacte o administrador.
|
||||||
login.oauth2RequestNotFound=Pedido de autorização não encontrado
|
login.oauth2RequestNotFound=Pedido de autorização não encontrado
|
||||||
login.oauth2InvalidUserInfoResponse=Resposta de Informação de Utilizador Inválida
|
login.oauth2InvalidUserInfoResponse=Resposta de Informação de Utilizador Inválida
|
||||||
login.oauth2invalidRequest=Pedido Inválido
|
login.oauth2invalidRequest=Pedido Inválido
|
||||||
@@ -1384,4 +1384,3 @@ validateSignature.cert.version=Versão
|
|||||||
validateSignature.cert.keyUsage=Utilização da Chave
|
validateSignature.cert.keyUsage=Utilização da Chave
|
||||||
validateSignature.cert.selfSigned=Auto-Assinado
|
validateSignature.cert.selfSigned=Auto-Assinado
|
||||||
validateSignature.cert.bits=bits
|
validateSignature.cert.bits=bits
|
||||||
compress.grayscale.label=Aplicar escala de cinzentos para compressão
|
|
||||||
|
|||||||
@@ -572,8 +572,8 @@ login.invalid=Nume de utilizator sau parolă invalidă.
|
|||||||
login.locked=Contul tău a fost blocat.
|
login.locked=Contul tău a fost blocat.
|
||||||
login.signinTitle=Te rugăm să te autentifici
|
login.signinTitle=Te rugăm să te autentifici
|
||||||
login.ssoSignIn=Conectare prin conectare unică
|
login.ssoSignIn=Conectare prin conectare unică
|
||||||
login.oAuth2AutoCreateDisabled=OAUTH2 Creare automată utilizator dezactivată
|
login.oauth2AutoCreateDisabled=OAUTH2 Creare automată utilizator dezactivată
|
||||||
login.oAuth2AdminBlockedUser=Înregistrarea sau conectarea utilizatorilor neînregistrați este în prezent blocată. Te rugăm să contactezi administratorul.
|
login.oauth2AdminBlockedUser=Înregistrarea sau conectarea utilizatorilor neînregistrați este în prezent blocată. Te rugăm să contactezi administratorul.
|
||||||
login.oauth2RequestNotFound=Cererea de autorizare nu a fost găsită
|
login.oauth2RequestNotFound=Cererea de autorizare nu a fost găsită
|
||||||
login.oauth2InvalidUserInfoResponse=Răspuns Invalid la Informațiile Utilizatorului
|
login.oauth2InvalidUserInfoResponse=Răspuns Invalid la Informațiile Utilizatorului
|
||||||
login.oauth2invalidRequest=Cerere Invalidă
|
login.oauth2invalidRequest=Cerere Invalidă
|
||||||
@@ -1384,4 +1384,3 @@ validateSignature.cert.version=Version
|
|||||||
validateSignature.cert.keyUsage=Key Usage
|
validateSignature.cert.keyUsage=Key Usage
|
||||||
validateSignature.cert.selfSigned=Self-Signed
|
validateSignature.cert.selfSigned=Self-Signed
|
||||||
validateSignature.cert.bits=bits
|
validateSignature.cert.bits=bits
|
||||||
compress.grayscale.label=Aplicare scală de gri pentru compresie
|
|
||||||
|
|||||||
@@ -572,8 +572,8 @@ login.invalid=Неверное имя пользователя или парол
|
|||||||
login.locked=Ваша учетная запись заблокирована.
|
login.locked=Ваша учетная запись заблокирована.
|
||||||
login.signinTitle=Пожалуйста, войдите
|
login.signinTitle=Пожалуйста, войдите
|
||||||
login.ssoSignIn=Вход через единый вход
|
login.ssoSignIn=Вход через единый вход
|
||||||
login.oAuth2AutoCreateDisabled=Автоматическое создание пользователей OAuth2 отключено
|
login.oauth2AutoCreateDisabled=Автоматическое создание пользователей OAuth2 отключено
|
||||||
login.oAuth2AdminBlockedUser=Регистрация или вход незарегистрированных пользователей в настоящее время заблокированы. Обратитесь к администратору.
|
login.oauth2AdminBlockedUser=Регистрация или вход незарегистрированных пользователей в настоящее время заблокированы. Обратитесь к администратору.
|
||||||
login.oauth2RequestNotFound=Запрос авторизации не найден
|
login.oauth2RequestNotFound=Запрос авторизации не найден
|
||||||
login.oauth2InvalidUserInfoResponse=Недействительный ответ с информацией о пользователе
|
login.oauth2InvalidUserInfoResponse=Недействительный ответ с информацией о пользователе
|
||||||
login.oauth2invalidRequest=Недействительный запрос
|
login.oauth2invalidRequest=Недействительный запрос
|
||||||
@@ -1384,4 +1384,3 @@ validateSignature.cert.version=Версия
|
|||||||
validateSignature.cert.keyUsage=Использование ключа
|
validateSignature.cert.keyUsage=Использование ключа
|
||||||
validateSignature.cert.selfSigned=Самоподписанный
|
validateSignature.cert.selfSigned=Самоподписанный
|
||||||
validateSignature.cert.bits=бит
|
validateSignature.cert.bits=бит
|
||||||
compress.grayscale.label=Применить шкалу серого для сжатия
|
|
||||||
|
|||||||
@@ -572,8 +572,8 @@ login.invalid=Neplatné používateľské meno alebo heslo.
|
|||||||
login.locked=Váš účet bol uzamknutý.
|
login.locked=Váš účet bol uzamknutý.
|
||||||
login.signinTitle=Prosím, prihláste sa
|
login.signinTitle=Prosím, prihláste sa
|
||||||
login.ssoSignIn=Prihlásiť sa cez Single Sign-on
|
login.ssoSignIn=Prihlásiť sa cez Single Sign-on
|
||||||
login.oAuth2AutoCreateDisabled=Vytváranie používateľa cez OAUTH2 je zakázané
|
login.oauth2AutoCreateDisabled=Vytváranie používateľa cez OAUTH2 je zakázané
|
||||||
login.oAuth2AdminBlockedUser=Registration or logging in of non-registered users is currently blocked. Please contact the administrator.
|
login.oauth2AdminBlockedUser=Registration or logging in of non-registered users is currently blocked. Please contact the administrator.
|
||||||
login.oauth2RequestNotFound=Authorization request not found
|
login.oauth2RequestNotFound=Authorization request not found
|
||||||
login.oauth2InvalidUserInfoResponse=Invalid User Info Response
|
login.oauth2InvalidUserInfoResponse=Invalid User Info Response
|
||||||
login.oauth2invalidRequest=Invalid Request
|
login.oauth2invalidRequest=Invalid Request
|
||||||
@@ -1384,4 +1384,3 @@ validateSignature.cert.version=Version
|
|||||||
validateSignature.cert.keyUsage=Key Usage
|
validateSignature.cert.keyUsage=Key Usage
|
||||||
validateSignature.cert.selfSigned=Self-Signed
|
validateSignature.cert.selfSigned=Self-Signed
|
||||||
validateSignature.cert.bits=bits
|
validateSignature.cert.bits=bits
|
||||||
compress.grayscale.label=Použiť odtiene šedej na kompresiu
|
|
||||||
|
|||||||
@@ -572,8 +572,8 @@ login.invalid=Neveljavno uporabniško ime ali geslo.
|
|||||||
login.locked=Vaš račun je bil zaklenjen.
|
login.locked=Vaš račun je bil zaklenjen.
|
||||||
login.signinTitle=Prosim prijavite se
|
login.signinTitle=Prosim prijavite se
|
||||||
login.ssoSignIn=Prijava prek enotne prijave
|
login.ssoSignIn=Prijava prek enotne prijave
|
||||||
login.oAuth2AutoCreateDisabled=OAUTH2 Samodejno ustvarjanje uporabnika onemogočeno
|
login.oauth2AutoCreateDisabled=OAUTH2 Samodejno ustvarjanje uporabnika onemogočeno
|
||||||
login.oAuth2AdminBlockedUser=Registracija ali prijava neregistriranih uporabnikov je trenutno blokirana. Prosimo kontaktirajte skrbnika.
|
login.oauth2AdminBlockedUser=Registracija ali prijava neregistriranih uporabnikov je trenutno blokirana. Prosimo kontaktirajte skrbnika.
|
||||||
login.oauth2RequestNotFound=Zahteva za avtorizacijo ni bila najdena
|
login.oauth2RequestNotFound=Zahteva za avtorizacijo ni bila najdena
|
||||||
login.oauth2InvalidUserInfoResponse=Neveljaven odgovor z informacijami o uporabniku
|
login.oauth2InvalidUserInfoResponse=Neveljaven odgovor z informacijami o uporabniku
|
||||||
login.oauth2invalidRequest=Neveljavna zahteva
|
login.oauth2invalidRequest=Neveljavna zahteva
|
||||||
@@ -1384,4 +1384,3 @@ validateSignature.cert.version=Različica
|
|||||||
validateSignature.cert.keyUsage=Uporaba ključa
|
validateSignature.cert.keyUsage=Uporaba ključa
|
||||||
validateSignature.cert.selfSigned=Samopodpisano
|
validateSignature.cert.selfSigned=Samopodpisano
|
||||||
validateSignature.cert.bits=bits
|
validateSignature.cert.bits=bits
|
||||||
compress.grayscale.label=Uporabi sivinsko lestvico za stiskanje
|
|
||||||
|
|||||||
@@ -572,8 +572,8 @@ login.invalid=Neispravno korisničko ime ili lozinka.
|
|||||||
login.locked=Vaš nalog je zaključan.
|
login.locked=Vaš nalog je zaključan.
|
||||||
login.signinTitle=Molimo vas da se prijavite
|
login.signinTitle=Molimo vas da se prijavite
|
||||||
login.ssoSignIn=Prijavite se putem jedinstvene prijave
|
login.ssoSignIn=Prijavite se putem jedinstvene prijave
|
||||||
login.oAuth2AutoCreateDisabled=OAUTH2 automatsko kreiranje korisnika je onemogućeno
|
login.oauth2AutoCreateDisabled=OAUTH2 automatsko kreiranje korisnika je onemogućeno
|
||||||
login.oAuth2AdminBlockedUser=Registration or logging in of non-registered users is currently blocked. Please contact the administrator.
|
login.oauth2AdminBlockedUser=Registration or logging in of non-registered users is currently blocked. Please contact the administrator.
|
||||||
login.oauth2RequestNotFound=Authorization request not found
|
login.oauth2RequestNotFound=Authorization request not found
|
||||||
login.oauth2InvalidUserInfoResponse=Invalid User Info Response
|
login.oauth2InvalidUserInfoResponse=Invalid User Info Response
|
||||||
login.oauth2invalidRequest=Invalid Request
|
login.oauth2invalidRequest=Invalid Request
|
||||||
@@ -1384,4 +1384,3 @@ validateSignature.cert.version=Version
|
|||||||
validateSignature.cert.keyUsage=Key Usage
|
validateSignature.cert.keyUsage=Key Usage
|
||||||
validateSignature.cert.selfSigned=Self-Signed
|
validateSignature.cert.selfSigned=Self-Signed
|
||||||
validateSignature.cert.bits=bits
|
validateSignature.cert.bits=bits
|
||||||
compress.grayscale.label=Primeni sivinu za kompresiju
|
|
||||||
|
|||||||
@@ -572,8 +572,8 @@ login.invalid=Ogiltigt användarnamn eller lösenord.
|
|||||||
login.locked=Ditt konto har låsts.
|
login.locked=Ditt konto har låsts.
|
||||||
login.signinTitle=Vänligen logga in
|
login.signinTitle=Vänligen logga in
|
||||||
login.ssoSignIn=Logga in via enkel inloggning
|
login.ssoSignIn=Logga in via enkel inloggning
|
||||||
login.oAuth2AutoCreateDisabled=OAUTH2 Auto-skapa användare inaktiverad
|
login.oauth2AutoCreateDisabled=OAUTH2 Auto-skapa användare inaktiverad
|
||||||
login.oAuth2AdminBlockedUser=Registrering eller inloggning av icke-registrerade användare är för närvarande blockerad. Kontakta administratören.
|
login.oauth2AdminBlockedUser=Registrering eller inloggning av icke-registrerade användare är för närvarande blockerad. Kontakta administratören.
|
||||||
login.oauth2RequestNotFound=Auktoriseringsbegäran hittades inte
|
login.oauth2RequestNotFound=Auktoriseringsbegäran hittades inte
|
||||||
login.oauth2InvalidUserInfoResponse=Ogiltigt svar på användarinformation
|
login.oauth2InvalidUserInfoResponse=Ogiltigt svar på användarinformation
|
||||||
login.oauth2invalidRequest=Ogiltig begäran
|
login.oauth2invalidRequest=Ogiltig begäran
|
||||||
@@ -1384,4 +1384,3 @@ validateSignature.cert.version=Version
|
|||||||
validateSignature.cert.keyUsage=Key Usage
|
validateSignature.cert.keyUsage=Key Usage
|
||||||
validateSignature.cert.selfSigned=Self-Signed
|
validateSignature.cert.selfSigned=Self-Signed
|
||||||
validateSignature.cert.bits=bits
|
validateSignature.cert.bits=bits
|
||||||
compress.grayscale.label=Tillämpa gråskala för komprimering
|
|
||||||
|
|||||||
@@ -572,8 +572,8 @@ login.invalid=ชื่อผู้ใช้หรือรหัสผ่าน
|
|||||||
login.locked=บัญชีของคุณถูกล็อค
|
login.locked=บัญชีของคุณถูกล็อค
|
||||||
login.signinTitle=กรุณาลงชื่อเข้าใช้
|
login.signinTitle=กรุณาลงชื่อเข้าใช้
|
||||||
login.ssoSignIn=เข้าสู่ระบบด้วย Single Sign-on
|
login.ssoSignIn=เข้าสู่ระบบด้วย Single Sign-on
|
||||||
login.oAuth2AutoCreateDisabled=การสร้างผู้ใช้ OAuth2 อัตโนมัติถูกปิดใช้งาน
|
login.oauth2AutoCreateDisabled=การสร้างผู้ใช้ OAuth2 อัตโนมัติถูกปิดใช้งาน
|
||||||
login.oAuth2AdminBlockedUser=Registration or logging in of non-registered users is currently blocked. Please contact the administrator.
|
login.oauth2AdminBlockedUser=Registration or logging in of non-registered users is currently blocked. Please contact the administrator.
|
||||||
login.oauth2RequestNotFound=ไม่พบคำขอการอนุญาต
|
login.oauth2RequestNotFound=ไม่พบคำขอการอนุญาต
|
||||||
login.oauth2InvalidUserInfoResponse=การตอบกลับข้อมูลผู้ใช้ไม่ถูกต้อง
|
login.oauth2InvalidUserInfoResponse=การตอบกลับข้อมูลผู้ใช้ไม่ถูกต้อง
|
||||||
login.oauth2invalidRequest=คำขอไม่ถูกต้อง
|
login.oauth2invalidRequest=คำขอไม่ถูกต้อง
|
||||||
@@ -1384,4 +1384,3 @@ validateSignature.cert.version=Version
|
|||||||
validateSignature.cert.keyUsage=Key Usage
|
validateSignature.cert.keyUsage=Key Usage
|
||||||
validateSignature.cert.selfSigned=Self-Signed
|
validateSignature.cert.selfSigned=Self-Signed
|
||||||
validateSignature.cert.bits=bits
|
validateSignature.cert.bits=bits
|
||||||
compress.grayscale.label=ใช้ระดับสีเทาสำหรับการบีบอัด
|
|
||||||
|
|||||||
@@ -572,8 +572,8 @@ login.invalid=Geçersiz kullanıcı adı veya şifre.
|
|||||||
login.locked=Hesabınız kilitlendi.
|
login.locked=Hesabınız kilitlendi.
|
||||||
login.signinTitle=Lütfen giriş yapınız.
|
login.signinTitle=Lütfen giriş yapınız.
|
||||||
login.ssoSignIn=Tek Oturum Açma ile Giriş Yap
|
login.ssoSignIn=Tek Oturum Açma ile Giriş Yap
|
||||||
login.oAuth2AutoCreateDisabled=OAUTH2 Otomatik Oluşturma Kullanıcı Devre Dışı Bırakıldı
|
login.oauth2AutoCreateDisabled=OAUTH2 Otomatik Oluşturma Kullanıcı Devre Dışı Bırakıldı
|
||||||
login.oAuth2AdminBlockedUser=Kayıtlı olmayan kullanıcıların kayıt veya giriş yapması şu anda engellenmiştir. Lütfen yöneticiyle iletişime geçin.
|
login.oauth2AdminBlockedUser=Kayıtlı olmayan kullanıcıların kayıt veya giriş yapması şu anda engellenmiştir. Lütfen yöneticiyle iletişime geçin.
|
||||||
login.oauth2RequestNotFound=Yetkilendirme isteği bulunamadı
|
login.oauth2RequestNotFound=Yetkilendirme isteği bulunamadı
|
||||||
login.oauth2InvalidUserInfoResponse=Geçersiz Kullanıcı Bilgisi Yanıtı
|
login.oauth2InvalidUserInfoResponse=Geçersiz Kullanıcı Bilgisi Yanıtı
|
||||||
login.oauth2invalidRequest=Geçersiz İstek
|
login.oauth2invalidRequest=Geçersiz İstek
|
||||||
@@ -1384,4 +1384,3 @@ validateSignature.cert.version=Version
|
|||||||
validateSignature.cert.keyUsage=Key Usage
|
validateSignature.cert.keyUsage=Key Usage
|
||||||
validateSignature.cert.selfSigned=Self-Signed
|
validateSignature.cert.selfSigned=Self-Signed
|
||||||
validateSignature.cert.bits=bits
|
validateSignature.cert.bits=bits
|
||||||
compress.grayscale.label=Sıkıştırma için Gri Ton Uygula
|
|
||||||
|
|||||||
@@ -572,8 +572,8 @@ login.invalid=Недійсне ім'я користувача або парол
|
|||||||
login.locked=Ваш обліковий запис заблоковано.
|
login.locked=Ваш обліковий запис заблоковано.
|
||||||
login.signinTitle=Будь ласка, увійдіть
|
login.signinTitle=Будь ласка, увійдіть
|
||||||
login.ssoSignIn=Увійти через єдиний вхід
|
login.ssoSignIn=Увійти через єдиний вхід
|
||||||
login.oAuth2AutoCreateDisabled=Автоматичне створення користувача OAUTH2 ВИМКНЕНО
|
login.oauth2AutoCreateDisabled=Автоматичне створення користувача OAUTH2 ВИМКНЕНО
|
||||||
login.oAuth2AdminBlockedUser=Registration or logging in of non-registered users is currently blocked. Please contact the administrator.
|
login.oauth2AdminBlockedUser=Registration or logging in of non-registered users is currently blocked. Please contact the administrator.
|
||||||
login.oauth2RequestNotFound=Запит на авторизація не знайдено
|
login.oauth2RequestNotFound=Запит на авторизація не знайдено
|
||||||
login.oauth2InvalidUserInfoResponse=Недійсна відповідь з інформацією користувача
|
login.oauth2InvalidUserInfoResponse=Недійсна відповідь з інформацією користувача
|
||||||
login.oauth2invalidRequest=Недійсний запит
|
login.oauth2invalidRequest=Недійсний запит
|
||||||
@@ -1384,4 +1384,3 @@ validateSignature.cert.version=Version
|
|||||||
validateSignature.cert.keyUsage=Key Usage
|
validateSignature.cert.keyUsage=Key Usage
|
||||||
validateSignature.cert.selfSigned=Self-Signed
|
validateSignature.cert.selfSigned=Self-Signed
|
||||||
validateSignature.cert.bits=bits
|
validateSignature.cert.bits=bits
|
||||||
compress.grayscale.label=Застосувати відтінки сірого для стиснення
|
|
||||||
|
|||||||
@@ -572,8 +572,8 @@ login.invalid=Tên đăng nhập hoặc mật khẩu không hợp lệ.
|
|||||||
login.locked=Tài khoản của bạn đã bị khóa.
|
login.locked=Tài khoản của bạn đã bị khóa.
|
||||||
login.signinTitle=Vui lòng đăng nhập
|
login.signinTitle=Vui lòng đăng nhập
|
||||||
login.ssoSignIn=Đăng nhập qua Single Sign-on
|
login.ssoSignIn=Đăng nhập qua Single Sign-on
|
||||||
login.oAuth2AutoCreateDisabled=Tự động tạo người dùng OAUTH2 bị vô hiệu hóa
|
login.oauth2AutoCreateDisabled=Tự động tạo người dùng OAUTH2 bị vô hiệu hóa
|
||||||
login.oAuth2AdminBlockedUser=Registration or logging in of non-registered users is currently blocked. Please contact the administrator.
|
login.oauth2AdminBlockedUser=Registration or logging in of non-registered users is currently blocked. Please contact the administrator.
|
||||||
login.oauth2RequestNotFound=Không tìm thấy yêu cầu ủy quyền
|
login.oauth2RequestNotFound=Không tìm thấy yêu cầu ủy quyền
|
||||||
login.oauth2InvalidUserInfoResponse=Phản hồi thông tin người dùng không hợp lệ
|
login.oauth2InvalidUserInfoResponse=Phản hồi thông tin người dùng không hợp lệ
|
||||||
login.oauth2invalidRequest=Yêu cầu không hợp lệ
|
login.oauth2invalidRequest=Yêu cầu không hợp lệ
|
||||||
@@ -1384,4 +1384,3 @@ validateSignature.cert.version=Version
|
|||||||
validateSignature.cert.keyUsage=Key Usage
|
validateSignature.cert.keyUsage=Key Usage
|
||||||
validateSignature.cert.selfSigned=Self-Signed
|
validateSignature.cert.selfSigned=Self-Signed
|
||||||
validateSignature.cert.bits=bits
|
validateSignature.cert.bits=bits
|
||||||
compress.grayscale.label=Áp dụng thang độ xám để nén
|
|
||||||
|
|||||||
@@ -572,8 +572,8 @@ login.invalid=སྤྱོད་མིང་ངམ་གསང་ཚིག་ན
|
|||||||
login.locked=ཁྱེད་ཀྱི་ཐོ་མཛོད་ཟྭ་རྒྱག་བརྒྱབ་ཟིན།
|
login.locked=ཁྱེད་ཀྱི་ཐོ་མཛོད་ཟྭ་རྒྱག་བརྒྱབ་ཟིན།
|
||||||
login.signinTitle=ནང་འཛུལ་གནང་རོགས།
|
login.signinTitle=ནང་འཛུལ་གནང་རོགས།
|
||||||
login.ssoSignIn=གཅིག་གྱུར་ནང་འཛུལ་བརྒྱུད་ནས་ནང་འཛུལ།
|
login.ssoSignIn=གཅིག་གྱུར་ནང་འཛུལ་བརྒྱུད་ནས་ནང་འཛུལ།
|
||||||
login.oAuth2AutoCreateDisabled=OAUTH2 རང་འགུལ་སྤྱོད་མཁན་གསར་བཟོ་བཀག་སྡོམ་བྱས་ཟིན།
|
login.oauth2AutoCreateDisabled=OAUTH2 རང་འགུལ་སྤྱོད་མཁན་གསར་བཟོ་བཀག་སྡོམ་བྱས་ཟིན།
|
||||||
login.oAuth2AdminBlockedUser=ད་ལྟ་ཐོ་འགོད་མ་བྱས་པའི་སྤྱོད་མཁན་གྱི་ཐོ་འགོད་དང་ནང་འཛུལ་བཀག་སྡོམ་བྱས་ཡོད། དོ་དམ་པར་འབྲེལ་བ་གནང་རོགས།
|
login.oauth2AdminBlockedUser=ད་ལྟ་ཐོ་འགོད་མ་བྱས་པའི་སྤྱོད་མཁན་གྱི་ཐོ་འགོད་དང་ནང་འཛུལ་བཀག་སྡོམ་བྱས་ཡོད། དོ་དམ་པར་འབྲེལ་བ་གནང་རོགས།
|
||||||
login.oauth2RequestNotFound=དབང་སྤྲོད་རེ་ཞུ་རྙེད་མ་བྱུང་།
|
login.oauth2RequestNotFound=དབང་སྤྲོད་རེ་ཞུ་རྙེད་མ་བྱུང་།
|
||||||
login.oauth2InvalidUserInfoResponse=སྤྱོད་མཁན་གྱི་གནས་ཚུལ་ལན་འདེབས་ནོར་འཁྲུལ།
|
login.oauth2InvalidUserInfoResponse=སྤྱོད་མཁན་གྱི་གནས་ཚུལ་ལན་འདེབས་ནོར་འཁྲུལ།
|
||||||
login.oauth2invalidRequest=རེ་ཞུ་ནོར་འཁྲུལ།
|
login.oauth2invalidRequest=རེ་ཞུ་ནོར་འཁྲུལ།
|
||||||
@@ -1384,4 +1384,3 @@ validateSignature.cert.version=པར་གཞི།
|
|||||||
validateSignature.cert.keyUsage=ལྡེ་མིག་བེད་སྤྱོད།
|
validateSignature.cert.keyUsage=ལྡེ་མིག་བེད་སྤྱོད།
|
||||||
validateSignature.cert.selfSigned=རང་མིང་རྟགས།
|
validateSignature.cert.selfSigned=རང་མིང་རྟགས།
|
||||||
validateSignature.cert.bits=གནས།
|
validateSignature.cert.bits=གནས།
|
||||||
compress.grayscale.label=应用灰度进行压缩
|
|
||||||
|
|||||||
@@ -572,8 +572,8 @@ login.invalid=用户名或密码无效。
|
|||||||
login.locked=您的账户已被锁定。
|
login.locked=您的账户已被锁定。
|
||||||
login.signinTitle=请登录
|
login.signinTitle=请登录
|
||||||
login.ssoSignIn=通过单点登录登录
|
login.ssoSignIn=通过单点登录登录
|
||||||
login.oAuth2AutoCreateDisabled=OAuth2 自动创建用户已禁用
|
login.oauth2AutoCreateDisabled=OAuth2 自动创建用户已禁用
|
||||||
login.oAuth2AdminBlockedUser=目前已阻止未注册用户的注册或登录。请联系管理员。
|
login.oauth2AdminBlockedUser=目前已阻止未注册用户的注册或登录。请联系管理员。
|
||||||
login.oauth2RequestNotFound=找不到验证请求
|
login.oauth2RequestNotFound=找不到验证请求
|
||||||
login.oauth2InvalidUserInfoResponse=无效的用户信息响应
|
login.oauth2InvalidUserInfoResponse=无效的用户信息响应
|
||||||
login.oauth2invalidRequest=无效请求
|
login.oauth2invalidRequest=无效请求
|
||||||
@@ -1384,4 +1384,3 @@ validateSignature.cert.version=版本
|
|||||||
validateSignature.cert.keyUsage=密钥用途
|
validateSignature.cert.keyUsage=密钥用途
|
||||||
validateSignature.cert.selfSigned=自签名
|
validateSignature.cert.selfSigned=自签名
|
||||||
validateSignature.cert.bits=比特
|
validateSignature.cert.bits=比特
|
||||||
compress.grayscale.label=应用灰度进行压缩
|
|
||||||
|
|||||||
@@ -138,7 +138,7 @@ analytics.settings=您可以在 config/settings.yml 檔案中變更分析功能
|
|||||||
# NAVBAR #
|
# NAVBAR #
|
||||||
#############
|
#############
|
||||||
navbar.favorite=我的最愛
|
navbar.favorite=我的最愛
|
||||||
navbar.recent=最新與近期更新
|
navbar.recent=New and recently updated
|
||||||
navbar.darkmode=深色模式
|
navbar.darkmode=深色模式
|
||||||
navbar.language=語言
|
navbar.language=語言
|
||||||
navbar.settings=設定
|
navbar.settings=設定
|
||||||
@@ -262,18 +262,18 @@ home.desc=您的本機一站式 PDF 解決方案。
|
|||||||
home.searchBar=搜尋功能...
|
home.searchBar=搜尋功能...
|
||||||
|
|
||||||
|
|
||||||
home.viewPdf.title=檢視/編輯 PDF
|
home.viewPdf.title=View/Edit PDF
|
||||||
home.viewPdf.desc=檢視、註釋、新增文字或圖片
|
home.viewPdf.desc=檢視、註釋、新增文字或圖片
|
||||||
viewPdf.tags=檢視,閱讀,註釋,文字,圖片
|
viewPdf.tags=檢視,閱讀,註釋,文字,圖片
|
||||||
|
|
||||||
home.setFavorites=設定我的最愛
|
home.setFavorites=Set Favourites
|
||||||
home.hideFavorites=隱藏我的最愛
|
home.hideFavorites=Hide Favourites
|
||||||
home.showFavorites=顯示我的最愛
|
home.showFavorites=Show Favourites
|
||||||
home.legacyHomepage=舊版首頁
|
home.legacyHomepage=Old homepage
|
||||||
home.newHomePage=嘗試使用全新首頁!
|
home.newHomePage=Try our new homepage!
|
||||||
home.alphabetical=按照字母排序
|
home.alphabetical=Alphabetical
|
||||||
home.globalPopularity=熱門程度
|
home.globalPopularity=Global Popularity
|
||||||
home.sortBy=排序方式:
|
home.sortBy=Sort by:
|
||||||
|
|
||||||
home.multiTool.title=PDF 複合工具
|
home.multiTool.title=PDF 複合工具
|
||||||
home.multiTool.desc=合併、旋轉、重新排列和移除頁面
|
home.multiTool.desc=合併、旋轉、重新排列和移除頁面
|
||||||
@@ -572,8 +572,8 @@ login.invalid=使用者名稱或密碼無效。
|
|||||||
login.locked=您的帳號已被鎖定。
|
login.locked=您的帳號已被鎖定。
|
||||||
login.signinTitle=請登入
|
login.signinTitle=請登入
|
||||||
login.ssoSignIn=透過 SSO 單一登入
|
login.ssoSignIn=透過 SSO 單一登入
|
||||||
login.oAuth2AutoCreateDisabled=OAuth 2.0 自動建立使用者功能已停用
|
login.oauth2AutoCreateDisabled=OAuth 2.0 自動建立使用者功能已停用
|
||||||
login.oAuth2AdminBlockedUser=目前不允許未註冊的使用者註冊或登入。請聯絡系統管理員。
|
login.oauth2AdminBlockedUser=目前不允許未註冊的使用者註冊或登入。請聯絡系統管理員。
|
||||||
login.oauth2RequestNotFound=找不到驗證請求
|
login.oauth2RequestNotFound=找不到驗證請求
|
||||||
login.oauth2InvalidUserInfoResponse=使用者資訊回應無效
|
login.oauth2InvalidUserInfoResponse=使用者資訊回應無效
|
||||||
login.oauth2invalidRequest=請求無效
|
login.oauth2invalidRequest=請求無效
|
||||||
@@ -623,7 +623,7 @@ redact.showAttatchments=顯示附件
|
|||||||
redact.showLayers=顯示圖層(按兩下可將所有圖層重設為預設狀態)
|
redact.showLayers=顯示圖層(按兩下可將所有圖層重設為預設狀態)
|
||||||
redact.colourPicker=顏色選擇器
|
redact.colourPicker=顏色選擇器
|
||||||
redact.findCurrentOutlineItem=尋找目前的大綱項目
|
redact.findCurrentOutlineItem=尋找目前的大綱項目
|
||||||
redact.applyChanges=套用變更
|
redact.applyChanges=Apply Changes
|
||||||
|
|
||||||
#showJS
|
#showJS
|
||||||
showJS.title=顯示 JavaScript
|
showJS.title=顯示 JavaScript
|
||||||
@@ -992,7 +992,7 @@ pdfOrganiser.mode.7=刪除第一頁
|
|||||||
pdfOrganiser.mode.8=刪除最後一頁
|
pdfOrganiser.mode.8=刪除最後一頁
|
||||||
pdfOrganiser.mode.9=刪除第一頁和最後一頁
|
pdfOrganiser.mode.9=刪除第一頁和最後一頁
|
||||||
pdfOrganiser.mode.10=奇偶合併
|
pdfOrganiser.mode.10=奇偶合併
|
||||||
pdfOrganiser.mode.11=複製所有頁面
|
pdfOrganiser.mode.11=Duplicate all pages
|
||||||
pdfOrganiser.placeholder=(例如 1,3,2 或 4-8,2,10-12 或 2n-1)
|
pdfOrganiser.placeholder=(例如 1,3,2 或 4-8,2,10-12 或 2n-1)
|
||||||
|
|
||||||
|
|
||||||
@@ -1035,7 +1035,7 @@ decrypt.success=檔案已成功解密。
|
|||||||
multiTool-advert.message=此功能也可以在我們的<a href="{0}">複合工具頁面</a>中使用。前往查看並體驗更強大的逐頁操作介面及其他進階功能!
|
multiTool-advert.message=此功能也可以在我們的<a href="{0}">複合工具頁面</a>中使用。前往查看並體驗更強大的逐頁操作介面及其他進階功能!
|
||||||
|
|
||||||
#view pdf
|
#view pdf
|
||||||
viewPdf.title=檢視/編輯 PDF
|
viewPdf.title=View/Edit PDF
|
||||||
viewPdf.header=檢視 PDF
|
viewPdf.header=檢視 PDF
|
||||||
|
|
||||||
#pageRemover
|
#pageRemover
|
||||||
@@ -1384,4 +1384,3 @@ validateSignature.cert.version=版本
|
|||||||
validateSignature.cert.keyUsage=金鑰用途
|
validateSignature.cert.keyUsage=金鑰用途
|
||||||
validateSignature.cert.selfSigned=自我簽署
|
validateSignature.cert.selfSigned=自我簽署
|
||||||
validateSignature.cert.bits=位元
|
validateSignature.cert.bits=位元
|
||||||
compress.grayscale.label=應用灰階進行壓縮
|
|
||||||
|
|||||||
@@ -28,38 +28,37 @@ security:
|
|||||||
clientId: '' # client ID for Keycloak OAuth2
|
clientId: '' # client ID for Keycloak OAuth2
|
||||||
clientSecret: '' # client secret for Keycloak OAuth2
|
clientSecret: '' # client secret for Keycloak OAuth2
|
||||||
scopes: openid, profile, email # scopes for Keycloak OAuth2
|
scopes: openid, profile, email # scopes for Keycloak OAuth2
|
||||||
useAsUsername: preferred_username # field to use as the username for Keycloak OAuth2. Available options are: [email | name | given_name | family_name | preferred_name]
|
useAsUsername: preferred_username # field to use as the username for Keycloak OAuth2
|
||||||
google:
|
google:
|
||||||
clientId: '' # client ID for Google OAuth2
|
clientId: '' # client ID for Google OAuth2
|
||||||
clientSecret: '' # client secret for Google OAuth2
|
clientSecret: '' # client secret for Google OAuth2
|
||||||
scopes: email, profile # scopes for Google OAuth2
|
scopes: https://www.googleapis.com/auth/userinfo.email, https://www.googleapis.com/auth/userinfo.profile # scopes for Google OAuth2
|
||||||
useAsUsername: email # field to use as the username for Google OAuth2. Available options are: [email | name | given_name | family_name]
|
useAsUsername: email # field to use as the username for Google OAuth2
|
||||||
github:
|
github:
|
||||||
clientId: '' # client ID for GitHub OAuth2
|
clientId: '' # client ID for GitHub OAuth2
|
||||||
clientSecret: '' # client secret for GitHub OAuth2
|
clientSecret: '' # client secret for GitHub OAuth2
|
||||||
scopes: read:user # scope for GitHub OAuth2
|
scopes: read:user # scope for GitHub OAuth2
|
||||||
useAsUsername: login # field to use as the username for GitHub OAuth2. Available options are: [email | login | name]
|
useAsUsername: login # field to use as the username for GitHub OAuth2
|
||||||
issuer: '' # set to any Provider that supports OpenID Connect Discovery (/.well-known/openid-configuration) endpoint
|
issuer: '' # set to any provider that supports OpenID Connect Discovery (/.well-known/openid-configuration) endpoint
|
||||||
clientId: '' # client ID from your Provider
|
clientId: '' # client ID from your provider
|
||||||
clientSecret: '' # client secret from your Provider
|
clientSecret: '' # client secret from your provider
|
||||||
autoCreateUser: true # set to 'true' to allow auto-creation of non-existing users
|
autoCreateUser: true # set to 'true' to allow auto-creation of non-existing users
|
||||||
blockRegistration: false # set to 'true' to deny login with SSO without prior registration by an admin
|
blockRegistration: false # set to 'true' to deny login with SSO without prior registration by an admin
|
||||||
useAsUsername: email # default is 'email'; custom fields can be used as the username
|
useAsUsername: email # default is 'email'; custom fields can be used as the username
|
||||||
scopes: openid, profile, email # specify the scopes for which the application will request permissions
|
scopes: openid, profile, email # specify the scopes for which the application will request permissions
|
||||||
provider: google # set this to your OAuth Provider's name, e.g., 'google' or 'keycloak'
|
provider: google # set this to your OAuth provider's name, e.g., 'google' or 'keycloak'
|
||||||
saml2:
|
saml2:
|
||||||
enabled: false # Only enabled for paid enterprise clients (enterpriseEdition.enabled must be true)
|
enabled: false # Only enabled for paid enterprise clients (enterpriseEdition.enabled must be true)
|
||||||
provider: '' # The name of your Provider
|
|
||||||
autoCreateUser: true # set to 'true' to allow auto-creation of non-existing users
|
autoCreateUser: true # set to 'true' to allow auto-creation of non-existing users
|
||||||
blockRegistration: false # set to 'true' to deny login with SSO without prior registration by an admin
|
blockRegistration: false # set to 'true' to deny login with SSO without prior registration by an admin
|
||||||
registrationId: stirling # The name of your Service Provider (SP) app name. Should match the name in the path for your SSO & SLO URLs
|
registrationId: stirling
|
||||||
idpMetadataUri: https://dev-XXXXXXXX.okta.com/app/externalKey/sso/saml/metadata # The uri for your Provider's metadata
|
idpMetadataUri: https://dev-XXXXXXXX.okta.com/app/externalKey/sso/saml/metadata
|
||||||
idpSingleLoginUrl: https://dev-XXXXXXXX.okta.com/app/dev-XXXXXXXX_stirlingpdf_1/externalKey/sso/saml # The URL for initiating SSO. Provided by your Provider
|
idpSingleLogoutUrl: https://dev-XXXXXXXX.okta.com/app/dev-XXXXXXXX_stirlingpdf_1/externalKey/slo/saml
|
||||||
idpSingleLogoutUrl: https://dev-XXXXXXXX.okta.com/app/dev-XXXXXXXX_stirlingpdf_1/externalKey/slo/saml # The URL for initiating SLO. Provided by your Provider
|
idpSingleLoginUrl: https://dev-XXXXXXXX.okta.com/app/dev-XXXXXXXX_stirlingpdf_1/externalKey/sso/saml
|
||||||
idpIssuer: '' # The ID of your Provider
|
idpIssuer: http://www.okta.com/externalKey
|
||||||
idpCert: classpath:okta.cert # The certificate your Provider will use to authenticate your app's SAML authentication requests. Provided by your Provider
|
idpCert: classpath:okta.crt
|
||||||
privateKey: classpath:saml-private-key.key # Your private key. Generated from your keypair
|
privateKey: classpath:saml-private-key.key
|
||||||
spCert: classpath:saml-public-cert.crt # Your signing certificate. Generated from your keypair
|
spCert: classpath:saml-public-cert.crt
|
||||||
|
|
||||||
enterpriseEdition:
|
enterpriseEdition:
|
||||||
enabled: false # set to 'true' to enable enterprise edition
|
enabled: false # set to 'true' to enable enterprise edition
|
||||||
@@ -86,7 +85,7 @@ system:
|
|||||||
showUpdateOnlyAdmin: false # only admins can see when a new update is available, depending on showUpdate it must be set to 'true'
|
showUpdateOnlyAdmin: false # only admins can see when a new update is available, depending on showUpdate it must be set to 'true'
|
||||||
customHTMLFiles: false # enable to have files placed in /customFiles/templates override the existing template HTML files
|
customHTMLFiles: false # enable to have files placed in /customFiles/templates override the existing template HTML files
|
||||||
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.
|
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.
|
||||||
enableAnalytics: 'undefined' # set to 'true' to enable analytics, set to 'false' to disable analytics; for enterprise users, this is set to true
|
enableAnalytics: 'true' # set to 'true' to enable analytics, set to 'false' to disable analytics; for enterprise users, this is set to true
|
||||||
disableSanitize: false # set to true to disable Sanitize HTML; (can lead to injections in HTML)
|
disableSanitize: false # set to true to disable Sanitize HTML; (can lead to injections in HTML)
|
||||||
datasource:
|
datasource:
|
||||||
enableCustomDatabase: false # Enterprise users ONLY, set this property to 'true' if you would like to use your own custom database configuration
|
enableCustomDatabase: false # Enterprise users ONLY, set this property to 'true' if you would like to use your own custom database configuration
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user