Package com.jaxo.mozutil

Source Code of com.jaxo.mozutil.ReceiptVerifier

/*
* (C) Copyright 2013 Jaxo Inc.
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0.  If a copy of the MPL was not distributed with this
* file, you can obtain one at http://mozilla.org/MPL/2.0/.
*
* Author:  Pierre G. Richard
* Written: 7/10/2013
*/
package com.jaxo.mozutil;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.StringReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/*-- class ReceiptVerifier --+
*//**
* A verifier for pre-paid mozApps.
*
* @author  Pierre G. Richard
*/
public class ReceiptVerifier {

   /*------------------------------------------------------------------verify-+
   *//**
   * Verify whether an Application has the proper payment receipt.
   * The String <code>app</code> is a JSON stringified Mozilla App object.
   *
   * For example, <code>window.navigator.mozApps.getSelf()</code> calls
   * <code>onsuccess</code> passing such an <code>App<code> object
   * in the <code>result</code> field of the <code>DomRequest</code> argument.
   *
   * @param app JSON stringified Mozilla App Object to check the receipt of
   * @return a ReceiptVerifier.Status enumeration value (see below)
   *//*
   +-------------------------------------------------------------------------*/
   static public Status verify(String app)
   {
      Status status = Status.ERROR;
      try {
         Json.Root appRoot = Json.parse(new StringReader(app));
         for (Json.Member appMbr : ((Json.Object)appRoot).members) {
            if (appMbr.getKey().equals("receipts")) {
               for (Object value : ((Json.Array)appMbr.getValue()).values) {
                  status = Status.getBestOf(
                     checkReceipt((String)value), status
                  );
                  if (status == Status.OK) return status;
               }
            }
         }
      }catch (Exception e) {}
      return status;
   }

   /*------------------------------------------------------------checkReceipt-+
   *//**
   *//*
   +-------------------------------------------------------------------------*/
   static private Status checkReceipt(String jwt)
   {
      try {
         // split up the JWT to get the part that contains the receipt
         int ix;
         ix = jwt.indexOf('~');
         if (ix == -1) {
            return Status.BAD_JWT;
         }
         String subJwt = jwt.substring(1+jwt.indexOf('.', ix+1));
         ix = subJwt.indexOf('.');
         if (ix == -1) {
            ix = subJwt.indexOf('~');
            if (ix == -1) {
               ix = subJwt.length();
            }
         }
         Json.Root rcpRoot = Json.parse(
            new StringReader(Base64.Url.decode(subJwt.substring(0, ix)))
         );
         ReceiptType rcpType = null;
         String storeUrl = null;
         Date issuedAt = null;
         String verifierUrl = null;
         for (Json.Member rcpMbr : ((Json.Object)rcpRoot).members) {
            String key = rcpMbr.getKey();
            if (key.equals("typ")) {
               rcpType = ReceiptType.make((String)rcpMbr.getValue());
            }else if (key.equals("iss")) {
               storeUrl = (String)rcpMbr.getValue();
            }else if (key.equals("iat")) {
               // currently, we do nothing with it
               issuedAt = new Date((Long)rcpMbr.getValue());
            }else if (key.equals("verify")) {
               verifierUrl = (String)rcpMbr.getValue();
            }
         }
         if (
            (rcpType == null) ||
            !rcpType.isAllowed() ||
            (issuedAt == null) ||
            (storeUrl == null) ||
            (verifierUrl == null) ||
            !isSubdomain(storeUrl, verifierUrl)
         ) {
            return Status.BAD_FIELDS;
         }else {
            Status status = getVerifierStatus(
               post(new URL(verifierUrl), jwt.getBytes("UTF-8"))
            );
            if ((status == Status.OK) && (rcpType == ReceiptType.TEST)) {
               return Status.TESTONLY;
            }else {
               return status;
            }
         }
      }catch (Exception e) {
         return Status.ERROR;
      }
   }

   /*---------------------------------------------------------------getDomain-+
   *//**
   *//*
   +-------------------------------------------------------------------------*/
   static Pattern pat = Pattern.compile("^https?:\\/\\/(.*?)(([:\\/].*)|$)");
   private static String getDomain(String url) {
      Matcher matcher = pat.matcher(url);
      matcher.matches();
      return matcher.group(1).toLowerCase();
   }

   /*-------------------------------------------------------------isSubdomain-+
   *//**
   * Returns true if url2 is the same as url1, or a subdomain of it,
   * irregardless of http/https protocol, and/or port
   *
   * @param url1 main url
   * @param url2 url the domain or subdomain of which
   *             must match the domain of <code>url1</code>
   * @return true if url2 is the same as url1, or a subdomain of it
   * @exception IllegalStateException or IndexOutOfBoundsException
   *//*
   +-------------------------------------------------------------------------*/
   private static boolean isSubdomain(String url1, String url2) {
      return getDomain(url2).endsWith(getDomain(url1));
   }

   /*--------------------------------------------------------------------post-+
   *//**
   * Post the full JWT to the verifier URL and returns the JSON response
   *//*
   +-------------------------------------------------------------------------*/
   private static Json.Root post(URL verifierUrl, byte[] jwt) throws Exception
   {
      HttpURLConnection conn = null;
      try {
         conn = (HttpURLConnection)verifierUrl.openConnection();
         conn.setRequestMethod("POST");
         conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
         conn.setRequestProperty("Content-Length", Integer.toString(jwt.length));
         conn.setDoInput(true);
         conn.setDoOutput(true);
         OutputStream out = conn.getOutputStream();
         out.write(jwt);
         out.close();
         return Json.parse(
            new InputStreamReader(conn.getInputStream(), "UTF-8")
         );
      }finally {
         if (conn != null) conn.disconnect();
      }
   }

   /*-------------------------------------------------------getVerifierStatus-+
   *//**
   *//*
   +-------------------------------------------------------------------------*/
   private static Status getVerifierStatus(Json.Root root)
   throws Exception
   {
      for (Json.Member mbr : ((Json.Object)root).members) {
         if (mbr.getKey().equals("status")) {
            return Status.make((String)mbr.getValue());
         }
      }
      return Status.UNKNOWN;
   }

   /*----------------------------------------------------------- enum Status -+
   *//**
   *//*
   +-------------------------------------------------------------------------*/
   public static enum Status {
      OK("ok", true, 20),
      PENDING("pending", true, 18),
      REFUNDED("refunded", true, 16),
      EXPIRED("expired", true, 14),
      INVALID("invalid", true, 12),
      UNKNOWN(null, true, 10),
      BAD_JWT("this receipt is unreadable", false, 4),
      BAD_FIELDS("this receipt cannot be verified", false, 3),
      TESTONLY("this receipt is only valid for tests", false, 2),
      ERROR("uncorrectable errors found", false, 1);

      private String m_name;
      private int m_rank;
      private boolean m_isFromStore;
      private static Map<String, Status> m_fromName = new HashMap<String, Status>();
      static {
         for (Status t : values()) {
            if (t.m_isFromStore) m_fromName.put(t.m_name, t);
         }
      }
      private Status(String name, boolean isFromStore, int rank) {
         m_isFromStore = isFromStore;
         m_name = name;
         m_rank = rank;
      }
      public static Status make(String name) {
         Status status = m_fromName.get(name);
         return (status == null)? UNKNOWN : status;
      }
      public static Status getBestOf(
         Status status1, Status status2
      ) {
         return (status1.m_rank > status2.m_rank)? status1 : status2;
      }
      public String toString() {
         if (m_isFromStore) {
            return("Issuer said: \"" + ((m_name!=null)? m_name : "?") + "\"");
         }else {
            return(m_name);
         }
      }
   }

   /*------------------------------------------------------ enum ReceiptType -+
   *//**
   *//*
   +-------------------------------------------------------------------------*/
   private static enum ReceiptType {
      PURCHASE("purchase-receipt", true),
      DEVELOPER("developer-receipt", true),
      REVIEWER("reviewer-receipt", true),
      TEST("test-receipt", true),
      UNKNOWN(null, false);

      private String m_name;
      private boolean m_isAllowed;
      private static Map<String, ReceiptType> m_fromName = new HashMap<String, ReceiptType>();
      static {
         for (ReceiptType t : values()) m_fromName.put(t.m_name, t);
      }
      private ReceiptType(String name, boolean isAllowed) {
         m_name = name;
         m_isAllowed = isAllowed;
      }
      public boolean isAllowed() {
         return m_isAllowed;
      }
      public static ReceiptType make(String name) {
         ReceiptType type = m_fromName.get(name);
         return (type == null)? UNKNOWN : type;
      }
   }
}
/*===========================================================================*/ 
TOP

Related Classes of com.jaxo.mozutil.ReceiptVerifier

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.