/** * (C) Copyright 2013 Jabylon (http://www.jabylon.org) and others. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html */ package org.jabylon.security.auth; import java.io.File; import java.io.IOException; import java.net.URI; import java.net.URL; import java.util.Set; import javax.security.auth.Subject; import javax.security.auth.callback.Callback; import javax.security.auth.callback.CallbackHandler; import javax.security.auth.callback.NameCallback; import javax.security.auth.callback.PasswordCallback; import javax.security.auth.callback.UnsupportedCallbackException; import javax.security.auth.login.LoginContext; import javax.security.auth.login.LoginException; import org.apache.felix.scr.annotations.Activate; import org.apache.felix.scr.annotations.Component; import org.apache.felix.scr.annotations.Deactivate; import org.apache.felix.scr.annotations.Reference; import org.apache.felix.scr.annotations.ReferenceCardinality; import org.apache.felix.scr.annotations.ReferencePolicy; import org.apache.felix.scr.annotations.Service; import org.apache.karaf.jaas.config.JaasRealm; import org.eclipse.emf.cdo.util.CommitException; import org.eclipse.equinox.security.auth.LoginContextFactory; import org.jabylon.cdo.connector.Modification; import org.jabylon.cdo.connector.RepositoryConnector; import org.jabylon.cdo.connector.TransactionUtil; import org.jabylon.cdo.server.ServerConstants; import org.jabylon.security.CommonPermissions; import org.jabylon.security.JabylonSecurityBundle; import org.jabylon.security.SubjectAttribute; import org.jabylon.security.internal.JabylonJaasRealmService; import org.jabylon.security.internal.LoginContextWrapper; import org.jabylon.users.User; import org.jabylon.users.UserManagement; import org.jabylon.users.UsersFactory; import org.osgi.framework.BundleContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @Component(enabled=true,immediate=true) @Service public class AuthenticatorServiceImpl implements AuthenticationService { private Logger logger = LoggerFactory.getLogger(AuthenticatorServiceImpl.class); private static final String JAAS_CONFIG_FILE = "jaas.config"; //$NON-NLS-1$ public static final String REALM_NAME = "Jabylon"; //$NON-NLS-1$ @Reference(policy=ReferencePolicy.DYNAMIC,cardinality=ReferenceCardinality.MANDATORY_UNARY,bind="setRepositoryConnector",unbind="unbindRepositoryConnector") private RepositoryConnector repositoryConnector; private UserManagement userManagement; private User anonymous; public boolean authenticate(final String username, final String password) { Subject subject = doAuthenticate(username, password); return subject != null; } protected Subject doAuthenticate(final String username, final String password) { try { LoginContext context = createLoginContext(new CallbackHandler() { @Override public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException { for (int i = 0; i < callbacks.length; i++) { Callback cb = callbacks[i]; if (cb instanceof NameCallback) { ((NameCallback) cb).setName(username); //$NON-NLS-1$ } else if (cb instanceof PasswordCallback) { ((PasswordCallback) cb).setPassword(password.toCharArray()); //$NON-NLS-1$ } } } }); context.login(); final Subject subject = context.getSubject(); Set<String> credentials = subject.getPublicCredentials(String.class); // in case it was an auth token, the username was null but now we // know the right one String actualUsername = credentials.isEmpty() ? username : credentials.iterator().next(); logger.info("Login for user {} successful", actualUsername); return subject; } catch (LoginException e) { logger.error("Login for user " + username + " failed: " + e.getMessage()); } return null; } public User authenticateUser(final String username, final String password) { String actualUsername = username; final Subject subject = doAuthenticate(username, password); if (subject == null) return null; Set<String> credentials = subject.getPublicCredentials(String.class); if(!credentials.isEmpty()) actualUsername = credentials.iterator().next(); UserManagement management = getUserManagement(); if (management == null) return null; User user = management.findUserByName(actualUsername); try { if (user == null) { logger.info("User {} logged in for the first time. Creating DB Entry",actualUsername); final User newUser = UsersFactory.eINSTANCE.createUser(); newUser.setName(actualUsername); user = TransactionUtil.commit(management, new Modification<UserManagement, User>() { @Override public User apply(UserManagement object) { CommonPermissions.addDefaultPermissions(object, newUser); applyAttributes(newUser, subject); object.getUsers().add(newUser); return newUser; } }); } else { user = TransactionUtil.commit(user, new Modification<User, User>() { @Override public User apply(User object) { applyAttributes(object, subject); return object; } }); } } catch (CommitException e) { logger.error("Failed to commit new user or updating exsiting after login", e); } return user; } protected void applyAttributes(User user, Subject subject) { Set<SubjectAttribute> attributes = subject.getPublicCredentials(SubjectAttribute.class); for (SubjectAttribute subjectAttribute : attributes) { subjectAttribute.applyTo(user); } } private LoginContext createLoginContext(CallbackHandler callbackHandler) throws LoginException { if(!ServerConstants.IS_KARAF) { URL configUrl = getJAASConfig(); return new LoginContextWrapper(REALM_NAME,LoginContextFactory.createContext(REALM_NAME, configUrl, callbackHandler)); } return new LoginContext(REALM_NAME, new Subject(), callbackHandler); } private URL getJAASConfig() { String configArea = System.getProperty("karaf.etc",System.getProperty("osgi.configuration.area")); if (configArea == null || configArea.isEmpty()) configArea = new File(new File(ServerConstants.WORKING_DIR), "configuration").toURI().toString(); try { URI uri = new URI(configArea); File jaasConfig = new File(uri.getPath(), JAAS_CONFIG_FILE); if (jaasConfig.isFile()) { return jaasConfig.toURI().toURL(); } } catch (Exception e) { logger.error("invalid jaas url", e); } // fallback return JabylonSecurityBundle.getBundleContext().getBundle().getEntry("META-INF/" + JAAS_CONFIG_FILE); } private UserManagement getUserManagement() { if(userManagement==null) { Object resolved = getRepositoryConnector().openView().getResource(ServerConstants.USERS_RESOURCE).getContents().get(0); if (resolved instanceof UserManagement) { userManagement = (UserManagement) resolved; } else { logger.error("Failed to obtain UserManagement"); } } return userManagement; } public RepositoryConnector getRepositoryConnector() { return repositoryConnector; } public void setRepositoryConnector(RepositoryConnector repositoryConnector) { this.repositoryConnector = repositoryConnector; } public void unbindRepositoryConnector(RepositoryConnector repositoryConnector) { if(repositoryConnector==this.repositoryConnector) { if(userManagement!=null) userManagement.cdoView().close(); userManagement = null; this.repositoryConnector = null; } } @Activate protected void activate(BundleContext context) { if(ServerConstants.IS_KARAF) { logger.info("Registering Jabylon JaasRealm in Karaf"); context.registerService(JaasRealm.class, new JabylonJaasRealmService(), null); } } @Deactivate protected void deactivate() { if(userManagement!=null) userManagement.cdoView().close(); userManagement = null; } @Override public User getAnonymousUser() { if(anonymous==null) { anonymous = getUserManagement().findUserByName(CommonPermissions.USER_ANONYMOUS); } return anonymous; } }