package hudson.plugins.collabnet.auth;
import com.collabnet.ce.webservices.CollabNetApp;
import hudson.Extension;
import hudson.Util;
import hudson.model.AbstractItem;
import hudson.model.AbstractProject;
import hudson.model.Computer;
import hudson.model.Descriptor;
import hudson.model.Job;
import hudson.model.User;
import hudson.model.View;
import hudson.plugins.collabnet.util.CNFormFieldValidator;
import hudson.security.ACL;
import hudson.security.AuthorizationStrategy;
import hudson.util.FormValidation;
import hudson.util.VersionNumber;
import net.sf.json.JSONObject;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.QueryParameter;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;
import java.io.IOException;
import java.rmi.RemoteException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.logging.Logger;
import static hudson.Util.fixNull;
import static hudson.Util.join;
import static hudson.plugins.collabnet.util.CommonUtil.splitCommaStr;
import static java.lang.Math.max;
/**
* Class for the CollabNet Authorization.
*/
public class CNAuthorizationStrategy extends AuthorizationStrategy {
private Collection<String> readUsers;
private Collection<String> readGroups;
private Collection<String> adminUsers;
private Collection<String> adminGroups;
private int mAuthCacheTimeoutMin;
private volatile ACL rootACL;
private static Logger log = Logger.getLogger("CNAuthorizationStrategy");
/**
* Constructs a new CNAUthorizationStrategy object. This
* AuthorizationStrategy depends upon the CNAuthentication SecurityRealm.
*
* @param readUsers a list of usernames (from CollabNet) that has
* system-wide read.
* @param readGroups a list of groupnames (from CollabNet) whose members
* have system-wide read.
* @param adminUsers a list of usernames (from CollabNet) that have
* all permissions in Hudson.
* @param adminGroups a list of groupnames (from CollabNet) whose members
* have all permissions in Hudson.
* @param permCacheTimeoutMin the cache timeout in min, after which the cache entries are cleared. -1 to disable.
*/
public CNAuthorizationStrategy(List<String> readUsers, List<String> readGroups,
List<String> adminUsers, List<String> adminGroups, int permCacheTimeoutMin)
{
this.readUsers = new ArrayList<String>(readUsers);
this.readGroups = new ArrayList<String>(readGroups);
this.adminUsers = new ArrayList<String>(adminUsers);
this.adminGroups = new ArrayList<String>(adminGroups);
mAuthCacheTimeoutMin = max(0,permCacheTimeoutMin); // can't be negative
this.rootACL = new CNRootACL(this.adminUsers, this.adminGroups,
this.readUsers, this.readGroups);
}
@DataBoundConstructor
public CNAuthorizationStrategy(String readUsersStr, String readGroupsStr, String adminUsersStr, String adminGroupsStr, int authCacheTimeoutMin) {
this(splitCommaStr(readUsersStr), splitCommaStr(readGroupsStr),
splitCommaStr(adminUsersStr), splitCommaStr(adminGroupsStr), max(0,authCacheTimeoutMin));
}
/**
* @return a comma-delimited string of the read-only users.
*/
public String getReadUsersStr() {
return join(this.readUsers, ", ");
}
/**
* @return a comma-delimited string of the read-only groups.
*/
public String getReadGroupsStr() {
return join(this.readGroups, ", ");
}
/**
* @return a comma-delimited string of the admin users.
*/
public String getAdminUsersStr() {
return join(this.adminUsers, ", ");
}
/**
* @return a comma-delimited string of the admin groups.
*/
public String getAdminGroupsStr() {
return join(this.adminGroups, ", ");
}
/**
* Get the number of min the cache is to be kept.
* @return number of min
*/
public int getAuthCacheTimeoutMin() {
return mAuthCacheTimeoutMin;
}
/**
* Get the number of ms the cache is to be kept.
* @return number of ms
*/
public long getAuthCacheTimeoutMs() {
return getAuthCacheTimeoutMin() * (60L * 1000);
}
/**
* @return the names of all groups/roles used in this authorization
* strategy.
*/
@Override
public Collection<String> getGroups() {
return CNProjectACL.CollabNetRoles.getNames();
}
/**
* @return the default ACL.
*/
@Override
public ACL getRootACL() {
if (this.rootACL == null) {
this.rootACL = new CNRootACL(this.adminUsers, this.adminGroups,
this.readUsers, this.readGroups);
}
return this.rootACL;
}
/**
* @return the ACL specific to the CSFE project, if available.
* Otherwise, return the root ACL.
*/
@Override
public ACL getACL(Job <?, ?> job) {
CNAuthProjectProperty capp = job.getProperty(CNAuthProjectProperty.class);
if (capp != null) {
String projectId = capp.getProjectId();
if (projectId != null && !projectId.equals("")) {
return new CNRootACL(this.adminUsers, this.adminGroups,
this.readUsers, this.readGroups,
new CNProjectACL(projectId));
}
}
// for jobs that are not associated with any project, we'll make it configuratble by any authenticated user
return new CNRootACL(this.adminUsers, this.adminGroups,
this.readUsers, this.readGroups, new CNAuthenticatedUserACL());
}
@Override
public ACL getACL(AbstractItem item) {
return this.getRootACL();
}
public ACL getACL(AbstractProject<?, ?> project) {
return this.getACL((Job)project);
}
@Override
public ACL getACL(View view) {
return this.getRootACL();
}
@Override
public ACL getACL(Computer computer) {
return this.getRootACL();
}
@Override
public ACL getACL(User user) {
return this.getRootACL();
}
/**
* The CNAuthorizationStrategy Descriptor class.
*/
@Extension
public static final class DescriptorImpl
extends Descriptor<AuthorizationStrategy> {
// any version later than this has the features
// we require for authorization to work correctly
public static String GOOD_VERSION = "5.2.0.0";
/**
* @return string to display for configuration screen.
*/
@Override
public String getDisplayName() {
return "CollabNet Authorization";
}
/**
* @return the currently saved configured CollabNet url
*/
public static String getCollabNetUrl() {
CollabNetApp conn = CNConnection.getInstance();
if (conn == null) {
return null;
}
return conn.getServerUrl();
}
/**
* @param url for the CollabNet server.
* @return the CollabNet version number.
*/
public static VersionNumber getVersion(String url) {
if (url == null) {
return null;
}
String version;
try {
version = CollabNetApp.getApiVersion(url);
} catch (RemoteException re) {
log.info("getVersion: failed with RemoteException: " +
re.getMessage());
return null;
}
try {
return new VersionNumber(version);
} catch (IllegalArgumentException iae) {
log.severe("getVersion: unexpected error when attempting to " +
"parse CollabNet version: " + iae.getMessage());
return null;
}
}
/**
* @return true if the CollabNet version is late enough (5.2+)
* that using this AuthorizationStrategy is effective.
*/
public static boolean isGoodCNVersion(String url) {
VersionNumber version = getVersion(url);
if (version == null) {
// we can't check, so we'll assume it's ok.
return true;
}
VersionNumber desiredVersion = new VersionNumber(GOOD_VERSION);
return version.compareTo(desiredVersion) >= 0;
}
/**
* Check whether the "incorrect version" msg should be displayed,
* and returns what the currently configured version is, in a json.
*/
public void doVersionCheck(StaplerRequest req, StaplerResponse rsp, @QueryParameter String url)
throws IOException {
rsp.setContentType("text/plain;charset=UTF-8");
JSONObject versionJSON = new JSONObject();
String error_display_style = "none";
if (!isGoodCNVersion(url)) {
error_display_style = "inline";
}
versionJSON.element("error_display_style", error_display_style);
VersionNumber version = getVersion(url);
if (version != null) {
versionJSON.element("version", version.toString());
} else {
versionJSON.element("version", "unknown");
}
rsp.getWriter().print(versionJSON.toString());
}
/**
* Check that the users are valid.
*/
public FormValidation doCheckAdminUsersStr(@QueryParameter String value) throws RemoteException {
return CNFormFieldValidator.userListCheck(value);
}
public FormValidation doCheckReadUsersStr(@QueryParameter String value) throws RemoteException {
return CNFormFieldValidator.userListCheck(value);
}
/**
* Check that the groups are valid.
*/
public FormValidation doCheckAdminGroupsStr(@QueryParameter String groups,
@QueryParameter String users) throws RemoteException {
return CNFormFieldValidator.groupListCheck(fixNull(groups), fixNull(users));
}
public FormValidation doCheckReadGroupsStr(@QueryParameter String value) throws RemoteException {
return CNFormFieldValidator.groupListCheck(value,null);
}
/**
* Check that the timeout number is greater than or equal to 0
* @param value the timeout to be checked
*/
public FormValidation doCheckAuthCacheTimeoutMin(@QueryParameter String value) {
return CNFormFieldValidator.numberCheck(value, true, true, false);
}
}
}