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