001 /**
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements. See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License. You may obtain a copy of the License at
008 *
009 * http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017 package org.apache.geronimo.console.securitymanager.realm;
018
019 import java.io.BufferedReader;
020 import java.io.File;
021 import java.io.FileWriter;
022 import java.io.IOException;
023 import java.io.PrintWriter;
024 import java.io.Serializable;
025 import java.io.StringReader;
026 import java.io.StringWriter;
027 import java.io.Writer;
028 import java.net.MalformedURLException;
029 import java.net.URI;
030 import java.net.URL;
031 import java.net.URLClassLoader;
032 import java.util.ArrayList;
033 import java.util.Arrays;
034 import java.util.Collections;
035 import java.util.HashMap;
036 import java.util.Iterator;
037 import java.util.LinkedHashMap;
038 import java.util.List;
039 import java.util.Map;
040 import java.util.Set;
041 import java.util.SortedSet;
042
043 import javax.enterprise.deploy.spi.DeploymentManager;
044 import javax.enterprise.deploy.spi.Target;
045 import javax.enterprise.deploy.spi.TargetModuleID;
046 import javax.enterprise.deploy.spi.status.ProgressObject;
047 import javax.management.MalformedObjectNameException;
048 import javax.management.ObjectName;
049 import javax.portlet.ActionRequest;
050 import javax.portlet.ActionResponse;
051 import javax.portlet.PortletConfig;
052 import javax.portlet.PortletException;
053 import javax.portlet.PortletRequest;
054 import javax.portlet.PortletRequestDispatcher;
055 import javax.portlet.PortletSession;
056 import javax.portlet.RenderRequest;
057 import javax.portlet.RenderResponse;
058 import javax.portlet.WindowState;
059 import javax.security.auth.Subject;
060 import javax.security.auth.spi.LoginModule;
061 import javax.xml.namespace.QName;
062
063 import org.apache.commons.logging.Log;
064 import org.apache.commons.logging.LogFactory;
065 import org.apache.geronimo.console.BasePortlet;
066 import org.apache.geronimo.console.car.ManagementHelper;
067 import org.apache.geronimo.console.util.PortletManager;
068 import org.apache.geronimo.deployment.xbeans.AbstractServiceType;
069 import org.apache.geronimo.deployment.xbeans.ArtifactType;
070 import org.apache.geronimo.deployment.xbeans.AttributeType;
071 import org.apache.geronimo.deployment.xbeans.DependenciesType;
072 import org.apache.geronimo.deployment.xbeans.EnvironmentType;
073 import org.apache.geronimo.deployment.xbeans.GbeanType;
074 import org.apache.geronimo.deployment.xbeans.ModuleDocument;
075 import org.apache.geronimo.deployment.xbeans.ModuleType;
076 import org.apache.geronimo.deployment.xbeans.ReferenceType;
077 import org.apache.geronimo.deployment.xbeans.ServiceDocument;
078 import org.apache.geronimo.deployment.xbeans.XmlAttributeType;
079 import org.apache.geronimo.gbean.AbstractName;
080 import org.apache.geronimo.j2ee.j2eeobjectnames.NameFactory;
081 import org.apache.geronimo.kernel.Kernel;
082 import org.apache.geronimo.kernel.KernelRegistry;
083 import org.apache.geronimo.kernel.config.Configuration;
084 import org.apache.geronimo.kernel.config.ConfigurationManager;
085 import org.apache.geronimo.kernel.config.ConfigurationModuleType;
086 import org.apache.geronimo.kernel.config.ConfigurationUtil;
087 import org.apache.geronimo.kernel.repository.Artifact;
088 import org.apache.geronimo.kernel.repository.ListableRepository;
089 import org.apache.geronimo.management.geronimo.JCAManagedConnectionFactory;
090 import org.apache.geronimo.security.jaas.JaasLoginModuleChain;
091 import org.apache.geronimo.security.jaas.JaasLoginModuleUse;
092 import org.apache.geronimo.security.jaas.LoginModuleSettings;
093 import org.apache.geronimo.security.jaas.LoginModuleControlFlag;
094 import org.apache.geronimo.security.jaas.LoginModuleControlFlagEditor;
095 import org.apache.geronimo.security.realm.SecurityRealm;
096 import org.apache.geronimo.security.realm.providers.FileAuditLoginModule;
097 import org.apache.geronimo.security.realm.providers.GeronimoPasswordCredentialLoginModule;
098 import org.apache.geronimo.security.realm.providers.NamedUsernamePasswordCredentialLoginModule;
099 import org.apache.geronimo.security.realm.providers.RepeatedFailureLockoutLoginModule;
100 import org.apache.geronimo.xbeans.geronimo.loginconfig.GerControlFlagType;
101 import org.apache.geronimo.xbeans.geronimo.loginconfig.GerLoginConfigDocument;
102 import org.apache.geronimo.xbeans.geronimo.loginconfig.GerLoginConfigType;
103 import org.apache.geronimo.xbeans.geronimo.loginconfig.GerLoginModuleType;
104 import org.apache.geronimo.xbeans.geronimo.loginconfig.GerOptionType;
105 import org.apache.xmlbeans.XmlCursor;
106 import org.apache.xmlbeans.XmlObject;
107 import org.apache.xmlbeans.XmlOptions;
108
109 /**
110 * A portlet that lists, creates, and edits security realms.
111 *
112 * @version $Rev: 706640 $ $Date: 2008-10-21 14:44:05 +0000 (Tue, 21 Oct 2008) $
113 */
114 public class SecurityRealmPortlet extends BasePortlet {
115 private final static Log log = LogFactory.getLog(SecurityRealmPortlet.class);
116 private final static String[] SKIP_ENTRIES_WITH = new String[]{"geronimo", "tomcat", "tranql", "commons", "directory", "activemq"};
117 private static final String LIST_VIEW = "/WEB-INF/view/realmwizard/list.jsp";
118 private static final String EDIT_VIEW = "/WEB-INF/view/realmwizard/edit.jsp";
119 private static final String SELECT_TYPE_VIEW = "/WEB-INF/view/realmwizard/selectType.jsp";
120 private static final String CONFIGURE_VIEW = "/WEB-INF/view/realmwizard/configure.jsp";
121 private static final String ADVANCED_VIEW = "/WEB-INF/view/realmwizard/advanced.jsp";
122 private static final String TEST_LOGIN_VIEW = "/WEB-INF/view/realmwizard/testLogin.jsp";
123 private static final String TEST_RESULTS_VIEW = "/WEB-INF/view/realmwizard/testResults.jsp";
124 private static final String SHOW_PLAN_VIEW = "/WEB-INF/view/realmwizard/showPlan.jsp";
125 private static final String USAGE_VIEW = "/WEB-INF/view/realmwizard/usage.jsp";
126 private static final String LIST_MODE = "list";
127 private static final String EDIT_MODE = "edit";
128 private static final String SELECT_TYPE_MODE = "type";
129 private static final String CONFIGURE_MODE = "configure";
130 private static final String ADVANCED_MODE = "advanced";
131 private static final String TEST_LOGIN_MODE = "test";
132 private static final String TEST_RESULTS_MODE = "results";
133 private static final String SHOW_PLAN_MODE = "plan";
134 private static final String EDIT_EXISTING_MODE = "editExisting";
135 private static final String USAGE_MODE = "usage";
136 private static final String SAVE_MODE = "save";
137 private static final String MODE_KEY = "mode";
138 private static final String CUSTOM_MODE = "custom";
139
140 private static Kernel kernel;
141
142 private PortletRequestDispatcher listView;
143 private PortletRequestDispatcher editView;
144 private PortletRequestDispatcher selectTypeView;
145 private PortletRequestDispatcher configureView;
146 private PortletRequestDispatcher advancedView;
147 private PortletRequestDispatcher testLoginView;
148 private PortletRequestDispatcher testResultsView;
149 private PortletRequestDispatcher planView;
150 private PortletRequestDispatcher usageView;
151 private static final QName GBEAN_QNAME = new QName(ServiceDocument.type.getDocumentElementName().getNamespaceURI(), "gbean");
152
153 public void init(PortletConfig portletConfig) throws PortletException {
154 super.init(portletConfig);
155 kernel = KernelRegistry.getSingleKernel();
156 listView = portletConfig.getPortletContext().getRequestDispatcher(LIST_VIEW);
157 editView = portletConfig.getPortletContext().getRequestDispatcher(EDIT_VIEW);
158 selectTypeView = portletConfig.getPortletContext().getRequestDispatcher(SELECT_TYPE_VIEW);
159 configureView = portletConfig.getPortletContext().getRequestDispatcher(CONFIGURE_VIEW);
160 advancedView = portletConfig.getPortletContext().getRequestDispatcher(ADVANCED_VIEW);
161 testLoginView = portletConfig.getPortletContext().getRequestDispatcher(TEST_LOGIN_VIEW);
162 testResultsView = portletConfig.getPortletContext().getRequestDispatcher(TEST_RESULTS_VIEW);
163 planView = portletConfig.getPortletContext().getRequestDispatcher(SHOW_PLAN_VIEW);
164 usageView = portletConfig.getPortletContext().getRequestDispatcher(USAGE_VIEW);
165 }
166
167 public void destroy() {
168 listView = null;
169 editView = null;
170 selectTypeView = null;
171 configureView = null;
172 advancedView = null;
173 testLoginView = null;
174 usageView = null;
175 planView = null;
176 super.destroy();
177 }
178
179 public void processAction(ActionRequest actionRequest,
180 ActionResponse actionResponse) throws PortletException, IOException {
181 String mode = actionRequest.getParameter(MODE_KEY);
182 RealmData data = new RealmData();
183 data.load(actionRequest);
184 if (mode.equals(SELECT_TYPE_MODE)) {
185 data.realmType = "Properties File Realm";
186 actionResponse.setRenderParameter(MODE_KEY, SELECT_TYPE_MODE);
187 } else if (mode.equals("process-" + SELECT_TYPE_MODE)) {
188 if (data.getName() != null && !data.getName().trim().equals("")) {
189 // Config properties have to be set in render since they have values of null
190 if (data.getRealmType().equals("Other")) {
191 actionResponse.setRenderParameter(MODE_KEY, CUSTOM_MODE);
192 } else {
193 actionResponse.setRenderParameter(MODE_KEY, CONFIGURE_MODE);
194 }
195 } else {
196 actionResponse.setRenderParameter(MODE_KEY, SELECT_TYPE_MODE);
197 }
198 } else if (mode.equals("process-" + CONFIGURE_MODE)) {
199 final String error = actionTestLoginModuleLoad(actionRequest, data);
200 if (error == null) {
201 actionResponse.setRenderParameter(MODE_KEY, ADVANCED_MODE);
202 } else {
203 actionResponse.setRenderParameter("LoginModuleError", error);
204 actionResponse.setRenderParameter(MODE_KEY, CONFIGURE_MODE);
205 }
206 } else if (mode.equals("process-" + ADVANCED_MODE)) {
207 String test = actionRequest.getParameter("test");
208 if (test == null || test.equals("true")) {
209 actionResponse.setRenderParameter(MODE_KEY, TEST_LOGIN_MODE);
210 } else {
211 actionSaveRealm(actionRequest, data);
212 actionResponse.setRenderParameter(MODE_KEY, LIST_MODE);
213 }
214 } else if (mode.equals("process-" + TEST_LOGIN_MODE)) {
215 actionAttemptLogin(data, actionRequest, actionRequest.getPortletSession(true), actionRequest.getParameter("username"), actionRequest.getParameter("password"));
216 actionResponse.setRenderParameter(MODE_KEY, TEST_RESULTS_MODE);
217 } else if (mode.equals(SHOW_PLAN_MODE)) {
218 XmlObject object = actionGeneratePlan(actionRequest, data);
219 savePlanToSession(actionRequest.getPortletSession(true), object);
220 actionResponse.setRenderParameter(MODE_KEY, SHOW_PLAN_MODE);
221 } else if (mode.equals(EDIT_EXISTING_MODE)) {
222 actionLoadExistingRealm(actionRequest, data);
223 actionResponse.setRenderParameter(MODE_KEY, EDIT_MODE);
224 } else if (mode.equals(CONFIGURE_MODE)) {
225 if (data.getAbstractName() != null) {
226 actionResponse.setRenderParameter(MODE_KEY, EDIT_MODE);
227 } else if((data.getRealmType() != null && data.getRealmType().equals("Other"))) {
228 actionResponse.setRenderParameter(MODE_KEY, CUSTOM_MODE);
229 } else {
230 actionResponse.setRenderParameter(MODE_KEY, CONFIGURE_MODE);
231 }
232 } else if (mode.equals(SAVE_MODE)) {
233 actionSaveRealm(actionRequest, data);
234 actionResponse.setRenderParameter(MODE_KEY, LIST_MODE);
235 } else {
236 actionResponse.setRenderParameter(MODE_KEY, mode);
237 }
238 data.store(actionResponse);
239 }
240
241 protected void doView(RenderRequest renderRequest, RenderResponse renderResponse) throws IOException, PortletException {
242 if (WindowState.MINIMIZED.equals(renderRequest.getWindowState())) {
243 return;
244 }
245 try {
246 String mode = renderRequest.getParameter(MODE_KEY);
247 RealmData data = new RealmData();
248 data.load(renderRequest);
249 renderRequest.setAttribute("realm", data);
250 if (mode == null || mode.equals("")) {
251 mode = LIST_MODE;
252 }
253 if (mode.equals(LIST_MODE)) {
254 renderList(renderRequest, renderResponse);
255 } else if (mode.equals(EDIT_MODE) || mode.equals(CUSTOM_MODE)) {
256 renderRequest.setAttribute("mode", mode);
257 if(mode.equals(CUSTOM_MODE)) loadDriverJARList(renderRequest);
258 renderEdit(renderRequest, renderResponse, data);
259 } else if (mode.equals(SELECT_TYPE_MODE)) {
260 renderSelectType(renderRequest, renderResponse);
261 } else if (mode.equals(CONFIGURE_MODE)) {
262 renderConfigure(renderRequest, renderResponse, data);
263 } else if (mode.equals(ADVANCED_MODE)) {
264 renderAdvanced(renderRequest, renderResponse, data);
265 } else if (mode.equals(TEST_LOGIN_MODE)) {
266 renderTestLoginForm(renderRequest, renderResponse);
267 } else if (mode.equals(TEST_RESULTS_MODE)) {
268 renderTestResults(renderRequest, renderResponse);
269 } else if (mode.equals(SHOW_PLAN_MODE)) {
270 renderPlan(renderRequest, renderResponse);
271 } else if (mode.equals(USAGE_MODE)) {
272 renderUsage(renderRequest, renderResponse);
273 }
274 } catch (Throwable e) {
275 log.error("Unable to render portlet", e);
276 }
277 }
278
279 private String actionTestLoginModuleLoad(PortletRequest request, RealmData data) {
280 Map options = new HashMap();
281 try {
282 LoginModule module = loadModule(request, data, options);
283 log.warn("Testing with options " + options);
284 try {
285 PortletManager.testLoginModule(request, module, options);
286 return null;
287 } catch (Exception e) {
288 log.warn("Unable to initialize LoginModule", e);
289 return "Unable to initialize LoginModule: " + e.getMessage();
290 }
291 } catch (Exception e) {
292 log.warn("Unable to load LoginModule class", e);
293 return "Unable to load LoginModule class: " + e.getMessage();
294 }
295 }
296
297 private LoginModule loadModule(PortletRequest request, RealmData data, Map options) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
298 ClassLoader loader = getClass().getClassLoader();
299 if (data.jar != null && !data.jar.equals("")) {
300 try {
301 Artifact one = Artifact.create(data.getJar());
302 ListableRepository[] repos = PortletManager.getCurrentServer(request).getRepositories();
303 for (int i = 0; i < repos.length; i++) {
304 ListableRepository repo = repos[i];
305 File file = repo.getLocation(one);
306 if (file != null) {
307 loader = new URLClassLoader(new URL[]{file.toURL()}, loader);
308 break;
309 }
310 }
311 } catch (MalformedURLException e) {
312 log.warn("Repository unable to look up JAR file", e);
313 }
314 }
315 Class cls = loader.loadClass(getSelectedModule(data).getClassName());
316 LoginModule module = (LoginModule) cls.newInstance();
317 for (Iterator it = data.getOptions().keySet().iterator(); it.hasNext();) {
318 String key = (String) it.next();
319 final Object value = data.getOptions().get(key);
320 if (value != null && !value.equals("")) {
321 options.put(key, value);
322 }
323 }
324 options.put(JaasLoginModuleUse.CLASSLOADER_LM_OPTION, loader);
325 return module;
326 }
327
328 private void actionAttemptLogin(RealmData data, PortletRequest request, PortletSession session, String username, String password) {
329 session.removeAttribute("TestLoginPrincipals");
330 session.removeAttribute("TestLoginError");
331 Map options = new HashMap();
332 try {
333 LoginModule module = loadModule(request, data, options);
334 Subject sub = PortletManager.testLoginModule(request, module, options, username, password);
335 session.setAttribute("TestLoginPrincipals", sub.getPrincipals());
336 } catch (Exception e) {
337 log.warn("Test login failed", e);
338 session.setAttribute("TestLoginError", "Login Failed: " + (e.getMessage() == null ? "no message" : e.getMessage()));
339 }
340 }
341
342 private XmlObject actionGeneratePlan(PortletRequest request, RealmData data) {
343 normalize(data);
344 ModuleDocument doc = ModuleDocument.Factory.newInstance();
345 ModuleType root = doc.addNewModule();
346 EnvironmentType environment = root.addNewEnvironment();
347 ArtifactType configId = environment.addNewModuleId();
348 configId.setGroupId("console.realm");
349 String artifactId = data.getName();
350 if(artifactId.indexOf('/') != -1) {
351 // slash in artifact-id results in invalid configuration-id and leads to deployment errors.
352 // Note: 0x002F = '/'
353 artifactId = artifactId.replaceAll("/", "%2F");
354 }
355 configId.setArtifactId(artifactId);
356 configId.setVersion("1.0");
357 configId.setType("car");
358
359 // Parent
360
361 DependenciesType dependenciesType = environment.addNewDependencies();
362 ArtifactType parent = dependenciesType.addNewDependency();
363 parent.setGroupId("org.apache.geronimo.framework");
364 parent.setArtifactId("j2ee-security");
365 parent.setType("car");
366 // Dependencies
367 if (data.getJar() != null) {
368 ArtifactType artifactType = dependenciesType.addNewDependency();
369 Artifact artifact = Artifact.create(data.getJar());
370 artifactType.setGroupId(artifact.getGroupId());
371 artifactType.setArtifactId(artifact.getArtifactId());
372 artifactType.setVersion(artifact.getVersion().toString());
373 artifactType.setType(artifact.getType());
374 }
375 // Build the realm GBean
376 GbeanType realm = GbeanType.Factory.newInstance();
377 realm.setName(data.getName());
378 realm.setClass1("org.apache.geronimo.security.realm.GenericSecurityRealm");
379 AttributeType realmName = realm.addNewAttribute();
380 realmName.setName("realmName");
381 realmName.setStringValue(data.getName());
382 ReferenceType serverInfo = realm.addNewReference();
383 serverInfo.setName2("ServerInfo");
384 serverInfo.setName((String) PortletManager.getNameFor(request, PortletManager.getCurrentServer(request).getServerInfo()).getName().get("name"));
385 XmlAttributeType config = realm.addNewXmlReference();
386 // Construct the content to put in the XmlAttributeType
387 GerLoginConfigDocument lcDoc = GerLoginConfigDocument.Factory.newInstance();
388 GerLoginConfigType login = lcDoc.addNewLoginConfig();
389 for (int i = 0; i < data.getModules().length; i++) {
390 LoginModuleDetails details = data.getModules()[i];
391 if (details.getLoginDomainName() == null || details.getLoginDomainName().equals("")) {
392 continue;
393 }
394 GerLoginModuleType module = login.addNewLoginModule();
395 module.setControlFlag(details.getControlFlag().equals(LoginModuleControlFlag.OPTIONAL) ? GerControlFlagType.OPTIONAL :
396 details.getControlFlag().equals(LoginModuleControlFlag.REQUIRED) ? GerControlFlagType.REQUIRED :
397 details.getControlFlag().equals(LoginModuleControlFlag.REQUISITE) ? GerControlFlagType.REQUISITE :
398 details.getControlFlag().equals(LoginModuleControlFlag.SUFFICIENT) ? GerControlFlagType.SUFFICIENT :
399 GerControlFlagType.OPTIONAL);
400 module.setLoginDomainName(details.getLoginDomainName());
401 module.setLoginModuleClass(details.getClassName());
402 module.setWrapPrincipals(details.isWrapPrincipals());
403 for (Iterator it = details.getOptions().entrySet().iterator(); it.hasNext();) {
404 Map.Entry entry = (Map.Entry) it.next();
405 GerOptionType option = module.addNewOption();
406 option.setName((String) entry.getKey());
407 option.setStringValue((String) entry.getValue());
408 }
409
410 // bit of a hack -- to put the DataSource module in as a parent for SQL modules
411 if (details.getClassName().indexOf("SQL") > -1) {
412 String poolName = (String) details.getOptions().get("dataSourceName");
413 String appName = (String) details.getOptions().get("dataSourceApplication");
414 if (poolName != null) {
415 if (appName == null) appName = "null";
416 JCAManagedConnectionFactory[] factories = PortletManager.getOutboundFactoriesOfType(request, "javax.sql.DataSource");
417 for (int j = 0; j < factories.length; j++) {
418 JCAManagedConnectionFactory factory = factories[j];
419 try {
420 ObjectName objectName = ObjectName.getInstance(factory.getObjectName());
421 final String testName = objectName.getKeyProperty(NameFactory.J2EE_NAME);
422 final String testApp = objectName.getKeyProperty(NameFactory.J2EE_APPLICATION);
423 if (testName.equals(poolName) && testApp.equals(appName)) {
424 String moduleName = objectName.getKeyProperty(NameFactory.JCA_RESOURCE);
425
426 ArtifactType artifactType = dependenciesType.addNewDependency();
427 Artifact artifact = Artifact.create(moduleName);
428 artifactType.setGroupId(artifact.getGroupId());
429 artifactType.setArtifactId(artifact.getArtifactId());
430 artifactType.setVersion(artifact.getVersion().toString());
431 artifactType.setType(artifact.getType());
432 break;
433 }
434 } catch (MalformedObjectNameException e) {
435 log.error("Unable to parse ObjectName", e);
436 }
437 }
438 }
439 }
440 }
441 // Copy the content into the XmlAttributeType
442 XmlCursor loginCursor = lcDoc.newCursor();
443 loginCursor.toFirstContentToken();
444 XmlCursor destination = config.newCursor();
445 destination.toNextToken();
446 loginCursor.moveXml(destination);
447 loginCursor.dispose();
448 destination.dispose();
449 config.setName("LoginModuleConfiguration");
450 root.setServiceArray(new AbstractServiceType[]{realm});
451
452
453 //Above code inserts gbean using xsi:type=dep:GBeanType. We also need to account for the substitution group
454 //by changing the qname:
455 XmlCursor gbeanCursor = root.newCursor();
456 try {
457 if (!gbeanCursor.toChild(ServiceDocument.type.getDocumentElementName())) {
458 throw new RuntimeException("Could not find service element");
459 }
460 gbeanCursor.setName(GBEAN_QNAME);
461 } finally {
462 gbeanCursor.dispose();
463 }
464
465 return doc;
466 }
467
468 private void actionLoadExistingRealm(PortletRequest request, RealmData data) {
469 SecurityRealm realm = (SecurityRealm) PortletManager.getManagedBean(request, new AbstractName(URI.create(data.getAbstractName())));
470 data.name = realm.getRealmName();
471 List list = new ArrayList();
472 JaasLoginModuleChain node = realm.getLoginModuleChain();
473 while (node != null) {
474 LoginModuleDetails details = new LoginModuleDetails();
475 details.setControlFlag(node.getControlFlag());
476 LoginModuleSettings module = node.getLoginModule();
477 details.setLoginDomainName(module.getLoginDomainName());
478 details.setClassName(module.getLoginModuleClass());
479 details.setWrapPrincipals(module.isWrapPrincipals());
480 details.setOptions(module.getOptions());
481 list.add(details);
482 node = node.getNext();
483 if (node == null) {
484 break;
485 }
486 }
487 data.modules = (LoginModuleDetails[]) list.toArray(new LoginModuleDetails[list.size()]);
488 }
489
490 private void actionSaveRealm(PortletRequest request, RealmData data) {
491 normalize(data);
492 if (data.getAbstractName() == null || data.getAbstractName().equals("")) { // we're creating a new realm
493 try {
494 XmlObject plan = actionGeneratePlan(request, data);
495 data.name = data.name.replaceAll("\\s", "");
496 DeploymentManager mgr = ManagementHelper.getManagementHelper(request).getDeploymentManager();
497 File tempFile = File.createTempFile("console-deployment", ".xml");
498 tempFile.deleteOnExit();
499 log.debug("Writing security realm deployment plan to " + tempFile.getAbsolutePath());
500 PrintWriter out = new PrintWriter(new FileWriter(tempFile));
501 savePlanToStream(plan, out);
502 out.flush();
503 out.close();
504 Target[] targets = mgr.getTargets();
505 if (null == targets) {
506 throw new IllegalStateException("No target to distribute to");
507 }
508 targets = new Target[] {targets[0]};
509
510 ProgressObject po = mgr.distribute(targets, null, tempFile);
511 waitForProgress(po);
512 if (po.getDeploymentStatus().isCompleted()) {
513 TargetModuleID[] ids = po.getResultTargetModuleIDs();
514 po = mgr.start(ids);
515 waitForProgress(po);
516 if (po.getDeploymentStatus().isCompleted()) {
517 log.info("Deployment completed successfully!");
518 }
519 }
520 } catch (IOException e) {
521 log.error("Unable to save security realm", e);
522 }
523 } else {
524 SecurityRealm realm = (SecurityRealm) PortletManager.getManagedBean(request, new AbstractName(URI.create(data.getAbstractName())));
525 // index existing modules
526 Map nodes = new HashMap();
527 JaasLoginModuleChain node = realm.getLoginModuleChain();
528 while (node != null) {
529 LoginModuleSettings module = node.getLoginModule();
530 nodes.put(module.getLoginDomainName(), node);
531 node = node.getNext();
532 if (node == null) {
533 break;
534 }
535 }
536 // apply settings
537 for (int i = 0; i < data.getModules().length; i++) {
538 LoginModuleDetails details = data.getModules()[i];
539 node = (JaasLoginModuleChain) nodes.get(details.getLoginDomainName());
540 node.setControlFlag(details.getControlFlag());
541 LoginModuleSettings module = node.getLoginModule();
542 module.setOptions(details.getOptions());
543 module.setWrapPrincipals(details.isWrapPrincipals());
544 module.setLoginModuleClass(details.getClassName());
545 }
546 }
547 }
548
549 private void renderList(RenderRequest request, RenderResponse response) throws IOException, PortletException {
550 // Unfortunately there are two classes named SecurityRealm; one extends the other
551 // The array type is management.geronimo.SecurityRealm (the superclass)
552 // The array entry types are security.realm.SecurityRealm (the subclass)
553 org.apache.geronimo.management.geronimo.SecurityRealm[] realms = PortletManager.getCurrentServer(request).getSecurityRealms();
554 ExistingRealm[] results = new ExistingRealm[realms.length];
555
556 // ConfigurationManager is used to determine if the SecurityRealm is deployed as a "SERVICE", i.e., "Server-wide"
557 ConfigurationManager configMgr = null;
558 if(results.length > 0) {
559 // Needed only when there are any SecurityRealms
560 configMgr = ConfigurationUtil.getConfigurationManager(kernel);
561 }
562 for (int i = 0; i < results.length; i++) {
563 AbstractName abstractName = PortletManager.getNameFor(request, realms[i]);
564 String parent;
565 Configuration parentConfig = configMgr.getConfiguration(abstractName.getArtifact());
566 ConfigurationModuleType parentType = parentConfig.getModuleType();
567 if(ConfigurationModuleType.SERVICE.equals(parentType)) {
568 parent = null; // Server-wide
569 } else {
570 parent = abstractName.getArtifact().toString();
571 }
572 results[i] = new ExistingRealm(realms[i].getRealmName(), abstractName, parent);
573 }
574 // Once done, release the ConfigurationManager
575 if(configMgr != null) {
576 ConfigurationUtil.releaseConfigurationManager(kernel, configMgr);
577 }
578 request.setAttribute("realms", results);
579 listView.include(request, response);
580 }
581
582 private void renderEdit(RenderRequest request, RenderResponse response, RealmData data) throws IOException, PortletException {
583 normalize(data);
584 editView.include(request, response);
585 }
586
587 private void renderSelectType(RenderRequest request, RenderResponse response) throws IOException, PortletException {
588 request.setAttribute("moduleTypes", MasterLoginModuleInfo.getAllModules());
589 selectTypeView.include(request, response);
590 }
591
592 private void renderConfigure(RenderRequest request, RenderResponse response, RealmData data) throws IOException, PortletException {
593 // Pass errors through
594 if (request.getParameter("LoginModuleError") != null) {
595 request.setAttribute("LoginModuleError", request.getParameter("LoginModuleError"));
596 }
597 // Clear out any cached modules
598 data.modules = null;
599 // Configure option list
600 MasterLoginModuleInfo info = getSelectedModule(data);
601 for (int i = 0; i < info.getOptions().length; i++) {
602 MasterLoginModuleInfo.OptionInfo option = info.getOptions()[i];
603 if (!data.getOptions().containsKey(option.getName())) {
604 data.getOptions().put(option.getName(), null);
605 }
606 }
607 data.reorderOptions(info.getOptions());
608 request.setAttribute("optionMap", info.getOptionMap());
609 if (info.getName().indexOf("SQL") > -1) {
610 loadDriverJARList(request);
611 loadDatabasePoolList(request);
612 }
613 configureView.include(request, response);
614 }
615
616 private void renderAdvanced(RenderRequest request, RenderResponse response, RealmData data) throws IOException, PortletException {
617 // Clear out any cached modules
618 data.modules = null;
619 // Show the page
620 advancedView.include(request, response);
621 }
622
623 private void renderTestLoginForm(RenderRequest request, RenderResponse response) throws IOException, PortletException {
624 testLoginView.include(request, response);
625 }
626
627 private void renderTestResults(RenderRequest request, RenderResponse response) throws IOException, PortletException {
628 PortletSession session = request.getPortletSession();
629 String status = (String) session.getAttribute("TestLoginError");
630 if (status == null) {
631 Set principals = (Set) session.getAttribute("TestLoginPrincipals");
632 status = "Login succeeded with " + (principals == null ? 0 : principals.size()) + " principals";
633 request.setAttribute("principals", principals);
634 }
635 request.setAttribute("LoginResults", status);
636 testResultsView.include(request, response);
637 }
638
639 private void renderPlan(RenderRequest request, RenderResponse response) throws IOException, PortletException {
640 String plan = (String) request.getPortletSession().getAttribute("SecurityRealmPlan");
641 request.setAttribute("deploymentPlan", plan);
642 planView.include(request, response);
643 }
644
645 private void renderUsage(RenderRequest request, RenderResponse response) throws IOException, PortletException {
646 usageView.include(request, response);
647 }
648
649 private static MasterLoginModuleInfo getSelectedModule(RealmData data) {
650 MasterLoginModuleInfo[] all = MasterLoginModuleInfo.getAllModules();
651 for (int i = 0; i < all.length; i++) {
652 MasterLoginModuleInfo info = all[i];
653 if (info.getName().equals(data.getRealmType())) {
654 return info;
655 }
656 }
657 return null;
658 }
659
660 private void loadDatabasePoolList(RenderRequest renderRequest) {
661 JCAManagedConnectionFactory[] factories = PortletManager.getOutboundFactoriesOfType(renderRequest, "javax.sql.DataSource");
662 List pools = new ArrayList();
663 try {
664 for (int i = 0; i < factories.length; i++) {
665 JCAManagedConnectionFactory factory = factories[i];
666 ObjectName objectName = ObjectName.getInstance(factory.getObjectName());
667 final String name = objectName.getKeyProperty(NameFactory.J2EE_NAME);
668 String display = name;
669 final String appName = objectName.getKeyProperty(NameFactory.J2EE_APPLICATION);
670 if (appName != null && !appName.equals("null")) {
671 display = display + " (" + appName + ")";
672 }
673 pools.add(new DatabasePool(name, display, appName, PortletManager.getNameFor(renderRequest, factory)));
674 }
675 renderRequest.setAttribute("pools", pools);
676 } catch (MalformedObjectNameException e) {
677 log.error("Unable to parse ObjectName", e);
678 }
679 }
680
681 private void loadDriverJARList(RenderRequest renderRequest) {
682 // List the available JARs
683 List list = new ArrayList();
684 ListableRepository[] repos = PortletManager.getCurrentServer(renderRequest).getRepositories();
685 for (int i = 0; i < repos.length; i++) {
686 ListableRepository repo = repos[i];
687
688 SortedSet artifacts = repo.list();
689 outer:
690 for (Iterator iterator = artifacts.iterator(); iterator.hasNext();) {
691 Artifact artifact = (Artifact) iterator.next();
692 String test = artifact.toString();
693 // todo should only test groupId and should check for long (org.apache.geronimo) and short form
694 for (int k = 0; k < SKIP_ENTRIES_WITH.length; k++) {
695 String skip = SKIP_ENTRIES_WITH[k];
696 if (test.indexOf(skip) > -1) {
697 continue outer;
698 }
699 }
700 list.add(test);
701 }
702 }
703 Collections.sort(list);
704 renderRequest.setAttribute("jars", list);
705 }
706
707 private void savePlanToSession(PortletSession session, XmlObject object) {
708 StringWriter out = new StringWriter();
709 try {
710 savePlanToStream(object, out);
711 session.setAttribute("SecurityRealmPlan", out.getBuffer().toString());
712 } catch (IOException e) {
713 log.error("Unable to write deployment plan", e);
714 }
715 }
716
717 private void savePlanToStream(XmlObject object, Writer out) throws IOException {
718 XmlOptions options = new XmlOptions();
719 options.setSavePrettyPrint();
720 options.setSavePrettyPrintIndent(4);
721 options.setUseDefaultNamespace();
722 object.save(out, options);
723 out.close();
724 }
725
726 private static void waitForProgress(ProgressObject po) {
727 while (po.getDeploymentStatus().isRunning()) {
728 try {
729 Thread.sleep(100);
730 } catch (InterruptedException e) {
731 log.error(e.getMessage(), e);
732 }
733 }
734 }
735
736 public static void normalize(RealmData data) {
737 List list = new ArrayList();
738 if (data.modules == null) {
739 LoginModuleDetails module = new LoginModuleDetails();
740 module.setClassName(getSelectedModule(data).getClassName());
741 module.setControlFlag(LoginModuleControlFlag.REQUIRED);
742 module.setLoginDomainName(data.getName());
743 Map<String, Object> props = module.getOptions();
744 for (Iterator it = data.getOptions().entrySet().iterator(); it.hasNext();) {
745 Map.Entry entry = (Map.Entry) it.next();
746 props.put((String) entry.getKey(), (String) entry.getValue());
747 }
748 list.add(module);
749 if (data.isStorePassword()) {
750 module = new LoginModuleDetails();
751 module.setClassName(GeronimoPasswordCredentialLoginModule.class.getName());
752 module.setControlFlag(LoginModuleControlFlag.OPTIONAL);
753 module.setLoginDomainName(data.getName() + "-Password");
754 list.add(module);
755 }
756 if (data.getAuditPath() != null) {
757 module = new LoginModuleDetails();
758 module.setClassName(FileAuditLoginModule.class.getName());
759 module.setControlFlag(LoginModuleControlFlag.OPTIONAL);
760 module.setLoginDomainName(data.getName() + "-Audit");
761 props = module.getOptions();
762 props.put("file", data.getAuditPath());
763 list.add(module);
764 }
765 if (data.isLockoutEnabled()) {
766 module = new LoginModuleDetails();
767 module.setClassName(RepeatedFailureLockoutLoginModule.class.getName());
768 module.setControlFlag(LoginModuleControlFlag.REQUISITE);
769 module.setLoginDomainName(data.getName() + "-Lockout");
770 props = module.getOptions();
771 props.put("failureCount", data.getLockoutCount());
772 props.put("failurePeriodSecs", data.getLockoutWindow());
773 props.put("lockoutDurationSecs", data.getLockoutDuration());
774 list.add(module);
775 }
776 if (data.getCredentialName() != null) {
777 module = new LoginModuleDetails();
778 module.setClassName(NamedUsernamePasswordCredentialLoginModule.class.getName());
779 module.setControlFlag(LoginModuleControlFlag.OPTIONAL);
780 module.setLoginDomainName(data.getName() + "-NamedUPC");
781 props = module.getOptions();
782 props.put(NamedUsernamePasswordCredentialLoginModule.CREDENTIAL_NAME, data.getCredentialName());
783 list.add(module);
784 }
785 } else {
786 list.addAll(Arrays.asList(data.modules));
787 }
788 if (data.getAbstractName() == null) {
789 for (int i = list.size(); i < 5; i++) {
790 LoginModuleDetails module = new LoginModuleDetails();
791 list.add(module);
792 }
793 }
794 data.modules = (LoginModuleDetails[]) list.toArray(new LoginModuleDetails[list.size()]);
795 }
796
797 public static class RealmData implements Serializable {
798 private String name;
799 private String realmType;
800 private String jar;
801 private Map options = new LinkedHashMap();
802 private String auditPath;
803 private String lockoutCount;
804 private String lockoutWindow;
805 private String lockoutDuration;
806 private boolean storePassword;
807 private String abstractName; // used when editing existing realms
808 private LoginModuleDetails[] modules;
809 private String credentialName;
810
811 public void load(PortletRequest request) {
812 name = request.getParameter("name");
813 if (name != null && name.equals("")) name = null;
814 realmType = request.getParameter("realmType");
815 if (realmType != null && realmType.equals("")) realmType = null;
816 jar = request.getParameter("jar");
817 if (jar != null && jar.equals("")) jar = null;
818 auditPath = request.getParameter("auditPath");
819 if (auditPath != null && auditPath.equals("")) auditPath = null;
820 lockoutCount = request.getParameter("lockoutCount");
821 if (lockoutCount != null && lockoutCount.equals("")) lockoutCount = null;
822 lockoutWindow = request.getParameter("lockoutWindow");
823 if (lockoutWindow != null && lockoutWindow.equals("")) lockoutWindow = null;
824 lockoutDuration = request.getParameter("lockoutDuration");
825 if (lockoutDuration != null && lockoutDuration.equals("")) lockoutDuration = null;
826 abstractName = request.getParameter("abstractName");
827 if (abstractName != null && abstractName.equals("")) abstractName = null;
828 String test = request.getParameter("storePassword");
829 storePassword = test != null && !test.equals("") && !test.equals("false");
830 credentialName = request.getParameter("credentialName");
831 if (credentialName != null && credentialName.equals("")) credentialName = null;
832 Map map = request.getParameterMap();
833 for (Iterator it = map.keySet().iterator(); it.hasNext();) {
834 String key = (String) it.next();
835 if (key.startsWith("option-")) {
836 if (key.equals("option-databasePoolAbstractName"))
837 { // special handling for a data source, where there's one select corresponding to two properties
838 String nameString = request.getParameter(key);
839 if (nameString != null && !nameString.equals("")) {
840 AbstractName an = new AbstractName(URI.create(nameString));
841 options.put("dataSourceName", an.getNameProperty(NameFactory.J2EE_NAME));
842 options.put("dataSourceApplication", an.getNameProperty(NameFactory.J2EE_APPLICATION));
843 }
844 } else {
845 final String optionName = key.substring(7);
846 final String value = request.getParameter(key);
847 if (value != null && !value.equals("")) {
848 options.put(optionName, value);
849 }
850 }
851 }
852 }
853 int count = 0;
854 List list = new ArrayList();
855 while (true) {
856 int index = count;
857 ++count;
858 String name = request.getParameter("module-domain-" + index);
859 if (name == null || name.equals("")) break;
860 LoginModuleDetails details = new LoginModuleDetails();
861 details.setLoginDomainName(name);
862 String cls = request.getParameter("module-class-" + index);
863 if (cls == null || cls.equals("")) continue;
864 details.setClassName(cls);
865 String flag = request.getParameter("module-control-" + index);
866 if (flag == null || flag.equals("")) continue;
867 details.setControlFlag(toFlag(flag));
868 String wrap = request.getParameter("module-wrap-" + index);
869 if (wrap == null || wrap.equals("")) continue;
870 details.setWrapPrincipals(Boolean.valueOf(wrap).booleanValue());
871 String options = request.getParameter("module-options-" + index);
872 if (options != null && !options.equals("")) {
873 BufferedReader in = new BufferedReader(new StringReader(options));
874 String line;
875 try {
876 while ((line = in.readLine()) != null) {
877 if (line.startsWith("#") || line.equals("")) {
878 continue;
879 }
880 int pos = line.indexOf('=');
881 if (pos > -1) {
882 details.getOptions().put(line.substring(0, pos), line.substring(pos + 1));
883 }
884 }
885 } catch (IOException e) {
886 log.error("Unable to read properties '" + options + "'", e);
887 }
888 }
889 list.add(details);
890 }
891 if (list.size() > 0) {
892 modules = (LoginModuleDetails[]) list.toArray(new LoginModuleDetails[list.size()]);
893 }
894 }
895
896 private LoginModuleControlFlag toFlag(String flag) {
897 LoginModuleControlFlagEditor editor = new LoginModuleControlFlagEditor();
898 editor.setAsText(flag);
899 return (LoginModuleControlFlag) editor.getValue();
900 }
901
902 public void reorderOptions(MasterLoginModuleInfo.OptionInfo[] info) {
903 if (info == null || info.length == 0) {
904 return; // Probably SQL or something that handles this manually
905 }
906 Map map = new LinkedHashMap();
907 for (int i = 0; i < info.length; i++) {
908 if (options.containsKey(info[i].getName())) {
909 map.put(info[i].getName(), options.get(info[i].getName()));
910 }
911 }
912 options = map;
913 }
914
915 public void store(ActionResponse response) {
916 if (name != null) response.setRenderParameter("name", name);
917 if (realmType != null) response.setRenderParameter("realmType", realmType);
918 if (jar != null) response.setRenderParameter("jar", jar);
919 if (auditPath != null) response.setRenderParameter("auditPath", auditPath);
920 if (lockoutCount != null) response.setRenderParameter("lockoutCount", lockoutCount);
921 if (lockoutWindow != null) response.setRenderParameter("lockoutWindow", lockoutWindow);
922 if (lockoutDuration != null) response.setRenderParameter("lockoutDuration", lockoutDuration);
923 if (abstractName != null) response.setRenderParameter("abstractName", abstractName);
924 if (storePassword) response.setRenderParameter("storePassword", "true");
925 if (credentialName != null) response.setRenderParameter("credentialName", credentialName);
926 for (Iterator it = options.keySet().iterator(); it.hasNext();) {
927 String name = (String) it.next();
928 String value = (String) options.get(name);
929 if (value != null) {
930 response.setRenderParameter("option-" + name, value);
931 }
932 }
933 if (modules != null) {
934 for (int i = 0; i < modules.length; i++) {
935 LoginModuleDetails module = modules[i];
936 if (module.getLoginDomainName() != null)
937 response.setRenderParameter("module-domain-" + i, module.getLoginDomainName());
938 if (module.getClassName() != null)
939 response.setRenderParameter("module-class-" + i, module.getClassName());
940 if (module.getControlFlag() != null)
941 response.setRenderParameter("module-control-" + i,module.getControlFlag().toString());
942 response.setRenderParameter("module-wrap-" + i, Boolean.toString(module.isWrapPrincipals()));
943 if (module.getOptions().size() > 0)
944 response.setRenderParameter("module-options-" + i, module.getOptionString());
945 }
946 }
947 }
948
949 public String getName() {
950 return name;
951 }
952
953 public String getRealmType() {
954 return realmType;
955 }
956
957 public Map getOptions() {
958 return options;
959 }
960
961 public Set getOptionNames() {
962 return options.keySet();
963 }
964
965 public String getJar() {
966 return jar;
967 }
968
969 public String getAuditPath() {
970 return auditPath;
971 }
972
973 public String getLockoutCount() {
974 return lockoutCount;
975 }
976
977 public String getLockoutWindow() {
978 return lockoutWindow;
979 }
980
981 public String getLockoutDuration() {
982 return lockoutDuration;
983 }
984
985 public boolean isStorePassword() {
986 return storePassword;
987 }
988
989 public boolean isLockoutEnabled() {
990 return lockoutCount != null || lockoutWindow != null || lockoutDuration != null;
991 }
992
993 public String getCredentialName() {
994 return credentialName;
995 }
996
997 public String getAbstractName() {
998 return abstractName;
999 }
1000
1001 public boolean isTestable() {
1002 return getSelectedModule(this).isTestable();
1003 }
1004
1005 public LoginModuleDetails[] getModules() {
1006 return modules;
1007 }
1008 }
1009
1010 public static class LoginModuleDetails implements Serializable {
1011 private String loginDomainName;
1012 private String className;
1013 private LoginModuleControlFlag controlFlag;
1014 private boolean wrapPrincipals = false;
1015 private Map<String, Object> options = new HashMap<String, Object>();
1016
1017 public String getLoginDomainName() {
1018 return loginDomainName;
1019 }
1020
1021 public void setLoginDomainName(String loginDomainName) {
1022 this.loginDomainName = loginDomainName;
1023 }
1024
1025 public String getClassName() {
1026 return className;
1027 }
1028
1029 public void setClassName(String className) {
1030 this.className = className;
1031 }
1032
1033 public LoginModuleControlFlag getControlFlag() {
1034 return controlFlag;
1035 }
1036
1037 public void setControlFlag(LoginModuleControlFlag controlFlag) {
1038 this.controlFlag = controlFlag;
1039 }
1040
1041 public Map<String, Object> getOptions() {
1042 return options;
1043 }
1044
1045 public void setOptions(Map<String, Object> options) {
1046 this.options = options;
1047 }
1048
1049 public boolean isWrapPrincipals() {
1050 return wrapPrincipals;
1051 }
1052
1053 public void setWrapPrincipals(boolean wrapPrincipals) {
1054 this.wrapPrincipals = wrapPrincipals;
1055 }
1056
1057 public String getOptionString() {
1058 StringBuffer buf = new StringBuffer();
1059 for (Iterator it = options.keySet().iterator(); it.hasNext();) {
1060 String key = (String) it.next();
1061 buf.append(key).append("=").append(options.get(key)).append("\n");
1062 }
1063 return buf.toString();
1064 }
1065 }
1066
1067 public static class ExistingRealm implements Serializable {
1068 private final String name;
1069 private final String abstractName;
1070 private final String parentName;
1071
1072 public ExistingRealm(String name, AbstractName abstractName, String parent) {
1073 this.name = name;
1074 this.abstractName = abstractName.toString();
1075 parentName = parent;
1076 }
1077
1078 public String getName() {
1079 return name;
1080 }
1081
1082 public String getAbstractName() {
1083 return abstractName;
1084 }
1085
1086 public String getParentName() {
1087 return parentName;
1088 }
1089
1090 }
1091
1092 public static class DatabasePool implements Serializable, Comparable {
1093 private final String name;
1094 private final String displayName;
1095 private final String applicationName;
1096 private final String abstractName;
1097
1098 public DatabasePool(String name, String displayName, String applicationName, AbstractName abstractName) {
1099 this.name = name;
1100 this.displayName = displayName;
1101 this.applicationName = applicationName;
1102 this.abstractName = abstractName.toString();
1103 }
1104
1105 public String getName() {
1106 return name;
1107 }
1108
1109 public String getApplicationName() {
1110 return applicationName;
1111 }
1112
1113 public String getAbstractName() {
1114 return abstractName;
1115 }
1116
1117 public String getDisplayName() {
1118 return displayName;
1119 }
1120
1121 public int compareTo(Object o) {
1122 final DatabasePool pool = (DatabasePool) o;
1123 int names = name.compareTo(pool.name);
1124 if (applicationName == null) {
1125 if (pool.applicationName == null) {
1126 return names;
1127 } else {
1128 return -1;
1129 }
1130 } else {
1131 if (pool.applicationName == null) {
1132 return 1;
1133 } else {
1134 int test = applicationName.compareTo(pool.applicationName);
1135 if (test != 0) {
1136 return test;
1137 } else {
1138 return names;
1139 }
1140 }
1141 }
1142 }
1143 }
1144 }