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}