
org.sakaiproject.tool.impl.ToolComponent Maven / Gradle / Ivy
/**********************************************************************************
* $URL$
* $Id$
***********************************************************************************
*
* Copyright (c) 2005, 2006, 2007, 2008 Sakai Foundation
*
* Licensed under the Educational Community 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.opensource.org/licenses/ECL-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.
*
**********************************************************************************/
package org.sakaiproject.tool.impl;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.Vector;
import java.util.concurrent.ConcurrentHashMap;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.sakaiproject.authz.api.SecurityService;
import org.sakaiproject.site.api.Site;
import org.sakaiproject.site.api.ToolConfiguration;
import org.sakaiproject.thread_local.api.ThreadLocalManager;
import org.sakaiproject.tool.api.Placement;
import org.sakaiproject.tool.api.Session;
import org.sakaiproject.tool.api.Tool;
import org.sakaiproject.tool.api.ToolManager;
import org.sakaiproject.tool.cover.SessionManager;
import org.sakaiproject.util.ResourceLoader;
import org.sakaiproject.util.StringUtil;
import org.sakaiproject.util.Xml;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
/**
*
* ToolComponent is the standard implementation of the Sakai Tool API.
*
*/
public abstract class ToolComponent implements ToolManager
{
/** Our log (commons). */
private static Log M_log = LogFactory.getLog(ToolComponent.class);
/** Key in the ThreadLocalManager for binding our current placement. */
protected final static String CURRENT_PLACEMENT = "sakai:ToolComponent:current.placement";
/** Key in the ThreadLocalManager for binding our current tool. */
protected final static String CURRENT_TOOL = "sakai:ToolComponent:current.tool";
/** Key in the ToolConfiguration Properties for checking what permissions a tool needs in order to be visible */
protected static final String TOOLCONFIG_REQUIRED_PERMISSIONS = "functions.require";
//Tool placement property for visibility
public static final String PORTAL_VISIBLE = "sakai-portal:visible";
/** The registered tools. */
protected Map m_tools = new ConcurrentHashMap();
/** tool ids to be hidden - their catagories don't matter, they don't show up on any catagorized listing. */
protected String[] m_toolIdsToHide = null;
/**********************************************************************************************************************************************************************************************************************************************************
* Dependencies
*********************************************************************************************************************************************************************************************************************************************************/
/**
* @return the ThreadLocalManager collaborator.
*/
protected abstract ThreadLocalManager threadLocalManager();
/**
* @return the SecurityService collaborator.
*/
protected abstract SecurityService securityService();
/**********************************************************************************************************************************************************************************************************************************************************
* Configuration
*********************************************************************************************************************************************************************************************************************************************************/
/** tool ids to be stealthed (hidden). */
protected Collection m_stealthToolIds = null;
/**
* Configuration - set the list of tool ids to be "stealthed". A stealthed tool does not show up in a category
* list of tools. The list of stealthed tools is set by the Sakai distribution.
* If a deployment wishes to override the values set they should use the {@link #setHiddenTools(String)} and
* {@link #setVisibleTools(String)}
*
* @param toolIds
* The comma-separated list of tool ids to be stealthed.
*/
public void setStealthTools(String toolIds)
{
if ((toolIds == null) || (toolIds.length() == 0))
{
m_stealthToolIds = null;
}
else
{
m_stealthToolIds = new Vector();
String[] items = StringUtil.split(toolIds, ",");
for (int i = 0; i < items.length; i++)
{
m_stealthToolIds.add(items[i]);
}
}
}
/** tool ids to be visible, not hidden, even if marked hidden or stealthed. */
protected Collection m_visibleToolIds = null;
/**
* Configuration - set the list of tool ids to be visible, not hidden, even if marked hidden or stealthed.
*
* @param toolIds
* The comma-separated list of tool ids to be visible.
*/
public void setVisibleTools(String toolIds)
{
if ((toolIds == null) || (toolIds.length() == 0))
{
m_visibleToolIds = null;
}
else
{
m_visibleToolIds = new Vector();
String[] items = StringUtil.split(toolIds, ",");
for (int i = 0; i < items.length; i++)
{
m_visibleToolIds.add(items[i]);
}
}
}
/** tool ids to be hidden, adding to the stealth list (but not trumping the visible list) */
protected Collection m_hiddenToolIds = null;
/**
* Configuration - set the list of tool ids to be hidden.
*
* @param toolIds
* The comma-separated list of tool ids to be hidden.
*/
public void setHiddenTools(String toolIds)
{
if ((toolIds == null) || (toolIds.length() == 0))
{
m_hiddenToolIds = null;
}
else
{
m_hiddenToolIds = new Vector();
String[] items = StringUtil.split(toolIds, ",");
for (int i = 0; i < items.length; i++)
{
m_hiddenToolIds.add(items[i]);
}
}
}
/**
* The ResourceLoader for getting tool internationalised tool properties.
* May be null.
*/
private ResourceLoader m_loader;
/**
* Setter for ResourceLoader.
*/
public void setResourceLoader(ResourceLoader loader)
{
m_loader = loader;
}
/**********************************************************************************************************************************************************************************************************************************************************
* Init and Destroy
*********************************************************************************************************************************************************************************************************************************************************/
/**
* Final initialization, once all dependencies are set.
*/
public void init()
{
// compute the tools to hide: these are the stealth tools plus the hidden tools, minus the visible ones
Collection toHide = new HashSet();
if (m_stealthToolIds != null)
{
toHide.addAll(m_stealthToolIds);
}
if (m_hiddenToolIds!= null)
{
toHide.addAll(m_hiddenToolIds);
}
if (m_visibleToolIds != null)
{
toHide.removeAll(m_visibleToolIds);
}
// collect the hiddens for logging
StringBuilder hidden = new StringBuilder();
if (!toHide.isEmpty())
{
m_toolIdsToHide = new String[toHide.size()];
int pos = 0;
for (String i : toHide) {
m_toolIdsToHide[pos] = i;
hidden.append(m_toolIdsToHide[pos]);
hidden.append(" ");
pos++;
}
Arrays.sort(m_toolIdsToHide);
}
M_log.info("init(): hidden tools: " + hidden.toString());
}
/**
* Final cleanup.
*/
public void destroy()
{
M_log.info("destroy()");
}
/**********************************************************************************************************************************************************************************************************************************************************
* Work interface
*********************************************************************************************************************************************************************************************************************************************************/
/**
* {@inheritDoc}
*/
@SuppressWarnings("unchecked")
public Set findTools(Set categories, Set keywords)
{
Set rv = new HashSet();
for (Iterator i = m_tools.values().iterator(); i.hasNext();)
{
Tool tool = (Tool) i.next();
if (matchCriteria(categories, tool.getCategories()) && matchCriteria(keywords, tool.getKeywords()))
{
// add if not hidden (requests for no (null) category include all, even hidden items)
if ((categories == null) || (m_toolIdsToHide == null) || (Arrays.binarySearch(m_toolIdsToHide, tool.getId()) < 0))
{
rv.add(tool);
}
}
}
return rv;
}
/**
* {@inheritDoc}
*/
public Placement getCurrentPlacement()
{
return (Placement) threadLocalManager().get(CURRENT_PLACEMENT);
}
/**
* {@inheritDoc}
*/
public Tool getCurrentTool()
{
return (Tool) threadLocalManager().get(CURRENT_TOOL);
}
/**
* {@inheritDoc}
*/
public Tool getTool(String id)
{
return (id != null) ? (Tool) m_tools.get(id) : null;
}
/**
* Check the target values for a match in the criteria. If criteria is empty or null, the target is a match.
*
* @param criteria
* The set of String values that is the criteria - any one in the target is a match
* @param target
* The set of String values to check against the criteria.
* @return true if the target meets the criteria, false if not.
*/
@SuppressWarnings("unchecked")
protected boolean matchCriteria(Set criteria, Set target)
{
if ((criteria == null) || (criteria.isEmpty())) return true;
for (Iterator i = target.iterator(); i.hasNext();)
{
String t = (String) i.next();
if (criteria.contains(t)) return true;
}
return false;
}
/**
* {@inheritDoc}
*/
public void register(Document toolXml)
{
Element root = toolXml.getDocumentElement();
if (!root.getTagName().equals("registration"))
{
M_log.info("register: invalid root element (expecting \"registration\"): " + root.getTagName());
return;
}
// read the children nodes (tools)
NodeList rootNodes = root.getChildNodes();
final int rootNodesLength = rootNodes.getLength();
for (int i = 0; i < rootNodesLength; i++)
{
Node rootNode = rootNodes.item(i);
if (rootNode.getNodeType() != Node.ELEMENT_NODE) continue;
Element rootElement = (Element) rootNode;
// for tool
if (rootElement.getTagName().equals("tool"))
{
org.sakaiproject.tool.impl.ToolImpl tool = new org.sakaiproject.tool.impl.ToolImpl(this);
tool.setId(rootElement.getAttribute("id").trim());
tool.setTitle(rootElement.getAttribute("title").trim());
tool.setDescription(rootElement.getAttribute("description").trim());
tool.setHome(StringUtils.trimToNull(rootElement.getAttribute("home")));
if ("tool".equals(rootElement.getAttribute("accessSecurity")))
{
tool.setAccessSecurity(Tool.AccessSecurity.TOOL);
}
else
{
tool.setAccessSecurity(Tool.AccessSecurity.PORTAL);
}
// collect values for these collections
Properties finalConfig = new Properties();
Properties mutableConfig = new Properties();
Set categories = new HashSet();
Set keywords = new HashSet();
NodeList kids = rootElement.getChildNodes();
final int kidsLength = kids.getLength();
for (int k = 0; k < kidsLength; k++)
{
Node kidNode = kids.item(k);
if (kidNode.getNodeType() != Node.ELEMENT_NODE) continue;
Element kidElement = (Element) kidNode;
// for configuration
if (kidElement.getTagName().equals("configuration"))
{
String name = kidElement.getAttribute("name").trim();
String value = kidElement.getAttribute("value").trim();
String type = kidElement.getAttribute("type").trim();
if (name.length() > 0)
{
if ("final".equals(type))
{
finalConfig.put(name, value);
}
else
{
mutableConfig.put(name, value);
}
}
}
// for category
if (kidElement.getTagName().equals("category"))
{
String name = kidElement.getAttribute("name").trim();
if (name.length() > 0)
{
categories.add(name);
}
}
// for keyword
if (kidElement.getTagName().equals("keyword"))
{
String name = kidElement.getAttribute("name").trim();
if (name.length() > 0)
{
keywords.add(name);
}
}
}
// set the tool's collected values
tool.setRegisteredConfig(finalConfig, mutableConfig);
tool.setCategories(categories);
tool.setKeywords(keywords);
register(tool);
}
}
}
/**
* {@inheritDoc}
*/
public void register(File toolXmlFile)
{
String path = toolXmlFile.getAbsolutePath();
if (!path.endsWith(".xml"))
{
M_log.info("register: skipping non .xml file: " + path);
return;
}
M_log.info("register: file: " + path);
Document doc = Xml.readDocument(path);
register(doc);
}
/**
* {@inheritDoc}
*/
public void register(InputStream toolXmlStream)
{
Document doc = Xml.readDocumentFromStream(toolXmlStream);
try
{
toolXmlStream.close();
}
catch (Exception e)
{
}
register(doc);
}
/**
* {@inheritDoc}
*/
public void register(Tool tool)
{
m_tools.put(tool.getId(), tool);
}
/**
* Establish the Tool associated with the current request / thread
*
* @param ToolImpl
* The current Tool, or null if there is none.
*/
protected void setCurrentPlacement(Placement placement)
{
threadLocalManager().set(CURRENT_PLACEMENT, placement);
}
/**
* Establish the Tool associated with the current request / thread
*
* @param ToolImpl
* The current Tool, or null if there is none.
*/
protected void setCurrentTool(Tool tool)
{
threadLocalManager().set(CURRENT_TOOL, tool);
}
/**
* Add the file passed as part of the tool title localization resource bundled based on it's embedded locale code.
*
* @param toolId Id string of the tool being set.
* @param filename Full filename of the properties file to be added to the resource bundle.
* @author Mark Norton for SAK-8908
*/
@SuppressWarnings("unchecked")
public void setResourceBundle (String toolId, String filename)
{
File file = new File(filename);
String shortname = file.getName();
org.sakaiproject.tool.impl.ToolImpl tool = (org.sakaiproject.tool.impl.ToolImpl) this.getTool(toolId);
// Initialize the HashSet if empty.
if (tool.m_title_bundle == null)
tool.m_title_bundle = new HashMap();
// Parse out the locale code.
String locale = null;
if (shortname.indexOf('_') == -1)
locale = "DEFAULT";
else {
// Extract the substring after the first underbar to the first period.
locale = shortname.substring (shortname.indexOf('_')+1, shortname.lastIndexOf('.'));
}
// Load the Properties file.
Properties props = new Properties ();
InputStream in = null;
try {
in = new FileInputStream(file);
props.load(in);
}
catch (IOException ex) {
M_log.warn ("Unable to load "+filename+" as a tool localizaton resource in tool "+tool.getId());
props = null;
}
finally {
if (in != null) {
try {
in.close();
} catch (IOException e) {
}
}
}
// Add the properties set to the resource bundle.
if (props != null)
tool.m_title_bundle.put (locale, props);
}
/**
* Check whether a tool is visible to the current user in this site,
* depending on permissions required to view the tool.
*
* The optional tool configuration tag "functions.require" describes a
* set of permission lists which decide the visibility of the tool link
* for this site user. Lists are separated by "|" and permissions within a
* list are separated by ",". Users must have all the permissions included in
* at least one of the permission lists.
*
* For example, a value like "section.role.student,annc.new|section.role.ta"
* would let a user with "section.role.ta" see the tool, and let a user with
* both "section.role.student" AND "annc.new" see the tool, but not let a user
* who only had "section.role.student" see the tool.
*
* If the configuration tag is not set or is null, then all users see the tool.
*
* Based on: portal/portal-impl/impl/src/java/org/sakaiproject/portal/charon/ToolHelperImpl.java
*
* @param site Site this tool is in
* @param config ToolConfiguration of the tool in the site
* @return
*/
public boolean isVisible(Site site, ToolConfiguration config) {
String toolPermissionsStr = config.getConfig().getProperty(TOOLCONFIG_REQUIRED_PERMISSIONS);
if (M_log.isDebugEnabled()) {
M_log.debug("tool: " + config.getToolId() + ", permissions: " + toolPermissionsStr);
}
//no special permissions required, it's visible
if(StringUtils.isBlank(toolPermissionsStr)) {
return true;
}
//check each set, if multiple permissions in the set, must have all.
String[] toolPermissionsSets = StringUtils.split(toolPermissionsStr, '|');
for (int i = 0; i < toolPermissionsSets.length; i++){
String[] requiredPermissions = StringUtils.split(toolPermissionsSets[i], ',');
boolean allowed = true;
for (int j = 0; j < requiredPermissions.length; j++) {
//since all in a set are required, if we are missing just one permission, set false, break and continue to check next set
//as that set may override and allow access
if (!securityService().unlock(requiredPermissions[j].trim(), site.getReference())){
allowed = false;
break;
}
}
//if allowed, we have matched the entire set so are satisfied
//otherwise we will check the next set
if(allowed) {
return true;
}
}
//no sets were completely matched
return false;
}
/**
* Get optional Localized Tool Properties (i.e. tool title, description)
**/
public String getLocalizedToolProperty(String toolId, String key) {
if (m_loader == null) {
return null;
}
final String toolProp = m_loader.getString(toolId + "." + key, "");
if (toolProp.length() < 1 || toolProp.equals("")) {
return null;
}
else {
return toolProp;
}
}
/**
* The optional tool configuration tag "functions.require" describes a
* set of permission lists which decide the visibility of the tool link
* for this site user. Lists are separated by "|" and permissions within a
* list are separated by ",". Users must have all the permissions included in
* at least one of the permission lists.
*
* For example, a value like "section.role.student,annc.new|section.role.ta"
* would let a user with "section.role.ta" see the tool, and let a user with
* both "section.role.student" AND "annc.new" see the tool, but not let a user
* who only had "section.role.student" see the tool.
*
* If the configuration tag is not set or is null, then all users see the tool.
*/
public boolean allowTool(Site site, Placement placement)
{
if(allowToolHelper(site, placement)){
if(!securityService().isSuperUser()){
try{
//delegated access sets a session attribute that determines if the user can't view a tool in a site
//delegatedaccess.deniedToolsMap = SiteId => List{toolid, toolid ...}
//if this tool shows up, return false, otherwise return true
Session session = SessionManager.getCurrentSession();
if(session.getAttribute("delegatedaccess.deniedToolsMap") != null && ((Map) session.getAttribute("delegatedaccess.deniedToolsMap")).containsKey(site.getReference())
&& arrayContains(((Map) session.getAttribute("delegatedaccess.deniedToolsMap")).get(site.getReference()), placement.getToolId())){
return false;
}
if(session.getAttribute("delegatedaccess.deniedToolsMap") == null ||
!((Map) session.getAttribute("delegatedaccess.deniedToolsMap")).containsKey(site.getReference())
|| ((Map) session.getAttribute("delegatedaccess.deniedToolsMap")).get(site.getReference()) == null){
//a delegated access admin would have this map and site (even if it was set to null), if its null, that means the user is just has access to a different site and not this one
if(site.getMember(session.getUserId()) == null &&
(site.getProperties().get("shopping-period-public-tools") != null || site.getProperties().get("shopping-period-auth-tools") != null)){
//this is .anon or .auth role in a site that needs to restrict the tools:
boolean anonAccess = site.getProperties().get("shopping-period-public-tools") != null
&& arrayContains(((String) site.getProperties().get("shopping-period-public-tools")).split(";"), placement.getToolId());
if(session.getUserId() == null){
return anonAccess;
}else{
return anonAccess || (site.getProperties().get("shopping-period-auth-tools") != null && arrayContains(((String) site.getProperties().get("shopping-period-auth-tools")).split(";"), placement.getToolId()));
}
}
}
}catch (Exception e) {
}
}
return true;
}else{
return false;
}
}
private boolean arrayContains(Object obj, String item){
if(obj != null && obj instanceof String[]){
String[] array = (String[]) obj;
for(int i = 0; i < array.length; i++){
if(array[i].equals(item))
return true;
}
}
return false;
}
public boolean allowToolHelper(Site site, Placement placement)
{
// No way to render an opinion
if (placement == null || site == null) return true;
String requiredPermissionsString = placement.getConfig().getProperty(TOOLCONFIG_REQUIRED_PERMISSIONS);
if (M_log.isDebugEnabled()) M_log.debug("requiredPermissionsString=" + requiredPermissionsString + " for " + placement.getToolId());
if (requiredPermissionsString == null)
return true;
requiredPermissionsString = requiredPermissionsString.trim();
if (requiredPermissionsString.length() == 0)
return true;
String[] allowedPermissionSets = requiredPermissionsString.split("\\|");
for (int i = 0; i < allowedPermissionSets.length; i++)
{
String[] requiredPermissions = allowedPermissionSets[i].split(",");
if (M_log.isDebugEnabled()) M_log.debug("requiredPermissions=" + Arrays.asList(requiredPermissions));
boolean gotAllInList = true;
for (int j = 0; j < requiredPermissions.length; j++)
{
if (!securityService().unlock(requiredPermissions[j].trim(), site.getReference()))
{
gotAllInList = false;
break;
}
}
if (gotAllInList)
{
return true;
}
}
// No permission sets were matched.
return false;
}
/**
* Check if the placement is hidden.
* @param placement
* @return true
if the current placement is hidden.
*/
public boolean isHidden(Placement placement)
{
if (placement == null) return true;
String visibility = placement.getConfig().getProperty(PORTAL_VISIBLE);
if ( "false".equals(visibility) ) return true;
String requiredPermissionsString = StringUtils.trimToNull(placement.getConfig().getProperty(TOOLCONFIG_REQUIRED_PERMISSIONS));
if (requiredPermissionsString == null)
return false;
return requiredPermissionsString.contains("site.upd");
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy