/****************************************************************************** * Copyright (C) 2012 Trek Global * * Product: iDempiere ERP & CRM Smart Business Solution * * This program is free software; you can redistribute it and/or modify it * * under the terms version 2 of the GNU General Public License as published * * by the Free Software Foundation. This program is distributed in the hope * * that it will be useful, but WITHOUT ANY WARRANTY; without even the implied * * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * * with this program; if not, write to the Free Software Foundation, Inc., * * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. * *****************************************************************************/ package org.idempiere.webservices; import java.math.BigDecimal; import java.sql.Timestamp; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Properties; import javax.annotation.Resource; import javax.servlet.http.HttpServletRequest; import javax.ws.rs.core.Context; import javax.xml.namespace.QName; import javax.xml.ws.WebServiceContext; import javax.xml.ws.handler.MessageContext; import org.adempiere.base.ServiceQuery; import org.adempiere.base.equinox.EquinoxExtensionLocator; import org.adempiere.exceptions.AdempiereException; import org.apache.commons.codec.binary.Base64; import org.compiere.model.Lookup; import org.compiere.model.MColumn; import org.compiere.model.MUser; import org.compiere.model.PO; import org.compiere.model.POInfo; import org.compiere.model.Query; import org.idempiere.webservices.model.X_WS_WebServiceMethod; import org.idempiere.webservices.model.X_WS_WebServiceTypeAccess; import org.compiere.util.CCache; import org.compiere.util.DB; import org.compiere.util.Env; import org.compiere.util.KeyNamePair; import org.compiere.util.Login; import org.compiere.util.NamePair; import org.compiere.util.Trx; import org.idempiere.adInterface.x10.ADLoginRequest; import org.idempiere.adInterface.x10.DataField; import org.idempiere.adInterface.x10.OutputField; import org.idempiere.adInterface.x10.OutputFields; import org.idempiere.adInterface.x10.StandardResponse; import org.idempiere.adInterface.x10.StandardResponseDocument; import org.idempiere.adinterface.CompiereService; import org.idempiere.cache.ImmutablePOCache; import org.idempiere.webservices.fault.IdempiereServiceFault; import org.idempiere.webservices.model.MWebService; import org.idempiere.webservices.model.MWebServiceType; /** * * @author Deepak Pansheriya * */ public class AbstractService { public static final String ROLE_TYPES_WEBSERVICE = "NULL,WS"; //webservice+null private static final String ROLE_ACCESS_SQL = "SELECT IsActive FROM WS_WebServiceTypeAccess WHERE AD_Role_ID IN (" + "SELECT AD_Role_ID FROM AD_Role WHERE AD_Role_ID=? UNION " + "SELECT Included_Role_ID as AD_Role_ID FROM AD_Role_Included WHERE AD_Role_ID=?) " + "AND WS_WebServiceType_ID=?"; private static final String COMPIERE_SERVICE = "CompiereService"; @Resource protected WebServiceContext jaxwsContext; //soap context @Context protected org.apache.cxf.jaxrs.ext.MessageContext jaxrsContext ; //rest context /** * Login to web Services * @param loginRequest * @param webService * @param method * @param serviceType * @return */ protected String login(ADLoginRequest loginRequest, String webService, String method, String serviceType) { CompiereService m_cs = getCompiereService(); if (m_cs.getUserName() == null) { HttpServletRequest req = getHttpServletRequest(); // search for a non-expired CompiereService with same login data CompiereService cachedCs = CompiereService.get(req, loginRequest); if (cachedCs != null) { m_cs = cachedCs; req.setAttribute(COMPIERE_SERVICE, cachedCs); m_cs.connectCacheInstance(); return authenticate(webService, method, serviceType, cachedCs); // already logged with same data } } if (m_cs.isLoggedIn() && m_cs.getAD_Client_ID() == loginRequest.getClientID() && loginRequest.getClientID() == Env.getAD_Client_ID(Env.getCtx()) && m_cs.getAD_Org_ID() == loginRequest.getOrgID() && m_cs.getAD_Role_ID() == loginRequest.getRoleID() && m_cs.getM_Warehouse_ID() == loginRequest.getWarehouseID() && loginRequest.getUser().equals(m_cs.getUserName())) return authenticate(webService, method, serviceType, m_cs); // already logged with same data String ret =invokeLoginValidator(loginRequest, m_cs.getCtx(), null, IWSValidator.TIMING_BEFORE_LOGIN); if(ret!=null && ret.length()>0) return ret; Login login = new Login(m_cs.getCtx()); KeyNamePair[] clients = login.getClients(loginRequest.getUser(), loginRequest.getPass(), ROLE_TYPES_WEBSERVICE); if (clients == null) return "Error login - User invalid"; m_cs.setPassword(loginRequest.getPass()); m_cs.setExpiryMinutes(loginRequest.getStage()); m_cs.setIPAddress(getHttpServletRequest().getRemoteAddr()); boolean okclient = false; KeyNamePair selectedClient = null; for (KeyNamePair client : clients) { if (client.getKey() == loginRequest.getClientID()) { okclient = true; selectedClient = client; break; } } if (!okclient) return "Error logging in - tenant not allowed for this user"; m_cs.getCtx().setProperty(Env.AD_CLIENT_ID, "" + loginRequest.getClientID()); Env.setContext(m_cs.getCtx(), Env.AD_CLIENT_ID, (String) selectedClient.getID()); MUser user = MUser.get (m_cs.getCtx(), loginRequest.getUser()); if (user != null) { Env.setContext(m_cs.getCtx(), Env.AD_USER_ID, user.getAD_User_ID() ); Env.setContext(m_cs.getCtx(), Env.AD_USER_NAME, user.getName() ); Env.setContext(m_cs.getCtx(), Env.SALESREP_ID, user.getAD_User_ID() ); String userAgent = getHttpServletRequest().getHeader("User-Agent"); Env.setContext(m_cs.getCtx(), "#UserAgent", userAgent == null ? "Unknown" : userAgent); } KeyNamePair[] roles = login.getRoles(loginRequest.getUser(), selectedClient, ROLE_TYPES_WEBSERVICE); if (roles != null) { boolean okrole = false; for (KeyNamePair role : roles) { if (role.getKey() == loginRequest.getRoleID()) { okrole = true; break; } } if (!okrole) return "Error logging in - role not allowed for this user"; KeyNamePair[] orgs = login.getOrgs(new KeyNamePair(loginRequest.getRoleID(), "")); if (orgs == null) return "Error logging in - no organizations for this role"; KeyNamePair orglogin = null; boolean okorg = false; for (KeyNamePair org : orgs) { if (org.getKey() == loginRequest.getOrgID()) { okorg = true; orglogin = org; break; } } if (!okorg) return "Error logging in - org not allowed for this role"; KeyNamePair[] warehouses = login.getWarehouses(new KeyNamePair(loginRequest.getOrgID(), "")); if (warehouses != null) { boolean okwh = false; for (KeyNamePair warehouse : warehouses) { if (warehouse.getKey() == loginRequest.getWarehouseID()) { okwh = true; break; } } if (!okwh) return "Error logging in - warehouse not allowed for this org"; } else if (warehouses == null && loginRequest.getWarehouseID() > 0) return "Error logging in - warehouse not allowed for this org"; String error = login.validateLogin(orglogin); if (error != null && error.length() > 0) return error; int AD_User_ID = Env.getAD_User_ID(m_cs.getCtx()); if (!m_cs.login(AD_User_ID, loginRequest.getRoleID(), loginRequest.getClientID(), loginRequest.getOrgID(), loginRequest.getWarehouseID(), loginRequest.getLang())) return "Error logging in"; } else { return "Error logging in - no roles or user/pwd invalid for user " + loginRequest.getUser(); } ret =invokeLoginValidator(loginRequest, m_cs.getCtx(), null, IWSValidator.TIMING_AFTER_LOGIN); if(ret!=null && ret.length()>0) return ret; return authenticate(webService, method, serviceType, m_cs); } private static ImmutablePOCache s_WebServiceTypeCache = new ImmutablePOCache(MWebServiceType.Table_Name, 10, 60); //60 minutes private static CCache s_RoleAccessCache = new CCache<>(X_WS_WebServiceTypeAccess.Table_Name, 60, CCache.DEFAULT_EXPIRE_MINUTE); /** * Authenticate user for requested service type * @param webServiceValue * @param methodValue * @param serviceTypeValue * @param m_cs * @return */ protected String authenticate(String webServiceValue, String methodValue, String serviceTypeValue, CompiereService m_cs) { MWebService m_webservice = MWebService.get(m_cs.getCtx(), webServiceValue); if (m_webservice == null || !m_webservice.isActive()) return "Web Service " + webServiceValue + " not registered"; X_WS_WebServiceMethod m_webservicemethod = m_webservice.getMethod(methodValue); if (m_webservicemethod == null || !m_webservicemethod.isActive()) return "Method " + methodValue + " not registered"; MWebServiceType m_webservicetype = null; String key = m_cs.getAD_Client_ID() + "|" + m_webservice.getWS_WebService_ID() + "|" + m_webservicemethod.getWS_WebServiceMethod_ID() + "|" + serviceTypeValue; synchronized (s_WebServiceTypeCache) { m_webservicetype = s_WebServiceTypeCache.get(m_cs.getCtx(), key, e -> new MWebServiceType(m_cs.getCtx(), e)); if (m_webservicetype == null) { m_webservicetype = new Query(m_cs.getCtx(), MWebServiceType.Table_Name, "AD_Client_ID IN (0,?) AND WS_WebService_ID=? AND WS_WebServiceMethod_ID=? AND Value=?", null) .setOnlyActiveRecords(true) .setParameters(m_cs.getAD_Client_ID(), m_webservice.getWS_WebService_ID(), m_webservicemethod.getWS_WebServiceMethod_ID(), serviceTypeValue) .setOrderBy("AD_Client_ID DESC") // IDEMPIERE-3394 give precedence to tenant defined if there are system+tenant .first(); if (m_webservicetype != null) { s_WebServiceTypeCache.put(key, m_webservicetype, e -> new MWebServiceType(Env.getCtx(), e)); } } } if (m_webservicetype == null) return "Service type " + serviceTypeValue + " not configured"; getHttpServletRequest().setAttribute("MWebServiceType", m_webservicetype); int AD_Role_ID = Env.getAD_Role_ID( m_cs.getCtx()); key = AD_Role_ID + "|" + m_webservicetype.get_ID(); synchronized (s_RoleAccessCache) { Boolean bAccess = s_RoleAccessCache.get(key); if (bAccess == null) { // Check if role has access on web-service String hasAccess = DB.getSQLValueStringEx(null, ROLE_ACCESS_SQL, AD_Role_ID, AD_Role_ID, m_webservicetype.get_ID()); bAccess = "Y".equals(hasAccess); s_RoleAccessCache.put(key, bAccess); } if (!bAccess.booleanValue()) { return "Web Service Error: Login role does not have access to the service type '" + serviceTypeValue + "'"; } } String ret=invokeLoginValidator(null, m_cs.getCtx(), m_webservicetype, IWSValidator.TIMING_ON_AUTHORIZATION); if(ret!=null && ret.length()>0) return ret; m_cs.refreshLastAuthorizationTime(); return null; } /** * Rollback and set error on standard response * @param trx * @param resp * @param ret * @param isError * @param string * @return */ protected static StandardResponseDocument rollbackAndSetError(Trx trx, StandardResponse resp, StandardResponseDocument ret, boolean isError, String string) { resp.setError(string); resp.setIsError(isError); trx.rollback(); trx.close(); return ret; } /** * * @return Compiere Service object for current request */ protected CompiereService getCompiereService() { HttpServletRequest req = getHttpServletRequest(); CompiereService m_cs = (CompiereService) req.getAttribute(COMPIERE_SERVICE); if (m_cs == null) { m_cs = new CompiereService(); req.setAttribute(COMPIERE_SERVICE, m_cs); } return m_cs; } /** * * @return */ protected MWebServiceType getWebServiceType() { return (MWebServiceType) getHttpServletRequest().getAttribute("MWebServiceType"); } /** * Get request Ctx * @return */ protected Map getRequestCtx() { HttpServletRequest req = getHttpServletRequest(); @SuppressWarnings("unchecked") Map reqCtx= (Map)req.getAttribute("RequestCtx"); if(reqCtx==null){ reqCtx = new HashMap(); req.setAttribute("RequestCtx", reqCtx); } return reqCtx; } /** * * @param resp * @param m_webservicetype */ protected void setOuputFields(StandardResponse resp,MWebServiceType m_webservicetype,PO po,POInfo poInfo){ String[] outCols = m_webservicetype.getOutputColumnNames(false); if(outCols.length>0){ OutputFields outputFields = resp.addNewOutputFields(); if(outputFields==null) outputFields=resp.addNewOutputFields(); for(String colName:outCols){ int indCol = poInfo.getColumnIndex(colName); if(indCol<0) continue; OutputField outField= outputFields.addNewOutputField(); outField.setColumn(colName); if(po.get_Value(indCol)!=null){ if(po.get_Value(indCol) instanceof byte[]) outField.setValue(new String(Base64.encodeBase64((byte[]) po.get_Value(indCol)))); else outField.setValue(po.get_Value(indCol).toString()); Lookup lookup = poInfo.getColumnLookup(indCol); if(lookup != null){ //Setting text if(lookup.getSize()==0) lookup.refresh(); NamePair pair = lookup.getDirect(po.get_Value(indCol), false, false); if (pair != null) outField.setText(pair.getName()); } } } } } /** * Parse variables inside SQL * @param sql * @param sqlParas * @param po * @param requestCtx * @return * @throws AdempiereException */ protected String parseSQL(String sql, ArrayList sqlParas, PO po,POInfo poInfo, Map requestCtx) throws AdempiereException { if (sql.startsWith(MColumn.VIRTUAL_UI_COLUMN_PREFIX)) sql = sql.substring(5); if (sql.toLowerCase().indexOf(" where ") == -1) throw new AdempiereException("Invalid SQL: Query do not have any filtering criteria"); StringBuilder sqlBuilder = new StringBuilder(); if (sql.indexOf('@') == -1) { sqlBuilder.append(sql); } else { int firstInd = sql.indexOf('@'); while (firstInd >= 0) { sqlBuilder.append(sql.substring(0, firstInd)); sql = sql.substring(firstInd + 1); firstInd = sql.indexOf('@'); if (firstInd == -1) { throw new AdempiereException("Missing closing '@' in SQL"); } String token = sql.substring(0, firstInd); boolean isNullable = false; if(token.charAt(0)=='!'){ isNullable = true; token = token.substring(1); } sql = sql.substring(firstInd + 1); Object val = parseVariable(token, po,poInfo, requestCtx); if(val == null && isNullable){ int ind = sqlBuilder.lastIndexOf("="); sqlBuilder.replace(ind, sqlBuilder.length(), " Is Null "); }else if (val == null) throw new AdempiereException("Can not resolve variable '" + token + "' in sql"); else{ sqlBuilder.append(" ? "); sqlParas.add(val); } firstInd = sql.indexOf('@'); } sqlBuilder.append(sql); } return sqlBuilder.toString(); } /** * Resolving context variable into object * @param varName * @param po * @param requestCtx * @return */ protected Object parseVariable(String varName, PO po,POInfo poInfo, Map requestCtx) { Object val = null; if (varName.charAt(0) == '@') varName = varName.substring(1); int indDot = varName.indexOf("."); if (indDot == -1) { if (varName.charAt(0) == '#') { val = getCompiereService().getCtx().getProperty(varName); if (varName.endsWith("_ID") && val != null) { Integer intVal = Integer.parseInt((String) val); val = intVal; } } else { // If there is no table name, then it should be // primitive data type if (po != null && poInfo.getColumnIndex(varName)!=-1) val = po.get_Value(varName); if (val == null) val = requestCtx.get(varName); } } else { String tblName = varName.substring(0, indDot); String colName = varName.substring(indDot + 1); if (colName.indexOf(".") >= 0) { throw new IdempiereServiceFault(" can not resolve " + varName + ". contains un supported multi level object resolution", new QName("resolveCtxVariable")); } Object obj = requestCtx.get(tblName); if (obj == null || !(obj instanceof PO)) { throw new IdempiereServiceFault(" can not found object of " + tblName + ". Request variable " + varName + " can not resolved", new QName("resolveCtxVariable")); } PO refPO = (PO) obj; val = refPO.get_Value(colName); } return val; } /** * @param strValue * @param columnClass * @param colName * @return */ protected Object convertToObj(String strValue,Class columnClass,String colName){ Object value = null; if (columnClass == Boolean.class) { if ("Y".equalsIgnoreCase(strValue) || "true".equalsIgnoreCase(strValue)) value = Boolean.TRUE; else if ("N".equalsIgnoreCase(strValue) || "false".equalsIgnoreCase(strValue)) value = Boolean.FALSE; else throw new IdempiereServiceFault(" input column " + colName + " wrong value " + strValue, new QName( "setValueAccordingToClass")); } else if (columnClass == Integer.class) { try { value = Integer.parseInt(strValue); } catch (NumberFormatException e) { throw new IdempiereServiceFault(e.getClass().toString() + " " + e.getMessage() + " for " + colName , e.getCause(), new QName( "setValueAccordingToClass")); } } else if (columnClass == BigDecimal.class) { try { value = new BigDecimal(strValue); } catch (Exception e) { throw new IdempiereServiceFault(e.getClass().toString() + " " + e.getMessage() + " for " + colName, e.getCause(), new QName( "setValueAccordingToClass")); } } else if (columnClass == Timestamp.class) { try { value = Timestamp.valueOf(strValue); } catch (Exception e) { throw new IdempiereServiceFault(e.getClass().toString() + " " + e.getMessage() + " for " + colName, e.getCause(), new QName( "setValueAccordingToClass")); } } else if (columnClass == byte[].class) { try { value = Base64.decodeBase64(strValue.getBytes()); } catch (Exception e) { throw new IdempiereServiceFault(e.getClass().toString() + " " + e.getMessage() + " for " + colName, e.getCause(), new QName( "setValueAccordingToClass")); } } else { value = strValue; } return value; } /** * Retrieve variable data type * @param columnName * @param displayType * @return */ protected Class getVariableType(String columnName,int displayType){ Class ColumnClass= null; if (columnName.equals("AD_Language") || columnName.equals("EntityType")) { ColumnClass = String.class; } else if (columnName.equals("Posted") || columnName.equals("Processed") || columnName.equals("Processing")) { ColumnClass = Boolean.class; } else if (columnName.equals("Record_ID")) { ColumnClass = Integer.class; } else ColumnClass = org.compiere.util.DisplayType.getClass(displayType, true); return ColumnClass; } /** * retrieve web service Validators * @param serviceType * @return */ public static List getValidators(String serviceType){ ServiceQuery query = new ServiceQuery(); query.put("WSType",serviceType); return EquinoxExtensionLocator.instance().list(IWSValidator.class, query).getExtensions(); } /** * Invoke webservice Validator * @param m_WebServiceType * @param time * @param po * @param fields * @param trx * @param requestCtx * @param resp * @param ret * @return */ public static StandardResponseDocument invokeWSValidator(MWebServiceType m_WebServiceType, int time,PO po, DataField fields[], Trx trx,Map requestCtx,StandardResponse resp,StandardResponseDocument ret){ List validators = getValidators(m_WebServiceType.getValue()); List globalValidators = getValidators("GLOBAL"); //Call global validator for(IWSValidator validator:globalValidators){ try{ validator.validate(po, m_WebServiceType, fields, time,trx.getTrxName(),requestCtx); }catch (IdempiereServiceFault e) { return rollbackAndSetError(trx, resp, ret, true, e.getMessage()); } } for(IWSValidator validator:validators){ try{ validator.validate(po, m_WebServiceType, fields, time,trx.getTrxName(),requestCtx); }catch (IdempiereServiceFault e) { return rollbackAndSetError(trx, resp, ret, true, e.getMessage()); } } return null; } /** * Invoke web Service Login Validator * @param loginRequest * @param ctx * @param m_webserviceType * @param time * @return */ public static String invokeLoginValidator(ADLoginRequest loginRequest, Properties ctx, MWebServiceType m_webserviceType, int time) { List validators = null; if (m_webserviceType != null) { validators = getValidators(m_webserviceType.getValue()); } List globalValidators = getValidators("GLOBAL"); // Call global validator for (IWSValidator validator : globalValidators) { try { validator.login(loginRequest, ctx, m_webserviceType, time); } catch (IdempiereServiceFault e) { return e.getMessage(); } } if (validators != null) { for (IWSValidator validator : validators) { try { validator.login(loginRequest, ctx, m_webserviceType, time); } catch (IdempiereServiceFault e) { return e.getMessage(); } } } return null; } /** * Get HttpServletRequest object * @return HttpServletRequest */ private HttpServletRequest getHttpServletRequest() { HttpServletRequest req; if (jaxrsContext != null) { req = (HttpServletRequest) jaxrsContext.getHttpServletRequest(); } else req = (HttpServletRequest) jaxwsContext.getMessageContext().get( MessageContext.SERVLET_REQUEST); return req; } }