package org.apereo.cas.services; import groovy.lang.Binding; import groovy.lang.GroovyShell; import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.builder.EqualsBuilder; import org.apache.commons.lang3.builder.HashCodeBuilder; import org.apereo.cas.authentication.principal.Principal; import org.apereo.cas.authentication.principal.Service; import org.apereo.cas.util.RegexUtils; import org.apereo.cas.util.ResourceUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * Resolves the username for the service to be the default principal id. * * @author Misagh Moayyed * @since 4.1.0 */ public class GroovyRegisteredServiceUsernameProvider extends BaseRegisteredServiceUsernameAttributeProvider { private static final long serialVersionUID = 5823989148794052951L; private static final Logger LOGGER = LoggerFactory.getLogger(GroovyRegisteredServiceUsernameProvider.class); private static final Pattern INLINE_GROOVY_PATTERN = RegexUtils.createPattern("groovy\\s*\\{(.+)\\}"); private static final Pattern FILE_GROOVY_PATTERN = RegexUtils.createPattern("file:(.+\\.groovy)"); private String groovyScript; public GroovyRegisteredServiceUsernameProvider() { } public GroovyRegisteredServiceUsernameProvider(final String groovyScript) { this.groovyScript = groovyScript; } @Override public String resolveUsernameInternal(final Principal principal, final Service service) { final Matcher matcherInline = INLINE_GROOVY_PATTERN.matcher(this.groovyScript); final Matcher matcherFile = FILE_GROOVY_PATTERN.matcher(this.groovyScript); if (matcherInline.find()) { return resolveUsernameFromInlineGroovyScript(principal, service, matcherInline.group(1)); } if (matcherFile.find()) { return resolveUsernameFromExternalGroovyScript(principal, service); } LOGGER.warn("Groovy script [{}] is not valid. CAS will switch to use the default principal identifier [{}]", this.groovyScript, principal.getId()); return principal.getId(); } private String resolveUsernameFromExternalGroovyScript(final Principal principal, final Service service) { try { LOGGER.debug("Found inline groovy script to execute"); final String script = IOUtils.toString(ResourceUtils.getResourceFrom(this.groovyScript).getInputStream(), StandardCharsets.UTF_8); final Object result = getGroovyAttributeValue(principal, script); if (result != null) { LOGGER.debug("Found username [{}] from script [{}]", result, this.groovyScript); return result.toString(); } } catch (final IOException e) { LOGGER.error(e.getMessage(), e); } LOGGER.warn("Groovy script [{}] returned no value for username attribute. Fallback to default [{}]", this.groovyScript, principal.getId()); return principal.getId(); } private String resolveUsernameFromInlineGroovyScript(final Principal principal, final Service service, final String script) { try { LOGGER.debug("Found groovy script to execute [{}]", this.groovyScript); final Object result = getGroovyAttributeValue(principal, script); if (result != null) { LOGGER.debug("Found username [{}] from script [{}]", result, this.groovyScript); return result.toString(); } } catch (final Exception e) { LOGGER.error(e.getMessage(), e); } LOGGER.warn("Groovy script [{}] returned no value for username attribute. Fallback to default [{}]", this.groovyScript, principal.getId()); return principal.getId(); } private static Object getGroovyAttributeValue(final Principal principal, final String script) { try { final Binding binding = new Binding(); final GroovyShell shell = new GroovyShell(binding); binding.setVariable("attributes", principal.getAttributes()); binding.setVariable("id", principal.getId()); binding.setVariable("logger", LOGGER); final Object res = shell.evaluate(script); return res; } catch (final Exception e) { LOGGER.error(e.getMessage(), e); } return null; } @Override public boolean equals(final Object obj) { if (obj == null) { return false; } if (obj == this) { return true; } if (obj.getClass() != getClass()) { return false; } final GroovyRegisteredServiceUsernameProvider rhs = (GroovyRegisteredServiceUsernameProvider) obj; return new EqualsBuilder() .appendSuper(super.equals(obj)) .append(this.groovyScript, rhs.groovyScript) .isEquals(); } @Override public int hashCode() { return new HashCodeBuilder() .appendSuper(super.hashCode()) .append(groovyScript) .toHashCode(); } public String getGroovyScript() { return groovyScript; } public void setGroovyScript(final String groovyScript) { this.groovyScript = groovyScript; } }