/* * 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. * under the License. */ package org.apache.karaf.jaas.modules.syncope; import org.apache.http.HttpStatus; import org.apache.http.auth.AuthScope; import org.apache.http.auth.Credentials; import org.apache.http.auth.UsernamePasswordCredentials; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpGet; import org.apache.http.impl.client.DefaultHttpClient; import org.apache.http.util.EntityUtils; import org.apache.karaf.jaas.boot.principal.RolePrincipal; import org.apache.karaf.jaas.boot.principal.UserPrincipal; import org.apache.karaf.jaas.modules.AbstractKarafLoginModule; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.security.auth.Subject; import javax.security.auth.callback.*; import javax.security.auth.login.LoginException; import java.io.IOException; import java.security.Principal; import java.util.*; /** * Karaf login module which uses Apache Syncope backend. */ public class SyncopeLoginModule extends AbstractKarafLoginModule { private final static Logger LOGGER = LoggerFactory.getLogger(SyncopeLoginModule.class); public final static String ADDRESS = "address"; public final static String ADMIN_USER = "admin.user"; // for the backing engine public final static String ADMIN_PASSWORD = "admin.password"; // for the backing engine private String address; public void initialize(Subject subject, CallbackHandler callbackHandler, Map<String, ?> sharedState, Map<String, ?> options) { super.initialize(subject, callbackHandler, options); address = (String) options.get(ADDRESS); } public boolean login() throws LoginException { Callback[] callbacks = new Callback[2]; callbacks[0] = new NameCallback("Username: "); callbacks[1] = new PasswordCallback("Password: ", false); try { callbackHandler.handle(callbacks); } catch (IOException ioException) { throw new LoginException(ioException.getMessage()); } catch (UnsupportedCallbackException unsupportedCallbackException) { throw new LoginException(unsupportedCallbackException.getMessage() + " not available to obtain information from user."); } user = ((NameCallback) callbacks[0]).getName(); char[] tmpPassword = ((PasswordCallback) callbacks[1]).getPassword(); if (tmpPassword == null) { tmpPassword = new char[0]; } String password = new String(tmpPassword); principals = new HashSet<Principal>(); // authenticate the user on Syncope LOGGER.debug("Authenticate user {} on Syncope located {}", user, address); DefaultHttpClient client = new DefaultHttpClient(); Credentials creds = new UsernamePasswordCredentials(user, password); client.getCredentialsProvider().setCredentials(AuthScope.ANY, creds); HttpGet get = new HttpGet(address + "/users/self"); get.setHeader("Content-Type", "application/xml"); List<String> roles = new ArrayList<String>(); try { CloseableHttpResponse response = client.execute(get); LOGGER.debug("Syncope HTTP response status code: {}", response.getStatusLine().getStatusCode()); if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK) { LOGGER.warn("User {} not authenticated", user); return false; } LOGGER.debug("User {} authenticated", user); LOGGER.debug("Populating principals with user"); principals.add(new UserPrincipal(user)); LOGGER.debug("Retrieving user {} roles", user); roles = extractingRoles(EntityUtils.toString(response.getEntity())); } catch (Exception e) { LOGGER.error("User {} authentication failed", user, e); throw new LoginException("User " + user + " authentication failed: " + e.getMessage()); } LOGGER.debug("Populating principals with roles"); for (String role : roles) { principals.add(new RolePrincipal(role)); } return true; } /** * Extract the user roles from the Syncope entity response. * * @param response the HTTP response from Syncope. * @return the list of user roles. * @throws Exception in case of extraction failure. */ protected List<String> extractingRoles(String response) throws Exception { List<String> roles = new ArrayList<String>(); if (response != null && !response.isEmpty()) { // extract the <memberships> element if it exists int index = response.indexOf("<memberships>"); if (index != -1) { response = response.substring(index + "<memberships>".length()); index = response.indexOf("</memberships>"); response = response.substring(0, index); // looking for the roleName elements index = response.indexOf("<roleName>"); while (index != -1) { response = response.substring(index + "<roleName>".length()); int end = response.indexOf("</roleName>"); if (end == -1) { index = -1; } String role = response.substring(0, end); roles.add(role); response = response.substring(end + "</roleName>".length()); index = response.indexOf("<roleName>"); } } } return roles; } public boolean abort() { return true; } public boolean logout() throws LoginException { subject.getPrincipals().removeAll(principals); principals.clear(); return true; } }