Como mejorar la seguridad al ingreso de Adempiere

From ADempiere
Revision as of 09:11, 20 August 2013 by Jdaison (Talk) (Introducción)

(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to: navigation, search
This Wiki is read-only for reference purposes to avoid broken links.


Note.gif Note:

EXENCIÓN DE RESPONSABILIDAD - Esto guia/tip es escrito por --John Agudelo 01:11, 19 August 2013 (UTC), de O.S. Group Colombia. Cualquier aporte es bienvenido para discutir y mejorar.

Introducción

Hemos visto el inconveniente que un usuario puede iniciar una sesión multiples veces en ADempiere, también que se puede realizar intentos de ingresar al sistema (Ataque de fuerza bruta) sin una limitante que dificulte que esto se explote como un hueco de seguridad. Con estos cambios se puede limitar el inicio de sesión a uno solo al mismo tiempo, la sesión debe ser finalizada correctamente, si no el sistema asumirá que la sessión está activa y solo permitira el inicio de la sesión 15 minutos mas tarde, y ademas tambien se valida que si se falla 3 veces consecutivas el ingreso de la contraseña de un usuario sea suspendido el ingreso por un espacio de 15 minutos.

Implementación

1. Loguearse como SuperUser y escoger rol System.
2. Vaya a la tabla AD_User y agregue un campo qty.
Qty in ad user.png

3. Modifique, reemplace o agregue en el archivo org.compiere.util.Login.java estos metodos este código

	private KeyNamePair[] getRoles (String app_user, String app_pwd, boolean force)
	{
		log.info("User=" + app_user);
		long start = System.currentTimeMillis();
		if (app_user == null)
		{
			log.warning("No Apps User");
			return null;
		}
		//<- OSG check if user has been blocked for 15 minutes because have 3 bad tries to login consecutive
		MUser user  = null;
		StringBuffer sql = new StringBuffer("SELECT AD_User_ID FROM AD_User WHERE name = ?");
		PreparedStatement pstmt = null;
		ResultSet rs = null;              
		try{
		 pstmt = DB.prepareStatement(sql.toString(), null); 
		 pstmt.setString(1, app_user);
		 rs = pstmt.executeQuery();
		 	if (rs.next()){
		 		//Doesn't work with the User System
				user = new MUser (m_ctx,rs.getInt("AD_User_ID"),null);
				if (user.get_ValueAsInt("Qty") >= 3){
					if (compare_time_now_add_min(15,user.getUpdated()) == 1){
						//Doesn't work with the WebUI
						JOptionPane.showMessageDialog(null, "Demasiados intentos fallidos!!\nDebe esperar unos minutos para intentar",
						"Error de inicio de sesión", JOptionPane.WARNING_MESSAGE );
						 return null; 
					}
				}							
		 	}				 		
		 }
		 catch (SQLException e){
		   log.log(Level.SEVERE, "Error consultando usuario", e); 
		 }
		 finally{
		  DB.close(rs, pstmt);
		  rs = null; pstmt = null;
		 } 
		 //OSG ->	 
		//	Authentification
		boolean authenticated = false;
		if (Ini.isClient())
			CConnection.get().setAppServerCredential(app_user, app_pwd);
		MSystem system = MSystem.get(m_ctx);
		if (system == null)
			throw new IllegalStateException("No System Info");
		
		if (app_pwd == null || app_pwd.length() == 0)
		{
			log.warning("No Apps Password");
			return null;
		}
		if (system.isLDAP())
		{
			authenticated = system.isLDAP(app_user, app_pwd);
			if (authenticated)
				app_pwd = null;
			// if not authenticated, use AD_User as backup
		}
		
		KeyNamePair[] retValue = null;
		ArrayList<KeyNamePair> list = new ArrayList<KeyNamePair>();
		//
		sql = new StringBuffer("SELECT u.AD_User_ID, r.AD_Role_ID,r.Name,")
			.append(" u.ConnectionProfile ")
			.append("FROM AD_User u")
			.append(" INNER JOIN AD_User_Roles ur ON (u.AD_User_ID=ur.AD_User_ID AND ur.IsActive='Y')")
			.append(" INNER JOIN AD_Role r ON (ur.AD_Role_ID=r.AD_Role_ID AND r.IsActive='Y') ")
			.append("WHERE COALESCE(u.LDAPUser,u.Name)=?")		//	#1
			.append(" AND u.IsActive='Y'")
			.append(" AND EXISTS (SELECT * FROM AD_Client c WHERE u.AD_Client_ID=c.AD_Client_ID AND c.IsActive='Y')");
		if (app_pwd != null)
			sql.append(" AND ((u.Password=? AND (SELECT IsEncrypted FROM AD_Column WHERE AD_Column_ID=417)='N') " 
					+     "OR (u.Password=? AND (SELECT IsEncrypted FROM AD_Column WHERE AD_Column_ID=417)='Y'))");	//  #2/3
		sql.append(" ORDER BY r.Name");
		pstmt = null;
		rs = null;
		try
		{
			pstmt = DB.prepareStatement(sql.toString(), null);
			pstmt.setString(1, app_user);
			if (app_pwd != null)
			{
				pstmt.setString(2, app_pwd);
				pstmt.setString(3, SecureEngine.encrypt(app_pwd));
			}
			//	execute a query
			rs = pstmt.executeQuery();

			if (!rs.next())		//	no record found
				if (force)
				{
					Env.setContext(m_ctx, "#AD_User_Name", "System");
					Env.setContext(m_ctx, "#AD_User_ID", "0");
					Env.setContext(m_ctx, "#AD_User_Description", "System Forced Login");
					Env.setContext(m_ctx, "#User_Level", "S  ");  	//	Format 'SCO'
					Env.setContext(m_ctx, "#User_Client", "0");		//	Format c1, c2, ...
					Env.setContext(m_ctx, "#User_Org", "0"); 		//	Format o1, o2, ...
					rs.close();
					pstmt.close();
					retValue = new KeyNamePair[] {new KeyNamePair(0, "System Administrator")};
					return retValue;
				}
				else
				{
					//<- OSG add counter
					user.set_CustomColumn("Qty", user.get_ValueAsInt("Qty") +1);
					user.saveEx();
					//OSG ->
					rs.close();
					pstmt.close();
					log.saveError("UserPwdError", app_user, false);
					return null;
				}
			//<- OSG counter reset
			user.set_CustomColumn("Qty", 0);
			//OSG ->
			user.save();
			Env.setContext(m_ctx, "#AD_User_Name", app_user);
			Env.setContext(m_ctx, "#AD_User_ID", rs.getInt(1));
			Env.setContext(m_ctx, "#SalesRep_ID", rs.getInt(1));
			//
			if (Ini.isClient())
			{
				if (MSystem.isSwingRememberUserAllowed())
					Ini.setProperty(Ini.P_UID, app_user);
				else
					Ini.setProperty(Ini.P_UID, "");
				if (Ini.isPropertyBool(Ini.P_STORE_PWD) && MSystem.isSwingRememberPasswordAllowed())
					Ini.setProperty(Ini.P_PWD, app_pwd);
				
				m_connectionProfile = rs.getString(4);		//	User Based
				if (m_connectionProfile != null)
				{
					CConnection cc = CConnection.get();
					if (!cc.getConnectionProfile().equals(m_connectionProfile))
					{
						cc.setConnectionProfile(m_connectionProfile);
						Ini.setProperty(Ini.P_CONNECTION, cc.toStringLong());
						Ini.saveProperties(false);
					}
				}
			}

			do	//	read all roles
			{
				int AD_Role_ID = rs.getInt(2);
				if (AD_Role_ID == 0)
					Env.setContext(m_ctx, "#SysAdmin", "Y");
				String Name = rs.getString(3);
				KeyNamePair p = new KeyNamePair(AD_Role_ID, Name);
				list.add(p);
			}
			while (rs.next());
		//
			retValue = new KeyNamePair[list.size()];
			list.toArray(retValue);
			log.fine("User=" + app_user + " - roles #" + retValue.length);
		}
		catch (Exception ex)
		{
			log.log(Level.SEVERE, sql.toString(), ex);
			log.saveError("DBLogin", ex);
			retValue = null;
		}
		//
		finally
		{
			DB.close(rs, pstmt);
			rs = null; pstmt = null;
		}
		long ms = System.currentTimeMillis () - start;
		return retValue;
	}	//	getRoles
	public String validateLogin (KeyNamePair org)
	{
		int AD_Client_ID = Env.getAD_Client_ID(m_ctx);
		int AD_Org_ID = org.getKey();
		int AD_Role_ID = Env.getAD_Role_ID(m_ctx);
		int AD_User_ID = Env.getAD_User_ID(m_ctx);
	
		//<- OSG  Don't allow star a second session with the same user
		String sql = "SELECT processed, created, AD_Session_ID FROM" +
		" ad_session WHERE createdby=" + AD_User_ID  +
		" ORDER BY ad_session_id DESC OFFSET 0 FETCH FIRST 1 ROWS ONLY " ; 
		 int minutes = MSysConfig.getIntValue("MINUTES_SESSION", 15 , AD_Client_ID);
		 if (minutes > 0 ){
			 PreparedStatement pstmt = null;   
			 ResultSet rs = null;              
			 try{
			  pstmt = DB.prepareStatement(sql, null); 
			  rs = pstmt.executeQuery();
			  if (rs.next()){			  
					if (rs.getString(1).compareTo("N")==0 ){
						if (compare_time_now_add_min(minutes,rs.getTimestamp(2)) == 1)
							return "Error en sesión:\n"+
							"Usuario ya está en el sistema, cierre la sesión en otro PC. \n"
							+ "Si la sesión se cerró mal espere " + minutes + " minutos a que se reactive automaticamente";
					}
				}
			  }
			  catch (SQLException e){
			    log.log(Level.SEVERE, "Obtener session", e); 
			  }
			  
			  finally{
		       DB.close(rs, pstmt);
		       rs = null; pstmt = null;
			  }
		 }
		// OSG ->
		String error = ModelValidationEngine.get().loginComplete(AD_Client_ID, AD_Org_ID, AD_Role_ID, AD_User_ID);
		if (error != null && error.length() > 0)
		{
			log.severe("Refused: " + error);
			return error;
		}
		return null;
	}	//	validateLogin
	public int compare_time_now_add_min(int minutes,Timestamp time){
		long m=minutes*60*1000;
		long t=time.getTime();
		Timestamp time_add_min=new Timestamp (t+m);
		java.util.Date date= new java.util.Date();
		Timestamp now = new Timestamp(date.getTime());
		return time_add_min.compareTo(now);
	}


4. Puntos a mejorar:

Que funcione con usuario System, mostrar mensaje para interfaz web