package hudson.plugins.collabnet.util;
import com.collabnet.ce.webservices.CTFPackage;
import com.collabnet.ce.webservices.CTFProject;
import com.collabnet.ce.webservices.CTFRelease;
import com.collabnet.ce.webservices.CTFScmRepository;
import com.collabnet.ce.webservices.CTFTracker;
import com.collabnet.ce.webservices.CollabNetApp;
import com.collabnet.cubit.api.CubitConnector;
import hudson.plugins.collabnet.auth.CNAuthentication;
import hudson.util.FormValidation;
import org.apache.axis.utils.StringUtils;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.contrib.ssl.EasySSLProtocolSocketFactory;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.commons.httpclient.protocol.Protocol;
import org.apache.commons.httpclient.protocol.ProtocolSocketFactory;
import org.kohsuke.stapler.StaplerRequest;
import java.io.IOException;
import java.rmi.RemoteException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Set;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
public abstract class CNFormFieldValidator {
private static Logger log = Logger.getLogger("CNFormFieldValidator");
// no special permisssion is required for our checks
// without proper rights, no data will be returned from
// CollabNet server anyhow.
/**
* Utility function to check that a str contains only valid
* environmental variables to interpret.
*
* @param str the string to test.
* @return error message, if any variables are missing, null if all
* are found.
*/
public static void checkInterpretedString(String str) throws FormValidation {
Pattern envPat = Pattern.compile("\\$\\{(\\w*)\\}");
Matcher matcher = envPat.matcher(str);
Set<String> envVars = new HashSet<String>(9);
envVars.add("BUILD_NUMBER");
envVars.add("BUILD_ID");
envVars.add("JOB_NAME");
envVars.add("BUILD_TAG");
envVars.add("EXECUTOR_NUMBER");
envVars.add("JAVA_HOME");
envVars.add("WORKSPACE");
envVars.add("HUDSON_URL");
envVars.add("SVN_REVISION");
envVars.add("CVS_BRANCH");
String message = null;
while (matcher.find()) {
String key = matcher.group(1);
if (!envVars.contains(key)) {
if (message == null) {
message = "Environmental Variables not found: " + key;
} else {
message += ", " + key;
}
}
}
if (message!=null)
throw FormValidation.error(message);
}
/**
* Returns true if a url is valid, false otherwise.
*/
public static boolean checkUrl(String url) {
HttpClient client = new HttpClient();
try {
GetMethod get = new GetMethod(url);
int status = client.executeMethod(get);
return status == 200;
} catch (IOException e) {
return false;
} catch (IllegalArgumentException iae) {
return false;
}
}
/**
* Class for checking that a required value is set. Expects a
* StaplerRequest with a value set to the value and a name set to
* the name of what is being set (used for error msg).
*/
public static FormValidation requiredCheck(String value, String name) {
value = StringUtils.strip(value);
if (CommonUtil.unset(name)) {
// ideally this should be set
name = "above value";
}
if (CommonUtil.unset(value)) {
return FormValidation.error("The " + name + " is required.");
}
return FormValidation.ok();
}
/**
* Class for checking an unrequired value that may include
* interpreted strings (i.e. Hudson environmental values). Expects a
* StaplerRequest with value. If it's a required value, expects a
* value name.
*/
public static FormValidation interpretedCheck(String str, String name, boolean isRequired) throws FormValidation {
if (CommonUtil.unset(str)) {
if (!isRequired) {
return FormValidation.ok();
} else {
if (CommonUtil.unset(name)) {
// ideally this should be set
name = "above value";
}
return FormValidation.error("The " + name + " is required.");
}
}
checkInterpretedString(str);
return FormValidation.ok();
}
/**
* Class for checking an interpreted string which is unrequired.
*/
public static FormValidation unrequiredInterpretedCheck(String str, String name) throws FormValidation {
return interpretedCheck(str, name, false);
}
/**
* Class for checking an interpreted string which is required.
*/
public static FormValidation requiredInterpretedCheck(String str, String name) throws FormValidation {
return interpretedCheck(str, name, true);
}
/**
* Class for checking if a Host URL is correct. Expects a StaplerRequest
* with value set to the url.
*/
public static FormValidation hostUrlCheck(String hostUrl) {
if (CommonUtil.unset(hostUrl)) {
return FormValidation.error("The Host URL is required.");
}
Protocol acceptAllSsl =
new Protocol("https",
(ProtocolSocketFactory)
new EasySSLProtocolSocketFactory(),
443);
Protocol.registerProtocol("https", acceptAllSsl);
if (!checkUrl(hostUrl)) {
return FormValidation.error("Invalid Host URL.");
}
return FormValidation.ok();
}
/**
* Class for checking if a URL is correct and corresponds to a
* CollabNet server. Expects a StaplerRequest with value set
* to the url.
*/
public static FormValidation soapUrlCheck(String collabNetUrl) {
if (CommonUtil.unset(collabNetUrl)) {
return FormValidation.error("The CollabNet TeamForge URL is required.");
}
if (!checkSoapUrl(collabNetUrl)) {
return FormValidation.error("Invalid CollabNet TeamForge URL.");
}
return FormValidation.ok();
}
/**
* Check that a URL has the expected SOAP service.
*
* @param collabNetUrl for the CollabNet server
* @return returns true if we can get a wsdl from the url, which
* indicates that it's a working CollabNet server.
*/
private static boolean checkSoapUrl(String collabNetUrl) {
String soapURL = collabNetUrl + CollabNetApp.SOAP_SERVICE +
"CollabNet?wsdl";
return checkUrl(soapURL);
}
/**
* Class for checking that a login to CollabNet is valid. Expects
* a StaplerRequest with url, username, and password set.
*/
public static FormValidation loginCheck(CollabNetApp app, String password) {
if (CommonUtil.unset(password)) {
return FormValidation.error("The password is required.");
}
if (app == null) {
return FormValidation.warning("Login fails with this CollabNet " +
"URL/username/password combination.");
} else {
CNHudsonUtil.logoff(app);
}
return FormValidation.ok();
}
/**
* Checks if a project name is valid, by using the given connection.
*/
public static FormValidation projectCheck(CollabNetApp app, String project) throws RemoteException {
if (CommonUtil.unset(project)) {
return FormValidation.error("The project is required.");
}
if (app != null) {
try {
if (app.getProjectByTitle(project) == null) {
return FormValidation.warning(String.format(
"Project '%s' cannot be found, or user %s does not have permission to access it.",
project, app.getUsername()));
}
} finally {
CNHudsonUtil.logoff(app);
}
}
return FormValidation.ok();
}
/**
* Class to check that the path to a document exists. Warns about
* any missing folders. Expects a StaplerRequest with url, username,
* password, project, and path.
*/
public static FormValidation documentPathCheck(CollabNetApp app, String project, String path) throws IOException {
path = path.replaceAll("/+", "/");
path = CommonUtil.stripSlashes(path);
if (CommonUtil.unset(path)) {
return FormValidation.error("The path is required.");
}
checkInterpretedString(path);
CTFProject p = app.getProjectByTitle(project);
if (p != null) {
String missing = p.verifyPath(path);
if (missing != null) {
CNHudsonUtil.logoff(app);
return FormValidation.warning(String.format(
"Folder '%s' could not be found in path '%s'. It (and any subfolders) will be created dynamically.", missing, path));
}
}
CNHudsonUtil.logoff(app);
return FormValidation.ok();
}
/**
* Class to check that a package exists. Expects a StaplerRequest with
* a url, username, password, project, and package.
*/
public static FormValidation packageCheck(CollabNetApp cna, String project, String rpackage) throws RemoteException {
if (CommonUtil.unset(rpackage)) {
return FormValidation.error("The package is required.");
}
try {
CTFProject p = cna.getProjectByTitle(project);
if (p != null) {
CTFPackage pkg = p.getPackages().byTitle(rpackage);
if (pkg == null) {
return FormValidation.warning("Package could not be found.");
}
}
return FormValidation.ok();
} finally {
CNHudsonUtil.logoff(cna);
}
}
/**
* Class to check that a release exists. Expects a StaplerRequest with
* a url, username, password, project, package (optional), and release.
*/
public static FormValidation releaseCheck(CollabNetApp cna, String project, String rpackage, String release, boolean required) throws RemoteException {
try {
if (CommonUtil.unset(release)) {
if (required) {
return FormValidation.error("The release is required.");
} else {
return FormValidation.ok();
}
}
CTFProject p = cna.getProjectByTitle(project);
if (p==null) return FormValidation.ok(); // not entered yet?
CTFPackage pkg = p.getPackages().byTitle(rpackage);
if (pkg != null) {
CTFRelease r = pkg.getReleaseByTitle(release);
if (r == null)
return FormValidation.warning("Release could not be found.");
} else {
// locate the release from all the packages
for (CTFPackage x : p.getPackages()) {
if (x.getReleaseByTitle(release)!=null)
return FormValidation.ok();
}
return FormValidation.warning("Release could not be found.");
}
return FormValidation.ok();
} finally {
CNHudsonUtil.logoff(cna);
}
}
/**
* Class to check that a repo exists. Expects a StaplerRequest with
* a url, username, password, and project.
*/
public static FormValidation repoCheck(StaplerRequest request) throws RemoteException {
String project = request.getParameter("project");
String repoName = request.getParameter("repo");
CollabNetApp cna = CNHudsonUtil.getCollabNetApp(request);
if (cna==null) return FormValidation.ok();
try {
CTFProject p = cna.getProjectByTitle(project);
if (CommonUtil.unset(repoName)) {
return FormValidation.error("The repository name is required.");
}
if (p != null) {
CTFScmRepository r = p.getScmRepositories().byTitle(repoName);
if (r == null) {
return FormValidation.warning("Repository could not be " +
"found.");
}
}
return FormValidation.ok();
} finally {
CNHudsonUtil.logoff(cna);
}
}
/**
* Class to check that a tracker exists. Expects a StaplerRequest with
* a url, username, password, project, and tracker.
*/
public static FormValidation trackerCheck(StaplerRequest request) throws RemoteException {
String tracker = request.getParameter("tracker");
String project = request.getParameter("project");
if (CommonUtil.unset(tracker)) {
return FormValidation.error("The tracker is required.");
}
CollabNetApp cna = CNHudsonUtil.getCollabNetApp(request);
CTFProject p = cna.getProjectByTitle(project);
if (p!=null) {
CTFTracker t = p.getTrackers().byTitle(tracker);
if (t == null) {
CNHudsonUtil.logoff(cna);
return FormValidation.warning("Tracker could not be found.");
}
}
CNHudsonUtil.logoff(cna);
return FormValidation.ok();
}
/**
* Class for checking if a user can be assigned a tracker artifact.
* Expects a StaplerRequest with login info (url, username, password),
* project, and assign (which is the username).
*/
public static FormValidation assignCheck(StaplerRequest request) throws RemoteException {
String assign = StringUtils.strip(request.getParameter("assign"));
if (CommonUtil.unset(assign)) {
return FormValidation.ok();
}
String project = request.getParameter("project");
CollabNetApp cna = CNHudsonUtil.getCollabNetApp(request);
if (cna == null) {
return FormValidation.ok();
}
try {
CTFProject p = cna.getProjectById(project);
if (p == null) {
return FormValidation.ok();
}
if (!p.hasMember(assign)) {
return FormValidation.warning("This user is not a member of the " +
"project.");
}
return FormValidation.ok();
} finally {
CNHudsonUtil.logoff(cna);
}
}
/**
* Check that a comma-separated list of users exists.
* The check only works for a logged-in site-admin. Otherwise,
* give a warning that we cannot check the users' validity.
*/
public static FormValidation userListCheck(String userStr) throws RemoteException {
if (userStr == null || userStr.equals("")) {
return FormValidation.ok();
}
CNAuthentication auth = CNAuthentication.get();
if (auth == null || !auth.isSuperUser()) {
return FormValidation.warning("Cannot check if users exist unless logged " +
"in as a TeamForge site admin. Be careful!");
}
Collection<String> invalidUsers = getInvalidUsers(auth.getCredentials(), userStr);
if (!invalidUsers.isEmpty()) {
return FormValidation.error("The following users do not exist: " +
invalidUsers);
}
return FormValidation.ok();
}
/**
* @param cna
* @param userStr
* @return the collection of users from the array which do not exist.
*/
private static Collection<String> getInvalidUsers(CollabNetApp cna, String userStr) throws RemoteException {
Collection<String> invalidUsers = new ArrayList<String>();
for (String user: CommonUtil.splitCommaStr(userStr)) {
if (!cna.isUsernameValid(user)) {
invalidUsers.add(user);
}
}
return invalidUsers;
}
/**
* Check that a comma-separated list of groups exists.
* The check only works for a logged-in site-admin. Also warns
* the current user if s/he will be locked out once that user
* saves the configuration.
*/
public static FormValidation groupListCheck(String groupStr, String userStr) throws RemoteException {
Collection<String> invalidGroups = getInvalidGroups(groupStr);
if (!invalidGroups.isEmpty()) {
return FormValidation.error("The following groups do not exist: " +
invalidGroups);
// anyone who can see if groups are invalid will
// never be locked out, so we can return here
}
if (userStr != null) {
if (locksOutCurrentUser(userStr, groupStr)) {
return FormValidation.error("The authorization settings would lock " +
"the current user out of this page. " +
"You may want to add your username to " +
"the user list.");
}
}
return FormValidation.ok();
}
/**
* @param groupStr
* @return the collection of groups from the array which do not exist.
*/
private static Collection<String> getInvalidGroups(String groupStr) throws RemoteException {
CNAuthentication auth = CNAuthentication.get();
if (auth == null) {
// cannot connect to check.
return Collections.emptyList();
}
if (!auth.isSuperUser()) {
// only super users can see all groups and do this check.
return Collections.emptyList();
}
Set<String> invalidGroups = new HashSet<String>(CommonUtil.splitCommaStr(groupStr));
invalidGroups.removeAll(auth.getCredentials().getGroups().getTitles());
return invalidGroups;
}
/**
* Return true if the given admin user/groups would mean that
* the current user would be locked out of the system.
*
* @param userStr
* @param groupStr
* @return true if the user would not have admin access with these
* authorizations.
*/
private static boolean locksOutCurrentUser(String userStr, String groupStr) {
CNAuthentication auth = CNAuthentication.get();
if (auth == null) {
// cannot check
return false;
}
if (auth.isSuperUser()) {
return false;
}
String currentUser = auth.getPrincipal();
for (String user: CommonUtil.splitCommaStr(userStr)) {
if (user.equals(currentUser)) {
return false;
}
}
return !auth.isMemberOfAny(CommonUtil.splitCommaStr(groupStr));
}
/**
* Class to check for validity of a regex expression. Expects
* a StaplerRequest with value set.
*/
public static FormValidation regexCheck(String regex) {
if(!CommonUtil.unset(regex)) {
try {
Pattern.compile(regex);
} catch (PatternSyntaxException ex){
return FormValidation.error("The regular expression is not syntactically "
+ "correct.");
}
}
return FormValidation.ok();
}
/**
* Class to check if a CUBiT key has the proper format and allows
* login. Expects a StaplerRequest with value (key), hostURL, and user
* set.
*/
public static FormValidation cubitKeyCheck(String hostUrl, String user, String key) {
if (CommonUtil.unset(key)) {
return FormValidation.error("The user API key is required.");
}
if (!key.matches("\\p{XDigit}{8}-\\p{XDigit}{4}"
+ "-\\p{XDigit}{4}-\\p{XDigit}{4}"
+ "-\\p{XDigit}{12}")) {
if (key.startsWith(" ")) {
return FormValidation.error("The key's format is invalid. "
+ "There is a leading space.");
} else if (key.endsWith(" ")) {
return FormValidation.error("The key's format is invalid. "
+ "There is a trailing space.");
} else {
return FormValidation.error("The key's format is invalid.");
}
}
if (!CommonUtil.unset(hostUrl) && !CommonUtil.unset(user)) {
boolean success;
try {
success = signedStatus(hostUrl, user, key);
} catch (IllegalArgumentException iae) {
// failure
success = false;
}
if (!success) {
return FormValidation.warning("This host URL, username, and user API "
+ "key combination cannot successfully "
+ "sign in.");
}
}
return FormValidation.ok();
}
/**
* Utility function to check that host, user, and key work.
*
* @param host URL.
* @param user to login as.
* @param key to login with.
* @return true if the status is good.
*/
private static boolean signedStatus(String host, String user, String key) {
key = key.toLowerCase();
CubitConnector conn = new CubitConnector(host, user, key);
String status;
try {
status = conn.callCubitApi("status_signed",
new HashMap<String, String>(),
true);
} catch (IOException e) {
return false;
}
Pattern pat = Pattern.compile(".*OK.*", Pattern.DOTALL);
return pat.matcher(status).matches();
}
/**
* Perform checking of number
* @param number the number
* @param allowPositive true to allow positive
* @param allowZero true to allow zero
* @param allowNegative true to allow negative
* @return validation
*/
public static FormValidation numberCheck(String number, boolean allowPositive, boolean allowZero,
boolean allowNegative) {
int integer;
try {
integer = Integer.parseInt(number);
} catch (Exception e) {
integer = 0;
}
if (integer < 0 && !allowNegative) {
return FormValidation.error("Integer cannot be negative: " + integer);
}
if (integer == 0 && !allowZero) {
return FormValidation.error("Integer cannot be zero: " + integer);
}
if (integer > 0 && !allowPositive) {
return FormValidation.error("Integer cannot be positive: " + integer);
}
return FormValidation.ok();
}
}