View Javadoc
1   /*
2    * Copyright 2001-2008 The Apache Software Foundation.
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   */
17  package org.apache.juddi.validation;
18  
19  import java.io.UnsupportedEncodingException;
20  import java.math.BigInteger;
21  import java.security.cert.CertificateException;
22  import java.util.List;
23  import java.util.Properties;
24  import java.util.concurrent.atomic.AtomicReference;
25  import java.util.logging.Level;
26  import java.util.logging.Logger;
27  import javax.persistence.EntityManager;
28  import javax.xml.ws.WebServiceContext;
29  import org.apache.commons.configuration.Configuration;
30  import org.apache.commons.configuration.ConfigurationException;
31  import org.apache.commons.logging.Log;
32  import org.apache.commons.logging.LogFactory;
33  import static org.apache.juddi.api.impl.AuthenticatedService.UTF8;
34  import org.apache.juddi.config.AppConfig;
35  import org.apache.juddi.config.Property;
36  import org.apache.juddi.model.Node;
37  import org.apache.juddi.model.UddiEntityPublisher;
38  import org.apache.juddi.v3.client.cryptor.CryptorFactory;
39  import org.apache.juddi.v3.client.cryptor.DigSigUtil;
40  import org.apache.juddi.v3.error.ErrorMessage;
41  import org.apache.juddi.v3.error.FatalErrorException;
42  import org.apache.juddi.v3.error.InvalidValueException;
43  import org.apache.juddi.v3.error.TransferNotAllowedException;
44  import org.apache.juddi.v3.error.ValueNotAllowedException;
45  import org.uddi.custody_v3.TransferEntities;
46  import org.uddi.repl_v3.CommunicationGraph.Edge;
47  import org.uddi.repl_v3.HighWaterMarkVectorType;
48  import org.uddi.repl_v3.NotifyChangeRecordsAvailable;
49  import org.uddi.repl_v3.Operator;
50  import org.uddi.repl_v3.ReplicationConfiguration;
51  import org.uddi.repl_v3.TransferCustody;
52  import org.uddi.v3_service.DispositionReportFaultMessage;
53  
54  /**
55   * @author <a href="mailto:alexoree@apache.org">Alex O'Ree</a>
56   * Processing an inbound replication message may fail due to a server internal
57   * error. The common behavior for all error cases is to return an E_fatalError
58   * error code. Error reporting SHALL be that specified by Section 4.8 – Success
59   * and Error Reporting of this specification.
60   */
61  public class ValidateReplication extends ValidateUDDIApi {
62  
63          private final static Log log = LogFactory.getLog(ValidateReplication.class);
64          public ValidateReplication(UddiEntityPublisher publisher) {
65                  super(publisher);
66          }
67          
68         public ValidateReplication(UddiEntityPublisher publisher, String nodeid) {
69  		 super(publisher, nodeid);
70  	}
71  
72          public void validateNotifyChangeRecordsAvailable(NotifyChangeRecordsAvailable body, WebServiceContext ctx) throws DispositionReportFaultMessage {
73                  //TODO
74          }
75  
76          public void validateGetChangeRecords(String requestingNode, HighWaterMarkVectorType changesAlreadySeen, BigInteger responseLimitCount, HighWaterMarkVectorType responseLimitVector, ReplicationConfiguration FetchEdges, WebServiceContext ctx) throws DispositionReportFaultMessage {
77                  //TODO
78  
79                  if (requestingNode == null || requestingNode.trim().equalsIgnoreCase("")) {
80                          //requestingNode: The requestingNode element provides the identity of the calling node.  
81                          //This is the unique key for the calling node and SHOULD be specified within the Replication Configuration Structure.
82                          throw new FatalErrorException(new ErrorMessage("errors.replication.nodeNotSpecified"));
83                  }
84                  //if (!ContainsNode(requestingNode, FetchEdges)) {
85                  //        throw new FatalErrorException(new ErrorMessage("errors.replication.unknownNode"));
86                  //}
87  
88                  if (changesAlreadySeen != null) {
89                          // changesAlreadySeen: The changesAlreadySeen element, if present, indicates changes from each
90                          //node that the requestor has successfully processed, and thus which should not be resent, if possible.
91  
92                          //no validation needed?
93                  }
94  
95                  if (responseLimitCount != null && responseLimitVector != null) {
96                          throw new FatalErrorException(new ErrorMessage("errors.replication.bothLimitsSpecified"));
97                  }
98                  if (responseLimitCount != null) {
99                          //can't be 0 since 0 is banned as being a change record id
100                         if (responseLimitCount.longValue() <= 0) {
101                                 throw new FatalErrorException(new ErrorMessage("errors.replication.negativeLimit", responseLimitCount.toString()));
102                         }
103                 }
104                 if (responseLimitVector != null) {
105                         for (int i = 0; i < responseLimitVector.getHighWaterMark().size(); i++) {
106                                 if (responseLimitVector.getHighWaterMark().get(i).getOriginatingUSN() == null
107                                         || responseLimitVector.getHighWaterMark().get(i).getOriginatingUSN() <= 0) {
108                                         throw new FatalErrorException(new ErrorMessage("errors.replication.limitVectorNull"));
109                                 }
110                                 if (responseLimitVector.getHighWaterMark().get(i).getNodeID() == null
111                                         || responseLimitVector.getHighWaterMark().get(i).getNodeID().trim().equalsIgnoreCase("")) {
112                                         throw new FatalErrorException(new ErrorMessage("errors.replication.limitVectorNoNode"));
113                                 }
114                         }
115                 }
116 
117                 /**
118                  * responseLimitCount or responseLimitVector: A caller MAY place
119                  * an upper bound on the number of change records he wishes to
120                  * receive in response to this message by either providing a
121                  * integer responseLimitCount, or, using responseLimitVector,
122                  * indicating for each node in the graph the first change
123                  * originating there that he does not wish to be returned.
124                  *
125                  */
126         }
127 
128         private static boolean ContainsNode(String requestingNode, ReplicationConfiguration FetchEdges) {
129                 if (FetchEdges == null) {
130                         return false;
131                 }
132                 if (FetchEdges.getCommunicationGraph() == null) {
133                         return false;
134                 }
135                 for (int i = 0; i < FetchEdges.getCommunicationGraph().getNode().size(); i++) {
136                         if (FetchEdges.getCommunicationGraph().getNode().get(i).equalsIgnoreCase(requestingNode)) {
137                                 return true;
138                         }
139                 }
140                 return false;
141         }
142 
143         public void validateSetReplicationNodes(ReplicationConfiguration replicationConfiguration, EntityManager em, String thisnode, Configuration config) throws DispositionReportFaultMessage, ConfigurationException {
144                 if (replicationConfiguration == null) {
145                         throw new InvalidValueException(new ErrorMessage("errors.replication.configNull"));
146 
147                 }
148                 if (replicationConfiguration.getCommunicationGraph() == null) {
149                         throw new InvalidValueException(new ErrorMessage("errors.replication.configNull"));
150                 }
151                 if (replicationConfiguration.getRegistryContact() == null) {
152                         throw new InvalidValueException(new ErrorMessage("errors.replication.contactNull"));
153                 }
154                 if (replicationConfiguration.getRegistryContact().getContact() == null) {
155                         throw new InvalidValueException(new ErrorMessage("errors.replication.contactNull"));
156                 }
157                 if (replicationConfiguration.getRegistryContact().getContact().getPersonName().get(0) == null) {
158                         throw new InvalidValueException(new ErrorMessage("errors.replication.contactNull"));
159                 }
160 
161                 if (replicationConfiguration.getOperator() == null || replicationConfiguration.getOperator().isEmpty()) {
162                         throw new InvalidValueException(new ErrorMessage("errors.replication.contactNull", "Operator is null or empty"));
163                 }
164                 for (int i = 0; i < replicationConfiguration.getOperator().size(); i++) {
165                         if (replicationConfiguration.getOperator().get(i).getSoapReplicationURL() == null
166                                 || "".equals(replicationConfiguration.getOperator().get(i).getSoapReplicationURL())) {
167                                 throw new InvalidValueException(new ErrorMessage("errors.replication.contactNull", "Replication URL is null or empty"));
168                         }
169                         if (!replicationConfiguration.getOperator().get(i).getSoapReplicationURL().toLowerCase().startsWith("http")) {
170                                 throw new InvalidValueException(new ErrorMessage("errors.replication.contactNull", "Replication URL is invalid, only HTTP is supported"));
171                         }
172                         if (replicationConfiguration.getOperator().get(i).getOperatorNodeID() == null
173                                 || replicationConfiguration.getOperator().get(i).getOperatorNodeID().equalsIgnoreCase("")) {
174                                 throw new InvalidValueException(new ErrorMessage("errors.replication.contactNull", "Node ID is not defined"));
175                         }
176                 }
177                 if (replicationConfiguration.getCommunicationGraph() != null) {
178                         for (String s : replicationConfiguration.getCommunicationGraph().getNode()) {
179                                 if (!Contains(replicationConfiguration.getOperator(), s)) {
180                                         throw new InvalidValueException(new ErrorMessage("errors.replication.configNodeNotFound"));
181                                 }
182                         }
183                         for (Edge s : replicationConfiguration.getCommunicationGraph().getEdge()) {
184                                 //TODO revisit this for correctness
185                                 //Node find = null;
186                                 //if (!thisnode.equalsIgnoreCase(s.getMessageReceiver())) {
187                                 if (!Contains(replicationConfiguration.getOperator(), s.getMessageReceiver())) {
188                                         throw new InvalidValueException(new ErrorMessage("errors.replication.configNodeNotFound"));
189                                         //}
190                                 }
191                                 //find = null;
192                                 //if (!thisnode.equalsIgnoreCase(s.getMessageSender())) {
193                                 if (!Contains(replicationConfiguration.getOperator(), s.getMessageSender())) {
194                                         throw new InvalidValueException(new ErrorMessage("errors.replication.configNodeNotFound"));
195                                         //}
196                                 }
197                                 if (s.getMessageReceiver().equalsIgnoreCase(s.getMessageSender())){
198                                         throw new InvalidValueException(new ErrorMessage("errors.replication.configNodeLoop"));
199                                 }
200                                 for (String id : s.getMessageReceiverAlternate()) {
201                                         if (!Contains(replicationConfiguration.getOperator(), id)) {
202                                                 throw new InvalidValueException(new ErrorMessage("errors.replication.configNodeNotFound"));
203                                         }
204                                 }
205 
206                         }
207                 }
208                 boolean shouldcheck = config.getBoolean(Property.JUDDI_REJECT_ENTITIES_WITH_INVALID_SIG_ENABLE, false);
209                 initDigSig(config);
210                 if (shouldcheck && !replicationConfiguration.getSignature().isEmpty() && ds != null) {
211                         AtomicReference<String> outmsg = new AtomicReference<String>();
212                         boolean ok = ds.verifySignedUddiEntity(replicationConfiguration, outmsg);
213                         if (!ok) {
214                                 throw new FatalErrorException(new ErrorMessage("errors.digitalsignature.validationfailure" + " " + outmsg.get()));
215                         }
216 
217                 }
218         }
219         
220          private org.apache.juddi.v3.client.cryptor.DigSigUtil ds = null;
221 
222         private synchronized void initDigSig(Configuration config) {
223                 if (ds == null) {
224                         
225                         Properties p = new Properties();
226                         /**
227                          * <trustStorePath>truststore.jks</trustStorePath>
228                          * <trustStoreType>JKS</trustStoreType>
229                          * <trustStorePassword
230                          * isPasswordEncrypted="false"
231                          * cryptoProvider="org.apache.juddi.v3.client.crypto.AES128Cryptor">password</trustStorePassword>
232                          *
233                          * <checkTimestamps>true</checkTimestamps>
234                          * <checkTrust>true</checkTrust>
235                          * <checkRevocationCRL>true</checkRevocationCRL>
236                          */
237                         p.put(DigSigUtil.TRUSTSTORE_FILE, config.getString(Property.JUDDI_REJECT_ENTITIES_WITH_INVALID_SIG_PREFIX + "trustStorePath", ""));
238                         p.put(DigSigUtil.TRUSTSTORE_FILETYPE, config.getString(Property.JUDDI_REJECT_ENTITIES_WITH_INVALID_SIG_PREFIX + "trustStoreType", ""));
239 
240                         String enc = config.getString(Property.JUDDI_REJECT_ENTITIES_WITH_INVALID_SIG_PREFIX + "trustStorePassword", "");
241                         if (config.getBoolean(Property.JUDDI_REJECT_ENTITIES_WITH_INVALID_SIG_PREFIX + "trustStorePassword[@isPasswordEncrypted]", false)) {
242                                 log.debug("trust password is encrypted, decrypting...");
243                                 
244                                 String prov = config.getString(Property.JUDDI_REJECT_ENTITIES_WITH_INVALID_SIG_PREFIX + "trustStorePassword[@cryptoProvider]", "");
245                                 try {
246                                         p.setProperty(DigSigUtil.TRUSTSTORE_FILE_PASSWORD, CryptorFactory.getCryptor(prov).decrypt(enc));
247                                 } catch (Exception ex) {
248                                         log.warn("unable to decrypt trust store password " + ex.getMessage());
249                                         log.debug("unable to decrypt trust store password " + ex.getMessage(), ex);
250                                 }
251 
252                         } else if (!"".equals(enc)){
253                                 log.warn("Hey, you should consider encrypting your trust store password!");
254                                 p.setProperty(DigSigUtil.TRUSTSTORE_FILE_PASSWORD, enc);
255                         }
256 
257                         p.put(DigSigUtil.CHECK_REVOCATION_STATUS_CRL, config.getString(Property.JUDDI_REJECT_ENTITIES_WITH_INVALID_SIG_PREFIX + "checkRevocationCRL", "true"));
258                         p.put(DigSigUtil.CHECK_TRUST_CHAIN, config.getString(Property.JUDDI_REJECT_ENTITIES_WITH_INVALID_SIG_PREFIX + "checkTrust", "true"));
259                         p.put(DigSigUtil.CHECK_TIMESTAMPS, config.getString(Property.JUDDI_REJECT_ENTITIES_WITH_INVALID_SIG_PREFIX + "checkTimestamps", "true"));
260 
261                         try {
262                                 ds = new DigSigUtil(p);
263                         } catch (CertificateException ex) {
264                                 log.error("", ex);
265                         }
266                         //System.out.println("loaded from " + AppConfig.getConfigFileURL());
267                         //p.list(System.out);
268                 }
269         }
270 
271       
272 
273         private boolean Contains(List<Operator> operator, String s) {
274                 if (operator == null) {
275                         return false;
276                 }
277                 for (Operator o : operator) {
278                         if (o.getOperatorNodeID().equalsIgnoreCase(s)) {
279                                 return true;
280                         }
281                 }
282                 return false;
283         }
284 
285         public void validateTransfer(EntityManager em, TransferCustody body) throws DispositionReportFaultMessage {
286 
287                 if (body == null) {
288                         throw new TransferNotAllowedException(new ErrorMessage("errors.NullInput"));
289                 }
290                 if (body.getTransferToken() == null) {
291                         throw new TransferNotAllowedException(new ErrorMessage("errors.NullInput"));
292                 }
293                 if (body.getKeyBag() == null) {
294                         throw new TransferNotAllowedException(new ErrorMessage("errors.NullInput"));
295                 }
296                 if (body.getTransferOperationalInfo() == null) {
297                         throw new TransferNotAllowedException(new ErrorMessage("errors.NullInput"));
298                 }
299 
300                 if (body.getTransferOperationalInfo().getNodeID() == null) {
301                         throw new TransferNotAllowedException(new ErrorMessage("errors.NullInput"));
302                 }
303                 if (body.getTransferOperationalInfo().getAuthorizedName() == null) {
304                         throw new TransferNotAllowedException(new ErrorMessage("errors.NullInput"));
305                 }
306 
307                 //confirm i own the records in question
308                 //confirm i issued the transfer token
309                 TransferEntities x = new TransferEntities();
310                 x.setKeyBag(body.getKeyBag());
311                 x.setTransferToken(body.getTransferToken());
312                 String transferTokenId;
313                 try {
314                     transferTokenId = new String(body.getTransferToken().getOpaqueToken(), UTF8);
315                 } catch (UnsupportedEncodingException ex) {
316                     throw new InvalidValueException(new ErrorMessage("errors.stringEncoding"));
317                 }
318                 new ValidateCustodyTransfer(null).validateTransferLocalEntities(em, transferTokenId, body.getKeyBag().getKey());
319 
320         }
321 
322 }