mirror of
https://github.com/keycloak/keycloak.git
synced 2026-02-03 20:39:33 -05:00
Prevent duplicate keys in message properties for themes (#37179)
Closes #33357 Signed-off-by: Alexander Schwartz <alexander.schwartz@gmx.net>
This commit is contained in:
parent
ee74c28741
commit
a0a5d0bcb2
14 changed files with 339 additions and 30 deletions
13
js/pom.xml
13
js/pom.xml
|
|
@ -50,6 +50,19 @@
|
|||
</pluginManagement>
|
||||
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>theme-verifier-maven-plugin</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>verify-theme</id>
|
||||
<goals>
|
||||
<goal>verify-theme</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<!-- Clean child modules from parent, as we trigger the build here for parallelization. -->
|
||||
<artifactId>maven-clean-plugin</artifactId>
|
||||
|
|
|
|||
87
misc/theme-verifier/pom.xml
Normal file
87
misc/theme-verifier/pom.xml
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
~ Copyright 2025 Red Hat, Inc. and/or its affiliates
|
||||
~ and other contributors as indicated by the @author tags.
|
||||
~
|
||||
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||
~ you may not use this file except in compliance with the License.
|
||||
~ You may obtain a copy of the License at
|
||||
~
|
||||
~ http://www.apache.org/licenses/LICENSE-2.0
|
||||
~
|
||||
~ Unless required by applicable law or agreed to in writing, software
|
||||
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
~ See the License for the specific language governing permissions and
|
||||
~ limitations under the License.
|
||||
-->
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>theme-verifier-maven-plugin</artifactId>
|
||||
<version>999.0.0-SNAPSHOT</version>
|
||||
|
||||
<name>Keycloak Theme verifier</name>
|
||||
|
||||
<packaging>maven-plugin</packaging>
|
||||
|
||||
<properties>
|
||||
<maven.compiler.source>17</maven.compiler.source>
|
||||
<maven.compiler.target>17</maven.compiler.target>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<maven-plugin-tools.version>3.15.1</maven-plugin-tools.version>
|
||||
<maven-plugin-api.version>3.9.9</maven-plugin-api.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.apache.maven</groupId>
|
||||
<artifactId>maven-plugin-api</artifactId>
|
||||
<version>${maven-plugin-api.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.maven.plugin-tools</groupId>
|
||||
<artifactId>maven-plugin-annotations</artifactId>
|
||||
<version>${maven-plugin-tools.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.maven</groupId>
|
||||
<artifactId>maven-core</artifactId>
|
||||
<version>${maven-plugin-api.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>commons-io</groupId>
|
||||
<artifactId>commons-io</artifactId>
|
||||
<version>2.18.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter-api</artifactId>
|
||||
<version>5.10.5</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.hamcrest</groupId>
|
||||
<artifactId>hamcrest</artifactId>
|
||||
<version>2.2</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
* Copyright 2025 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.keycloak.themeverifier;
|
||||
|
||||
import org.apache.commons.io.filefilter.AbstractFileFilter;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
public class MessagePropertiesFilter extends AbstractFileFilter {
|
||||
public static MessagePropertiesFilter INSTANCE = new MessagePropertiesFilter();
|
||||
|
||||
@Override
|
||||
public boolean accept(File file) {
|
||||
return file.getName().startsWith("messages_") && file.getName().endsWith(".properties");
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
/*
|
||||
* Copyright 2025 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.keycloak.themeverifier;
|
||||
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.apache.commons.io.filefilter.DirectoryFileFilter;
|
||||
import org.apache.maven.model.Resource;
|
||||
import org.apache.maven.plugin.AbstractMojo;
|
||||
import org.apache.maven.plugin.MojoExecutionException;
|
||||
import org.apache.maven.plugin.MojoFailureException;
|
||||
import org.apache.maven.plugins.annotations.LifecyclePhase;
|
||||
import org.apache.maven.plugins.annotations.Mojo;
|
||||
import org.apache.maven.plugins.annotations.Parameter;
|
||||
import org.apache.maven.project.MavenProject;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
@Mojo(name = "verify-theme", defaultPhase = LifecyclePhase.INSTALL, threadSafe = true)
|
||||
public class ThemeVerifierMojo extends AbstractMojo {
|
||||
|
||||
@Parameter(defaultValue = "${project}", readonly = true)
|
||||
private MavenProject mavenProject;
|
||||
|
||||
@Override
|
||||
public void execute() throws MojoExecutionException, MojoFailureException {
|
||||
Iterator<Resource> resources = mavenProject.getResources().iterator();
|
||||
List<String> messages = new ArrayList<>();
|
||||
while (resources.hasNext()) {
|
||||
Resource resource = resources.next();
|
||||
File dir = new File(resource.getDirectory());
|
||||
Iterator<File> fileIterator = FileUtils.iterateFiles(dir, MessagePropertiesFilter.INSTANCE, DirectoryFileFilter.INSTANCE);
|
||||
while (fileIterator.hasNext()) {
|
||||
File file = fileIterator.next();
|
||||
messages.addAll(new VerifyMessageProperties(file).verify());
|
||||
}
|
||||
}
|
||||
if (!messages.isEmpty()) {
|
||||
throw new MojoFailureException("Validation errors: " + messages);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,73 @@
|
|||
/*
|
||||
* Copyright 2025 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.keycloak.themeverifier;
|
||||
|
||||
import org.apache.maven.plugin.MojoExecutionException;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.StringReader;
|
||||
import java.nio.file.Files;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
|
||||
public class VerifyMessageProperties {
|
||||
|
||||
private final File file;
|
||||
private List<String> messages;
|
||||
|
||||
public VerifyMessageProperties(File file) {
|
||||
this.file = file;
|
||||
}
|
||||
|
||||
public List<String> verify() throws MojoExecutionException {
|
||||
messages = new ArrayList<>();
|
||||
try {
|
||||
String contents = Files.readString(file.toPath());
|
||||
verifyNoDuplicateKeys(contents);
|
||||
} catch (IOException e) {
|
||||
throw new MojoExecutionException("Can not read file " + file, e);
|
||||
}
|
||||
return messages;
|
||||
}
|
||||
|
||||
private void verifyNoDuplicateKeys(String contents) throws IOException {
|
||||
BufferedReader bufferedReader = new BufferedReader(new StringReader(contents));
|
||||
String line;
|
||||
HashSet<String> seenKeys = new HashSet<>();
|
||||
HashSet<String> duplicateKeys = new HashSet<>();
|
||||
while ((line = bufferedReader.readLine()) != null) {
|
||||
if (line.startsWith("#") || line.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
int split = line.indexOf("=");
|
||||
if (split != -1) {
|
||||
String key = line.substring(0, split).trim();
|
||||
if (seenKeys.contains(key)) {
|
||||
duplicateKeys.add(key);
|
||||
} else {
|
||||
seenKeys.add(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!duplicateKeys.isEmpty()) {
|
||||
messages.add("Duplicate keys in file '" + file.getAbsolutePath() + "': " + duplicateKeys);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
* Copyright 2025 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.keycloak.themeverifier;
|
||||
|
||||
import org.apache.maven.plugin.MojoExecutionException;
|
||||
import org.hamcrest.MatcherAssert;
|
||||
import org.hamcrest.Matchers;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.io.File;
|
||||
import java.net.URL;
|
||||
import java.util.List;
|
||||
|
||||
class VerifyMessagePropertiesTest {
|
||||
|
||||
@Test
|
||||
void verifyDuplicateKeysDetected() throws MojoExecutionException {
|
||||
List<String> verify = getFile("duplicate_keys.properties").verify();
|
||||
MatcherAssert.assertThat(verify, Matchers.contains(Matchers.containsString("Duplicate keys in file")));
|
||||
}
|
||||
|
||||
private static VerifyMessageProperties getFile(String fixture) {
|
||||
URL resource = VerifyMessageProperties.class.getResource("/" + fixture);
|
||||
if (resource == null) {
|
||||
throw new RuntimeException("Resource not found: " + fixture);
|
||||
}
|
||||
return new VerifyMessageProperties(new File(resource.getFile()));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
#
|
||||
# Copyright 2025 Red Hat, Inc. and/or its affiliates
|
||||
# and other contributors as indicated by the @author tags.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
key=value
|
||||
key=value
|
||||
1
pom.xml
1
pom.xml
|
|
@ -291,6 +291,7 @@
|
|||
<module>federation</module>
|
||||
<module>services</module>
|
||||
<module>themes</module>
|
||||
<module>misc/theme-verifier</module>
|
||||
<module>model</module>
|
||||
<module>util</module>
|
||||
<module>rest</module>
|
||||
|
|
|
|||
|
|
@ -33,6 +33,21 @@
|
|||
<directory>src/main/resources</directory>
|
||||
</resource>
|
||||
</resources>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>theme-verifier-maven-plugin</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>verify-theme</id>
|
||||
<goals>
|
||||
<goal>verify-theme</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
<profiles>
|
||||
|
|
|
|||
|
|
@ -289,31 +289,23 @@ authenticatorBackupCodesSetupTitle=Backup Kode Opsætning
|
|||
realmName=Rige
|
||||
doDownload=Download
|
||||
doPrint=Print
|
||||
doCopy=Kopier
|
||||
generateNewBackupCodes=Generer Nye Backup Koder
|
||||
backtoAuthenticatorPage=Tilbage til Authenticator siden
|
||||
|
||||
|
||||
#Resources
|
||||
resources=Ressourcer
|
||||
myResources=Mine Ressourcer
|
||||
sharedwithMe=Delt med mig
|
||||
share=Del
|
||||
resource=Ressource
|
||||
application=Applikation
|
||||
date=Dato
|
||||
sharedwith=Delt med
|
||||
owner=Ejer
|
||||
accessPermissions=Adgangstilladelser
|
||||
permissionRequests=Rettigheds forespørgsler
|
||||
approve=Godkend
|
||||
approveAll=Godkend alle
|
||||
sharedwith=Delt med
|
||||
people=Folk
|
||||
perPage=per side
|
||||
currentPage=Nuværende Side
|
||||
sharetheResource=Del Ressourcen
|
||||
user=Bruger
|
||||
group=Gruppe
|
||||
selectPermission=Vælg tilladelse
|
||||
addPeople=Tilføj folk at dele ressourcen med
|
||||
|
|
|
|||
|
|
@ -323,7 +323,6 @@ backupcodesIntroMessage=Jos menetät pääsyn puhelimeesi, voit silti kirjautua
|
|||
realmName=Realm
|
||||
doDownload=Lataa
|
||||
doPrint=Tulosta
|
||||
doCopy=Copy
|
||||
backupCodesTips-1=Jokaisen varmuuskoodin voi käyttää yhden kerran.
|
||||
backupCodesTips-2=Nämä koodit on luotu
|
||||
generateNewBackupCodes=Luo uudet varmuuskoodit
|
||||
|
|
|
|||
|
|
@ -155,7 +155,6 @@ backTo=Назад в {0}
|
|||
date=Дата
|
||||
event=Событие
|
||||
ip=IP
|
||||
client=Клиент
|
||||
clients=Клиенты
|
||||
details=Детали
|
||||
started=Начата
|
||||
|
|
|
|||
|
|
@ -477,7 +477,6 @@ webauthn-registration-init-label-prompt=등록한 패스키의 라벨을 입력
|
|||
|
||||
# WebAuthn Error
|
||||
webauthn-error-title=패스키 오류
|
||||
webauthn-error-registration=패
|
||||
webauthn-error-registration=패스키 등록에 실패했습니다.<br/> {0}
|
||||
webauthn-error-api-get=패스키로 인증하는 데 실패했습니다.<br/> {0}
|
||||
webauthn-error-different-user=첫 번째 인증된 사용자가 패스키로 인증된 사용자가 아닙니다.
|
||||
|
|
|
|||
|
|
@ -247,24 +247,6 @@ notMatchPasswordMessage=Şifreler eşleşmiyor.
|
|||
error-invalid-value=Geçersiz değer.
|
||||
error-invalid-blank=Lütfen bir değer sağlayın.
|
||||
error-empty=Lütfen bir değer sağlayın.
|
||||
error-invalid-length=Uzunluk {1} ile {2} arasında olmalıdır.
|
||||
error-invalid-length-too-short=Minimum uzunluk {1}.
|
||||
error-invalid-length-too-long=Maksimum uzunluk {2}.
|
||||
error-invalid-email=Geçersiz e-posta adresi.
|
||||
error-invalid-number=Geçersiz numara.
|
||||
error-number-out-of-range=Numara {1} ile {2} arasında olmalıdır.
|
||||
error-number-out-of-range-too-small=Numara en az {1} değerinde olmalıdır.
|
||||
error-number-out-of-range-too-big=Numara en fazla {2} değerinde olmalıdır.
|
||||
error-pattern-no-match=Geçersiz değer.
|
||||
error-invalid-uri=Geçersiz URL.
|
||||
error-invalid-uri-scheme=Geçersiz URL şeması.
|
||||
error-invalid-uri-fragment=Geçersiz URL parçası.
|
||||
error-user-attribute-required=Bu alanı belirtiniz.
|
||||
error-invalid-date=Geçersiz tarih.
|
||||
error-user-attribute-read-only=Bu alan sadece okunabilir.
|
||||
error-username-invalid-character=Değer geçersiz karakter içeriyor.
|
||||
error-person-name-invalid-character=Değer geçersiz karakter içeriyor.
|
||||
error-reset-otp-missing-id=Lütfen bir OTP yapılandırması seçin.
|
||||
|
||||
invalidPasswordExistingMessage=Mevcut şifre geçersiz.
|
||||
invalidPasswordBlacklistedMessage=Geçersiz şifre: şifre kara listeye alındı.
|
||||
|
|
@ -511,7 +493,6 @@ frontchannel-logout.message= Aşağıdaki uygulamalardan oturumunuzu kapatıyors
|
|||
logoutConfirmTitle= Oturum kapatılıyor,
|
||||
logoutConfirmHeader= Oturumunuzu kapatmak istiyor musunuz?,
|
||||
doLogout= Oturumu kapat,
|
||||
readOnlyUsernameMessage= Kullanıcı adınızı güncelleyemezsiniz çünkü sadece okunabilir durumda.,
|
||||
error-invalid-multivalued-size= {0} özniteliği en az {1} ve en fazla {2} değer(ler)e sahip olmalıdır.,
|
||||
shouldBeEqual= {0} {1} ile eşit olmalıdır,
|
||||
shouldBeDifferent= {0} {1} ile farklı olmalıdır,
|
||||
|
|
|
|||
Loading…
Reference in a new issue