]> git.argeo.org Git - lgpl/argeo-commons.git/blob - org.argeo.cms/src/org/argeo/cms/internal/kernel/InitUtils.java
Merge remote-tracking branch 'origin/unstable' into testing
[lgpl/argeo-commons.git] / org.argeo.cms / src / org / argeo / cms / internal / kernel / InitUtils.java
1 package org.argeo.cms.internal.kernel;
2
3 import static org.argeo.cms.internal.kernel.KernelUtils.getFrameworkProp;
4
5 import java.io.File;
6 import java.io.FileFilter;
7 import java.io.IOException;
8 import java.io.Reader;
9 import java.net.InetAddress;
10 import java.net.URI;
11 import java.net.URISyntaxException;
12 import java.nio.charset.StandardCharsets;
13 import java.nio.file.Files;
14 import java.nio.file.Path;
15 import java.security.KeyStore;
16 import java.util.ArrayList;
17 import java.util.Arrays;
18 import java.util.Dictionary;
19 import java.util.HashMap;
20 import java.util.Hashtable;
21 import java.util.List;
22 import java.util.Map;
23
24 import javax.jcr.Repository;
25 import javax.jcr.RepositoryException;
26 import javax.jcr.RepositoryFactory;
27 import javax.security.auth.x500.X500Principal;
28
29 import org.apache.commons.io.FileUtils;
30 import org.apache.commons.logging.Log;
31 import org.apache.commons.logging.LogFactory;
32 import org.argeo.api.NodeConstants;
33 import org.argeo.cms.internal.http.InternalHttpConstants;
34 import org.argeo.cms.internal.jcr.RepoConf;
35 import org.argeo.jackrabbit.client.ClientDavexRepositoryFactory;
36 import org.argeo.jcr.JcrException;
37 import org.argeo.naming.LdapAttrs;
38 import org.argeo.osgi.useradmin.UserAdminConf;
39 import org.osgi.framework.BundleContext;
40 import org.osgi.framework.Constants;
41
42 /**
43 * Interprets framework properties in order to generate the initial deploy
44 * configuration.
45 */
46 class InitUtils {
47 private final static Log log = LogFactory.getLog(InitUtils.class);
48
49 /** Override the provided config with the framework properties */
50 static Dictionary<String, Object> getNodeRepositoryConfig(Dictionary<String, Object> provided) {
51 Dictionary<String, Object> props = provided != null ? provided : new Hashtable<String, Object>();
52 for (RepoConf repoConf : RepoConf.values()) {
53 Object value = getFrameworkProp(NodeConstants.NODE_REPO_PROP_PREFIX + repoConf.name());
54 if (value != null) {
55 props.put(repoConf.name(), value);
56 if (log.isDebugEnabled())
57 log.debug("Set node repo configuration " + repoConf.name() + " to " + value);
58 }
59 }
60 props.put(NodeConstants.CN, NodeConstants.NODE_REPOSITORY);
61 return props;
62 }
63
64 static Dictionary<String, Object> getRepositoryConfig(String dataModelName, Dictionary<String, Object> provided) {
65 if (dataModelName.equals(NodeConstants.NODE_REPOSITORY) || dataModelName.equals(NodeConstants.EGO_REPOSITORY))
66 throw new IllegalArgumentException("Data model '" + dataModelName + "' is reserved.");
67 Dictionary<String, Object> props = provided != null ? provided : new Hashtable<String, Object>();
68 for (RepoConf repoConf : RepoConf.values()) {
69 Object value = getFrameworkProp(
70 NodeConstants.NODE_REPOS_PROP_PREFIX + dataModelName + '.' + repoConf.name());
71 if (value != null) {
72 props.put(repoConf.name(), value);
73 if (log.isDebugEnabled())
74 log.debug("Set " + dataModelName + " repo configuration " + repoConf.name() + " to " + value);
75 }
76 }
77 if (props.size() != 0)
78 props.put(NodeConstants.CN, dataModelName);
79 return props;
80 }
81
82 /** Override the provided config with the framework properties */
83 static Dictionary<String, Object> getHttpServerConfig(Dictionary<String, Object> provided) {
84 String httpPort = getFrameworkProp("org.osgi.service.http.port");
85 String httpsPort = getFrameworkProp("org.osgi.service.http.port.secure");
86 /// TODO make it more generic
87 String httpHost = getFrameworkProp(
88 InternalHttpConstants.JETTY_PROPERTY_PREFIX + InternalHttpConstants.HTTP_HOST);
89 String httpsHost = getFrameworkProp(
90 InternalHttpConstants.JETTY_PROPERTY_PREFIX + InternalHttpConstants.HTTPS_HOST);
91 String webSocketEnabled = getFrameworkProp(
92 InternalHttpConstants.JETTY_PROPERTY_PREFIX + InternalHttpConstants.WEBSOCKET_ENABLED);
93
94 final Hashtable<String, Object> props = new Hashtable<String, Object>();
95 // try {
96 if (httpPort != null || httpsPort != null) {
97 boolean httpEnabled = httpPort != null;
98 props.put(InternalHttpConstants.HTTP_ENABLED, httpEnabled);
99 boolean httpsEnabled = httpsPort != null;
100 props.put(InternalHttpConstants.HTTPS_ENABLED, httpsEnabled);
101
102 if (httpEnabled) {
103 props.put(InternalHttpConstants.HTTP_PORT, httpPort);
104 if (httpHost != null)
105 props.put(InternalHttpConstants.HTTP_HOST, httpHost);
106 }
107
108 if (httpsEnabled) {
109 props.put(InternalHttpConstants.HTTPS_PORT, httpsPort);
110 if (httpsHost != null)
111 props.put(InternalHttpConstants.HTTPS_HOST, httpsHost);
112
113 // server certificate
114 Path keyStorePath = KernelUtils.getOsgiInstancePath(KernelConstants.DEFAULT_KEYSTORE_PATH);
115 Path pemKeyPath = KernelUtils.getOsgiInstancePath(KernelConstants.DEFAULT_PEM_KEY_PATH);
116 Path pemCertPath = KernelUtils.getOsgiInstancePath(KernelConstants.DEFAULT_PEM_CERT_PATH);
117 String keyStorePasswordStr = getFrameworkProp(
118 InternalHttpConstants.JETTY_PROPERTY_PREFIX + InternalHttpConstants.SSL_PASSWORD);
119 char[] keyStorePassword;
120 if (keyStorePasswordStr == null)
121 keyStorePassword = "changeit".toCharArray();
122 else
123 keyStorePassword = keyStorePasswordStr.toCharArray();
124
125 // if PEM files both exists, update the PKCS12 file
126 if (Files.exists(pemCertPath) && Files.exists(pemKeyPath)) {
127 // TODO check certificate update time? monitor changes?
128 KeyStore keyStore = PkiUtils.getKeyStore(keyStorePath, keyStorePassword, PkiUtils.PKCS12);
129 try (Reader key = Files.newBufferedReader(pemKeyPath, StandardCharsets.US_ASCII);
130 Reader cert = Files.newBufferedReader(pemCertPath, StandardCharsets.US_ASCII);) {
131 PkiUtils.loadPem(keyStore, key, keyStorePassword, cert);
132 PkiUtils.saveKeyStore(keyStorePath, keyStorePassword, keyStore);
133 if (log.isDebugEnabled())
134 log.debug("PEM certificate stored in " + keyStorePath);
135 } catch (IOException e) {
136 log.error("Cannot read PEM files " + pemKeyPath + " and " + pemCertPath, e);
137 }
138 }
139
140 if (!Files.exists(keyStorePath))
141 createSelfSignedKeyStore(keyStorePath, keyStorePassword, PkiUtils.PKCS12);
142 props.put(InternalHttpConstants.SSL_KEYSTORETYPE, PkiUtils.PKCS12);
143 props.put(InternalHttpConstants.SSL_KEYSTORE, keyStorePath.toString());
144 props.put(InternalHttpConstants.SSL_PASSWORD, new String(keyStorePassword));
145
146 // props.put(InternalHttpConstants.SSL_KEYSTORETYPE, "PKCS11");
147 // props.put(InternalHttpConstants.SSL_KEYSTORE, "../../nssdb");
148 // props.put(InternalHttpConstants.SSL_PASSWORD, keyStorePassword);
149
150 // client certificate authentication
151 String wantClientAuth = getFrameworkProp(
152 InternalHttpConstants.JETTY_PROPERTY_PREFIX + InternalHttpConstants.SSL_WANTCLIENTAUTH);
153 if (wantClientAuth != null)
154 props.put(InternalHttpConstants.SSL_WANTCLIENTAUTH, Boolean.parseBoolean(wantClientAuth));
155 String needClientAuth = getFrameworkProp(
156 InternalHttpConstants.JETTY_PROPERTY_PREFIX + InternalHttpConstants.SSL_NEEDCLIENTAUTH);
157 if (needClientAuth != null)
158 props.put(InternalHttpConstants.SSL_NEEDCLIENTAUTH, Boolean.parseBoolean(needClientAuth));
159 }
160
161 // web socket
162 if (webSocketEnabled != null && webSocketEnabled.equals("true"))
163 props.put(InternalHttpConstants.WEBSOCKET_ENABLED, true);
164
165 props.put(NodeConstants.CN, NodeConstants.DEFAULT);
166 }
167 return props;
168 }
169
170 static List<Dictionary<String, Object>> getUserDirectoryConfigs() {
171 List<Dictionary<String, Object>> res = new ArrayList<>();
172 File nodeBaseDir = KernelUtils.getOsgiInstancePath(KernelConstants.DIR_NODE).toFile();
173 List<String> uris = new ArrayList<>();
174
175 // node roles
176 String nodeRolesUri = getFrameworkProp(NodeConstants.ROLES_URI);
177 String baseNodeRoleDn = NodeConstants.ROLES_BASEDN;
178 if (nodeRolesUri == null) {
179 nodeRolesUri = baseNodeRoleDn + ".ldif";
180 File nodeRolesFile = new File(nodeBaseDir, nodeRolesUri);
181 if (!nodeRolesFile.exists())
182 try {
183 FileUtils.copyInputStreamToFile(InitUtils.class.getResourceAsStream(baseNodeRoleDn + ".ldif"),
184 nodeRolesFile);
185 } catch (IOException e) {
186 throw new RuntimeException("Cannot copy demo resource", e);
187 }
188 // nodeRolesUri = nodeRolesFile.toURI().toString();
189 }
190 uris.add(nodeRolesUri);
191
192 // node tokens
193 String nodeTokensUri = getFrameworkProp(NodeConstants.TOKENS_URI);
194 String baseNodeTokensDn = NodeConstants.TOKENS_BASEDN;
195 if (nodeTokensUri == null) {
196 nodeTokensUri = baseNodeTokensDn + ".ldif";
197 File nodeTokensFile = new File(nodeBaseDir, nodeTokensUri);
198 if (!nodeTokensFile.exists())
199 try {
200 FileUtils.copyInputStreamToFile(InitUtils.class.getResourceAsStream(baseNodeTokensDn + ".ldif"),
201 nodeTokensFile);
202 } catch (IOException e) {
203 throw new RuntimeException("Cannot copy demo resource", e);
204 }
205 // nodeRolesUri = nodeRolesFile.toURI().toString();
206 }
207 uris.add(nodeTokensUri);
208
209 // Business roles
210 String userAdminUris = getFrameworkProp(NodeConstants.USERADMIN_URIS);
211 if (userAdminUris == null) {
212 String demoBaseDn = "dc=example,dc=com";
213 userAdminUris = demoBaseDn + ".ldif";
214 File businessRolesFile = new File(nodeBaseDir, userAdminUris);
215 File systemRolesFile = new File(nodeBaseDir, "ou=roles,ou=node.ldif");
216 if (!businessRolesFile.exists())
217 try {
218 FileUtils.copyInputStreamToFile(InitUtils.class.getResourceAsStream(demoBaseDn + ".ldif"),
219 businessRolesFile);
220 if (!systemRolesFile.exists())
221 FileUtils.copyInputStreamToFile(
222 InitUtils.class.getResourceAsStream("example-ou=roles,ou=node.ldif"), systemRolesFile);
223 } catch (IOException e) {
224 throw new RuntimeException("Cannot copy demo resources", e);
225 }
226 // userAdminUris = businessRolesFile.toURI().toString();
227 log.warn("## DEV Using dummy base DN " + demoBaseDn);
228 // TODO downgrade security level
229 }
230 for (String userAdminUri : userAdminUris.split(" "))
231 uris.add(userAdminUri);
232
233 // Interprets URIs
234 for (String uri : uris) {
235 URI u;
236 try {
237 u = new URI(uri);
238 if (u.getPath() == null)
239 throw new IllegalArgumentException(
240 "URI " + uri + " must have a path in order to determine base DN");
241 if (u.getScheme() == null) {
242 if (uri.startsWith("/") || uri.startsWith("./") || uri.startsWith("../"))
243 u = new File(uri).getCanonicalFile().toURI();
244 else if (!uri.contains("/")) {
245 // u = KernelUtils.getOsgiInstanceUri(KernelConstants.DIR_NODE + '/' + uri);
246 u = new URI(uri);
247 } else
248 throw new IllegalArgumentException("Cannot interpret " + uri + " as an uri");
249 } else if (u.getScheme().equals(UserAdminConf.SCHEME_FILE)) {
250 u = new File(u).getCanonicalFile().toURI();
251 }
252 } catch (Exception e) {
253 throw new RuntimeException("Cannot interpret " + uri + " as an uri", e);
254 }
255 Dictionary<String, Object> properties = UserAdminConf.uriAsProperties(u.toString());
256 res.add(properties);
257 }
258
259 return res;
260 }
261
262 /**
263 * Called before node initialisation, in order populate OSGi instance are with
264 * some files (typically LDIF, etc).
265 */
266 static void prepareFirstInitInstanceArea() {
267 String nodeInits = getFrameworkProp(NodeConstants.NODE_INIT);
268 if (nodeInits == null)
269 nodeInits = "../../init";
270
271 for (String nodeInit : nodeInits.split(",")) {
272
273 if (nodeInit.startsWith("http")) {
274 registerRemoteInit(nodeInit);
275 } else {
276
277 // TODO use java.nio.file
278 File initDir;
279 if (nodeInit.startsWith("."))
280 initDir = KernelUtils.getExecutionDir(nodeInit);
281 else
282 initDir = new File(nodeInit);
283 // TODO also uncompress archives
284 if (initDir.exists())
285 try {
286 FileUtils.copyDirectory(initDir, KernelUtils.getOsgiInstanceDir(), new FileFilter() {
287
288 @Override
289 public boolean accept(File pathname) {
290 if (pathname.getName().equals(".svn") || pathname.getName().equals(".git"))
291 return false;
292 return true;
293 }
294 });
295 log.info("CMS initialized from " + initDir.getCanonicalPath());
296 } catch (IOException e) {
297 throw new RuntimeException("Cannot initialize from " + initDir, e);
298 }
299 }
300 }
301 }
302
303 private static void registerRemoteInit(String uri) {
304 try {
305 BundleContext bundleContext = KernelUtils.getBundleContext();
306 Repository repository = createRemoteRepository(new URI(uri));
307 Hashtable<String, Object> properties = new Hashtable<>();
308 properties.put(NodeConstants.CN, NodeConstants.NODE_INIT);
309 properties.put(LdapAttrs.labeledURI.name(), uri);
310 properties.put(Constants.SERVICE_RANKING, -1000);
311 bundleContext.registerService(Repository.class, repository, properties);
312 } catch (RepositoryException e) {
313 throw new JcrException(e);
314 } catch (URISyntaxException e) {
315 throw new IllegalArgumentException(e);
316 }
317 }
318
319 private static Repository createRemoteRepository(URI uri) throws RepositoryException {
320 RepositoryFactory repositoryFactory = new ClientDavexRepositoryFactory();
321 Map<String, String> params = new HashMap<String, String>();
322 params.put(ClientDavexRepositoryFactory.JACKRABBIT_DAVEX_URI, uri.toString());
323 // TODO make it configurable
324 params.put(ClientDavexRepositoryFactory.JACKRABBIT_REMOTE_DEFAULT_WORKSPACE, NodeConstants.SYS_WORKSPACE);
325 return repositoryFactory.getRepository(params);
326 }
327
328 private static void createSelfSignedKeyStore(Path keyStorePath, char[] keyStorePassword, String keyStoreType) {
329 // for (Provider provider : Security.getProviders())
330 // System.out.println(provider.getName());
331 // File keyStoreFile = keyStorePath.toFile();
332 char[] keyPwd = Arrays.copyOf(keyStorePassword, keyStorePassword.length);
333 if (!Files.exists(keyStorePath)) {
334 try {
335 Files.createDirectories(keyStorePath.getParent());
336 KeyStore keyStore = PkiUtils.getKeyStore(keyStorePath, keyStorePassword, keyStoreType);
337 PkiUtils.generateSelfSignedCertificate(keyStore,
338 new X500Principal("CN=" + InetAddress.getLocalHost().getHostName() + ",OU=UNSECURE,O=UNSECURE"),
339 1024, keyPwd);
340 PkiUtils.saveKeyStore(keyStorePath, keyStorePassword, keyStore);
341 if (log.isDebugEnabled())
342 log.debug("Created self-signed unsecure keystore " + keyStorePath);
343 } catch (Exception e) {
344 try {
345 if (Files.size(keyStorePath) == 0)
346 Files.delete(keyStorePath);
347 } catch (IOException e1) {
348 // silent
349 }
350 log.error("Cannot create keystore " + keyStorePath, e);
351 }
352 } else {
353 throw new IllegalStateException("Keystore " + keyStorePath + " already exists");
354 }
355 }
356
357 }