001/* 002 * (C) Copyright 2006-2007 Nuxeo SAS (http://nuxeo.com/) and contributors. 003 * 004 * All rights reserved. This program and the accompanying materials 005 * are made available under the terms of the GNU Lesser General Public License 006 * (LGPL) version 2.1 which accompanies this distribution, and is available at 007 * http://www.gnu.org/licenses/lgpl.html 008 * 009 * This library is distributed in the hope that it will be useful, 010 * but WITHOUT ANY WARRANTY; without even the implied warranty of 011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 012 * Lesser General Public License for more details. 013 * 014 * Contributors: 015 * Nuxeo - initial API and implementation 016 * 017 * $Id: JOOoConvertPluginImpl.java 18651 2007-05-13 20:28:53Z sfermigier $ 018 */ 019 020package org.nuxeo.ecm.platform.login; 021 022import java.io.IOException; 023import java.security.Principal; 024import java.security.acl.Group; 025import java.util.HashMap; 026import java.util.List; 027import java.util.Map; 028import java.util.Random; 029 030import javax.security.auth.Subject; 031import javax.security.auth.callback.Callback; 032import javax.security.auth.callback.CallbackHandler; 033import javax.security.auth.callback.NameCallback; 034import javax.security.auth.callback.PasswordCallback; 035import javax.security.auth.callback.UnsupportedCallbackException; 036import javax.security.auth.login.LoginException; 037 038import org.apache.commons.logging.Log; 039import org.apache.commons.logging.LogFactory; 040import org.nuxeo.ecm.core.api.NuxeoPrincipal; 041import org.nuxeo.ecm.core.api.SystemPrincipal; 042import org.nuxeo.ecm.core.api.security.SecurityConstants; 043import org.nuxeo.ecm.directory.DirectoryException; 044import org.nuxeo.ecm.platform.api.login.RestrictedLoginHelper; 045import org.nuxeo.ecm.platform.api.login.UserIdentificationInfo; 046import org.nuxeo.ecm.platform.api.login.UserIdentificationInfoCallback; 047import org.nuxeo.ecm.platform.usermanager.NuxeoPrincipalImpl; 048import org.nuxeo.ecm.platform.usermanager.UserManager; 049import org.nuxeo.runtime.RuntimeService; 050import org.nuxeo.runtime.api.Framework; 051import org.nuxeo.runtime.api.login.LoginComponent; 052 053public class NuxeoLoginModule extends NuxeoAbstractServerLoginModule { 054 055 private static final Log log = LogFactory.getLog(NuxeoLoginModule.class); 056 057 private UserManager manager; 058 059 private Random random; 060 061 private NuxeoPrincipal identity; 062 063 private LoginPluginRegistry loginPluginManager; 064 065 private boolean useUserIdentificationInfoCB = false; 066 067 @Override 068 public void initialize(Subject subject, CallbackHandler callbackHandler, Map<String, ?> sharedState, 069 Map<String, ?> options) { 070 // explicit cast to match the direct superclass method declaration 071 // (JBoss implementation) 072 // rather than the newer (jdk1.5) LoginModule (... Map<String,?>...) 073 // This is needed to avoid compilation errors when the linker wants to 074 // bind 075 // with the (interface) LoginModule method (which is abstract of course 076 // and cannot be called) 077 String useUIICB = (String) options.get("useUserIdentificationInfoCB"); 078 if (useUIICB != null && useUIICB.equalsIgnoreCase("true")) { 079 useUserIdentificationInfoCB = true; 080 } 081 082 super.initialize(subject, callbackHandler, sharedState, options); 083 random = new Random(System.currentTimeMillis()); 084 085 manager = Framework.getService(UserManager.class); 086 log.debug("NuxeoLoginModule initialized"); 087 088 final RuntimeService runtime = Framework.getRuntime(); 089 loginPluginManager = (LoginPluginRegistry) runtime.getComponent(LoginPluginRegistry.NAME); 090 } 091 092 /** 093 * Gets the roles the user belongs to. 094 */ 095 @Override 096 protected Group[] getRoleSets() throws LoginException { 097 log.debug("getRoleSets"); 098 if (manager == null) { 099 // throw new LoginException("UserManager implementation not found"); 100 } 101 String username = identity.getName(); 102 List<String> roles = identity.getRoles(); 103 104 Group roleSet = new GroupImpl("Roles"); 105 log.debug("Getting roles for user=" + username); 106 for (String roleName : roles) { 107 Principal role = new PrincipalImpl(roleName); 108 log.debug("Found role=" + roleName); 109 roleSet.addMember(role); 110 } 111 Group callerPrincipal = new GroupImpl("CallerPrincipal"); 112 callerPrincipal.addMember(identity); 113 114 return new Group[] { roleSet, callerPrincipal }; 115 } 116 117 @SuppressWarnings({ "unchecked" }) 118 protected NuxeoPrincipal getPrincipal() throws LoginException { 119 UserIdentificationInfo userIdent = null; 120 121 // **** init the callbacks 122 // Std login/password callbacks 123 NameCallback nc = new NameCallback("Username: ", SecurityConstants.ANONYMOUS); 124 PasswordCallback pc = new PasswordCallback("Password: ", false); 125 126 // Nuxeo specific cb : handle LoginPlugin initialization 127 UserIdentificationInfoCallback uic = new UserIdentificationInfoCallback(); 128 129 // JBoss specific cb : handle web=>ejb propagation 130 // SecurityAssociationCallback ac = new SecurityAssociationCallback(); 131 // ObjectCallback oc = new ObjectCallback("UserInfo:"); 132 133 // **** handle callbacks 134 // We can't check the callback handler class to know what will be 135 // supported 136 // because the cbh is wrapped by JAAS 137 // => just try and swalow exceptions 138 // => will be externalised to plugins via EP to avoid JBoss dependency 139 boolean cb_handled = false; 140 141 try { 142 // only try this cbh when called from the web layer 143 if (useUserIdentificationInfoCB) { 144 callbackHandler.handle(new Callback[] { uic }); 145 // First check UserInfo CB return 146 userIdent = uic.getUserInfo(); 147 cb_handled = true; 148 } 149 } catch (UnsupportedCallbackException e) { 150 log.debug("UserIdentificationInfoCallback is not supported"); 151 } catch (IOException e) { 152 log.warn("Error calling callback handler with UserIdentificationInfoCallback : " + e.getMessage()); 153 } 154 155 Principal principal = null; 156 Object credential = null; 157 158 if (!cb_handled) { 159 CallbackResult result = loginPluginManager.handleSpecifcCallbacks(callbackHandler); 160 161 if (result != null && result.cb_handled) { 162 if (result.userIdent != null && result.userIdent.containsValidIdentity()) { 163 userIdent = result.userIdent; 164 cb_handled = true; 165 } else { 166 principal = result.principal; 167 credential = result.credential; 168 if (principal != null) { 169 cb_handled = true; 170 } 171 } 172 } 173 } 174 175 if (!cb_handled) { 176 try { 177 // Std CBH : will only works for L/P 178 callbackHandler.handle(new Callback[] { nc, pc }); 179 cb_handled = true; 180 } catch (UnsupportedCallbackException e) { 181 LoginException le = new LoginException("Authentications Failure - " + e.getMessage()); 182 le.initCause(e); 183 } catch (IOException e) { 184 LoginException le = new LoginException("Authentications Failure - " + e.getMessage()); 185 le.initCause(e); 186 } 187 } 188 189 // Login via the Web Interface : may be using a plugin 190 if (userIdent != null && userIdent.containsValidIdentity()) { 191 NuxeoPrincipal nxp = validateUserIdentity(userIdent); 192 193 if (nxp != null) { 194 sharedState.put("javax.security.auth.login.name", nxp.getName()); 195 sharedState.put("javax.security.auth.login.password", userIdent); 196 } 197 return nxp; 198 } 199 200 if (LoginComponent.isSystemLogin(principal)) { 201 return new SystemPrincipal(principal.getName()); 202 } 203 // if (principal instanceof NuxeoPrincipal) { // a nuxeo principal 204 // return validatePrincipal((NuxeoPrincipal) principal); 205 // } else 206 if (principal != null) { // a non null principal 207 String password = null; 208 if (credential instanceof char[]) { 209 password = new String((char[]) credential); 210 } else if (credential != null) { 211 password = credential.toString(); 212 } 213 return validateUsernamePassword(principal.getName(), password); 214 } else { // we don't have a principal - try the username & 215 // password 216 String username = nc.getName(); 217 if (username == null) { 218 return null; 219 } 220 char[] password = pc.getPassword(); 221 return validateUsernamePassword(username, password != null ? new String(password) : null); 222 } 223 } 224 225 public boolean login() throws LoginException { 226 if (manager == null) { 227 // throw new LoginException("UserManager implementation not found"); 228 } 229 230 loginOk = false; 231 232 identity = getPrincipal(); 233 if (identity == null) { // auth failed 234 throw new LoginException("Authentication Failed"); 235 } 236 237 if (RestrictedLoginHelper.isRestrictedModeActivated()) { 238 if (!identity.isAdministrator()) { 239 throw new LoginException("Only Administrators can login when restricted mode is activated"); 240 } 241 } 242 243 loginOk = true; 244 log.trace("User '" + identity + "' authenticated"); 245 246 /* 247 * if( getUseFirstPass() == true ) { // Add the username and password to the shared state map // not sure it's 248 * needed sharedState.put("javax.security.auth.login.name", identity.getName()); 249 * sharedState.put("javax.security.auth.login.password", identity.getPassword()); } 250 */ 251 252 return true; 253 } 254 255 @Override 256 public Principal getIdentity() { 257 return identity; 258 } 259 260 @Override 261 public Principal createIdentity(String username) throws LoginException { 262 log.debug("createIdentity: " + username); 263 try { 264 NuxeoPrincipal principal; 265 if (manager == null) { 266 principal = new NuxeoPrincipalImpl(username); 267 } else { 268 principal = manager.getPrincipal(username); 269 if (principal == null) { 270 throw new LoginException(String.format("principal %s does not exist", username)); 271 } 272 } 273 274 String principalId = String.valueOf(random.nextLong()); 275 principal.setPrincipalId(principalId); 276 return principal; 277 } catch (LoginException e) { 278 log.error("createIdentity failed", e); 279 LoginException le = new LoginException("createIdentity failed for user " + username); 280 le.initCause(e); 281 throw le; 282 } 283 } 284 285 protected NuxeoPrincipal validateUserIdentity(UserIdentificationInfo userIdent) throws LoginException { 286 String loginPluginName = userIdent.getLoginPluginName(); 287 if (loginPluginName == null) { 288 // we don't use a specific plugin 289 boolean authenticated; 290 try { 291 authenticated = manager.checkUsernamePassword(userIdent.getUserName(), userIdent.getPassword()); 292 } catch (DirectoryException e) { 293 throw (LoginException) new LoginException("Unable to validate identity").initCause(e); 294 } 295 if (authenticated) { 296 return (NuxeoPrincipal) createIdentity(userIdent.getUserName()); 297 } else { 298 return null; 299 } 300 } else { 301 LoginPlugin lp = loginPluginManager.getPlugin(loginPluginName); 302 if (lp == null) { 303 log.error("Can't authenticate against a null loginModul plugin"); 304 return null; 305 } 306 // set the parameters and reinit if needed 307 LoginPluginDescriptor lpd = loginPluginManager.getPluginDescriptor(loginPluginName); 308 if (!lpd.getInitialized()) { 309 Map<String, String> existingParams = lp.getParameters(); 310 if (existingParams == null) { 311 existingParams = new HashMap<String, String>(); 312 } 313 Map<String, String> loginParams = userIdent.getLoginParameters(); 314 if (loginParams != null) { 315 existingParams.putAll(loginParams); 316 } 317 boolean init = lp.initLoginModule(); 318 if (init) { 319 lpd.setInitialized(true); 320 } else { 321 log.error("Unable to initialize LoginModulePlugin " + lp.getName()); 322 return null; 323 } 324 } 325 326 String username = lp.validatedUserIdentity(userIdent); 327 if (username == null) { 328 return null; 329 } else { 330 return (NuxeoPrincipal) createIdentity(username); 331 } 332 } 333 } 334 335 protected NuxeoPrincipal validateUsernamePassword(String username, String password) throws LoginException { 336 if (!manager.checkUsernamePassword(username, password)) { 337 return null; 338 } 339 return (NuxeoPrincipal) createIdentity(username); 340 } 341 342}