/
JpaUserCredentialStore.java
241 lines (200 loc) · 9.8 KB
/
JpaUserCredentialStore.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
/*
* Copyright 2016 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.models.jpa;
import org.jboss.logging.Logger;
import org.keycloak.common.util.Base64;
import org.keycloak.credential.CredentialModel;
import org.keycloak.credential.UserCredentialStore;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.jpa.entities.CredentialEntity;
import org.keycloak.models.jpa.entities.UserEntity;
import org.keycloak.models.utils.KeycloakModelUtils;
import javax.persistence.EntityManager;
import javax.persistence.TypedQuery;
import java.util.ArrayList;
import java.util.List;
import javax.persistence.LockModeType;
import java.util.stream.Collectors;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class JpaUserCredentialStore implements UserCredentialStore {
// Typical priority difference between 2 credentials
public static final int PRIORITY_DIFFERENCE = 10;
protected static final Logger logger = Logger.getLogger(JpaUserCredentialStore.class);
private final KeycloakSession session;
protected final EntityManager em;
public JpaUserCredentialStore(KeycloakSession session, EntityManager em) {
this.session = session;
this.em = em;
}
@Override
public void updateCredential(RealmModel realm, UserModel user, CredentialModel cred) {
CredentialEntity entity = em.find(CredentialEntity.class, cred.getId());
if (!checkCredentialEntity(entity, user)) return;
entity.setCreatedDate(cred.getCreatedDate());
entity.setUserLabel(cred.getUserLabel());
entity.setType(cred.getType());
entity.setSecretData(cred.getSecretData());
entity.setCredentialData(cred.getCredentialData());
}
@Override
public CredentialModel createCredential(RealmModel realm, UserModel user, CredentialModel cred) {
CredentialEntity entity = createCredentialEntity(realm, user, cred);
return toModel(entity);
}
@Override
public boolean removeStoredCredential(RealmModel realm, UserModel user, String id) {
CredentialEntity entity = removeCredentialEntity(realm, user, id);
return entity != null;
}
@Override
public CredentialModel getStoredCredentialById(RealmModel realm, UserModel user, String id) {
CredentialEntity entity = em.find(CredentialEntity.class, id);
if (!checkCredentialEntity(entity, user)) return null;
CredentialModel model = toModel(entity);
return model;
}
CredentialModel toModel(CredentialEntity entity) {
CredentialModel model = new CredentialModel();
model.setId(entity.getId());
model.setType(entity.getType());
model.setCreatedDate(entity.getCreatedDate());
model.setUserLabel(entity.getUserLabel());
// Backwards compatibility - users from previous version still have "salt" in the DB filled.
// We migrate it to new secretData format on-the-fly
if (entity.getSalt() != null) {
String newSecretData = entity.getSecretData().replace("__SALT__", Base64.encodeBytes(entity.getSalt()));
entity.setSecretData(newSecretData);
entity.setSalt(null);
}
model.setSecretData(entity.getSecretData());
model.setCredentialData(entity.getCredentialData());
return model;
}
@Override
public List<CredentialModel> getStoredCredentials(RealmModel realm, UserModel user) {
List<CredentialEntity> results = getStoredCredentialEntities(realm, user);
// list is ordered correctly by priority (lowest priority value first)
return results.stream().map(this::toModel).collect(Collectors.toList());
}
private List<CredentialEntity> getStoredCredentialEntities(RealmModel realm, UserModel user) {
UserEntity userEntity = em.getReference(UserEntity.class, user.getId());
TypedQuery<CredentialEntity> query = em.createNamedQuery("credentialByUser", CredentialEntity.class)
.setParameter("user", userEntity);
return query.getResultList();
}
@Override
public List<CredentialModel> getStoredCredentialsByType(RealmModel realm, UserModel user, String type) {
return getStoredCredentials(realm, user).stream().filter(credential -> type.equals(credential.getType())).collect(Collectors.toList());
}
@Override
public CredentialModel getStoredCredentialByNameAndType(RealmModel realm, UserModel user, String name, String type) {
List<CredentialModel> results = getStoredCredentials(realm, user).stream().filter(credential ->
type.equals(credential.getType()) && name.equals(credential.getUserLabel())).collect(Collectors.toList());
if (results.isEmpty()) return null;
return results.get(0);
}
@Override
public void close() {
}
CredentialEntity createCredentialEntity(RealmModel realm, UserModel user, CredentialModel cred) {
CredentialEntity entity = new CredentialEntity();
String id = cred.getId() == null ? KeycloakModelUtils.generateId() : cred.getId();
entity.setId(id);
entity.setCreatedDate(cred.getCreatedDate());
entity.setUserLabel(cred.getUserLabel());
entity.setType(cred.getType());
entity.setSecretData(cred.getSecretData());
entity.setCredentialData(cred.getCredentialData());
UserEntity userRef = em.getReference(UserEntity.class, user.getId());
entity.setUser(userRef);
//add in linkedlist to last position
List<CredentialEntity> credentials = getStoredCredentialEntities(realm, user);
int priority = credentials.isEmpty() ? PRIORITY_DIFFERENCE : credentials.get(credentials.size() - 1).getPriority() + PRIORITY_DIFFERENCE;
entity.setPriority(priority);
em.persist(entity);
return entity;
}
CredentialEntity removeCredentialEntity(RealmModel realm, UserModel user, String id) {
CredentialEntity entity = em.find(CredentialEntity.class, id, LockModeType.PESSIMISTIC_WRITE);
if (!checkCredentialEntity(entity, user)) return null;
int currentPriority = entity.getPriority();
List<CredentialEntity> credentials = getStoredCredentialEntities(realm, user);
// Decrease priority of all credentials after our
for (CredentialEntity cred : credentials) {
if (cred.getPriority() > currentPriority) {
cred.setPriority(cred.getPriority() - PRIORITY_DIFFERENCE);
}
}
em.remove(entity);
em.flush();
return entity;
}
////Operations to handle the linked list of credentials
@Override
public boolean moveCredentialTo(RealmModel realm, UserModel user, String id, String newPreviousCredentialId) {
List<CredentialEntity> sortedCreds = getStoredCredentialEntities(realm, user);
// 1 - Create new list and move everything to it.
List<CredentialEntity> newList = new ArrayList<>();
newList.addAll(sortedCreds);
// 2 - Find indexes of our and newPrevious credential
int ourCredentialIndex = -1;
int newPreviousCredentialIndex = -1;
CredentialEntity ourCredential = null;
int i = 0;
for (CredentialEntity credential : newList) {
if (id.equals(credential.getId())) {
ourCredentialIndex = i;
ourCredential = credential;
} else if(newPreviousCredentialId != null && newPreviousCredentialId.equals(credential.getId())) {
newPreviousCredentialIndex = i;
}
i++;
}
if (ourCredentialIndex == -1) {
logger.warnf("Not found credential with id [%s] of user [%s]", id, user.getUsername());
return false;
}
if (newPreviousCredentialId != null && newPreviousCredentialIndex == -1) {
logger.warnf("Can't move up credential with id [%s] of user [%s]", id, user.getUsername());
return false;
}
// 3 - Compute index where we move our credential
int toMoveIndex = newPreviousCredentialId==null ? 0 : newPreviousCredentialIndex + 1;
// 4 - Insert our credential to new position, remove it from the old position
newList.add(toMoveIndex, ourCredential);
int indexToRemove = toMoveIndex < ourCredentialIndex ? ourCredentialIndex + 1 : ourCredentialIndex;
newList.remove(indexToRemove);
// 5 - newList contains credentials in requested order now. Iterate through whole list and change priorities accordingly.
int expectedPriority = 0;
for (CredentialEntity credential : newList) {
expectedPriority += PRIORITY_DIFFERENCE;
if (credential.getPriority() != expectedPriority) {
credential.setPriority(expectedPriority);
logger.tracef("Priority of credential [%s] of user [%s] changed to [%d]", credential.getId(), user.getUsername(), expectedPriority);
}
}
return true;
}
private boolean checkCredentialEntity(CredentialEntity entity, UserModel user) {
return entity != null && entity.getUser() != null && entity.getUser().getId().equals(user.getId());
}
}