001/*
002 * Copyright (c) 2006-2011 Nuxeo SA (http://nuxeo.com/) and others.
003 *
004 * All rights reserved. This program and the accompanying materials
005 * are made available under the terms of the Eclipse Public License v1.0
006 * which accompanies this distribution, and is available at
007 * http://www.eclipse.org/legal/epl-v10.html
008 *
009 * Contributors:
010 *     Nuxeo - initial API and implementation
011 *
012 * $Id$
013 */
014
015package org.nuxeo.runtime.api.login;
016
017import java.io.Serializable;
018import java.security.AccessController;
019import java.security.Principal;
020import java.security.PrivilegedActionException;
021import java.security.PrivilegedExceptionAction;
022import java.util.HashSet;
023import java.util.Hashtable;
024import java.util.Map;
025import java.util.Set;
026
027import javax.security.auth.Subject;
028import javax.security.auth.callback.CallbackHandler;
029import javax.security.auth.login.AppConfigurationEntry;
030import javax.security.auth.login.LoginContext;
031import javax.security.auth.login.LoginException;
032
033import org.apache.commons.logging.Log;
034import org.apache.commons.logging.LogFactory;
035import org.nuxeo.runtime.api.RuntimeInstanceIdentifier;
036import org.nuxeo.runtime.model.ComponentContext;
037import org.nuxeo.runtime.model.ComponentInstance;
038import org.nuxeo.runtime.model.ComponentName;
039import org.nuxeo.runtime.model.DefaultComponent;
040
041/**
042 * @author <a href="mailto:bs@nuxeo.com">Bogdan Stefanescu</a>
043 * @author <a href="mailto:td@nuxeo.com">Thierry Delprat</a>
044 */
045public class LoginComponent extends DefaultComponent implements LoginService {
046
047    public static final ComponentName NAME = new ComponentName("org.nuxeo.runtime.LoginComponent");
048
049    public static final String SYSTEM_LOGIN = "nuxeo-system-login";
050
051    public static final String CLIENT_LOGIN = "nuxeo-client-login";
052
053    public static final String SYSTEM_USERNAME = "system";
054
055    protected static final String instanceId = RuntimeInstanceIdentifier.getId();
056
057    protected static final SystemLoginRestrictionManager systemLoginManager = new SystemLoginRestrictionManager();
058
059    protected static final Log log = LogFactory.getLog(LoginComponent.class);
060
061    private final Map<String, SecurityDomain> domains = new Hashtable<String, SecurityDomain>();
062
063    private SecurityDomain systemLogin;
064
065    private SecurityDomain clientLogin;
066
067    @Override
068    public void activate(ComponentContext context) {
069        LoginConfiguration.INSTANCE.install(new LoginConfiguration.Provider() {
070
071            @Override
072            public AppConfigurationEntry[] getAppConfigurationEntry(String name) {
073                return LoginComponent.this.getAppConfigurationEntry(name);
074            }
075
076        });
077    }
078
079    @Override
080    public void deactivate(ComponentContext context) {
081        LoginConfiguration.INSTANCE.uninstall();
082    }
083
084    @Override
085    public void registerContribution(Object contribution, String extensionPoint, ComponentInstance contributor) {
086        if (extensionPoint.equals("domains")) {
087            SecurityDomain domain = (SecurityDomain) contribution;
088            addSecurityDomain(domain);
089        }
090    }
091
092    @Override
093    public void unregisterContribution(Object contribution, String extensionPoint, ComponentInstance contributor) {
094        if (extensionPoint.equals("domains")) {
095            SecurityDomain domain = (SecurityDomain) contribution;
096            removeSecurityDomain(domain.getName());
097        }
098    }
099
100    public AppConfigurationEntry[] getAppConfigurationEntry(String name) {
101        SecurityDomain domain = domains.get(name);
102        if (domain != null) {
103            return domain.getAppConfigurationEntries();
104        }
105        return null;
106    }
107
108    @Override
109    @SuppressWarnings("unchecked")
110    public <T> T getAdapter(Class<T> adapter) {
111        if (LoginService.class.isAssignableFrom(adapter)) {
112            return (T) this;
113        }
114        return null;
115    }
116
117    @Override
118    public SecurityDomain getSecurityDomain(String name) {
119        return domains.get(name);
120    }
121
122    @Override
123    public void addSecurityDomain(SecurityDomain domain) {
124        domains.put(domain.getName(), domain);
125        if (SYSTEM_LOGIN.equals(domain.getName())) {
126            systemLogin = domain;
127        } else if (CLIENT_LOGIN.equals(domain.getName())) {
128            clientLogin = domain;
129        }
130    }
131
132    @Override
133    public void removeSecurityDomain(String name) {
134        domains.remove(name);
135        if (SYSTEM_LOGIN.equals(name)) {
136            systemLogin = null;
137        } else if (CLIENT_LOGIN.equals(name)) {
138            clientLogin = null;
139        }
140    }
141
142    @Override
143    public SecurityDomain[] getSecurityDomains() {
144        return domains.values().toArray(new SecurityDomain[domains.size()]);
145    }
146
147    @Override
148    public void removeSecurityDomains() {
149        domains.clear();
150        systemLogin = null;
151        clientLogin = null;
152    }
153
154    private LoginContext systemLogin(String username) throws LoginException {
155        if (systemLogin != null) {
156            Set<Principal> principals = new HashSet<Principal>();
157            SystemID sysId = new SystemID(username);
158            principals.add(sysId);
159            Subject subject = new Subject(false, principals, new HashSet<String>(), new HashSet<String>());
160            return systemLogin.login(subject, new CredentialsCallbackHandler(sysId.getName(), sysId));
161        }
162        return null;
163    }
164
165    @Override
166    public LoginContext login() throws LoginException {
167        return loginAs(null);
168    }
169
170    @Override
171    public LoginContext loginAs(final String username) throws LoginException {
172        // login as system user is a privileged action
173        try {
174            return AccessController.doPrivileged(new PrivilegedExceptionAction<LoginContext>() {
175                @Override
176                public LoginContext run() throws LoginException {
177                    SecurityManager sm = System.getSecurityManager();
178                    if (sm != null) {
179                        sm.checkPermission(new SystemLoginPermission());
180                    }
181                    return systemLogin(username);
182                }
183            });
184        } catch (PrivilegedActionException e) {
185            throw (LoginException) e.getException();
186        }
187    }
188
189    @Override
190    public LoginContext login(String username, Object credentials) throws LoginException {
191        if (clientLogin != null) {
192            return clientLogin.login(username, credentials);
193        }
194        return null;
195    }
196
197    @Override
198    public LoginContext login(CallbackHandler cbHandler) throws LoginException {
199        if (clientLogin != null) {
200            return clientLogin.login(cbHandler);
201        }
202        return null;
203    }
204
205    @Override
206    public boolean isSystemId(Principal principal) {
207        return isSystemLogin(principal);
208    }
209
210    public static boolean isSystemLogin(Object principal) {
211        if (principal != null && principal.getClass() == SystemID.class) {
212            if (!systemLoginManager.isRemoteSystemLoginRestricted()) {
213                return true;
214            } else {
215                SystemID sys = (SystemID) principal;
216                String sourceInstanceId = sys.getSourceInstanceId();
217                if (sourceInstanceId == null) {
218                    log.warn("Can not accept a system login without InstanceID of the source : System login is rejected");
219                    return false;
220                } else {
221                    if (sourceInstanceId.equals(instanceId)) {
222                        return true;
223                    } else {
224                        if (systemLoginManager.isRemoveSystemLoginAllowedForInstance(sourceInstanceId)) {
225                            if (log.isTraceEnabled()) {
226                                log.trace("Remote SystemLogin from instance " + sourceInstanceId + " accepted");
227                            }
228                            return true;
229                        } else {
230                            log.warn("Remote SystemLogin attempt from instance " + sourceInstanceId + " was denied");
231                            return false;
232                        }
233                    }
234                }
235            }
236        }
237        return false;
238    }
239
240    public static class SystemID implements Principal, Serializable {
241
242        private static final long serialVersionUID = 2758247997191809993L;
243
244        private final String userName;
245
246        protected final String sourceInstanceId = instanceId;
247
248        public SystemID() {
249            userName = null;
250        }
251
252        public SystemID(String origUser) {
253            userName = origUser == null ? SYSTEM_USERNAME : origUser;
254        }
255
256        @Override
257        public String getName() {
258            return userName;
259        }
260
261        public String getSourceInstanceId() {
262            return sourceInstanceId;
263        }
264
265        @Override
266        public boolean equals(Object other) {
267            if (other instanceof Principal) {
268                Principal oPal = (Principal) other;
269                String oName = oPal.getName();
270                if (userName == null && oName != null) {
271                    return false;
272                } else if (!userName.equals(oName)) {
273                    return false;
274                }
275                if (systemLoginManager.isRemoteSystemLoginRestricted() && (other instanceof LoginComponent.SystemID)) {
276                    // compare sourceInstanceId
277                    String oSysId = ((LoginComponent.SystemID) other).sourceInstanceId;
278                    if (sourceInstanceId == null) {
279                        return oSysId == null;
280                    } else {
281                        return sourceInstanceId.equals(oSysId);
282                    }
283                } else {
284                    return true;
285                }
286            }
287            return false;
288        }
289
290        @Override
291        public int hashCode() {
292            if (!systemLoginManager.isRemoteSystemLoginRestricted()) {
293                return userName == null ? 0 : userName.hashCode();
294            } else {
295                return userName == null ? 0 : userName.hashCode() + sourceInstanceId.hashCode();
296            }
297        }
298
299    }
300
301}