martes 10 de noviembre de 2009

Silent pdf printing without applet

I my current project I have to print a pdf without using an applet (all because the spanish electronic identity card ask for its password any time it open a connection). We have to do it silently, or a least with the less posible user intervention.

The idea is to insert an iframe which calls for the pdf, inside it we set javascript into the pdf for printing. I didn’t know that I could insert js into a pdf, but it is possible and easy with itext/itextsharp library.

Here is the page_load event in the page that returns the pdf. This is where all the magic is done:

   1: protected void Page_Load(object sender, EventArgs e)
   2:  {
   3:      MemoryStream ms = new MemoryStream();
   4:  
   5:      var urlPdf = new Uri(getBaseUrl() + "prueba.pdf");
   6:  
   7:      PdfReader ps = new PdfReader(urlPdf);
   8:  
   9:      /*inserts js into pdf*/
  10:      PdfStamper pdf = new PdfStamper(ps, ms);
  11:      pdf.JavaScript="this.print(false);";
  12:      pdf.Close();
  13:  
  14:      HttpContext.Current.Response.ClearContent();
  15:      HttpContext.Current.Response.ClearHeaders();
  16:      HttpContext.Current.Response.ContentType = "application/pdf";
  17:      HttpContext.Current.Response.AddHeader("Content-Disposition", "inline;filename=recibo.pdf");
  18:      HttpContext.Current.Response.BinaryWrite(ms.ToArray());
  19:      HttpContext.Current.Response.End();
  20:  }




And later we have just a page in which the iframe is inserted using jQuery:





   1: <%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="SilentPDFPrinting._Default" %>
   2:  
   3: <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
   4:  
   5: <html xmlns="http://www.w3.org/1999/xhtml" >
   6: <head runat="server">
   7:     <title></title>
   8:     <script src="js/jquery-1.3.2.min.js" type="text/javascript"></script>
   1:  
   2:     <script src="js/default.js" type="text/javascript">
</script>
   9: </head>
  10: <body>
  11:     <form id="form1" runat="server">
  12:     <div>
  13:         <input id="Button1" type="button" value="button" onclick="printPDFSilently();" /></div>
  14:         <div id="diviframe"></div>
  15:     </form>
  16: </body>
  17: </html>




Here the jQuery:


   1: function printPDFSilently() {
   2: /*inserts an iframe which downloads the pdf*/
   3:     var jIframe = jQuery("<iframe id='iframehidden' src='/PdfPrinter.aspx' width='0' height='0' ></iframe>");
   4:     jIframe.insertAfter("#diviframe"); 
   5: }




As you can see it is very easy. Here you have the code



Hope this helps to anyone.



domingo 17 de mayo de 2009

WSSecurity with jax-ws and WSS4J

Hello everyone,

In my current project I have one non functional requisite quite challenging (at least for me, a dotnetboy playing around in the java world). As usual it came when the project was half made. It said… soap messages will be encrypted, ssl is not possible.

Well, so we have a few jax-ws services done, and ¡ups! jax-ws is incomplete (one friend told me it was like wcf, nothing further away from the truth, it leaks lots of ws-* features). The thing got harder because we have lots of differents app servers (tomcat, weblogic and jboss). I would be happy having the possibility of using Metro, but I need something independent.

So googling I found the first important thing: the jax-ws handlers. Basically it is a pre and post process of a message. We can modify the message just before is sent through the wire and before it get parsed by the service. How can we modify the service with a handler?

   2: @SOAPBinding



   3: @Stateless



   4: @HandlerChain(file = "../../../../../../handlers.xml")



   5: public class Service implements IService {




@HandlerChain points to the file which defines the classes which will be used as a handler.





   1: <?xml version="1.0" encoding="UTF-8"?>



   2: <jws:handler-chains xmlns:jws="http://java.sun.com/xml/ns/javaee">



   3:    <jws:handler-chain>



   4:       <jws:handler>



   5:       <jws:handler-class>



   6:             es.pablocastilla.WSSecurityHandler



   7:          </jws:handler-class>



   8:       </jws:handler>



   9:    </jws:handler-chain>



  10: </jws:handler-chains>




 



This classes should implement one special interface (as usual in this kind of things)





   1: public class WSSecurityHandler implements



   2:         SOAPHandler<SOAPMessageContext>




So know we have something in the service that will transform an encrypted message to a normal one.



And now, how do I code and decode the messages, well, all can be done with a few single classes:





   1: public class WSSecurityHandler implements



   2:         SOAPHandler<SOAPMessageContext>, CallbackHandler {



   3:  



   4:     Properties prop;



   5:  



   6:     /**



   7:      * Constructor initializes properties



   8:      * */



   9:     public WSSecurityHandler () throws Exception {



  10:         prop = PropSingleton.getSingleton().GetProp();



  11:     }



  12:  



  13:  



  14:     /**



  15:      * Returns special headers.



  16:      */



  17:     public Set<QName> getHeaders() {



  18:         Set<QName> HEADERS = new HashSet<QName>();



  19:  



  20:         HEADERS.add(new QName(WSConstants.WSSE_NS, "Security"));



  21:         HEADERS.add(new QName(WSConstants.WSSE11_NS, "Security"));



  22:         HEADERS.add(new QName(WSConstants.ENC_NS, "EncryptedData"));



  23:  



  24:         return HEADERS;



  25:     }



  26:  



  27:     /**



  28:      * Handles the message, code and decode it depending if it is an outgoing or incoming one.



  29:      */



  30:     public boolean handleMessage(SOAPMessageContext messageContext) {



  31:         try {



  32:             // got the message from the context



  33:             SOAPMessage msg = messageContext.getMessage();



  34:  



  35:             



  36:             // is outgoing?



  37:             Boolean isOutGoing = (Boolean) messageContext



  38:                     .get(MessageContext.MESSAGE_OUTBOUND_PROPERTY);



  39:  



  40:            



  41:             if (isOutGoing) {



  42:                 // if it is outgoing code and sign



  43:                 EncryptUtil.EncryptSOAPEnvelope(msg, prop);



  44:                 EncryptUtil.SignSOAPEnvelope(msg, prop);



  45:             } else {



  46:                 // if it is incooming decode and check signature.



  47:                 EncryptUtil.CheckSignAndDecode(



  48:                         msg, this, prop);



  49:             }



  50:  



  51:         } catch (Exception ex) {



  52:             ex.printStackTrace();



  53:             throw new RuntimeException(ex.getMessage());



  54:         }



  55:  



  56:         return true;



  57:     }



  58:  



  59:     public boolean handleFault(SOAPMessageContext messageContext) {



  60:         return true;



  61:     }



  62:  



  63:     /**



  64:      * Return the jks key, not necesary



  65:      */



  66:     @SuppressWarnings("deprecation")



  67:     public void handle(Callback[] callbacks) throws IOException,



  68:             UnsupportedCallbackException {



  69:  



  70:         String password;



  71:  



  72:         for (Callback cb : callbacks) {



  73:             if (cb instanceof WSPasswordCallback) {



  74:                 WSPasswordCallback pc = (WSPasswordCallback) cb;



  75:  



  76:                 try {



  77:                     password = prop.getProperty("password");



  78:                 } catch (Exception e) {



  79:                     throw new UnsupportedCallbackException(pc,



  80:                             "fallo al recuperar la clave en el properties");



  81:                 }



  82:  



  83:                 if (pc.getIdentifer() != null) {



  84:                     pc.setPassword(password);



  85:                 }



  86:             }



  87:         }



  88:     }



  89:  



  90:     public void close(MessageContext messageContext) {



  91:     }



  92:  



  93:  



  94: }




 



The class which uses Apache´s WSS4J for encrypting the messages:





   1: public class EncryptUtil{



   2:  



   3:     @SuppressWarnings( { "unchecked", "deprecation" })



   4:     static void CheckSignatureAndDecode(SOAPMessage msg,



   5:             CallbackHandler cb,Properties prop) throws WSSecurityException,



   6:             TransformerConfigurationException, TransformerException,



   7:             SOAPException, IOException, Exception {



   8:  



   9:         WSSecurityEngine secEngine = new WSSecurityEngine();



  10:         WSSignEnvelope signer = new WSSignEnvelope();



  11:  



  12:         String alias = prop.getProperty("alias");// login of jks file



  13:         String password = prop.getProperty("password");// password of jks file



  14:  



  15:         signer.setUserInfo(alias, password);



  16:  



  17:         Crypto crypto = CryptoFactory.getInstance(prop);



  18:         org.w3c.dom.Document doc = toDocument(mensaje);



  19:  



  20: //after we set the encrypt stuff the processsecurity does all the work



  21:         Vector v = secEngine.processSecurityHeader(doc, null, cb, crypto);



  22:  



  23:         if (v == null) {



  24:  



  25:             throw new Exception("Access not granted.");



  26:  



  27:         }



  28:  



  29:         //put the decoded message into the object



  30:         updateSOAPMessage(doc, msg);



  31:     }



  32:  



  33:     /**



  34:      * Updates the message with the unencrypt form



  35:      */



  36:     private static SOAPMessage updateSOAPMessage(Document doc,



  37:             SOAPMessage message) throws SOAPException {



  38:         DOMSource domSource = new DOMSource(doc);



  39:         message.getSOAPPart().setContent(domSource);



  40:         return message;



  41:     }



  42:  



  43:     /**



  44:      * Changes the SOAPMessage to a dom.Document.



  45:      */



  46:     public static org.w3c.dom.Document toDocument(SOAPMessage soapMsg)



  47:             throws SOAPException, TransformerException {



  48:         Source src = soapMsg.getSOAPPart().getContent();



  49:         TransformerFactory tf = TransformerFactory.newInstance();



  50:         Transformer transformer = tf.newTransformer();



  51:         DOMResult result = new DOMResult();



  52:         transformer.transform(src, result);



  53:  



  54:         return (Document) result.getNode();



  55:     }



  56:  



  57:     /**



  58:      * Signs  a SOAPMessage



  59:      * 



  60:      * @param mensaje



  61:      * @throws Exception



  62:      */



  63:     @SuppressWarnings("deprecation")



  64:     static void SignSOAPEnvelope(SOAPMessage mensaje, Properties prop) throws Exception {



  65:         // WSSignEnvelope signs a SOAP envelope according to the



  66:         // WS Specification (X509 profile) and adds the signature data



  67:         // to the envelope.



  68:         WSSignEnvelope signer = new WSSignEnvelope();



  69:  



  70:         



  71:  



  72:         String alias = prop.getProperty("alias");// "autentiaserver";



  73:         String password = prop.getProperty("password");// "changeit";



  74:  



  75:         signer.setUserInfo(alias, password);



  76:  



  77:         Crypto crypto = CryptoFactory.getInstance(prop);



  78:  



  79:         Document doc = toDocument(mensaje);



  80:         signer.setMustUnderstand(false);



  81:         Document signedDoc = signer.build(doc, crypto);



  82:  



  83:         DOMSource domSource = new DOMSource(signedDoc);



  84:         mensaje.getSOAPPart().setContent(domSource);



  85:  



  86:     }



  87:  



  88:     /**



  89:      * Codes a SOAPMessage.



  90:      */



  91:     @SuppressWarnings("deprecation")



  92:     static void EncryptSOAPEnvelope(SOAPMessage mensaje,Properties prop) throws Exception {



  93:         // WSSignEnvelope signs a SOAP envelope according to the



  94:         // WS Specification (X509 profile) and adds the signature data



  95:         // to the envelope.



  96:         WSEncryptBody encriptador = new WSEncryptBody();



  97:  



  98:         String alias = prop.getProperty("alias");



  99:         String password = prop.getProperty("password");



 100:         encriptador.setUserInfo(alias, password);



 101:  



 102:         Crypto crypto = CryptoFactory.getInstance(prop);



 103:  



 104:         Document doc = toDocument(mensaje);



 105:         encriptador.setMustUnderstand(false);



 106:         Document signedDoc = encriptador.build(doc, crypto);



 107:  



 108:         DOMSource domSource = new DOMSource(signedDoc);



 109:         mensaje.getSOAPPart().setContent(domSource);



 110:  



 111:     }



 112:  



 113:     



 114: }




All this stuff needs a jdk and some configuration. It is the one which should be place with the properties object which is seen in the classes. Some are repeated, you can improve that code :P





   1: org.apache.ws.security.crypto.provider=org.apache.ws.security.components.crypto.Merlin



   2: org.apache.ws.security.crypto.merlin.keystore.type=jks



   3: org.apache.ws.security.crypto.merlin.keystore.password=password



   4: org.apache.ws.security.crypto.merlin.keystore.alias=login



   5: org.apache.ws.security.crypto.merlin.file=/home/webuser/certs/beaclient/keystore.jks



   6: alias=login



   7: password=password




 



So we have a jax-ws service using WSSecurity :).



libs needed can be found here:



http://ws.apache.org/wss4j/ 



Hope it can help you, any comment will be appreciated. I am sorry, but I have no time for developing a working example.



Regards.

Why do I write this blog?

Well, I have been working in IT for 5 years. In all this years I have just received help from google, books in amazon and good mates. So I want to help other people publishing my solutions.