/* * Copyright (c) 2013 Oracle Corporation. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Winston Prakash */ package org.eclipse.hudson.security.team; import com.thoughtworks.xstream.converters.Converter; import com.thoughtworks.xstream.converters.MarshallingContext; import com.thoughtworks.xstream.converters.UnmarshallingContext; import com.thoughtworks.xstream.io.HierarchicalStreamReader; import com.thoughtworks.xstream.io.HierarchicalStreamWriter; import hudson.model.Computer; import hudson.model.Item; import hudson.model.Items; import hudson.model.View; import hudson.security.ACL; import hudson.security.AccessControlled; import hudson.security.AuthorizationStrategy; import hudson.security.Permission; import hudson.security.SecurityRealm; import java.io.File; import java.io.FileFilter; import java.io.IOException; import java.io.StringWriter; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.TreeSet; import java.util.concurrent.CopyOnWriteArrayList; import org.eclipse.hudson.security.HudsonSecurityEntitiesHolder; import org.eclipse.hudson.security.HudsonSecurityManager; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.security.access.AccessDeniedException; import org.springframework.security.core.Authentication; /** * A simple model to hold team members and name of jobs belong to the team * * @since 3.1.0 * @author Winston Prakash */ public class Team implements AccessControlled { public static final String PUBLIC_TEAM_NAME = "public"; private final List<TeamMember> teamMembers = new CopyOnWriteArrayList<TeamMember>(); private final List<TeamJob> jobs = new CopyOnWriteArrayList<TeamJob>(); private final List<TeamNode> nodes = new CopyOnWriteArrayList<TeamNode>(); private final List<TeamView> views = new CopyOnWriteArrayList<TeamView>(); private String name; protected static final String JOBS_FOLDER_NAME = "jobs"; private String description; private transient TeamManager teamManager; private String customFolderName; private String primaryView; /** * List of nodes visible to this team that are enabled. All visible nodes (except public) are * disabled by default. When a node is disabled, jobs can not be scheduled on that node */ protected Set<String> enabledVisibleNodes = new HashSet<String>(); /** * List of public nodes that are disabled. Public nodes are visible to any * team and are enabled by default. They need to be explicitly disabled to * stop jobs being scheduled on those nodes. */ protected Set<String> disabledPublicNodes = new HashSet<String>(); private transient Logger logger = LoggerFactory.getLogger(Team.class); //Used for unmarshalling Team() { } Team(String name, TeamManager teamManager) { this(name, name, teamManager); } Team(String teamName, String description, TeamManager teamManager) { this(teamName, description, null, teamManager); } Team(String teamName, String description, String customFolderName, TeamManager teamManager) { this.name = teamName; this.description = description; this.teamManager = teamManager; this.customFolderName = customFolderName; } public String getName() { return name; } public String getDescription() { return description; } void setDescription(String description) throws IOException { this.description = description; getTeamManager().save(); } String getCustomFolderName() { return customFolderName; } void setCustomFolderName(String customTeamFolderName) { this.customFolderName = customTeamFolderName; } public boolean isAdmin(String userName) { // Team Manager ACL always assume userName current user boolean isAdmin = false; HudsonSecurityManager hudsonSecurityManager = HudsonSecurityEntitiesHolder.getHudsonSecurityManager(); SecurityRealm securityRealm = null; if (hudsonSecurityManager != null) { securityRealm = hudsonSecurityManager.getSecurityRealm(); } if ((securityRealm != null) && securityRealm instanceof TeamAwareSecurityRealm) { TeamAwareSecurityRealm teamAwareSecurityRealm = (TeamAwareSecurityRealm) securityRealm; isAdmin = teamAwareSecurityRealm.isCurrentUserTeamAdmin(); } else { TeamMember member = findMember(userName); if (member != null) { isAdmin = member.isTeamAdmin(); } } return isAdmin; } public List<TeamMember> getMembers() { return Collections.unmodifiableList(teamMembers); } public List<TeamJob> getJobs() { return Collections.unmodifiableList(jobs); } public List<TeamNode> getNodes() { return Collections.unmodifiableList(nodes); } public List<TeamView> getViews() { return Collections.unmodifiableList(views); } public Set<String> getJobNames() { Set<String> jobNames = new TreeSet<String>(); for (TeamJob job : getJobs()) { jobNames.add(job.getId()); } return jobNames; } public Set<String> getViewNames() { Set<String> viewNames = new TreeSet<String>(); for (TeamView view : getViews()) { viewNames.add(view.getId()); } return viewNames; } public Set<String> getNodeNames() { Set<String> nodeNames = new TreeSet<String>(); for (TeamNode node : getNodes()) { nodeNames.add(node.getId()); } return nodeNames; } public TeamMember findMember(String userName) { for (TeamMember member : teamMembers) { if (userName.equalsIgnoreCase(member.getName())) { return member; } } return null; } void addMember(String teamMemberSid, boolean isTeamAdmin, boolean canCreate, boolean canDelete, boolean canConfigure, boolean canBuild, boolean canCreateNode, boolean canDeleteNode, boolean canConfigureNode, boolean canCreateView, boolean canDeleteView, boolean canConfigureView) throws IOException { TeamMember newMember = new TeamMember(); newMember.setName(teamMemberSid); newMember.setAsTeamAdmin(isTeamAdmin); if (canCreate) { newMember.addPermission(Item.CREATE); newMember.addPermission(Item.EXTENDED_READ); } if (canDelete) { newMember.addPermission(Item.DELETE); newMember.addPermission(Item.WIPEOUT); } if (canConfigure) { newMember.addPermission(Item.CONFIGURE); newMember.addPermission(Item.EXTENDED_READ); } if (canBuild) { newMember.addPermission(Item.BUILD); } if (canCreateNode) { newMember.addPermission(Computer.CREATE); } if (canDeleteNode) { newMember.addPermission(Computer.DELETE); } if (canConfigureNode) { newMember.addPermission(Computer.CONFIGURE); } if (canCreateView) { newMember.addPermission(View.CREATE); } if (canDeleteView) { newMember.addPermission(View.DELETE); } if (canConfigureView) { newMember.addPermission(View.CONFIGURE); } newMember.addPermission(Item.READ); newMember.addPermission(Item.WORKSPACE); addMember(newMember); } void updateMember(String teamMemberSid, boolean isTeamAdmin, boolean canCreate, boolean canDelete, boolean canConfigure, boolean canBuild, boolean canCreateNode, boolean canDeleteNode, boolean canConfigureNode, boolean canCreateView, boolean canDeleteView, boolean canConfigureView) throws IOException { TeamMember currentMember = findMember(teamMemberSid); if (currentMember != null) { currentMember.setAsTeamAdmin(isTeamAdmin); if (canCreate) { currentMember.addPermission(Item.CREATE); currentMember.addPermission(Item.EXTENDED_READ); } else { currentMember.removePermission(Item.CREATE); if (!canConfigure) { currentMember.removePermission(Item.EXTENDED_READ); } } if (canDelete) { currentMember.addPermission(Item.DELETE); currentMember.addPermission(Item.WIPEOUT); } else { currentMember.removePermission(Item.DELETE); currentMember.removePermission(Item.WIPEOUT); } if (canConfigure) { currentMember.addPermission(Item.CONFIGURE); currentMember.addPermission(Item.EXTENDED_READ); } else { currentMember.removePermission(Item.CONFIGURE); if (!canCreate) { currentMember.removePermission(Item.EXTENDED_READ); } } if (canBuild) { currentMember.addPermission(Item.BUILD); } else { currentMember.removePermission(Item.BUILD); } if (canCreateNode) { currentMember.addPermission(Computer.CREATE); } else { currentMember.removePermission(Computer.CREATE); } if (canDeleteNode) { currentMember.addPermission(Computer.DELETE); } else { currentMember.removePermission(Computer.DELETE); } if (canConfigureNode) { currentMember.addPermission(Computer.CONFIGURE); } else { currentMember.removePermission(Computer.CONFIGURE); } if (canCreateView) { currentMember.addPermission(View.CREATE); } else { currentMember.removePermission(View.CREATE); } if (canDeleteView) { currentMember.addPermission(View.DELETE); } else { currentMember.removePermission(View.DELETE); } if (canConfigureView) { currentMember.addPermission(View.CONFIGURE); } else { currentMember.removePermission(View.CONFIGURE); } getTeamManager().save(); } } void addMember(TeamMember member) throws IOException { if (!teamMembers.contains(member)) { teamMembers.add(member); getTeamManager().save(); } } void removeMember(String userName) throws IOException { TeamMember member = findMember(userName); if (member != null) { teamMembers.remove(member); getTeamManager().save(); } } public boolean isMember(String userName) { HudsonSecurityManager hudsonSecurityManager = HudsonSecurityEntitiesHolder.getHudsonSecurityManager(); SecurityRealm securityRealm = null; if (hudsonSecurityManager != null) { securityRealm = hudsonSecurityManager.getSecurityRealm(); } if ((securityRealm != null) && securityRealm instanceof TeamAwareSecurityRealm) { TeamAwareSecurityRealm teamAwareSecurityRealm = (TeamAwareSecurityRealm) securityRealm; Team currentUserTeam = teamAwareSecurityRealm.GetCurrentUserTeam(); if (currentUserTeam == this) { return true; } else { return false; } } else { return findMember(userName) != null; } } public TeamView findView(String viewName) { for (TeamView view : views) { if (viewName.equals(view.getId())) { return view; } } return null; } void addView(TeamView view) throws IOException { if (!views.contains(view)) { views.add(view); getTeamManager().save(); } } boolean removeView(String viewName) throws IOException { for (TeamView view : views) { if (viewName.equals(view.getId())) { return removeView(view); } } return false; } boolean removeView(TeamView view) throws IOException { if (views.contains(view)) { if (views.remove(view)) { getTeamManager().save(); return true; } } return false; } void renameView(String oldViewName, String newViewId) throws IOException { TeamView view = findView(oldViewName); if (view != null) { view.setId(newViewId); getTeamManager().save(); } } public boolean isViewOwner(String viewName) { return findView(viewName) != null; } public TeamNode findNode(String nodeName) { for (TeamNode node : nodes) { if (nodeName.equals(node.getId())) { return node; } } return null; } void addNode(TeamNode node) throws IOException { if (!nodes.contains(node)) { nodes.add(node); getTeamManager().save(); } } boolean removeNode(String nodeName) throws IOException { for (TeamNode node : nodes) { if (nodeName.equals(node.getId())) { return removeNode(node); } } return false; } boolean removeNode(TeamNode node) throws IOException { if (nodes.contains(node)) { if (nodes.remove(node)) { getTeamManager().save(); return true; } } return false; } void renameNode(String oldNodeName, String newNodeId) throws IOException { TeamNode node = findNode(oldNodeName); if (node != null) { node.setId(newNodeId); getTeamManager().save(); } } public boolean isNodeOwner(String nodeName) { return findNode(nodeName) != null; } public TeamJob findJob(String jobName) { for (TeamJob job : jobs) { if (jobName.equals(job.getId())) { return job; } } return null; } void addJob(TeamJob job) throws IOException { addJob(job, true); } void addJob(TeamJob job, boolean save) throws IOException { if (!jobs.contains(job)) { jobs.add(job); if (save) { getTeamManager().save(); } } } boolean removeJob(String jobName) throws IOException { for (TeamJob job : jobs) { if (jobName.equals(job.getId())) { return removeJob(job); } } return false; } boolean removeJob(TeamJob job) throws IOException { if (jobs.contains(job)) { if (jobs.remove(job)) { getTeamManager().save(); return true; } } return false; } public boolean isJobOwner(String jobName) { return findJob(jobName) != null; } void renameJob(String oldJobName, String newJobId) throws IOException { TeamJob job = findJob(oldJobName); if (job != null) { job.setId(newJobId); getTeamManager().save(); } } List<File> getJobsRootFolders(File teamsFolder) { return getJobsRootFolders(teamsFolder, false); } List<File> getJobsRootFolders(File teamsFolder, final boolean initializingTeam) { // if !initializingTeam make sure jobs on disk with no entries in teams.xml are ignored File jobsFolder = getJobsFolder(teamsFolder); if (jobsFolder.exists()) { final Set<String> jobNames = new HashSet<String>();; if (!initializingTeam) { for (TeamJob job : jobs) { jobNames.add(job.getId()); } } File[] jobsRootFolders = jobsFolder.listFiles(new FileFilter() { @Override public boolean accept(File child) { if (!initializingTeam && !jobNames.contains(child.getName())) { return false; } return child.isDirectory() && Items.getConfigFile(child).exists(); } }); if (jobsRootFolders != null) { return Arrays.asList(jobsRootFolders); } } return Collections.EMPTY_LIST; } /** * Return the team folder. * * @param teamsFolder the outer "teams" folder, or for public, hudson home * @return team folder that will contain "jobs" folder */ File getTeamFolder(File teamsFolder) { if (PUBLIC_TEAM_NAME.equals(name)) { return teamsFolder; } if ((customFolderName != null) && !"".equals(customFolderName.trim())) { return new File(customFolderName); } return new File(teamsFolder, name); } /** * The folder where all the jobs of this team are saved * * @return File */ File getJobsFolder(File teamsFolder) { return new File(getTeamFolder(teamsFolder), JOBS_FOLDER_NAME); } @Override public ACL getACL() { AuthorizationStrategy authorizationStrategy = HudsonSecurityEntitiesHolder.getHudsonSecurityManager().getAuthorizationStrategy(); if (authorizationStrategy instanceof TeamBasedAuthorizationStrategy) { TeamBasedAuthorizationStrategy teamBasedAuthorizationStrategy = (TeamBasedAuthorizationStrategy) authorizationStrategy; return teamBasedAuthorizationStrategy.getACL(this); } // Team will not be used if Team Based Authorization Strategy is not used return new ACL() { @Override public boolean hasPermission(Authentication a, Permission permission) { return false; } }; } void addToEnabledVisibleNodes(String nodeName) throws IOException { if (!getTeamManager().getPublicTeam().isNodeOwner(nodeName)) { enabledVisibleNodes.add(nodeName); }else{ disabledPublicNodes.remove(nodeName); } getTeamManager().save(); } void removeFromEnabledVisibleNodes(String nodeName) throws IOException { if (!getTeamManager().getPublicTeam().isNodeOwner(nodeName)) { enabledVisibleNodes.remove(nodeName); }else{ disabledPublicNodes.add(nodeName); } getTeamManager().save(); } // Called from jelly also public boolean isVisibleNodeEnabled(String nodeName) { if (enabledVisibleNodes.contains(nodeName)) { return true; } else if (getTeamManager().getPublicTeam().isNodeOwner(nodeName)) { return !disabledPublicNodes.contains(nodeName); } return false; } // Used in Jelly public List<TeamJob> getVisibleJobs() { List<TeamJob> visibleJobs = new ArrayList<TeamJob>(); for (Team team : getTeamManager().getTeams().values()) { for (TeamJob teamJob : team.getJobs()) { if (teamJob.isVisible(name) || Team.PUBLIC_TEAM_NAME.equals(team.name)) { visibleJobs.add(teamJob); } } } return visibleJobs; } // Used in Jelly public List<TeamNode> getVisibleNodes() { List<TeamNode> visibleNodes = new ArrayList<TeamNode>(); for (Team team : getTeamManager().getTeams().values()) { for (TeamNode teamNode : team.getNodes()) { if (teamNode.isVisible(name) || Team.PUBLIC_TEAM_NAME.equals(team.name)) { visibleNodes.add(teamNode); } } } return visibleNodes; } // Used in Jelly public List<TeamView> getVisibleViews() { List<TeamView> visibleViews = new ArrayList<TeamView>(); for (Team team : getTeamManager().getTeams().values()) { for (TeamView teamView : team.getViews()) { if (teamView.isVisible(name) || Team.PUBLIC_TEAM_NAME.equals(team.name)) { visibleViews.add(teamView); } } } return visibleViews; } List<String> getAllViewNames() { List<String> viewNames = new ArrayList<String>(); for (TeamView teamView : getVisibleViews()) { viewNames.add(teamView.getId()); } for (TeamView teamView : views) { viewNames.add(teamView.getId()); } return viewNames; } @Override public void checkPermission(Permission permission) throws AccessDeniedException { getACL().checkPermission(permission); } @Override public boolean hasPermission(Permission permission) { return getACL().hasPermission(permission); } // When the Team is unmarshalled it would not have Team Manager set // used in the jelly public TeamManager getTeamManager() { if (teamManager == null) { return HudsonSecurityEntitiesHolder.getHudsonSecurityManager().getTeamManager(); } else { return teamManager; } } public String getPrimaryView() { if (primaryView != null) { if (findView(primaryView) != null) { return primaryView; } for (TeamView view : this.getVisibleViews()) { if (primaryView.equals(view.getId())) { return primaryView; } } primaryView = null; try { getTeamManager().save(); } catch (IOException ex) { logger.error(ex.getLocalizedMessage()); } } return primaryView; } void setPrimaryView(String viewName) { primaryView = viewName; try { getTeamManager().save(); } catch (IOException ex) { logger.error(ex.getLocalizedMessage()); } } public static class ConverterImpl implements Converter { @Override public boolean canConvert(Class type) { return type == Team.class; } @Override public void marshal(Object source, HierarchicalStreamWriter writer, MarshallingContext context) { Team team = (Team) source; writer.startNode("name"); writer.setValue(team.getName()); writer.endNode(); if ((team.getDescription() != null) && !"".equals(team.getDescription().trim())) { writer.startNode("description"); writer.setValue(team.getDescription()); writer.endNode(); } if ((team.getCustomFolderName() != null) && !"".equals(team.getCustomFolderName().trim())) { writer.startNode("customFolderName"); writer.setValue(team.getCustomFolderName()); writer.endNode(); } if ((team.getPrimaryView() != null) && !"".equals(team.getPrimaryView().trim())) { writer.startNode("primaryView"); writer.setValue(team.getPrimaryView()); writer.endNode(); } for (TeamJob job : team.getJobs()) { writer.startNode("job"); context.convertAnother(job); writer.endNode(); } for (TeamNode node : team.getNodes()) { writer.startNode("node"); context.convertAnother(node); writer.endNode(); } for (TeamView view : team.getViews()) { writer.startNode("view"); context.convertAnother(view); writer.endNode(); } for (TeamMember member : team.getMembers()) { writer.startNode("member"); context.convertAnother(member); writer.endNode(); } StringWriter strWriter = new StringWriter(); if (team.enabledVisibleNodes.size() > 0) { for (String nodeName : team.enabledVisibleNodes) { strWriter.append(nodeName); strWriter.append(","); } writer.startNode("enabledNodes"); writer.setValue(strWriter.toString()); writer.endNode(); } StringWriter strWriter2 = new StringWriter(); if (team.disabledPublicNodes.size() > 0) { for (String nodeName : team.disabledPublicNodes) { strWriter2.append(nodeName); strWriter2.append(","); } writer.startNode("disabledPublicNodes"); writer.setValue(strWriter2.toString()); writer.endNode(); } } @Override public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext uc) { Team team = new Team(); while (reader.hasMoreChildren()) { reader.moveDown(); if ("name".equals(reader.getNodeName())) { team.name = reader.getValue(); } if ("description".equals(reader.getNodeName())) { team.description = reader.getValue(); } if ("customFolderName".equals(reader.getNodeName())) { team.customFolderName = reader.getValue(); } if ("primaryView".equals(reader.getNodeName())) { team.primaryView = reader.getValue(); } if ("job".equals(reader.getNodeName())) { TeamJob teamJob = (TeamJob) uc.convertAnother(team, TeamJob.class); team.jobs.add(teamJob); } else if ("node".equals(reader.getNodeName())) { TeamNode teamSlave = (TeamNode) uc.convertAnother(team, TeamNode.class); team.nodes.add(teamSlave); } else if ("view".equals(reader.getNodeName())) { TeamView teamView = (TeamView) uc.convertAnother(team, TeamView.class); team.views.add(teamView); } else if ("member".equals(reader.getNodeName())) { TeamMember teamMember = (TeamMember) uc.convertAnother(team, TeamMember.class); team.teamMembers.add(teamMember); } else if ("enabledNodes".equals(reader.getNodeName())) { String nodeNames = reader.getValue(); team.enabledVisibleNodes.addAll(Arrays.asList(nodeNames.split(","))); }else if ("disabledPublicNodes".equals(reader.getNodeName())) { String nodeNames = reader.getValue(); team.disabledPublicNodes.addAll(Arrays.asList(nodeNames.split(","))); } reader.moveUp(); } return team; } } }