Skip to content

Commit

Permalink
[FIXED JENKINS-12582] Adding CVS Authentication across projects
Browse files Browse the repository at this point in the history
  • Loading branch information
mc1arke committed Aug 23, 2012
1 parent d35e4a6 commit 2aedd2f
Show file tree
Hide file tree
Showing 11 changed files with 215 additions and 74 deletions.
38 changes: 26 additions & 12 deletions src/main/java/hudson/scm/AbstractCvs.java
Expand Up @@ -216,7 +216,7 @@ private boolean perform(final Command cvsCommand, final FilePath workspace, fina
final CvsRepository repository, final String moduleName, final EnvVars envVars)
throws IOException, InterruptedException {

final Client cvsClient = getCvsClient(repository, envVars);
final Client cvsClient = getCvsClient(repository, envVars, listener);
final GlobalOptions globalOptions = getGlobalOptions(repository, envVars);


Expand Down Expand Up @@ -273,12 +273,26 @@ public Boolean invoke(final File workspace, final VirtualChannel channel) throws
* @param envVars variables to use for macro expansion
* @return a CVS client capable of connecting to the specified repository
*/
public Client getCvsClient(final CvsRepository repository, final EnvVars envVars) {
final CVSRoot cvsRoot = CVSRoot.parse(envVars.expand(repository.getCvsRoot()));
public Client getCvsClient(final CvsRepository repository, final EnvVars envVars, final TaskListener listener) {
CVSRoot cvsRoot = CVSRoot.parse(envVars.expand(repository.getCvsRoot()));

if (repository.isPasswordRequired()) {
listener.getLogger().println("Using locally configured password for connection to " + cvsRoot.toString());
cvsRoot.setPassword(Secret.toString(repository.getPassword()));
}
else {
String partialRoot = cvsRoot.getHostName() + ":" + cvsRoot.getPort() + cvsRoot.getRepository();
String sanitisedRoot = ":" + cvsRoot.getMethod() + ":" + partialRoot;
for (CvsAuthentication authentication : getDescriptor().getAuthentication()) {
if (authentication.getCvsRoot().equals(sanitisedRoot) && (cvsRoot.getUserName() == null || authentication.getUsername().equals(cvsRoot.getUserName()))) {
cvsRoot = CVSRoot.parse(":" + cvsRoot.getMethod() + ":" + (authentication.getUsername() != null ? authentication.getUsername() + "@" :"") + partialRoot);
cvsRoot.setPassword(authentication.getPassword().getPlainText());
listener.getLogger().println("Using globally configured password for connection to '" + sanitisedRoot
+ "' with username '" + authentication.getUsername() + "'");
break;
}
}
}

ConnectionIdentity connectionIdentity = ConnectionFactory.getConnectionIdentity();
connectionIdentity.setKnownHostsFile(envVars.expand(getDescriptor().getKnownHostsLocation()));
Expand Down Expand Up @@ -489,7 +503,7 @@ protected List<CvsFile> calculateRepositoryState(final Date startTime, final Dat
for (final CvsRepositoryItem item : repository.getRepositoryItems()) {
for (final CvsModule module : item.getModules()) {

CvsLog logContents = getRemoteLogForModule(repository, module, listener.getLogger(), startTime, endTime, envVars);
CvsLog logContents = getRemoteLogForModule(repository, module, startTime, endTime, envVars, listener);

// use the parser to build up a list of changed files and add it to
// the list we've been creating
Expand All @@ -508,7 +522,7 @@ protected List<CvsFile> calculateRepositoryState(final Date startTime, final Dat
* the repository to connect to for running rlog against
* @param module
* the module to check for changes against
* @param errorStream
* @param listener
* where to log any error messages to
* @param startTime
* don't list any changes before this time
Expand All @@ -519,9 +533,9 @@ protected List<CvsFile> calculateRepositoryState(final Date startTime, final Dat
* on underlying communication failure
*/
private CvsLog getRemoteLogForModule(final CvsRepository repository, final CvsModule module,
final PrintStream errorStream, final Date startTime, final Date endTime,
final EnvVars envVars) throws IOException {
final Client cvsClient = getCvsClient(repository, envVars);
final Date startTime, final Date endTime,
final EnvVars envVars, final TaskListener listener) throws IOException {
final Client cvsClient = getCvsClient(repository, envVars, listener);

RlogCommand rlogCommand = new RlogCommand();

Expand All @@ -546,11 +560,11 @@ private CvsLog getRemoteLogForModule(final CvsRepository repository, final CvsMo
final PrintStream logStream = new PrintStream(outputStream);

// set a listener with our output stream that we parse the log from
final CVSListener basicListener = new BasicListener(logStream, errorStream);
final CVSListener basicListener = new BasicListener(logStream, listener.getLogger());
cvsClient.getEventManager().addCVSListener(basicListener);

// log the command to the current run/polling log
errorStream.println("cvs " + rlogCommand.getCVSCommand());
listener.getLogger().println("cvs " + rlogCommand.getCVSCommand());

// send the command to be run, we can't continue of the task fails
try {
Expand All @@ -567,7 +581,7 @@ private CvsLog getRemoteLogForModule(final CvsRepository repository, final CvsMo
try {
cvsClient.getConnection().close();
} catch(IOException ex) {
errorStream.println("Could not close client connection: " + ex.getMessage());
listener.getLogger().println("Could not close client connection: " + ex.getMessage());
}
}

Expand Down Expand Up @@ -627,7 +641,7 @@ protected List<CVSChangeLogSet.CVSChangeLog> calculateChangeLog(final Date start
for (final CvsRepositoryItem item : repository.getRepositoryItems()) {
for (final CvsModule module : item.getModules()) {

CvsLog logContents = getRemoteLogForModule(repository, module, listener.getLogger(), startTime, endTime, envVars);
CvsLog logContents = getRemoteLogForModule(repository, module, startTime, endTime, envVars, listener);

// use the parser to build up a list of changes and add it to the
// list we've been creating
Expand Down
51 changes: 36 additions & 15 deletions src/main/java/hudson/scm/CVSSCM.java
Expand Up @@ -28,11 +28,14 @@
import hudson.Launcher;
import hudson.model.*;
import hudson.scm.cvstagging.LegacyTagAction;
import hudson.util.FormValidation;
import hudson.util.Secret;
import net.sf.json.JSONObject;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.QueryParameter;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.export.Exported;
import org.netbeans.lib.cvsclient.CVSRoot;

import java.io.File;
import java.io.IOException;
Expand Down Expand Up @@ -357,7 +360,7 @@ private class RepositoryBrowser {
private String privateKeyLocation = System.getProperty("user.home") + "/.ssh/id_rsa";
private Secret privateKeyPassword = null;
private String knownHostsLocation = System.getProperty("user.home") + "/.ssh/known_hosts";
private Map<String, Secret> passwords = new HashMap<String, Secret>();
private CvsAuthentication[] authTokens = new CvsAuthentication[]{};

public DescriptorImpl() {
super(CVSRepositoryBrowser.class);
Expand All @@ -382,34 +385,24 @@ public String getPrivateKeyLocation() {
return privateKeyLocation;
}

public void setPrivateKeyLocation(String privateKeyLocation) {
this.privateKeyLocation = privateKeyLocation;
}

@Exported
public Secret getPrivateKeyPassword() {
return privateKeyPassword;
}

public void setPrivateKeyPassword(String privateKeyPassword) {
this.privateKeyPassword = Secret.fromString(privateKeyPassword);
}

@Exported
public String getKnownHostsLocation() {
return knownHostsLocation;
}

public void setKnownHostsLocation(String knownHostsLocation) {
this.knownHostsLocation = knownHostsLocation;
}

@Exported
public int getCompressionLevel() {
return compressionLevel;
}

public void setCompressionLevel(final int compressionLevel) {
this.compressionLevel = compressionLevel;
@Exported
public CvsAuthentication[] getAuthentication() {
return authTokens;
}

@Override
Expand Down Expand Up @@ -449,6 +442,8 @@ public boolean configure(final StaplerRequest req, final JSONObject o) {

privateKeyPassword = Secret.fromString(fixEmptyAndTrim(o.getString("privateKeyPassword")));

List<CvsAuthentication> authTokens = req.bindParametersToList(CvsAuthentication.class, "cvsAuthentication.");
this.authTokens = authTokens.toArray(new CvsAuthentication[authTokens.size()]);
save();

return true;
Expand Down Expand Up @@ -483,6 +478,32 @@ public String getCvsPassFile() {
return cvsPassFile;
}

public FormValidation doCheckAuthenticationCvsRoot(@QueryParameter final String value) {
try {
CVSRoot cvsRoot = CVSRoot.parse(value);

if (cvsRoot.getUserName() != null) {
return FormValidation.error("Do not specify a username in the CVS root; use the username field.");
}

if (cvsRoot.getPassword() != null) {
return FormValidation.error("Do not specify a password in the CVS root; use the password field.");
}

if (cvsRoot.getMethod().equals(CVSRoot.METHOD_FORK)
|| cvsRoot.getMethod().equals(CVSRoot.METHOD_LOCAL)) {
return FormValidation.error(cvsRoot.getMethod() +
" does not support authentication so should not be specified");
}

return FormValidation.ok();
} catch(IllegalArgumentException ex) {
return FormValidation.error(value +
" does not seem to be a valid CVS root so would not match any repositories.");
}

}

//
// web methods
//
Expand Down
59 changes: 59 additions & 0 deletions src/main/java/hudson/scm/CvsAuthentication.java
@@ -0,0 +1,59 @@
/*
* The MIT License
*
* Copyright (c) 2012, Michael Clarke
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package hudson.scm;


import hudson.Util;
import hudson.util.Secret;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.export.Exported;

public class CvsAuthentication {

private String cvsRoot;
private String username;
private Secret password;

@DataBoundConstructor
public CvsAuthentication(final String cvsRoot, final String username, final String password) {
this.cvsRoot = cvsRoot;
this.username = Util.fixNull(username);
this.password = Secret.fromString(password);
}

@Exported
public String getCvsRoot() {
return cvsRoot;
}

@Exported
public String getUsername() {
return username;
}

@Exported
public Secret getPassword() {
return password;
}
}
64 changes: 47 additions & 17 deletions src/main/java/hudson/scm/CvsProjectset.java
Expand Up @@ -23,12 +23,15 @@
*/
package hudson.scm;

import hudson.*;
import hudson.Extension;
import hudson.FilePath;
import hudson.Launcher;
import hudson.Util;
import hudson.model.*;
import hudson.scm.cvs.Messages;
import hudson.util.Secret;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.export.Exported;
import hudson.scm.cvs.Messages;

import java.io.File;
import java.io.IOException;
Expand All @@ -42,8 +45,8 @@

public class CvsProjectset extends AbstractCvs {

private static final Pattern PSF_PATTERN = Pattern.compile("<project reference=\"[^,]+,(:[a-z]+:)([a-z|A-Z|0-9]+)" +
"(:([0-9]+))?([/|a-z|A-Z|_|0-9]+),([A-Z|a-z|0-9|_]+),([A-Z|a-z|0-9|_]+)(,(.*?)){0,1}\"/>");
private static final Pattern PSF_PATTERN = Pattern.compile("<project reference=\"[^,]+,((:[a-z]+:)([a-z|A-Z|0-9]+)" +
"(:([0-9]+))?([/|a-z|A-Z|_|0-9]+)),([A-Z|a-z|0-9|_]+),([A-Z|a-z|0-9|_]+)(,(.*?)){0,1}\"/>");

private final CvsRepository[] repositories;
private final boolean canUseUpdate;
Expand Down Expand Up @@ -144,30 +147,42 @@ private CvsRepository[] getInnerRepositories(FilePath workspace) throws IOExcept
String psfContents = projectsetFile.readToString();

for (Matcher matcher = PSF_PATTERN.matcher(psfContents); matcher.find();) {
CvsModule innerModule = new CvsModule(matcher.group(6), matcher.group(7));
CvsModule innerModule = new CvsModule(matcher.group(7), matcher.group(8));
CvsRepositoryLocation innerLocation;
if (matcher.group(9) == null) {
if (matcher.group(10) == null) {
innerLocation = new CvsRepositoryLocation.HeadRepositoryLocation();
}
else {
innerLocation = new CvsRepositoryLocation.BranchRepositoryLocation(matcher.group(9), false);
innerLocation = new CvsRepositoryLocation.BranchRepositoryLocation(matcher.group(10), false);
}
CvsRepositoryItem innerItem = new CvsRepositoryItem(innerLocation,
new CvsModule[]{innerModule});

CvsAuthentication authentication = getAuthenticationForCvsRoot(matcher.group(1));

StringBuilder root = new StringBuilder();
root.append(matcher.group(1));
if (username != null) {
root.append(getUsername());
root.append("@");
}
root.append(matcher.group(2));
if (matcher.group(4) != null) {
root.append(":").append(matcher.group(4));

String password = null;
if (authentication == null) {
if (username != null) {
root.append(getUsername());
root.append("@");
}
password = getPassword().getPlainText();
}
// we don't actually do anything with the authentication details if they're available just now
// as they're automatically configured in a later call

root.append(matcher.group(3));
if (matcher.group(5) != null) {
root.append(":").append(matcher.group(5));
}
root.append(matcher.group(5));
CvsRepository innerRepository = new CvsRepository(root.toString(),
getPassword().getPlainText() != null, Secret.toString(getPassword()),
root.append(matcher.group(6));

CvsRepository innerRepository = new CvsRepository(root.toString(), password != null, password,
Arrays.asList(innerItem), new ArrayList<ExcludedRegion>(), 0);

psfList.add(innerRepository);
}
}
Expand All @@ -176,6 +191,16 @@ private CvsRepository[] getInnerRepositories(FilePath workspace) throws IOExcept
return psfList.toArray(new CvsRepository[psfList.size()]);
}

private CvsAuthentication getAuthenticationForCvsRoot(final String cvsRoot) {
for(CvsAuthentication authentication : getDescriptor().getAuthentication()) {
if (authentication.getCvsRoot().equals(cvsRoot)) {
return authentication;
}
}

return null;
}

private CvsRepository[] getAllRepositories(FilePath workspace) throws IOException, InterruptedException {
List<CvsRepository> returnList = new ArrayList<CvsRepository>();
returnList.addAll(Arrays.asList(getRepositories()));
Expand Down Expand Up @@ -265,6 +290,11 @@ public int getCompressionLevel() {
return getCvsDescriptor().getCompressionLevel();
}

@Override
public CvsAuthentication[] getAuthentication() {
return getCvsDescriptor().getAuthentication();
}

}


Expand Down
2 changes: 2 additions & 0 deletions src/main/java/hudson/scm/ICvsDescriptor.java
Expand Up @@ -34,4 +34,6 @@ public interface ICvsDescriptor {
public Secret getPrivateKeyPassword();

public int getCompressionLevel();

public CvsAuthentication[] getAuthentication();
}
Expand Up @@ -41,7 +41,7 @@ protected void perform(final TaskListener listener) throws Exception {
for (CvsRepository repository : revisionState.getModuleFiles().keySet()) {
for (CvsFile file : revisionState.getModuleState(repository)) {
AbstractCvs owner = parent.getParent();
final Client cvsClient = owner.getCvsClient(repository, build.getEnvironment(listener));
final Client cvsClient = owner.getCvsClient(repository, build.getEnvironment(listener), listener);
final GlobalOptions globalOptions = owner.getGlobalOptions(repository, build.getEnvironment(listener));

globalOptions.setCVSRoot(repository.getCvsRoot());
Expand Down

0 comments on commit 2aedd2f

Please sign in to comment.