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.HashMap;
25 import java.util.Map;
26 import java.util.UUID;
27
28 import javax.sql.DataSource;
29
30 import org.opensaml.xml.util.Base64;
31 import org.opensaml.xml.util.DatatypeHelper;
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 String persistentId = getStoredId(resolutionContext);
138 BasicAttribute<String> attribute = new BasicAttribute<String>();
139 attribute.setId(getGeneratedAttributeId());
140 attribute.getValues().add(persistentId);
141
142 Map<String, BaseAttribute> attributes = new HashMap<String, BaseAttribute>();
143 attributes.put(attribute.getId(), attribute);
144 return attributes;
145 }
146
147
148 public void validate() throws AttributeResolutionException {
149 if (getDependencyIds() == null || getDependencyIds().size() != 1) {
150 log.error("Computed ID " + getId() + " data connectore requires exactly one dependency");
151 throw new AttributeResolutionException("Computed ID " + getId()
152 + " data connectore requires exactly one dependency");
153 }
154 }
155
156
157
158
159
160
161
162
163
164
165 protected String getStoredId(ShibbolethResolutionContext resolutionContext) throws AttributeResolutionException {
166 SAMLProfileRequestContext requestCtx = resolutionContext.getAttributeRequestContext();
167
168 String localId = getLocalId(resolutionContext);
169 PersistentIdEntry idEntry;
170 try {
171 idEntry = pidStore.getActivePersistentIdEntry(requestCtx.getLocalEntityId(), requestCtx
172 .getInboundMessageIssuer(), localId);
173 if (idEntry == null) {
174 idEntry = createPersistentId(resolutionContext, localId, salt);
175 pidStore.storePersistentIdEntry(idEntry);
176 log.debug("Created stored ID {}", idEntry);
177 }else{
178 log.debug("Located existing stored ID {}", idEntry);
179 }
180
181 return idEntry.getPersistentId();
182 } catch (SQLException e) {
183 log.error("Database error retrieving persistent identifier", e);
184 throw new AttributeResolutionException("Database error retrieving persistent identifier", e);
185 }
186 }
187
188
189
190
191
192
193
194
195
196
197 protected String getLocalId(ShibbolethResolutionContext resolutionContext) throws AttributeResolutionException {
198 Collection<Object> sourceIdValues = getValuesFromAllDependencies(resolutionContext, getSourceAttributeId());
199 if (sourceIdValues == null || sourceIdValues.isEmpty()) {
200 log.error("Source attribute {} for connector {} provide no values", getSourceAttributeId(), getId());
201 throw new AttributeResolutionException("Source attribute " + getSourceAttributeId() + " for connector "
202 + getId() + " provided no values");
203 }
204
205 if (sourceIdValues.size() > 1) {
206 log.warn("Source attribute {} for connector {} has more than one value, only the first value is used",
207 getSourceAttributeId(), getId());
208 }
209
210 return sourceIdValues.iterator().next().toString();
211 }
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230 protected PersistentIdEntry createPersistentId(ShibbolethResolutionContext resolutionContext, String localId,
231 byte[] salt) throws SQLException {
232 PersistentIdEntry entry = pidStore.new PersistentIdEntry();
233 entry.setLocalEntityId(resolutionContext.getAttributeRequestContext().getLocalEntityId());
234 entry.setPeerEntityId(resolutionContext.getAttributeRequestContext().getInboundMessageIssuer());
235 entry.setPrincipalName(resolutionContext.getAttributeRequestContext().getPrincipalName());
236 entry.setLocalId(localId);
237
238 String persisentId;
239 int numberOfExistingEntries = pidStore.getNumberOfPersistentIdEntries(entry.getLocalEntityId(), entry
240 .getPeerEntityId(), entry.getLocalId());
241 if (numberOfExistingEntries == 0) {
242 try {
243 MessageDigest md = MessageDigest.getInstance("SHA");
244 md.update(entry.getPeerEntityId().getBytes());
245 md.update((byte) '!');
246 md.update(localId.getBytes());
247 md.update((byte) '!');
248 persisentId = Base64.encodeBytes(md.digest(salt));
249 } catch (NoSuchAlgorithmException e) {
250 log.error("JVM error, SHA-1 is not supported, unable to compute ID");
251 throw new SQLException("SHA-1 is not supported, unable to compute ID");
252 }
253 } else {
254 persisentId = UUID.randomUUID().toString();
255 }
256 entry.setPersistentId(persisentId);
257
258 entry.setCreationTime(new Timestamp(System.currentTimeMillis()));
259
260 return entry;
261 }
262
263 }