package org.apereo.cas.services;
import org.apache.commons.lang3.StringUtils;
import org.apereo.cas.authentication.principal.Principal;
import org.apereo.cas.util.RegexUtils;
import org.apereo.cas.util.ResourceUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.script.Bindings;
import javax.script.Invocable;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
import javax.script.SimpleBindings;
import java.io.File;
import java.io.FileReader;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* This is {@link ScriptedRegisteredServiceAttributeReleasePolicy}.
*
* @author Misagh Moayyed
* @since 5.1.0
*/
public class ScriptedRegisteredServiceAttributeReleasePolicy extends AbstractRegisteredServiceAttributeReleasePolicy {
private static final long serialVersionUID = -979532578142774128L;
private static final Logger LOGGER = LoggerFactory.getLogger(ScriptedRegisteredServiceAttributeReleasePolicy.class);
private static final Pattern INLINE_GROOVY_PATTERN = RegexUtils.createPattern("groovy\\s*\\{(.+)\\}");
private String scriptFile;
public ScriptedRegisteredServiceAttributeReleasePolicy() {
}
public ScriptedRegisteredServiceAttributeReleasePolicy(final String scriptFile) {
this.scriptFile = scriptFile;
}
public String getScriptFile() {
return scriptFile;
}
public void setScriptFile(final String scriptFile) {
this.scriptFile = scriptFile;
}
@Override
protected Map<String, Object> getAttributesInternal(final Principal principal,
final Map<String, Object> attributes,
final RegisteredService service) {
try {
if (StringUtils.isBlank(this.scriptFile)) {
return new HashMap<>();
}
final Matcher matcherInline = INLINE_GROOVY_PATTERN.matcher(this.scriptFile);
if (matcherInline.find()) {
return getAttributesFromInlineGroovyScript(attributes, matcherInline);
}
return getScriptedAttributesFromFile(attributes);
} catch (final Exception e) {
LOGGER.error(e.getMessage(), e);
}
return new HashMap<>();
}
private static Map<String, Object> getAttributesFromInlineGroovyScript(final Map<String, Object> attributes,
final Matcher matcherInline) throws ScriptException {
final String script = matcherInline.group(1).trim();
final ScriptEngine engine = new ScriptEngineManager().getEngineByName("groovy");
if (engine == null) {
LOGGER.warn("Script engine is not available for Groovy");
return new HashMap<>();
}
final Object[] args = {attributes, LOGGER};
LOGGER.debug("Executing script, with parameters [{}]", args);
final Bindings binding = new SimpleBindings();
binding.put("attributes", attributes);
binding.put("logger", LOGGER);
return (Map<String, Object>) engine.eval(script, binding);
}
private Map<String, Object> getScriptedAttributesFromFile(final Map<String, Object> attributes) throws Exception {
final String engineName = getScriptEngineName();
final ScriptEngine engine = new ScriptEngineManager().getEngineByName(engineName);
if (engine == null || StringUtils.isBlank(engineName)) {
LOGGER.warn("Script engine is not available for [{}]", engineName);
return new HashMap<>();
}
final File theScriptFile = ResourceUtils.getResourceFrom(this.scriptFile).getFile();
if (theScriptFile.exists()) {
LOGGER.debug("Created object instance from class [{}]", theScriptFile.getCanonicalPath());
final Object[] args = {attributes, LOGGER};
LOGGER.debug("Executing script's run method, with parameters [{}]", args);
engine.eval(new FileReader(theScriptFile));
final Invocable invocable = (Invocable) engine;
final Map<String, Object> personAttributesMap = (Map<String, Object>) invocable.invokeFunction("run", args);
LOGGER.debug("Final set of attributes determined by the script are [{}]", personAttributesMap);
return personAttributesMap;
}
LOGGER.warn("[{}] script [{}] does not exist, or cannot be loaded", StringUtils.capitalize(engineName), scriptFile);
return new HashMap<>();
}
private String getScriptEngineName() {
String engineName = null;
if (this.scriptFile.endsWith(".py")) {
engineName = "python";
} else if (this.scriptFile.endsWith(".js")) {
engineName = "js";
} else if (this.scriptFile.endsWith(".groovy")) {
engineName = "groovy";
}
return engineName;
}
}