package org.searchisko.api.security.jaas; import java.io.IOException; import java.security.acl.Group; import java.util.HashMap; import java.util.Map; import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; import javax.inject.Inject; import javax.naming.NamingException; 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.LoginException; import org.jasig.cas.client.authentication.SimpleGroup; import org.jasig.cas.client.authentication.SimplePrincipal; import org.jasig.cas.client.jaas.AssertionPrincipal; import org.jasig.cas.client.jaas.CasLoginModule; import org.jasig.cas.client.util.CommonUtils; import org.searchisko.api.service.AppConfigurationService; import org.searchisko.api.service.ContributorProfileService; import org.searchisko.api.service.ContributorService; import org.searchisko.api.service.ProviderService; import org.searchisko.api.util.CdiHelper; /** * CAS JAAS Login Module * <br/> * Configuration:<br/> * <li> * <ul>contributorTypeSpecificCodeIdentifier - Name of type specific field in Contributor document used for exact match for finding corresponding contributor </ul> * </li> * * @author Libor Krzyzanek * @see org.searchisko.api.security.Role */ public class ContributorCasLoginModule extends CasLoginModule { @Inject protected Logger log; @Inject protected ProviderService providerService; @Inject protected AppConfigurationService appConfigurationService; @Inject protected ContributorService contributorService; /** * Name of type specific field in Contributor document used for exact match for finding corresponding contributor */ protected String contributorTypeSpecificCodeIdentifier = ContributorProfileService.FIELD_TSC_JBOSSORG_USERNAME; @Override public void initialize(Subject subject, CallbackHandler handler, Map<String, ?> state, Map<String, ?> options) { try { CdiHelper.programmaticInjection(ContributorCasLoginModule.class, this); } catch (NamingException e) { throw new RuntimeException("Cannot initialize Login module", e); } log.log(Level.FINE, "Initializing JAAS ContributorCasLoginModule"); if (options.containsKey("contributorTypeSpecificCodeIdentifier")) { contributorTypeSpecificCodeIdentifier = options.get("contributorTypeSpecificCodeIdentifier").toString(); } log.log(Level.FINE, "contributorTypeSpecificCodeIdentifier: " + contributorTypeSpecificCodeIdentifier); HashMap<String, Object> ops = new HashMap<>(options); String casServerUrl = appConfigurationService.getAppConfiguration().getCasConfig().getServerUrl(); ops.put("casServerUrlPrefix", casServerUrl); super.initialize(subject, handler, state, ops); } @Override public boolean login() throws LoginException { log.log(Level.FINEST, "Check if service is http(s)"); final NameCallback serviceCallback = new NameCallback("service"); final PasswordCallback ticketCallback = new PasswordCallback("ticket", false); try { this.callbackHandler.handle(new Callback[]{ticketCallback, serviceCallback}); } catch (final IOException e) { log.log(Level.SEVERE, "Login failed due to IO exception in callback handler: {0}", e); throw (LoginException) new LoginException("IO exception in callback handler: " + e).initCause(e); } catch (final UnsupportedCallbackException e) { log.log(Level.SEVERE, "Login failed due to unsupported callback: {0}", e); throw (LoginException) new LoginException("Callback handler does not support PasswordCallback and TextInputCallback.").initCause(e); } if (CommonUtils.isBlank(serviceCallback.getName())) { log.log(Level.FINE, "No Service passed. No check performed"); return super.login(); } if (serviceCallback.getName().startsWith("http://") || serviceCallback.getName().startsWith("https://")) { return super.login(); } else { log.log(Level.FINE, "Service (username) {0} does not start with http(s)://. Skip this login module", serviceCallback.getName()); return false; } } @Override public boolean commit() throws LoginException { boolean success = super.commit(); if (!success) { return false; } ContributorPrincipal contributorPrincipal = fixPrincipal(); Set<SimpleGroup> groups = subject.getPrincipals(org.jasig.cas.client.authentication.SimpleGroup.class); log.log(Level.FINE, "Add Roles to authenticated contributor, default roles: {0}", groups); Set<String> roles = contributorPrincipal.getRoles(); for (final String defaultRole : defaultRoles) { roles.add(defaultRole); } for (SimpleGroup g : groups) { if (this.roleGroupName.equals(g.getName())) { Set<String> contributorRoles = getContributorRoles(this.assertion.getPrincipal().getName()); if (contributorRoles != null) { for (String role : contributorRoles) { g.addMember(new SimplePrincipal(role)); contributorPrincipal.getRoles().add(role); } } log.log(Level.FINE, "Actual roles in role group: {0}", g); log.log(Level.FINE, "Actual roles in principal: {0}", roles); break; } } return success; } protected Set<String> getContributorRoles(String username) { return contributorService.getRolesByTypeSpecificCode(contributorTypeSpecificCodeIdentifier, username); } protected ContributorPrincipal fixPrincipal() { log.log(Level.FINEST, "Remove CAS principal and default group. Assertion name: {0}", this.assertion.getPrincipal().getName()); this.subject.getPrincipals().remove(new AssertionPrincipal(this.assertion.getPrincipal().getName(), this.assertion)); this.subject.getPrincipals().remove(new SimpleGroup(this.principalGroupName)); log.log(Level.FINEST, "Add ContributorPrincipal"); final ContributorPrincipal contributorPrincipal = new ContributorPrincipal(this.assertion.getPrincipal().getName(), this.assertion); this.subject.getPrincipals().add(contributorPrincipal); final Group principalGroup = new SimpleGroup(this.principalGroupName); principalGroup.addMember(contributorPrincipal); this.subject.getPrincipals().add(principalGroup); return contributorPrincipal; } }