Skip to content

Commit 2bdbcec

Browse files
committed
Further fix LDAP sync not encrypted issue.
Also, the spi-user-provider flag is no longer needed.
1 parent 3c6c74d commit 2bdbcec

File tree

13 files changed

+181
-48
lines changed

13 files changed

+181
-48
lines changed

Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,5 +18,5 @@ FROM quay.io/keycloak/keycloak:$KEYCLOAK_VERSION
1818
COPY --from=provider-pii /app/target/*.jar /opt/keycloak/providers
1919

2020
# Need to build after adding providers
21-
RUN /opt/keycloak/bin/kc.sh build --db=mysql --features="declarative-ui" --spi-user-provider=jpa-encrypted
21+
RUN /opt/keycloak/bin/kc.sh build --db=mysql --features="declarative-ui"
2222

README.md

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -60,14 +60,13 @@ RUN /opt/keycloak/bin/kc.sh build --db=mysql --features="declarative-ui" --spi-u
6060

6161
This provider requires the encryption key to be provided via environment variable **`KC_PII_ENCKEY`** and it needs to be **at least 16 characters long**. If the encryption key is not provided, however, there is a default fallback that uses MD5 hash of the database JDBC URL, either using configuration parameter `db-url` or environment variable `KC_DB_URL`. If you rely on this fallback and in the future need to migrate your Keycloak data into another databases that results in a different value of JDBC URL, you need to get the old value of JDBC URL, encode it using lowercased MD5 hash and set the value to the `KC_PII_ENCKEY` environment variable.
6262

63-
### Enabling 'jpa-encrypted' user provider and 'declarative-ui' feature
63+
### Enabling 'declarative-ui' feature
6464

65-
This provider requires the Keycloak instance to be either built or started with two flags:
65+
This provider requires the Keycloak instance to be either built or started with the following flag:
6666

67-
- `--spi-user-provider=jpa-encrypted`
68-
- `--features="declarative-ui"`
67+
- `--features="declarative-ui"`
6968

70-
These flags can be added to the `build` command with a condition that the `start` command has the `--optimized` flag, or for `start` without that flag or `start-dev` command, the two flags are to be added to the `start` or `start-dev` commands.
69+
The flag can be added to the `build` command with a condition that the `start` command has the `--optimized` flag, or for `start` without that flag or `start-dev` command, the flag is to be added to the `start` or `start-dev` commands.
7170

7271
Note: the "declarative-ui" flag is only necessary while the feature is still considered experimental. Once it is enabled by default in future Keycloak versions, the flag will no longer be necessary.
7372

licenseheader.txt

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
/*
2+
* Copyright (C) 2025 Muhammad Lukman Nasaruddin <lukman.nasaruddin@gmail.com>
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/

nb-configuration.xml

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project-shared-configuration>
3+
<!--
4+
This file contains additional configuration written by modules in the NetBeans IDE.
5+
The configuration is intended to be shared among all the users of project and
6+
therefore it is assumed to be part of version control checkout.
7+
Without this configuration present, some functionality in the IDE may be limited or fail altogether.
8+
-->
9+
<properties xmlns="http://www.netbeans.org/ns/maven-properties-data/1">
10+
<!--
11+
Properties that influence various parts of the IDE, especially code formatting and the like.
12+
You can copy and paste the single properties, into the pom.xml file and the IDE will pick them up.
13+
That way multiple projects can share the same settings (useful for formatting rules for example).
14+
Any value defined here will override the pom.xml file value but is only applicable to the current project.
15+
-->
16+
<netbeans.hint.licensePath>${project.basedir}/licenseheader.txt</netbeans.hint.licensePath>
17+
</properties>
18+
</project-shared-configuration>

pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
<modelVersion>4.0.0</modelVersion>
44
<groupId>my.unifi.eset</groupId>
55
<artifactId>keycloak-pii-data-encryption</artifactId>
6-
<version>2.5</version>
6+
<version>2.6</version>
77
<packaging>jar</packaging>
88
<properties>
99
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>

src/main/java/my/unifi/eset/keycloak/piidataencryption/jpa/EncryptedUserProvider.java

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313
* See the License for the specific language governing permissions and
1414
* limitations under the License.
1515
*/
16-
1716
package my.unifi.eset.keycloak.piidataencryption.jpa;
1817

1918
import com.fasterxml.jackson.core.JsonProcessingException;
@@ -80,4 +79,13 @@ public Stream<UserModel> searchForUserStream(RealmModel realm, Map<String, Strin
8079
return super.searchForUserStream(realm, attributes, firstResult, maxResults);
8180
}
8281

82+
@Override
83+
public UserModel addUser(RealmModel realm, String id, String username, boolean addDefaultRoles, boolean addDefaultRequiredActions) {
84+
UserModel userModel = super.addUser(realm, id, username, addDefaultRoles, addDefaultRequiredActions);
85+
LogicUtils.encryptUserEntity(ks, em, LogicUtils.getUserEntity(em, userModel.getId()));
86+
em.flush();
87+
logger.debugf("addUser (encrypted): " + username);
88+
return userModel;
89+
}
90+
8391
}

src/main/java/my/unifi/eset/keycloak/piidataencryption/jpa/EncryptedUserProviderFactory.java

Lines changed: 6 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -13,40 +13,25 @@
1313
* See the License for the specific language governing permissions and
1414
* limitations under the License.
1515
*/
16-
1716
package my.unifi.eset.keycloak.piidataencryption.jpa;
1817

1918
import jakarta.persistence.EntityManager;
20-
import org.keycloak.Config;
2119
import org.keycloak.connections.jpa.JpaConnectionProvider;
2220
import org.keycloak.models.KeycloakSession;
23-
import org.keycloak.models.KeycloakSessionFactory;
24-
import org.keycloak.models.UserProviderFactory;
25-
import org.keycloak.provider.Provider;
21+
import org.keycloak.models.UserProvider;
22+
import org.keycloak.models.jpa.JpaUserProviderFactory;
2623

27-
public class EncryptedUserProviderFactory implements UserProviderFactory {
24+
public class EncryptedUserProviderFactory extends JpaUserProviderFactory {
2825

2926
@Override
30-
public Provider create(KeycloakSession ks) {
27+
public UserProvider create(KeycloakSession ks) {
3128
EntityManager em = ks.getProvider(JpaConnectionProvider.class).getEntityManager();
3229
return new EncryptedUserProvider(ks, em);
3330
}
3431

3532
@Override
36-
public void init(Config.Scope scope) {
37-
}
38-
39-
@Override
40-
public void postInit(KeycloakSessionFactory ksf) {
41-
}
42-
43-
@Override
44-
public void close() {
45-
}
46-
47-
@Override
48-
public String getId() {
49-
return "jpa-encrypted";
33+
public int order() {
34+
return 1000;
5035
}
5136

5237
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/*
2+
* Copyright (C) 2025 Muhammad Lukman Nasaruddin <lukman.nasaruddin@gmail.com>
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package my.unifi.eset.keycloak.piidataencryption.jpa;
17+
18+
public class EncryptedUserProviderFactoryDeprecated extends EncryptedUserProviderFactory {
19+
20+
@Override
21+
public String getId() {
22+
return "jpa-encrypted";
23+
}
24+
25+
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/*
2+
* Copyright (C) 2025 Muhammad Lukman Nasaruddin <lukman.nasaruddin@gmail.com>
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package my.unifi.eset.keycloak.piidataencryption.ldap;
17+
18+
import jakarta.persistence.EntityManager;
19+
import my.unifi.eset.keycloak.piidataencryption.utils.LogicUtils;
20+
import org.jboss.logging.Logger;
21+
import org.keycloak.component.ComponentModel;
22+
import org.keycloak.connections.jpa.JpaConnectionProvider;
23+
import org.keycloak.models.KeycloakSession;
24+
import org.keycloak.models.RealmModel;
25+
import org.keycloak.models.UserModel;
26+
import org.keycloak.storage.ldap.LDAPStorageProvider;
27+
import org.keycloak.storage.ldap.LDAPStorageProviderFactory;
28+
import org.keycloak.storage.ldap.idm.model.LDAPObject;
29+
import org.keycloak.storage.ldap.idm.store.ldap.LDAPIdentityStore;
30+
31+
public class EncryptedLDAPStorageProvider extends LDAPStorageProvider {
32+
33+
private static final Logger logger = Logger.getLogger(EncryptedLDAPStorageProvider.class);
34+
35+
public EncryptedLDAPStorageProvider(LDAPStorageProviderFactory factory, KeycloakSession session, ComponentModel model, LDAPIdentityStore ldapIdentityStore) {
36+
super(factory, session, model, ldapIdentityStore);
37+
}
38+
39+
@Override
40+
protected UserModel importUserFromLDAP(KeycloakSession session, RealmModel realm, LDAPObject ldapUser, ImportType importType) {
41+
logger.debugf("importUserFromLDAP(KeycloakSession session, RealmModel realm, LDAPObject ldapUser, ImportType importType)");
42+
UserModel userModel = super.importUserFromLDAP(session, realm, ldapUser, importType);
43+
EntityManager em = session.getProvider(JpaConnectionProvider.class).getEntityManager();
44+
LogicUtils.encryptUserEntity(session, em, LogicUtils.getUserEntity(em, userModel.getId()));
45+
em.flush();
46+
logger.debugf("importUserFromLDAP (encrypted): " + userModel.getUsername());
47+
return userModel;
48+
}
49+
50+
}
Lines changed: 39 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,56 @@
1+
/*
2+
* Copyright (C) 2025 Muhammad Lukman Nasaruddin <lukman.nasaruddin@gmail.com>
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
116
package my.unifi.eset.keycloak.piidataencryption.ldap;
217

3-
import jakarta.persistence.EntityManager;
4-
import my.unifi.eset.keycloak.piidataencryption.utils.LogicUtils;
18+
import java.util.Map;
19+
import org.keycloak.Config;
520
import org.keycloak.component.ComponentModel;
6-
import org.keycloak.connections.jpa.JpaConnectionProvider;
721
import org.keycloak.models.KeycloakSession;
8-
import org.keycloak.models.KeycloakSessionFactory;
22+
import org.keycloak.storage.ldap.LDAPIdentityStoreRegistry;
23+
import org.keycloak.storage.ldap.LDAPStorageProvider;
924
import org.keycloak.storage.ldap.LDAPStorageProviderFactory;
10-
import org.keycloak.storage.ldap.idm.query.internal.LDAPQuery;
11-
import org.keycloak.storage.user.SynchronizationResult;
25+
import org.keycloak.storage.ldap.idm.store.ldap.LDAPIdentityStore;
26+
import org.keycloak.storage.ldap.mappers.LDAPConfigDecorator;
1227

1328
public class EncryptedLDAPStorageProviderFactory extends LDAPStorageProviderFactory {
1429

30+
LDAPIdentityStoreRegistry ldapStoreRegistryOverride;
31+
1532
@Override
1633
public int order() {
1734
return 1000;
1835
}
1936

2037
@Override
21-
protected SynchronizationResult syncImpl(KeycloakSessionFactory sessionFactory, LDAPQuery userQuery, String realmId, ComponentModel fedModel) {
22-
SynchronizationResult result = super.syncImpl(sessionFactory, userQuery, realmId, fedModel);
23-
KeycloakSession session = sessionFactory.create();
24-
EntityManager em = session.getProvider(JpaConnectionProvider.class).getEntityManager();
25-
LogicUtils.encryptExistingUserEntities(session, em, session.realms().getRealm(realmId));
26-
em.flush();
27-
return result;
38+
public void init(Config.Scope config) {
39+
super.init(config);
40+
this.ldapStoreRegistryOverride = new LDAPIdentityStoreRegistry();
41+
}
42+
43+
@Override
44+
public void close() {
45+
super.close();
46+
this.ldapStoreRegistryOverride = null;
47+
}
48+
49+
@Override
50+
public LDAPStorageProvider create(KeycloakSession session, ComponentModel model) {
51+
Map<ComponentModel, LDAPConfigDecorator> configDecorators = getLDAPConfigDecorators(session, model);
52+
LDAPIdentityStore ldapIdentityStore = this.ldapStoreRegistryOverride.getLdapStore(session, model, configDecorators);
53+
return new EncryptedLDAPStorageProvider(this, session, model, ldapIdentityStore);
2854
}
2955

3056
}

0 commit comments

Comments
 (0)