/*
* (C) Copyright 2006-2011 Nuxeo SA (http://nuxeo.com/) and others.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Contributors:
* Nuxeo - initial API and implementation
*
* $Id$
*/
package org.nuxeo.runtime.api.login;
import java.io.Serializable;
import java.security.AccessController;
import java.security.Principal;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Map;
import java.util.Set;
import javax.security.auth.Subject;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.login.AppConfigurationEntry;
import javax.security.auth.login.LoginContext;
import javax.security.auth.login.LoginException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.nuxeo.runtime.api.RuntimeInstanceIdentifier;
import org.nuxeo.runtime.model.ComponentContext;
import org.nuxeo.runtime.model.ComponentInstance;
import org.nuxeo.runtime.model.ComponentName;
import org.nuxeo.runtime.model.DefaultComponent;
/**
* @author <a href="mailto:bs@nuxeo.com">Bogdan Stefanescu</a>
* @author <a href="mailto:td@nuxeo.com">Thierry Delprat</a>
*/
public class LoginComponent extends DefaultComponent implements LoginService {
public static final ComponentName NAME = new ComponentName("org.nuxeo.runtime.LoginComponent");
public static final String SYSTEM_LOGIN = "nuxeo-system-login";
public static final String CLIENT_LOGIN = "nuxeo-client-login";
public static final String SYSTEM_USERNAME = "system";
protected static final String instanceId = RuntimeInstanceIdentifier.getId();
protected static final SystemLoginRestrictionManager systemLoginManager = new SystemLoginRestrictionManager();
protected static final Log log = LogFactory.getLog(LoginComponent.class);
private final Map<String, SecurityDomain> domains = new Hashtable<String, SecurityDomain>();
private SecurityDomain systemLogin;
private SecurityDomain clientLogin;
@Override
public void activate(ComponentContext context) {
LoginConfiguration.INSTANCE.install(new LoginConfiguration.Provider() {
@Override
public AppConfigurationEntry[] getAppConfigurationEntry(String name) {
return LoginComponent.this.getAppConfigurationEntry(name);
}
});
}
@Override
public void deactivate(ComponentContext context) {
LoginConfiguration.INSTANCE.uninstall();
}
@Override
public void registerContribution(Object contribution, String extensionPoint, ComponentInstance contributor) {
if (extensionPoint.equals("domains")) {
SecurityDomain domain = (SecurityDomain) contribution;
addSecurityDomain(domain);
}
}
@Override
public void unregisterContribution(Object contribution, String extensionPoint, ComponentInstance contributor) {
if (extensionPoint.equals("domains")) {
SecurityDomain domain = (SecurityDomain) contribution;
removeSecurityDomain(domain.getName());
}
}
public AppConfigurationEntry[] getAppConfigurationEntry(String name) {
SecurityDomain domain = domains.get(name);
if (domain != null) {
return domain.getAppConfigurationEntries();
}
return null;
}
@Override
@SuppressWarnings("unchecked")
public <T> T getAdapter(Class<T> adapter) {
if (LoginService.class.isAssignableFrom(adapter)) {
return (T) this;
}
return null;
}
@Override
public SecurityDomain getSecurityDomain(String name) {
return domains.get(name);
}
@Override
public void addSecurityDomain(SecurityDomain domain) {
domains.put(domain.getName(), domain);
if (SYSTEM_LOGIN.equals(domain.getName())) {
systemLogin = domain;
} else if (CLIENT_LOGIN.equals(domain.getName())) {
clientLogin = domain;
}
}
@Override
public void removeSecurityDomain(String name) {
domains.remove(name);
if (SYSTEM_LOGIN.equals(name)) {
systemLogin = null;
} else if (CLIENT_LOGIN.equals(name)) {
clientLogin = null;
}
}
@Override
public SecurityDomain[] getSecurityDomains() {
return domains.values().toArray(new SecurityDomain[domains.size()]);
}
@Override
public void removeSecurityDomains() {
domains.clear();
systemLogin = null;
clientLogin = null;
}
private LoginContext systemLogin(String username) throws LoginException {
if (systemLogin != null) {
Set<Principal> principals = new HashSet<Principal>();
SystemID sysId = new SystemID(username);
principals.add(sysId);
Subject subject = new Subject(false, principals, new HashSet<String>(), new HashSet<String>());
return systemLogin.login(subject, new CredentialsCallbackHandler(sysId.getName(), sysId));
}
return null;
}
@Override
public LoginContext login() throws LoginException {
return loginAs(null);
}
@Override
public LoginContext loginAs(final String username) throws LoginException {
// login as system user is a privileged action
try {
return AccessController.doPrivileged(new PrivilegedExceptionAction<LoginContext>() {
@Override
public LoginContext run() throws LoginException {
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
sm.checkPermission(new SystemLoginPermission());
}
return systemLogin(username);
}
});
} catch (PrivilegedActionException e) {
throw (LoginException) e.getException();
}
}
@Override
public LoginContext login(String username, Object credentials) throws LoginException {
if (clientLogin != null) {
return clientLogin.login(username, credentials);
}
return null;
}
@Override
public LoginContext login(CallbackHandler cbHandler) throws LoginException {
if (clientLogin != null) {
return clientLogin.login(cbHandler);
}
return null;
}
@Override
public boolean isSystemId(Principal principal) {
return isSystemLogin(principal);
}
public static boolean isSystemLogin(Object principal) {
if (principal != null && principal.getClass() == SystemID.class) {
if (!systemLoginManager.isRemoteSystemLoginRestricted()) {
return true;
} else {
SystemID sys = (SystemID) principal;
String sourceInstanceId = sys.getSourceInstanceId();
if (sourceInstanceId == null) {
log.warn("Can not accept a system login without InstanceID of the source : System login is rejected");
return false;
} else {
if (sourceInstanceId.equals(instanceId)) {
return true;
} else {
if (systemLoginManager.isRemoveSystemLoginAllowedForInstance(sourceInstanceId)) {
if (log.isTraceEnabled()) {
log.trace("Remote SystemLogin from instance " + sourceInstanceId + " accepted");
}
return true;
} else {
log.warn("Remote SystemLogin attempt from instance " + sourceInstanceId + " was denied");
return false;
}
}
}
}
}
return false;
}
public static class SystemID implements Principal, Serializable {
private static final long serialVersionUID = 2758247997191809993L;
private final String userName;
protected final String sourceInstanceId = instanceId;
public SystemID() {
userName = null;
}
public SystemID(String origUser) {
userName = origUser == null ? SYSTEM_USERNAME : origUser;
}
@Override
public String getName() {
return userName;
}
public String getSourceInstanceId() {
return sourceInstanceId;
}
@Override
public boolean equals(Object other) {
if (other instanceof Principal) {
Principal oPal = (Principal) other;
String oName = oPal.getName();
if (userName == null && oName != null) {
return false;
} else if (!userName.equals(oName)) {
return false;
}
if (systemLoginManager.isRemoteSystemLoginRestricted() && (other instanceof LoginComponent.SystemID)) {
// compare sourceInstanceId
String oSysId = ((LoginComponent.SystemID) other).sourceInstanceId;
if (sourceInstanceId == null) {
return oSysId == null;
} else {
return sourceInstanceId.equals(oSysId);
}
} else {
return true;
}
}
return false;
}
@Override
public int hashCode() {
if (!systemLoginManager.isRemoteSystemLoginRestricted()) {
return userName == null ? 0 : userName.hashCode();
} else {
return userName == null ? 0 : userName.hashCode() + sourceInstanceId.hashCode();
}
}
}
}