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