Changeset 78:3b4abb1ec5a3
      
      
      
          - Timestamp:
- 02/08/12 13:15:23 (14 years ago)
- Author:
- František Kučera <franta-hg@…>
- Branch:
- default
- Message:
- 
            
            
              
Limit počtu řádků (10000) a doby provádění SQL dotazu (3 vteřiny) v pískovišti,
 nedokonalá ochrana proti DoS útoku (kartézský součin, náročný dotaz).
 
 
- Location:
- java/sql-vyuka/src/java/cz/frantovo/sql/vyuka/dao
- Files:
- 
          
          
 
        
          Legend:
          
            - Unmodified
- Added
- Removed
 
        
  
      - 
        
        
              
              
                
                  | r53 | r78 |  |  
                          | 15 | 15 | /** |  
                          | 16 | 16 | * Pro spouštění uživatelových příkazů. |  
                          |  | 17 | * |  
                          | 17 | 18 | * @author fiki |  
                          | 18 | 19 | */ |  
                          | 19 | 20 | public class PiskovisteDAO extends VyukaSuperDAO { |  
                          | 20 | 21 |  |  
                        | 21 |  | private enum VLASTNOSTI { |  
                        |  | 22 | /** maximální doba trvání SQL dotazu – vteřiny */ |  
                        |  | 23 | private static final int LIMIT_ČASU = 3; |  
                        |  | 24 | /** maximální počet řádků */ |  
                        |  | 25 | private static final int LIMIT_POČTU = 10000; |  
                          | 22 | 26 |  |  
                        | 23 |  | VYCHOZI_CESTA |  
                        | 24 |  | } |  
                        | 25 |  | TipyDAO tipy = new TipyDAO(); |  
                        | 26 |  | HistorieDAO historie = new HistorieDAO(); |  
                        |  | 27 | private enum VLASTNOSTI { |  
                          | 27 | 28 |  |  
                        | 28 |  | public VysledekSQL vykonejSQL(String sql, Uzivatel uzivatel) { |  
                        | 29 |  | VysledekSQL v = new VysledekSQL(); |  
                        | 30 |  | if (historie.ulozPrikaz(sql, uzivatel)) { |  
                        |  | 29 | VYCHOZI_CESTA, |  
                        |  | 30 | LIMIT_ČASU |  
                        |  | 31 | } |  
                        |  | 32 | TipyDAO tipy = new TipyDAO(); |  
                        |  | 33 | HistorieDAO historie = new HistorieDAO(); |  
                          | 31 | 34 |  |  
                        | 32 |  | Connection db = getSpojeni(DATABAZE.PISKOVISTE); |  
                        | 33 |  | if (db == null) { |  
                        | 34 |  | v.getHlasky().add(new Hlaska("Došlo k chybě spojení.", Typ.Chyba)); |  
                        | 35 |  | } else { |  
                        | 36 |  | PreparedStatement ps = null; |  
                        | 37 |  | ResultSet rs = null; |  
                        | 38 |  | try { |  
                        | 39 |  | /** |  
                        | 40 |  | * Uživatelskému SQL příkazu předřadíme výchozí cestu (search_path). |  
                        | 41 |  | * Protože uživatelé si ji mohou měnit a kvůli recyklaci databázových zdrojů |  
                        | 42 |  | * by jeden uživatel mohl ovlivnit jiného. |  
                        | 43 |  | */ |  
                        | 44 |  | if (getVlastnost(VLASTNOSTI.VYCHOZI_CESTA) != null) { |  
                        | 45 |  | sql = orizni(getVlastnost(VLASTNOSTI.VYCHOZI_CESTA)) + sql; |  
                        | 46 |  | } |  
                        |  | 35 | public VysledekSQL vykonejSQL(String sql, Uzivatel uzivatel) { |  
                        |  | 36 | VysledekSQL v = new VysledekSQL(); |  
                        |  | 37 | if (historie.ulozPrikaz(sql, uzivatel)) { |  
                          | 47 | 38 |  |  
                        | 48 |  | long casPred = System.currentTimeMillis(); |  
                        | 49 |  | ps = db.prepareStatement(sql); |  
                        | 50 |  | boolean isRS = ps.execute(); |  
                        |  | 39 | Connection db = getSpojeni(DATABAZE.PISKOVISTE); |  
                        |  | 40 | if (db == null) { |  
                        |  | 41 | v.getHlasky().add(new Hlaska("Došlo k chybě spojení.", Typ.Chyba)); |  
                        |  | 42 | } else { |  
                        |  | 43 | PreparedStatement ps = null; |  
                        |  | 44 | ResultSet rs = null; |  
                        |  | 45 | try { |  
                        |  | 46 | /** |  
                        |  | 47 | * Uživatelskému SQL příkazu předřadíme výchozí cestu (search_path). |  
                        |  | 48 | * Protože uživatelé si ji mohou měnit a kvůli recyklaci databázových zdrojů |  
                        |  | 49 | * by jeden uživatel mohl ovlivnit jiného. |  
                        |  | 50 | */ |  
                        |  | 51 | if (getVlastnost(VLASTNOSTI.VYCHOZI_CESTA) != null) { |  
                        |  | 52 | sql = orizni(getVlastnost(VLASTNOSTI.VYCHOZI_CESTA)) + sql; |  
                        |  | 53 | } |  
                        |  | 54 |  |  
                        |  | 55 | /** |  
                        |  | 56 | * TODO: |  
                        |  | 57 | * použít ps.setQueryTimeout(LIMIT_ČASU); |  
                        |  | 58 | * až ho bude podporovat JDBC ovladač, |  
                        |  | 59 | * viz níže. |  
                        |  | 60 | * Uživatel ale stejně může zadat: |  
                        |  | 61 | * SET statement_timeout 0; |  
                        |  | 62 | * do svého SQL dotazu. |  
                        |  | 63 | */ |  
                        |  | 64 | if (getVlastnost(VLASTNOSTI.LIMIT_ČASU) != null) { |  
                        |  | 65 | sql = orizni(getVlastnost(VLASTNOSTI.LIMIT_ČASU)) + sql; |  
                        |  | 66 | } |  
                          | 51 | 67 |  |  
                        | 52 |  | if (isRS) { |  
                        | 53 |  | rs = ps.getResultSet(); |  
                        | 54 |  | v.getTabulky().add(zpracujVysledek(rs)); |  
                        | 55 |  | } |  
                        |  | 68 | long casPred = System.currentTimeMillis(); |  
                        |  | 69 | ps = db.prepareStatement(sql); |  
                        |  | 70 | /** |  
                        |  | 71 | * Limit času bohužel není podporován JDBC ovladačem. |  
                        |  | 72 | * Alespoň ne v postgresql-9.1-901.jdbc4.jar |  
                        |  | 73 | * http://jdbc.postgresql.org/todo.html |  
                        |  | 74 | * |  
                        |  | 75 | * TODO: |  
                        |  | 76 | * ps.setQueryTimeout(LIMIT_ČASU); |  
                        |  | 77 | */ |  
                        |  | 78 | ps.setMaxRows(LIMIT_POČTU); |  
                        |  | 79 | boolean isRS = ps.execute(); |  
                          | 56 | 80 |  |  
                        | 57 |  | /** |  
                        | 58 |  | * Ošetříme případ, kdy uživatel zadá SQL příkaz, který nevrací výsledkovou sadu. |  
                        | 59 |  | * Typicky nastavení výchozího schématu: SET search_path = '…'; |  
                        | 60 |  | * Poznámka: jeden „SET search_path TO "…"“ se obvykle předřazuje uživatelskému SQL (viz PiskovisteDAO.xml). |  
                        | 61 |  | */ |  
                        | 62 |  | while (ps.getMoreResults() || ps.getUpdateCount() > -1) { |  
                        | 63 |  | rs = ps.getResultSet(); |  
                        | 64 |  | if (rs == null) { |  
                        | 65 |  | /** Jedná se o „update count“. */ |  
                        | 66 |  | } else { |  
                        | 67 |  | v.getTabulky().add(zpracujVysledek(rs)); |  
                        | 68 |  | } |  
                        | 69 |  | } |  
                        | 70 |  | long dobaProvadeni = System.currentTimeMillis() - casPred; |  
                        |  | 81 | if (isRS) { |  
                        |  | 82 | rs = ps.getResultSet(); |  
                        |  | 83 | v.getTabulky().add(zpracujVysledek(rs)); |  
                        |  | 84 | } |  
                          | 71 | 85 |  |  
                        | 72 |  | /** Varování */ |  
                        | 73 |  | if (v.getHlasky().size() < 1 && v.getTabulky().size() < 1) { |  
                        | 74 |  | v.getHlasky().add(new Hlaska("SQL příkaz proběhl, ale nevrátil žádná data.", Typ.Varovani)); |  
                        | 75 |  | } |  
                        |  | 86 | /** |  
                        |  | 87 | * Ošetříme případ, kdy uživatel zadá SQL příkaz, který nevrací výsledkovou |  
                        |  | 88 | * sadu. |  
                        |  | 89 | * Typicky nastavení výchozího schématu: SET search_path = '…'; |  
                        |  | 90 | * Poznámka: jeden „SET search_path TO "…"“ se obvykle předřazuje uživatelskému |  
                        |  | 91 | * SQL (viz PiskovisteDAO.xml). |  
                        |  | 92 | */ |  
                        |  | 93 | while (ps.getMoreResults() || ps.getUpdateCount() > -1) { |  
                        |  | 94 | rs = ps.getResultSet(); |  
                        |  | 95 | if (rs == null) { |  
                        |  | 96 | /** Jedná se o „update count“. */ |  
                        |  | 97 | } else { |  
                        |  | 98 | v.getTabulky().add(zpracujVysledek(rs)); |  
                        |  | 99 | } |  
                        |  | 100 | } |  
                        |  | 101 | long dobaProvadeni = System.currentTimeMillis() - casPred; |  
                          | 76 | 102 |  |  
                        | 77 |  | /** Varování */ |  
                        | 78 |  | int pocitadloTabulek = 1; |  
                        | 79 |  | for (Tabulka t : v.getTabulky()) { |  
                        | 80 |  | if (t.getHodnoty().size() < 1) { |  
                        | 81 |  | v.getHlasky().add(new Hlaska("Tabulka " + pocitadloTabulek + "  je prázdná.", Typ.Varovani)); |  
                        | 82 |  | } |  
                        | 83 |  | pocitadloTabulek++; |  
                        | 84 |  | } |  
                        |  | 103 | /** Varování */ |  
                        |  | 104 | if (v.getHlasky().size() < 1 && v.getTabulky().size() < 1) { |  
                        |  | 105 | v.getHlasky().add(new Hlaska("SQL příkaz proběhl, ale nevrátil žádná data.", Typ.Varovani)); |  
                        |  | 106 | } |  
                          | 85 | 107 |  |  
                        | 86 |  | v.getHlasky().add(new Hlaska("SQL příkaz byl proveden úspěšně, během " + dobaProvadeni + " ms.", Typ.OK)); |  
                        |  | 108 | /** Varování */ |  
                        |  | 109 | int pocitadloTabulek = 1; |  
                        |  | 110 | for (Tabulka t : v.getTabulky()) { |  
                        |  | 111 | if (t.getHodnoty().size() < 1) { |  
                        |  | 112 | v.getHlasky().add(new Hlaska("Tabulka " + pocitadloTabulek + "  je prázdná.", Typ.Varovani)); |  
                        |  | 113 | } |  
                        |  | 114 | pocitadloTabulek++; |  
                        |  | 115 | } |  
                          | 87 | 116 |  |  
                        | 88 |  | } catch (SQLException e) { |  
                        | 89 |  | log.log(Level.SEVERE, "SQL chyba při vykonávání uživatelského dotazu.", e); |  
                        | 90 |  | v.getHlasky().add(new Hlaska("Chybné SQL: " + e.getMessage(), Typ.Chyba)); |  
                        | 91 |  | } catch (Exception e) { |  
                        | 92 |  | log.log(Level.SEVERE, "Chyba při vykonávání uživatelského dotazu.", e); |  
                        | 93 |  | v.getHlasky().add(new Hlaska("Došlo k chybě dotazu.", Typ.Chyba)); |  
                        | 94 |  | } finally { |  
                        | 95 |  | zavri(db, ps, rs); |  
                        | 96 |  | } |  
                        | 97 |  | } |  
                        |  | 117 | v.getHlasky().add(new Hlaska("SQL příkaz byl proveden úspěšně, během " + dobaProvadeni + " ms.", Typ.OK)); |  
                          | 98 | 118 |  |  
                        | 99 |  | /** Tip pro uživatele */ |  
                        | 100 |  | String tip = tipy.getTip(); |  
                        | 101 |  | if (tip != null) { |  
                        | 102 |  | v.getHlasky().add(new Hlaska(tip, Typ.Tip, false)); |  
                        | 103 |  | } |  
                        |  | 119 | } catch (SQLException e) { |  
                        |  | 120 | log.log(Level.SEVERE, "SQL chyba při vykonávání uživatelského dotazu.", e); |  
                        |  | 121 | v.getHlasky().add(new Hlaska("Chybné SQL: " + e.getMessage(), Typ.Chyba)); |  
                        |  | 122 | } catch (Exception e) { |  
                        |  | 123 | log.log(Level.SEVERE, "Chyba při vykonávání uživatelského dotazu.", e); |  
                        |  | 124 | v.getHlasky().add(new Hlaska("Došlo k chybě dotazu.", Typ.Chyba)); |  
                        |  | 125 | } finally { |  
                        |  | 126 | zavri(db, ps, rs); |  
                        |  | 127 | } |  
                        |  | 128 | } |  
                          | 104 | 129 |  |  
                        | 105 |  | } else { |  
                        | 106 |  |             v.getHlasky().add(new Hlaska("Došlo k chybě historie.", Typ.Chyba)); |  
                        | 107 |  | } |  
                        | 108 |  |         return v; |  
                        | 109 |  |     } |  
                        |  | 130 | /** Tip pro uživatele */ |  
                        |  | 131 | String tip = tipy.getTip(); |  
                        |  | 132 | if (tip != null) { |  
                        |  | 133 | v.getHlasky().add(new Hlaska(tip, Typ.Tip, false)); |  
                        |  | 134 | } |  
                          | 110 | 135 |  |  
                        | 111 |  | private Tabulka zpracujVysledek(ResultSet rs) throws SQLException { |  
                        | 112 |  | Tabulka t = new Tabulka(); |  
                        |  | 136 | } else { |  
                        |  | 137 | v.getHlasky().add(new Hlaska("Došlo k chybě historie.", Typ.Chyba)); |  
                        |  | 138 | } |  
                        |  | 139 | return v; |  
                        |  | 140 | } |  
                          | 113 | 141 |  |  
                        | 114 |  | int pocetSloupecku = rs.getMetaData().getColumnCount(); |  
                        | 115 |  | String[] zahlavi = new String[pocetSloupecku]; |  
                        | 116 |  | t.setZahlavi(zahlavi); |  
                        | 117 |  | for (int i = 0; i < pocetSloupecku; i++) { |  
                        | 118 |  | zahlavi[i] = rs.getMetaData().getColumnName(i + 1); |  
                        | 119 |  | } |  
                        |  | 142 | private Tabulka zpracujVysledek(ResultSet rs) throws SQLException { |  
                        |  | 143 | Tabulka t = new Tabulka(); |  
                          | 120 | 144 |  |  
                        | 121 |  | while (rs.next()) { |  
                        | 122 |  | Object[] hodnoty = new Object[pocetSloupecku]; |  
                        | 123 |  | for (int i = 0; i < pocetSloupecku; i++) { |  
                        | 124 |  | hodnoty[i] = rs.getObject(i + 1); |  
                        | 125 |  | } |  
                        | 126 |  | t.getHodnoty().add(hodnoty); |  
                        | 127 |  | } |  
                        |  | 145 | int pocetSloupecku = rs.getMetaData().getColumnCount(); |  
                        |  | 146 | String[] zahlavi = new String[pocetSloupecku]; |  
                        |  | 147 | t.setZahlavi(zahlavi); |  
                        |  | 148 | for (int i = 0; i < pocetSloupecku; i++) { |  
                        |  | 149 | zahlavi[i] = rs.getMetaData().getColumnName(i + 1); |  
                        |  | 150 | } |  
                          | 128 | 151 |  |  
                        | 129 |  | return t; |  
                        | 130 |  | } |  
                        |  | 152 | while (rs.next()) { |  
                        |  | 153 | Object[] hodnoty = new Object[pocetSloupecku]; |  
                        |  | 154 | for (int i = 0; i < pocetSloupecku; i++) { |  
                        |  | 155 | hodnoty[i] = rs.getObject(i + 1); |  
                        |  | 156 | } |  
                        |  | 157 | t.getHodnoty().add(hodnoty); |  
                        |  | 158 | } |  
                        |  | 159 |  |  
                        |  | 160 | return t; |  
                        |  | 161 | } |  
                          | 131 | 162 | } |  
 
- 
        
        
              
              
                
                  | r18 | r78 |  |  
                          | 3 | 3 | <properties> |  
                          | 4 | 4 | <!-- |  
                        | 5 |  |     PostgreSQL proměnná „search_path“ – nastavíme ji před každým uživatelským SQL dotazem, |  
                        | 6 |  |     aby se uživatelé vzájemně neovlivňovali. |  
                        |  | 5 | PostgreSQL proměnná „search_path“ – nastavíme ji před každým uživatelským SQL dotazem, |  
                        |  | 6 | aby se uživatelé vzájemně neovlivňovali. |  
                          | 7 | 7 | --> |  
                        | 8 |  |     <entry key="VYCHOZI_CESTA"> |  
                        |  | 8 | <entry key="VYCHOZI_CESTA"> |  
                          | 9 | 9 | <![CDATA[ |  
                          | 10 | 10 | SET search_path TO "$user",public; |  
                          | 11 | 11 | ]]> |  
                        | 12 |  | </entry> |  
                        |  | 12 | </entry> |  
                        |  | 13 | <!-- |  
                        |  | 14 | Limit (vteřiny) pro vykonání jednoho SQL příkazu. |  
                        |  | 15 | TODO: |  
                        |  | 16 | použít ps.setQueryTimeout(LIMIT_ČASU); |  
                        |  | 17 | až ho bude podporovat JDBC ovladač. |  
                        |  | 18 | --> |  
                        |  | 19 | <entry key="LIMIT_ČASU"> |  
                        |  | 20 | <![CDATA[ |  
                        |  | 21 | SET statement_timeout TO 3; |  
                        |  | 22 | ]]> |  
                        |  | 23 | </entry> |  
                          | 13 | 24 | </properties> |