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}