/*******************************************************************************
* Copyright (c) 2014, 2015 IBM Corporation and others.
* 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:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package com.ibm.team.build.internal.hjplugin;
import hudson.Util;
import hudson.model.Job;
import hudson.security.ACL;
import hudson.util.FormValidation;
import java.io.File;
import java.lang.reflect.InvocationTargetException;
import java.util.List;
import java.util.Locale;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.apache.commons.lang.StringUtils;
import org.jvnet.localizer.LocaleProvider;
import com.cloudbees.plugins.credentials.CredentialsMatchers;
import com.cloudbees.plugins.credentials.CredentialsProvider;
import com.cloudbees.plugins.credentials.common.StandardUsernamePasswordCredentials;
import com.cloudbees.plugins.credentials.domains.URIRequirementBuilder;
import com.ibm.team.build.internal.hjplugin.RTCFacadeFactory.RTCFacadeWrapper;
import com.ibm.team.build.internal.hjplugin.util.Helper;
public class RTCLoginInfo {
private static final Logger LOGGER = Logger.getLogger(RTCScm.class.getName());
private String serverUri;
private String userId;
private String password;
private int timeout;
/**
* Figure out the info needed to log into RTC based on a variety of ways to authenticate
* It is expected that supplied values have been validated to some degree
* @param project The project to be built (hence credentials needed for) may be
* <code>null</code> when dealing with the global case
* @param buildToolkitPath The path of the build toolkit. Could be <code>null</code>
* if not using a password file
* @param serverUri The server to log into. Required
* @param userId The user id to use. May be <code>null</code> when working with
* credentials
* @param password The password to use. May be <code>null</code> when working with
* credentials or a password file.
* @param passwordFile The file containing the password. May be <code>null</code>
* when working with credentials or a password.
* @param credentialsId The id of Jenkins credentials. May be <code>null</code>
* when working with userId & either password/password file
* @param timeout The time out to use (in seconds). Required
* @throws Exception When something goes wrong determining the credentials.
*/
public RTCLoginInfo(Job<?,?> project, String buildToolkitPath,
String serverUri, String userId, String password,
String passwordFile, String credentialsId, int timeout) throws InvalidCredentialsException {
credentialsId = Util.fixEmptyAndTrim(credentialsId);
password = Util.fixEmptyAndTrim(password);
passwordFile = Util.fixEmptyAndTrim(passwordFile);
userId = Util.fixEmptyAndTrim(userId);
if (credentialsId != null) {
// figure out userid & password from the credentials
if (LOGGER.isLoggable(Level.FINER)) {
LOGGER.finer("Looking up credentials for " + //$NON-NLS-1$
"credentialId=\"" + credentialsId + //$NON-NLS-1$
"\" serverURI=\"" + serverUri + //$NON-NLS-1$
"\" project=" + (project == null ? "null" : "\"" + project.getName() + "\"")); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ $NON-NLS-2$ $NON-NLS-3$ $NON-NLS-4$
}
List<StandardUsernamePasswordCredentials> allMatchingCredentials = CredentialsProvider.lookupCredentials(StandardUsernamePasswordCredentials.class, project, ACL.SYSTEM,
URIRequirementBuilder.fromUri(serverUri).build());
StandardUsernamePasswordCredentials credentials = CredentialsMatchers.firstOrNull(allMatchingCredentials,
CredentialsMatchers.withId(credentialsId));
if (credentials != null) {
this.userId = credentials.getUsername();
this.password = credentials.getPassword().getPlainText();
} else {
throw new InvalidCredentialsException(Messages.RTCLoginInfo_creds_unresolvable());
}
} else {
this.userId = userId;
if (this.userId == null) {
if (passwordFile == null && password == null) {
throw new InvalidCredentialsException(Messages.RTCLoginInfo_missing_creds());
} else {
throw new InvalidCredentialsException(Messages.RTCLoginInfo_missing_userid());
}
}
if (passwordFile != null) {
// figure out the password in the file
if (LOGGER.isLoggable(Level.FINER)) {
LOGGER.finer("Looking up credentials for " + //$NON-NLS-1$
"userId=\"" + userId + //$NON-NLS-1$
"\" passwordFile=\"" + passwordFile); //$NON-NLS-1$
}
try {
RTCFacadeWrapper facade = RTCFacadeFactory.getFacade(buildToolkitPath, null);
this.password = (String) facade.invoke("determinePassword", new Class[] { //$NON-NLS-1$
File.class, // passwordFile,
Locale.class // clientLocale
}, new File(passwordFile), LocaleProvider.getLocale());
} catch (Exception e) {
Throwable eToReport = e;
if (eToReport instanceof InvocationTargetException && e.getCause() != null) {
eToReport = e.getCause();
}
if (LOGGER.isLoggable(Level.FINER)) {
LOGGER.log(Level.FINER, "Failed to resolve password from passwordFile=\"" + passwordFile + "\" : " + eToReport.getMessage(), e); //$NON-NLS-1$ //$NON-NLS-2$
}
throw new InvalidCredentialsException(Messages.RTCLoginInfo_missing_password(passwordFile, eToReport.getMessage()), e);
}
} else if (password != null) {
// password was given
if (LOGGER.isLoggable(Level.FINER)) {
LOGGER.finer("Credentials supplied for " + //$NON-NLS-1$
"userId=\"" + userId + //$NON-NLS-1$
"\" password=" + (password == null ? "null" : "non-null")); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
}
this.password = password;
} else {
// no password info supplied
if (LOGGER.isLoggable(Level.FINER)) {
LOGGER.finer("No password supplied for " + //$NON-NLS-1$
"userId=\"" + userId + //$NON-NLS-1$
"\" password=" + (password == null ? "null" : "non-null")); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
}
throw new InvalidCredentialsException(Messages.RTCLoginInfo_supply_password_file_or_password());
}
}
this.serverUri = serverUri;
this.timeout = timeout;
}
public String getServerUri() {
return serverUri;
}
public String getUserId() {
return userId;
}
public String getPassword() {
return password;
}
public int getTimeout() {
return timeout;
}
/**
* Perform lightweight validation of the info required for logging in. By lightweight that means
* validate that information is supplied and combinations make sense.
* @param credentialsId The credentials id. Either it or userid/password/file info is valid
* @param userId The userId. Requires password or password file, not to be used in conjunction with credentials
* @param passwordFile The password file, must exist (contents not validated). To be used in conjunction with userId.
* not to be used in conjunction with credentials nor password.
* @param password The password, To be used in conjunction with userId. Not to be used in conjunction with credentials
* nor passwordFile.
* @param timeout The timeout. Should be a positive integer.
* @return validation result. Might be ok or warning or error.
*/
public static FormValidation basicValidate(String credentialsId, String userId, String passwordFile, String password, String timeout) {
// validate the timeout value
FormValidation result = validateTimeout(timeout);
if (result.kind.equals(FormValidation.Kind.ERROR)) {
return result;
}
// validate credentials
result = validateCredentials(credentialsId, userId, passwordFile, password);
if (result.kind.equals(FormValidation.Kind.ERROR)) {
return result;
}
// validate userid
FormValidation validationCheck = validateUserId(credentialsId, userId, passwordFile, password);
if (validationCheck.kind.equals(FormValidation.Kind.ERROR)) {
return validationCheck;
} else if (validationCheck.kind.equals(FormValidation.Kind.WARNING)) {
result = validationCheck;
}
// validate password file if given since thats what we will use to authenticate with
// otherwise validate the password
if (passwordFile != null) {
validationCheck = validatePasswordFile(credentialsId, userId, passwordFile, password);
} else {
validationCheck = validatePassword(credentialsId, userId, passwordFile, password);
}
result = Helper.mergeValidationResults(result, validationCheck);
return result;
}
/**
* Intended to validate the password field in isolation
* If credentials are being used and password info supplied, warn it will be ignored
* If both password and password file supplied warning that password file will be used
*/
public static FormValidation validatePassword(String credentialsId,
String userId, String passwordFile, String password) {
userId = Util.fixEmptyAndTrim(userId);
password = Util.fixEmptyAndTrim(password);
passwordFile = Util.fixEmptyAndTrim(passwordFile);
credentialsId = Util.fixEmptyAndTrim(credentialsId);
if (password != null) {
if (credentialsId != null && userId == null) {
return FormValidation.warning(Messages.RTCLoginInfo_password_ignored());
} else if (credentialsId == null && passwordFile != null) {
// assume they are still working with userids
LOGGER.finer("Both password (" + password.length() + " characters) and password file are supplied : " + passwordFile); //$NON-NLS-1$ //$NON-NLS-2$
return FormValidation.warning(Messages.RTCLoginInfo_both_supplied_password_ignored());
}
} else if (userId != null && credentialsId == null && passwordFile == null) {
LOGGER.finer("Missing password file (and password file) when using a user id to authenticate"); //$NON-NLS-1$
return FormValidation.error(Messages.RTCLoginInfo_supply_password_file_or_password());
}
return FormValidation.ok();
}
/**
* Intended to validate the password file field in isolation
* If credentials are being used and password file info supplied, warn it will be ignored
* if password file doesn't exist or is not a file, its an error
* If both password and password file supplied warning that password file will be used
*/
public static FormValidation validatePasswordFile(String credentialsId,
String userId, String passwordFile, String password) {
userId = Util.fixEmptyAndTrim(userId);
password = Util.fixEmptyAndTrim(password);
passwordFile = Util.fixEmptyAndTrim(passwordFile);
credentialsId = Util.fixEmptyAndTrim(credentialsId);
if (passwordFile != null) {
if (credentialsId != null && userId == null) {
return FormValidation.warning(Messages.RTCLoginInfo_password_file_ignored());
}
File passwordFileFile = new File(passwordFile);
if (!passwordFileFile.exists()) {
LOGGER.finer("Password file does not exist " + passwordFileFile.getAbsolutePath()); //$NON-NLS-1$
return FormValidation.error(Messages.RTCScm_password_file_not_found(passwordFile));
}
if (passwordFileFile.isDirectory()) {
LOGGER.finer("Password file is a directory : " + passwordFileFile.getAbsolutePath()); //$NON-NLS-1$
return FormValidation.error(Messages.RTCScm_password_file_is_directory(passwordFile));
}
if (credentialsId == null && password != null) {
// assume they are still working with userids
LOGGER.finer("Both password (" + password.length() + " characters) and password file are supplied : " + passwordFile); //$NON-NLS-1$ //$NON-NLS-2$
return FormValidation.warning(Messages.RTCLoginInfo_both_supplied_password_file_used());
}
} else if (userId != null && credentialsId == null && password == null) {
LOGGER.finer("Missing password file (and password) when using a user id to authenticate"); //$NON-NLS-1$
return FormValidation.error(Messages.RTCLoginInfo_supply_password_file_or_password());
}
return FormValidation.ok();
}
/**
* Validate the user id
* If credentials and user id supplied then warn that user id is ignored
* If no credentials and password info supplied but no user id then error user id is required.
* Otherwise ok.
*/
public static FormValidation validateUserId(String credentialsId,
String userId, String passwordFile, String password) {
userId = Util.fixEmptyAndTrim(userId);
password = Util.fixEmptyAndTrim(password);
passwordFile = Util.fixEmptyAndTrim(passwordFile);
credentialsId = Util.fixEmptyAndTrim(credentialsId);
if (credentialsId == null && userId == null && (password != null || passwordFile != null)) {
return FormValidation.error(Messages.RTCLoginInfo_missing_userid());
} else if (credentialsId != null && userId != null) {
return FormValidation.warning(Messages.RTCLoginInfo_credentials_used());
}
return FormValidation.ok();
}
/**
* Validate the timeout
* Must be a positive integer
*/
public static FormValidation validateTimeout(String timeout) {
timeout = Util.fixEmptyAndTrim(timeout);
if (StringUtils.isEmpty(timeout)) {
LOGGER.finer("timeout value missing"); //$NON-NLS-1$
return FormValidation.error(Messages.RTCScm_timeout_required());
}
return FormValidation.validatePositiveInteger(timeout);
}
/**
* Validate the credentials id
* We want credentials id if no auth given
*/
public static FormValidation validateCredentials(String credentialsId,
String userId, String passwordFile, String password) {
userId = Util.fixEmptyAndTrim(userId);
password = Util.fixEmptyAndTrim(password);
passwordFile = Util.fixEmptyAndTrim(passwordFile);
credentialsId = Util.fixEmptyAndTrim(credentialsId);
if (credentialsId == null && userId == null && password == null && passwordFile == null) {
return FormValidation.error(Messages.RTCScm_credentials_required());
}
return FormValidation.ok();
}
}