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