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.xml.util.Base64;
30 import org.opensaml.xml.util.DatatypeHelper;
31 import org.opensaml.xml.util.LazyMap;
32 import org.slf4j.Logger;
33 import org.slf4j.LoggerFactory;
34
35 import edu.internet2.middleware.shibboleth.common.attribute.BaseAttribute;
36 import edu.internet2.middleware.shibboleth.common.attribute.provider.BasicAttribute;
37 import edu.internet2.middleware.shibboleth.common.attribute.resolver.AttributeResolutionException;
38 import edu.internet2.middleware.shibboleth.common.attribute.resolver.provider.ShibbolethResolutionContext;
39 import edu.internet2.middleware.shibboleth.common.attribute.resolver.provider.dataConnector.StoredIDStore.PersistentIdEntry;
40 import edu.internet2.middleware.shibboleth.common.profile.provider.SAMLProfileRequestContext;
41
42
43
44
45
46
47
48
49
50
51 public class StoredIDDataConnector extends BaseDataConnector {
52
53
54 private final Logger log = LoggerFactory.getLogger(StoredIDDataConnector.class);
55
56
57 private StoredIDStore pidStore;
58
59
60 private String generatedAttribute;
61
62
63 private String sourceAttribute;
64
65
66 private byte[] salt;
67
68
69
70
71
72
73
74
75
76 public StoredIDDataConnector(DataSource source, String generatedAttributeId, String sourceAttributeId, byte[] idSalt) {
77 if (source == null) {
78 throw new IllegalArgumentException("Data source may not be null");
79 }
80 pidStore = new StoredIDStore(source);
81
82 if (DatatypeHelper.isEmpty(generatedAttributeId)) {
83 throw new IllegalArgumentException("Provided generated attribute ID must not be empty");
84 }
85 generatedAttribute = generatedAttributeId;
86
87 if (DatatypeHelper.isEmpty(sourceAttributeId)) {
88 throw new IllegalArgumentException("Provided source attribute ID must not be empty");
89 }
90 sourceAttribute = sourceAttributeId;
91
92 if (idSalt.length < 16) {
93 throw new IllegalArgumentException("Provided salt must be at least 16 bytes in size.");
94 }
95 salt = idSalt;
96 }
97
98
99
100
101
102
103 public StoredIDStore getStoredIDStore() {
104 return pidStore;
105 }
106
107
108
109
110
111
112 public byte[] getSalt() {
113 return salt;
114 }
115
116
117
118
119
120
121 public String getSourceAttributeId() {
122 return sourceAttribute;
123 }
124
125
126
127
128
129
130 public String getGeneratedAttributeId() {
131 return generatedAttribute;
132 }
133
134
135 public Map<String, BaseAttribute> resolve(ShibbolethResolutionContext resolutionContext)
136 throws AttributeResolutionException {
137 if (resolutionContext.getAttributeRequestContext().getLocalEntityId() == null) {
138 throw new AttributeResolutionException("No local entity ID given in resolution context");
139 }
140 if (resolutionContext.getAttributeRequestContext().getInboundMessageIssuer() == null) {
141 throw new AttributeResolutionException("No relying party entity ID given in resolution context");
142 }
143 if (resolutionContext.getAttributeRequestContext().getPrincipalName() == null) {
144 throw new AttributeResolutionException("No principal name given in resolution context");
145 }
146
147 Map<String, BaseAttribute> attributes = new LazyMap<String, BaseAttribute>();
148
149 String persistentId = getStoredId(resolutionContext);
150 if (persistentId != null) {
151 BasicAttribute<String> attribute = new BasicAttribute<String>();
152 attribute.setId(getGeneratedAttributeId());
153 attribute.getValues().add(persistentId);
154 attributes.put(attribute.getId(), attribute);
155 }
156 return attributes;
157 }
158
159
160 public void validate() throws AttributeResolutionException {
161 if (getDependencyIds() == null || getDependencyIds().size() != 1) {
162 log.error("Computed ID " + getId() + " data connectore requires exactly one dependency");
163 throw new AttributeResolutionException("Computed ID " + getId()
164 + " data connectore requires exactly one dependency");
165 }
166
167 try {
168 pidStore.getActivePersistentIdEntry("1");
169 } catch (SQLException e) {
170 throw new AttributeResolutionException("Unable to connect to persistent ID store.");
171 }
172 }
173
174
175
176
177
178
179
180
181
182
183 protected String getStoredId(ShibbolethResolutionContext resolutionContext) throws AttributeResolutionException {
184 SAMLProfileRequestContext requestCtx = resolutionContext.getAttributeRequestContext();
185
186 String localId = getLocalId(resolutionContext);
187 if (localId == null) {
188 return null;
189 }
190
191 PersistentIdEntry idEntry;
192 try {
193 idEntry = pidStore.getActivePersistentIdEntry(requestCtx.getLocalEntityId(), requestCtx
194 .getInboundMessageIssuer(), localId);
195 if (idEntry == null) {
196 idEntry = createPersistentId(resolutionContext, localId, salt);
197 pidStore.storePersistentIdEntry(idEntry);
198 log.debug("Created stored ID {}", idEntry);
199 } else {
200 log.debug("Located existing stored ID {}", idEntry);
201 }
202
203 return idEntry.getPersistentId();
204 } catch (SQLException e) {
205 log.error("Database error retrieving persistent identifier", e);
206 throw new AttributeResolutionException("Database error retrieving persistent identifier", e);
207 }
208 }
209
210
211
212
213
214
215
216
217
218
219 protected String getLocalId(ShibbolethResolutionContext resolutionContext) throws AttributeResolutionException {
220 Collection<Object> sourceIdValues = getValuesFromAllDependencies(resolutionContext, getSourceAttributeId());
221 if (sourceIdValues == null || sourceIdValues.isEmpty()) {
222 log.debug("Source attribute {} for connector {} provide no values. No identifier will be generated.",
223 getSourceAttributeId(), getId());
224 return null;
225 }
226
227 if (sourceIdValues.size() > 1) {
228 log.warn("Source attribute {} for connector {} has more than one value, only the first value is used",
229 getSourceAttributeId(), getId());
230 }
231
232 return sourceIdValues.iterator().next().toString();
233 }
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252 protected PersistentIdEntry createPersistentId(ShibbolethResolutionContext resolutionContext, String localId,
253 byte[] salt) throws SQLException {
254 PersistentIdEntry entry = pidStore.new PersistentIdEntry();
255 entry.setLocalEntityId(resolutionContext.getAttributeRequestContext().getLocalEntityId());
256 entry.setPeerEntityId(resolutionContext.getAttributeRequestContext().getInboundMessageIssuer());
257 entry.setPrincipalName(resolutionContext.getAttributeRequestContext().getPrincipalName());
258 entry.setLocalId(localId);
259
260 String persistentId;
261 int numberOfExistingEntries = pidStore.getNumberOfPersistentIdEntries(entry.getLocalEntityId(), entry
262 .getPeerEntityId(), entry.getLocalId());
263
264 if (numberOfExistingEntries == 0) {
265 try {
266 MessageDigest md = MessageDigest.getInstance("SHA");
267 md.update(entry.getPeerEntityId().getBytes());
268 md.update((byte) '!');
269 md.update(localId.getBytes());
270 md.update((byte) '!');
271 persistentId = Base64.encodeBytes(md.digest(salt));
272 } catch (NoSuchAlgorithmException e) {
273 log.error("JVM error, SHA-1 is not supported, unable to compute ID");
274 throw new SQLException("SHA-1 is not supported, unable to compute ID");
275 }
276 } else {
277 persistentId = UUID.randomUUID().toString();
278 }
279
280 while (pidStore.getPersistentIdEntry(persistentId, false) != null) {
281 log.debug("Generated persistent ID was already assigned to another user, regenerating");
282 persistentId = UUID.randomUUID().toString();
283 }
284
285 entry.setPersistentId(persistentId);
286
287 entry.setCreationTime(new Timestamp(System.currentTimeMillis()));
288
289 return entry;
290 }
291
292 }