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