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