1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package edu.internet2.middleware.shibboleth.common.attribute.resolver.provider.dataConnector;
18
19 import java.security.MessageDigest;
20 import java.security.NoSuchAlgorithmException;
21 import java.sql.SQLException;
22 import java.sql.Timestamp;
23 import java.util.Collection;
24 import java.util.Map;
25 import java.util.UUID;
26
27 import javax.sql.DataSource;
28
29 import org.opensaml.saml2.core.AuthnRequest;
30 import org.opensaml.xml.XMLObject;
31 import org.opensaml.xml.util.Base64;
32 import org.opensaml.xml.util.DatatypeHelper;
33 import org.opensaml.xml.util.LazyMap;
34 import org.slf4j.Logger;
35 import org.slf4j.LoggerFactory;
36
37 import edu.internet2.middleware.shibboleth.common.attribute.BaseAttribute;
38 import edu.internet2.middleware.shibboleth.common.attribute.provider.BasicAttribute;
39 import edu.internet2.middleware.shibboleth.common.attribute.resolver.AttributeResolutionException;
40 import edu.internet2.middleware.shibboleth.common.attribute.resolver.provider.ShibbolethResolutionContext;
41 import edu.internet2.middleware.shibboleth.common.attribute.resolver.provider.dataConnector.StoredIDStore.PersistentIdEntry;
42
43
44
45
46
47
48
49
50
51
52 public class StoredIDDataConnector extends BaseDataConnector {
53
54
55 private final Logger log = LoggerFactory.getLogger(StoredIDDataConnector.class);
56
57
58 private StoredIDStore pidStore;
59
60
61 private String generatedAttribute;
62
63
64 private String sourceAttribute;
65
66
67 private byte[] salt;
68
69
70
71
72
73
74
75
76
77
78 public StoredIDDataConnector(DataSource source, int queryTimeout, String generatedAttributeId,
79 String sourceAttributeId, byte[] idSalt) {
80 if (source == null) {
81 throw new IllegalArgumentException("Data source may not be null");
82 }
83 pidStore = new StoredIDStore(source, queryTimeout);
84
85 if (DatatypeHelper.isEmpty(generatedAttributeId)) {
86 throw new IllegalArgumentException("Provided generated attribute ID must not be empty");
87 }
88 generatedAttribute = generatedAttributeId;
89
90 if (DatatypeHelper.isEmpty(sourceAttributeId)) {
91 throw new IllegalArgumentException("Provided source attribute ID must not be empty");
92 }
93 sourceAttribute = sourceAttributeId;
94
95 if (idSalt.length < 16) {
96 throw new IllegalArgumentException("Provided salt must be at least 16 bytes in size.");
97 }
98 salt = idSalt;
99 }
100
101
102
103
104
105
106 public StoredIDStore getStoredIDStore() {
107 return pidStore;
108 }
109
110
111
112
113
114
115 public byte[] getSalt() {
116 return salt;
117 }
118
119
120
121
122
123
124 public String getSourceAttributeId() {
125 return sourceAttribute;
126 }
127
128
129
130
131
132
133 public String getGeneratedAttributeId() {
134 return generatedAttribute;
135 }
136
137
138 public void validate() throws AttributeResolutionException {
139 if (getDependencyIds() == null || getDependencyIds().size() != 1) {
140 log.error("Stored ID " + getId() + " data connectore requires exactly one dependency");
141 throw new AttributeResolutionException("Computed ID " + getId()
142 + " data connectore requires exactly one dependency");
143 }
144
145 try {
146 pidStore.getActivePersistentIdEntry("1");
147 } catch (SQLException e) {
148 throw new AttributeResolutionException("Unable to connect to persistent ID store.");
149 }
150 }
151
152
153 public Map<String, BaseAttribute> resolve(ShibbolethResolutionContext resolutionContext)
154 throws AttributeResolutionException {
155 Map<String, BaseAttribute> attributes = new LazyMap<String, BaseAttribute>();
156
157 String principalName = DatatypeHelper.safeTrimOrNullString(resolutionContext.getAttributeRequestContext()
158 .getPrincipalName());
159
160 String localId = getLocalId(resolutionContext);
161 if (localId == null) {
162 log.debug("No user local ID available, skipping ID creation.");
163 return attributes;
164 }
165
166 String localEntityId = DatatypeHelper.safeTrimOrNullString(resolutionContext.getAttributeRequestContext()
167 .getLocalEntityId());
168 if (localEntityId == null) {
169 log.debug("No local entity ID available, skipping ID creation.");
170 return attributes;
171 }
172
173 String peerEntityId = getPeerEntityId(resolutionContext);
174 if (peerEntityId == null) {
175 log.debug("No peer entity ID available, skipping ID creation.");
176 return attributes;
177 }
178
179 String persistentId = getStoredId(principalName, localEntityId, peerEntityId, localId);
180 if (persistentId != null) {
181 BasicAttribute<String> attribute = new BasicAttribute<String>();
182 attribute.setId(getGeneratedAttributeId());
183 attribute.getValues().add(persistentId);
184 attributes.put(attribute.getId(), attribute);
185 }
186 return attributes;
187 }
188
189
190
191
192
193
194
195
196
197
198
199
200
201 protected String getStoredId(String principalName, String localEntityId, String peerEntityId, String localId)
202 throws AttributeResolutionException {
203 PersistentIdEntry idEntry;
204 try {
205 log.debug("Checking for existing, active, stored ID for principal '{}'", principalName);
206 idEntry = pidStore.getActivePersistentIdEntry(localEntityId, peerEntityId, localId);
207 if (idEntry == null) {
208 log.debug("No existing, active, stored ID, creating a new one for principal '{}'", principalName);
209 idEntry = createPersistentId(principalName, localEntityId, peerEntityId, localId);
210 pidStore.storePersistentIdEntry(idEntry);
211 log.debug("Created stored ID '{}'", idEntry);
212 } else {
213 log.debug("Located existing stored ID {}", idEntry);
214 }
215
216 return idEntry.getPersistentId();
217 } catch (SQLException e) {
218 log.debug("Database error retrieving persistent identifier", e);
219 throw new AttributeResolutionException("Database error retrieving persistent identifier", e);
220 }
221 }
222
223
224
225
226
227
228
229
230
231
232 protected String getLocalId(ShibbolethResolutionContext resolutionContext) throws AttributeResolutionException {
233 Collection<Object> sourceIdValues = getValuesFromAllDependencies(resolutionContext, getSourceAttributeId());
234 if (sourceIdValues == null || sourceIdValues.isEmpty()) {
235 log.debug("Source attribute {} for connector {} provide no values. No identifier will be generated.",
236 getSourceAttributeId(), getId());
237 return null;
238 }
239
240 if (sourceIdValues.size() > 1) {
241 log.warn("Source attribute {} for connector {} has more than one value, only the first value is used",
242 getSourceAttributeId(), getId());
243 }
244
245 return sourceIdValues.iterator().next().toString();
246 }
247
248
249
250
251
252
253
254
255
256 protected String getPeerEntityId(ShibbolethResolutionContext resolutionContext) {
257 String peerEntityId = null;
258
259 log.debug("Determining if peer entity ID will be the SPNameQualifier from a SAML 2 authentication statement");
260 XMLObject inboundMessage = resolutionContext.getAttributeRequestContext().getInboundMessage();
261 if (inboundMessage instanceof AuthnRequest) {
262 AuthnRequest authnRequest = (AuthnRequest) inboundMessage;
263 if (authnRequest.getNameIDPolicy() != null) {
264 peerEntityId = DatatypeHelper.safeTrimOrNullString(authnRequest.getNameIDPolicy().getSPNameQualifier());
265 if (peerEntityId == null) {
266 log
267 .debug("SAML 2 authentication request did not contain an SPNameQualifier within its NameIDPolicy");
268 } else {
269 log
270 .debug("SAML 2 authentication request contained an SPNameQualifier, within its NameIDPolicy. Using that as peer entity ID");
271 }
272 } else {
273 log.debug("SAML 2 authentication request did not contain a NameIDPolicy");
274 }
275 } else {
276 log.debug("Inbound message was not a SAML 2 authentication request");
277 }
278
279 if (peerEntityId == null) {
280 log.debug("Determining if inbound message issuer is available for use as peer entity ID");
281 peerEntityId = resolutionContext.getAttributeRequestContext().getInboundMessageIssuer();
282 }
283
284 return peerEntityId;
285 }
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305 protected PersistentIdEntry createPersistentId(String principalName, String localEntityId, String peerEntityId,
306 String localId) throws SQLException {
307 PersistentIdEntry entry = pidStore.new PersistentIdEntry();
308 entry.setLocalEntityId(localEntityId);
309 entry.setPeerEntityId(peerEntityId);
310 entry.setPrincipalName(principalName);
311 entry.setLocalId(localId);
312
313 String persistentId;
314 int numberOfExistingEntries = pidStore.getNumberOfPersistentIdEntries(entry.getLocalEntityId(), entry
315 .getPeerEntityId(), entry.getLocalId());
316
317 if (numberOfExistingEntries == 0) {
318 try {
319 MessageDigest md = MessageDigest.getInstance("SHA");
320 md.update(entry.getPeerEntityId().getBytes());
321 md.update((byte) '!');
322 md.update(localId.getBytes());
323 md.update((byte) '!');
324 persistentId = Base64.encodeBytes(md.digest(salt));
325 } catch (NoSuchAlgorithmException e) {
326 log.error("JVM error, SHA-1 is not supported, unable to compute ID");
327 throw new SQLException("SHA-1 is not supported, unable to compute ID");
328 }
329 } else {
330 persistentId = UUID.randomUUID().toString();
331 }
332
333 while (pidStore.getPersistentIdEntry(persistentId, false) != null) {
334 log.debug("Generated persistent ID was already assigned to another user, regenerating");
335 persistentId = UUID.randomUUID().toString();
336 }
337
338 entry.setPersistentId(persistentId);
339
340 entry.setCreationTime(new Timestamp(System.currentTimeMillis()));
341
342 return entry;
343 }
344 }