54 /** |
54 /** |
55 * Timeout in seconds for the validation test. |
55 * Timeout in seconds for the validation test. |
56 */ |
56 */ |
57 private static final int DB_TEST_TIMEOUT = 10; |
57 private static final int DB_TEST_TIMEOUT = 10; |
58 |
58 |
|
59 public static enum Dialect { |
|
60 Postgres; |
|
61 } |
|
62 |
|
63 /** |
|
64 * The database dialect to use. |
|
65 * |
|
66 * May be override by context parameter. |
|
67 * |
|
68 * @see Constants#CTX_ATTR_DB_DIALECT |
|
69 */ |
|
70 private Dialect dialect = Dialect.Postgres; |
|
71 |
59 /** |
72 /** |
60 * The default schema to test against when validating the connection. |
73 * The default schema to test against when validating the connection. |
61 * |
74 * |
62 * May be overridden by context parameter. |
75 * May be overridden by context parameter. |
|
76 * |
|
77 * @see Constants#CTX_ATTR_DB_SCHEMA |
63 */ |
78 */ |
64 private static final String DB_DEFAULT_SCHEMA = "lightpit"; |
79 private static final String DB_DEFAULT_SCHEMA = "lightpit"; |
65 |
80 |
66 /** |
81 /** |
67 * The attribute name in the servlet context under which an instance of this class can be found. |
82 * The attribute name in the Servlet context under which an instance of this class can be found. |
68 */ |
83 */ |
69 public static final String SC_ATTR_NAME = DatabaseFacade.class.getName(); |
84 public static final String SC_ATTR_NAME = DatabaseFacade.class.getName(); |
70 private ServletContext sc; |
85 private ServletContext sc; |
71 |
86 |
72 private static final String PRIVILEGED_DS_JNDI_NAME = "jdbc/lightpit/dbo"; |
87 private static final String DS_JNDI_NAME = "jdbc/lightpit/app"; |
73 private Optional<DataSource> privilegedDataSource; |
88 private Optional<DataSource> dataSource; |
74 |
89 |
75 private static final String UNPRIVILEGED_DS_JNDI_NAME = "jdbc/lightpit/app"; |
|
76 private Optional<DataSource> unprivilegedDataSource; |
|
77 |
|
78 |
|
79 /** |
90 /** |
80 * Returns an optional privileged data source. |
91 * Returns the data source. |
81 * |
|
82 * Privileged data sources should be able to execute any kind of DDL |
|
83 * statements to perform installation or configuration steps. |
|
84 * |
|
85 * This optional should always be empty in live operation. Modules which |
|
86 * provide installation or configuration steps MUST check the presence of |
|
87 * a privileged data source and SHOULD display an informative message if |
|
88 * it is currently disabled. |
|
89 * |
|
90 * @return an optional privileged data source |
|
91 */ |
|
92 public Optional<DataSource> getPrivilegedDataSource() { |
|
93 return privilegedDataSource; |
|
94 } |
|
95 |
|
96 /** |
|
97 * Returns an optional unprivileged data source. |
|
98 * |
92 * |
99 * The Optional returned should never be empty. However, if something goes |
93 * The Optional returned should never be empty. However, if something goes |
100 * wrong during initialization, the data source might be absent. |
94 * wrong during initialization, the data source might be absent. |
101 * Hence, users of this data source are forced to check the existence. |
95 * Hence, users of this data source are forced to check the existence. |
102 * |
96 * |
103 * @return an optional unprivileged data source |
97 * @return a data source |
104 */ |
98 */ |
105 public Optional<DataSource> getUnprivilegedDataSource() { |
99 public Optional<DataSource> getDataSource() { |
106 return unprivilegedDataSource; |
100 return dataSource; |
107 } |
101 } |
108 |
102 |
109 /** |
103 public Dialect getSQLDialect() { |
110 * Returns the JNDI resource name of the privileged data source. |
104 return dialect; |
111 * |
|
112 * Modules may use this information to provide useful information to the user. |
|
113 * |
|
114 * @return the JNDI resource name of the privileged data source |
|
115 */ |
|
116 public String getPrivilegedDataSourceJNDIName() { |
|
117 return PRIVILEGED_DS_JNDI_NAME; |
|
118 } |
|
119 |
|
120 /** |
|
121 * Returns the JNDI resource name of the unprivileged data source. |
|
122 * |
|
123 * Modules may use this information to provide useful information to the user. |
|
124 * |
|
125 * @return the JNDI resource name of the unprivileged data source |
|
126 */ |
|
127 public String getUnprivilegedDataSourceJNDIName() { |
|
128 return UNPRIVILEGED_DS_JNDI_NAME; |
|
129 } |
105 } |
130 |
106 |
131 private static void checkConnection(DataSource ds, String testSchema, String errMsg) { |
107 private static void checkConnection(DataSource ds, String testSchema, String errMsg) { |
132 try (Connection conn = ds.getConnection()) { |
108 try (Connection conn = ds.getConnection()) { |
133 if (!conn.isValid(DB_TEST_TIMEOUT)) { |
109 if (!conn.isValid(DB_TEST_TIMEOUT)) { |
144 } catch (SQLException ex) { |
120 } catch (SQLException ex) { |
145 LOG.error(errMsg, ex); |
121 LOG.error(errMsg, ex); |
146 } |
122 } |
147 } |
123 } |
148 |
124 |
149 private static Optional<DataSource> retrievePrivilegedDataSource(Context ctx) { |
125 private static Optional<DataSource> retrieveDataSource(Context ctx) { |
150 DataSource ret = null; |
126 DataSource ret = null; |
151 try { |
127 try { |
152 ret = (DataSource)ctx.lookup(PRIVILEGED_DS_JNDI_NAME); |
128 ret = (DataSource)ctx.lookup(DS_JNDI_NAME); |
153 LOG.info("Privileged data source {} retrieved from context.", PRIVILEGED_DS_JNDI_NAME); |
129 LOG.info("Data source retrieved."); |
154 LOG.warn("Your application may be vulnerable due to privileged database access. Make sure that privileged data sources are only available during installation or configuration."); |
|
155 } catch (NamingException ex) { |
130 } catch (NamingException ex) { |
156 LOG.info("Privileged data source not available. This is perfectly OK. Activate only, if you need to do installation or configuration."); |
131 LOG.error("Data source {} not available.", DS_JNDI_NAME); |
157 /* in case the absence of the DataSource is not intended, log something more useful on debug level */ |
|
158 LOG.debug("Reason for the missing data source: ", ex); |
|
159 } |
|
160 return Optional.ofNullable(ret); |
|
161 } |
|
162 |
|
163 private static Optional<DataSource> retrieveUnprivilegedDataSource(Context ctx) { |
|
164 DataSource ret = null; |
|
165 try { |
|
166 ret = (DataSource)ctx.lookup(UNPRIVILEGED_DS_JNDI_NAME); |
|
167 LOG.info("Unprivileged data source retrieved."); |
|
168 } catch (NamingException ex) { |
|
169 LOG.error("Unprivileged data source {} not available.", UNPRIVILEGED_DS_JNDI_NAME); |
|
170 /* for the unprivileged DataSource log the exception on error level (ordinary admins could find this useful) */ |
|
171 LOG.error("Reason for the missing data source: ", ex); |
132 LOG.error("Reason for the missing data source: ", ex); |
172 } |
133 } |
173 return Optional.ofNullable(ret); |
134 return Optional.ofNullable(ret); |
174 } |
135 } |
175 |
136 |
176 @Override |
137 @Override |
177 public void contextInitialized(ServletContextEvent sce) { |
138 public void contextInitialized(ServletContextEvent sce) { |
178 sc = sce.getServletContext(); |
139 sc = sce.getServletContext(); |
179 |
140 |
180 privilegedDataSource = unprivilegedDataSource = null; |
141 dataSource = null; |
181 |
142 |
182 final String contextName = Optional |
143 final String contextName = Optional |
183 .ofNullable(sc.getInitParameter(Constants.CTX_ATTR_JNDI_CONTEXT)) |
144 .ofNullable(sc.getInitParameter(Constants.CTX_ATTR_JNDI_CONTEXT)) |
184 .orElse("java:comp/env"); |
145 .orElse("java:comp/env"); |
185 final String dbSchema = Optional |
146 final String dbSchema = Optional |
186 .ofNullable(sc.getInitParameter(Constants.CTX_ATTR_DB_SCHEMA)) |
147 .ofNullable(sc.getInitParameter(Constants.CTX_ATTR_DB_SCHEMA)) |
187 .orElse(DB_DEFAULT_SCHEMA); |
148 .orElse(DB_DEFAULT_SCHEMA); |
|
149 final String dbDialect = sc.getInitParameter(Constants.CTX_ATTR_DB_DIALECT); |
|
150 if (dbDialect != null) { |
|
151 try { |
|
152 dialect = Dialect.valueOf(dbDialect); |
|
153 } catch (IllegalArgumentException ex) { |
|
154 LOG.error(String.format("Unknown or unsupported database dialect %s. Defaulting to %s.", dbDialect, dialect)); |
|
155 } |
|
156 } |
188 |
157 |
189 try { |
158 try { |
190 LOG.debug("Trying to access JNDI context {}...", contextName); |
159 LOG.debug("Trying to access JNDI context {}...", contextName); |
191 Context initialCtx = new InitialContext(); |
160 Context initialCtx = new InitialContext(); |
192 Context ctx = (Context) initialCtx.lookup(contextName); |
161 Context ctx = (Context) initialCtx.lookup(contextName); |
193 |
162 |
194 privilegedDataSource = retrievePrivilegedDataSource(ctx); |
163 dataSource = retrieveDataSource(ctx); |
195 unprivilegedDataSource = retrieveUnprivilegedDataSource(ctx); |
|
196 |
164 |
197 privilegedDataSource.ifPresent((ds) -> checkConnection(ds, dbSchema, "Checking privileged connection failed")); |
165 dataSource.ifPresent((ds) -> checkConnection(ds, dbSchema, "Checking database connection failed")); |
198 unprivilegedDataSource.ifPresent((ds) -> checkConnection(ds, dbSchema, "Checking unprivileged connection failed")); |
|
199 } catch (NamingException | ClassCastException ex) { |
166 } catch (NamingException | ClassCastException ex) { |
200 LOG.error("Cannot access JNDI resources.", ex); |
167 LOG.error("Cannot access JNDI resources.", ex); |
201 } |
168 } |
202 |
169 |
203 sc.setAttribute(SC_ATTR_NAME, this); |
170 sc.setAttribute(SC_ATTR_NAME, this); |
204 LOG.info("Database facade injected into ServletContext."); |
171 LOG.info("Database facade injected into ServletContext."); |
205 } |
172 } |
206 |
173 |
207 @Override |
174 @Override |
208 public void contextDestroyed(ServletContextEvent sce) { |
175 public void contextDestroyed(ServletContextEvent sce) { |
209 privilegedDataSource = unprivilegedDataSource = null; |
176 dataSource = null; |
210 } |
177 } |
211 } |
178 } |