Skip to content

Commit

Permalink
[FIXED JENKINS-9915] Make captcha support optional; remove LGPL jcaptcha
Browse files Browse the repository at this point in the history
This change is largely based on changes made by Winston Prakash for Hudson.
The main details about his changes can be found here:

http://issues.hudson-ci.org/browse/HUDSON-8844

I deviated a bit from his changes where it was appropriate (e.g. the
description for allow-signup.html was just plain wrong). Also, there
was a lot of collateral damage from formatting in his changeset, which
I have factored out.

I tested the basic functionality of this change and it seems to be OK.
  • Loading branch information
jieryn committed Jun 9, 2011
1 parent 2bfa37c commit 7689840
Show file tree
Hide file tree
Showing 10 changed files with 192 additions and 75 deletions.
3 changes: 3 additions & 0 deletions changelog.html
Expand Up @@ -55,6 +55,9 @@
<!-- Record your changes in the trunk here. -->
<div id="trunk" style="display:none"><!--=TRUNK-BEGIN=-->
<ul class=image>
<li class=rfe>
Make captcha support optional; remove LGPL jcaptcha
(<a href="http://issues.jenkins-ci.org/browse/JENKINS-9915">issue 9915</a>)
<li class=bug>
Validate new view name relative to current context
<li class=bug>
Expand Down
43 changes: 0 additions & 43 deletions core/pom.xml
Expand Up @@ -703,49 +703,6 @@ THE SOFTWARE.
<artifactId>memory-monitor</artifactId>
<version>1.5</version>
</dependency>
<dependency>
<groupId>com.octo.captcha</groupId>
<artifactId>jcaptcha-all</artifactId>
<version>1.0-RC6</version>
<exclusions>
<exclusion>
<groupId>hsqldb</groupId>
<artifactId>hsqldb</artifactId>
</exclusion>
<exclusion>
<groupId>hsqldb</groupId>
<artifactId>hsqldb</artifactId>
</exclusion>
<exclusion>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache</artifactId>
</exclusion>
<exclusion>
<groupId>quartz</groupId>
<artifactId>quartz</artifactId>
</exclusion>
<exclusion>
<groupId>xerces</groupId>
<artifactId>xercesImpl</artifactId>
</exclusion>
<exclusion>
<groupId>xerces</groupId>
<artifactId>xmlParserAPIs</artifactId>
</exclusion>
<exclusion>
<groupId>org.springframework</groupId>
<artifactId>spring</artifactId>
</exclusion>
<exclusion>
<groupId>commons-dbcp</groupId>
<artifactId>commons-dbcp</artifactId>
</exclusion>
<exclusion>
<groupId>concurrent</groupId>
<artifactId>concurrent</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency><!-- StAX implementation. See HUDSON-2547. -->
<groupId>org.codehaus.woodstox</groupId>
<artifactId>wstx-asl</artifactId>
Expand Down
5 changes: 5 additions & 0 deletions core/src/main/java/hudson/Functions.java
Expand Up @@ -34,6 +34,7 @@
import hudson.security.AuthorizationStrategy;
import hudson.security.Permission;
import hudson.security.SecurityRealm;
import hudson.security.captcha.CaptchaSupport;
import hudson.security.csrf.CrumbIssuer;
import hudson.slaves.Cloud;
import hudson.slaves.ComputerLauncher;
Expand Down Expand Up @@ -687,6 +688,10 @@ public static List<ParameterDescriptor> getParameterDescriptors() {
return ParameterDefinition.all();
}

public static List<Descriptor<CaptchaSupport>> getCaptchaSupportDescriptors() {
return CaptchaSupport.all();
}

public static List<Descriptor<ViewsTabBar>> getViewsTabBarDescriptors() {
return ViewsTabBar.all();
}
Expand Down
26 changes: 24 additions & 2 deletions core/src/main/java/hudson/security/HudsonPrivateSecurityRealm.java
Expand Up @@ -35,6 +35,7 @@
import hudson.model.UserProperty;
import hudson.model.UserPropertyDescriptor;
import hudson.security.FederatedLoginService.FederatedIdentity;
import hudson.security.captcha.CaptchaSupport;
import hudson.tasks.Mailer;
import hudson.util.PluginServletFilter;
import hudson.util.Protector;
Expand Down Expand Up @@ -92,9 +93,21 @@ public class HudsonPrivateSecurityRealm extends AbstractPasswordBasedSecurityRea
*/
private final boolean disableSignup;

@DataBoundConstructor
/**
* If true, captcha will be enabled.
*/
private final boolean enableCaptcha;

@Deprecated
public HudsonPrivateSecurityRealm(boolean allowsSignup) {
this(allowsSignup, false, (CaptchaSupport) null);
}

@DataBoundConstructor
public HudsonPrivateSecurityRealm(boolean allowsSignup, boolean enableCaptcha, CaptchaSupport captchaSupport) {
this.disableSignup = !allowsSignup;
this.enableCaptcha = enableCaptcha;
setCaptchaSupport(captchaSupport);
if(!allowsSignup && !hasSomeUser()) {
// if Hudson is newly set up with the security realm and there's no user account created yet,
// insert a filter that asks the user to create one
Expand All @@ -111,6 +124,15 @@ public boolean allowsSignup() {
return !disableSignup;
}

/**
* Checks if captcha is disabled on signup.
*
* @return true if captcha is disabled on signup.
*/
public boolean isEnableCaptcha() {
return enableCaptcha;
}

/**
* Computes if this Hudson has some user accounts configured.
*
Expand Down Expand Up @@ -194,7 +216,7 @@ private User _doCreateAccount(StaplerRequest req, StaplerResponse rsp, String fo
throw HttpResponses.error(SC_UNAUTHORIZED,new Exception("User sign up is prohibited"));

boolean firstUser = !hasSomeUser();
User u = createAccount(req, rsp, true, formView);
User u = createAccount(req, rsp, enableCaptcha, formView);
if(u!=null) {
if(firstUser)
tryToMakeAdmin(u); // the first user should be admin, or else there's a risk of lock out
Expand Down
51 changes: 30 additions & 21 deletions core/src/main/java/hudson/security/SecurityRealm.java
Expand Up @@ -23,9 +23,6 @@
*/
package hudson.security;

import com.octo.captcha.service.CaptchaServiceException;
import com.octo.captcha.service.image.DefaultManageableImageCaptchaService;
import com.octo.captcha.service.image.ImageCaptchaService;
import groovy.lang.Binding;
import hudson.ExtensionPoint;
import hudson.DescriptorExtensionList;
Expand All @@ -35,6 +32,7 @@
import hudson.model.Descriptor;
import jenkins.model.Jenkins;
import hudson.security.FederatedLoginService.FederatedIdentity;
import hudson.security.captcha.CaptchaSupport;
import hudson.util.DescriptorList;
import hudson.util.PluginServletFilter;
import hudson.util.spring.BeanBuilder;
Expand All @@ -57,13 +55,13 @@
import org.springframework.web.context.WebApplicationContext;
import org.springframework.dao.DataAccessException;

import javax.imageio.ImageIO;
import javax.servlet.Filter;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpSession;
import javax.servlet.http.Cookie;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
Expand Down Expand Up @@ -127,6 +125,11 @@
* @see PluginServletFilter
*/
public abstract class SecurityRealm extends AbstractDescribableImpl<SecurityRealm> implements ExtensionPoint {
/**
* Captcha Support to be used with this SecurityRealm for User Signup
*/
private CaptchaSupport captchaSupport;

/**
* Creates fully-configured {@link AuthenticationManager} that performs authentication
* against the user realm. The implementation hides how such authentication manager
Expand Down Expand Up @@ -236,6 +239,18 @@ protected String getPostLogOutUrl(StaplerRequest req, Authentication auth) {
return req.getContextPath()+"/";
}

public CaptchaSupport getCaptchaSupport() {
return captchaSupport;
}

public void setCaptchaSupport(CaptchaSupport captchaSupport) {
this.captchaSupport = captchaSupport;
}

public List<Descriptor<CaptchaSupport>> getCaptchaSupportDescriptors() {
return CaptchaSupport.all();
}

/**
* Handles the logout processing.
*
Expand Down Expand Up @@ -324,35 +339,29 @@ public HttpResponse commenceSignup(FederatedIdentity identity) {
throw new UnsupportedOperationException();
}

/**
* {@link DefaultManageableImageCaptchaService} holder to defer initialization.
*/
public static final class CaptchaService {
public static ImageCaptchaService INSTANCE = new DefaultManageableImageCaptchaService();
}

/**
* Generates a captcha image.
*/
public final void doCaptcha(StaplerRequest req, StaplerResponse rsp) throws IOException {
String id = req.getSession().getId();
rsp.setContentType("image/png");
rsp.addHeader("Cache-Control","no-cache");
ImageIO.write( CaptchaService.INSTANCE.getImageChallengeForID(id), "PNG", rsp.getOutputStream() );
if (captchaSupport != null) {
String id = req.getSession().getId();
rsp.setContentType("image/png");
rsp.addHeader("Cache-Control", "no-cache");
captchaSupport.generateImage(id, rsp.getOutputStream());
}
}

/**
* Validates the captcha.
*/
protected final boolean validateCaptcha(String text) {
try {
if (captchaSupport != null) {
String id = Stapler.getCurrentRequest().getSession().getId();
Boolean b = CaptchaService.INSTANCE.validateResponseForID(id, text);
return b!=null && b;
} catch (CaptchaServiceException e) {
LOGGER.log(Level.INFO, "Captcha validation had a problem",e);
return false;
return captchaSupport.validateCaptcha(id, text);
}

// If no Captcha Support then bogus validation always returns true
return true;
}

/**
Expand Down
65 changes: 65 additions & 0 deletions core/src/main/java/hudson/security/captcha/CaptchaSupport.java
@@ -0,0 +1,65 @@
/*
* The MIT License
*
* Copyright (c) 2011, Winston.Prakash@oracle.com
*
* 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.security.captcha;


import hudson.DescriptorExtensionList;
import hudson.ExtensionPoint;
import hudson.model.AbstractDescribableImpl;
import hudson.model.Descriptor;
import hudson.model.Hudson;

import java.io.IOException;
import java.io.OutputStream;


/**
* Extension point for adding Captcha Support to User Registration Page {@link CaptchaSupport}.
*
* <p>
* This object can have an optional <tt>config.jelly</tt> to configure the Captcha Support
* <p>
* A default constructor is needed to create CaptchaSupport in
* the default configuration.
*
* @author Winston Prakash
* @since 1.416
* @see CaptchaSupportDescriptor
*/
public abstract class CaptchaSupport extends AbstractDescribableImpl<CaptchaSupport> implements ExtensionPoint {
/**
* Returns all the registered {@link CaptchaSupport} descriptors.
*/
public static DescriptorExtensionList<CaptchaSupport, Descriptor<CaptchaSupport>> all() {
return Hudson.getInstance().<CaptchaSupport, Descriptor<CaptchaSupport>>getDescriptorList(CaptchaSupport.class);
}

abstract public boolean validateCaptcha(String id, String text);

abstract public void generateImage(String id, OutputStream ios) throws IOException;

public CaptchaSupportDescriptor getDescriptor() {
return (CaptchaSupportDescriptor)super.getDescriptor();
}
}
@@ -0,0 +1,37 @@
/*
* The MIT License
*
* Copyright (c) 2011, Winston.Prakash@oracle.com
*
* 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.security.captcha;

import hudson.model.Descriptor;

/**
* {@link Descriptor} for {@link CaptchaSupport}.
*
* @author Winston Prakash
* @since 2.0.1
*/
public abstract class CaptchaSupportDescriptor extends Descriptor<CaptchaSupport> {
// so far nothing different from plain Descriptor
// but it may prove useful for future expansion
}
Expand Up @@ -23,9 +23,25 @@ THE SOFTWARE.
-->

<?jelly escape-by-default='true'?>
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout" xmlns:t="/lib/hudson" xmlns:f="/lib/form">
<f:entry title="" help="/help/security/private-realm/allow-signup.html">
<f:checkbox name="privateRealm.allowsSignup" checked="${h.defaultToTrue(instance.allowsSignup())}"
title="${%Allow users to sign up}"/>
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout" xmlns:t="/lib/hudson"
xmlns:f="/lib/form">
<f:entry title="" help="/help/security/private-realm/allow-signup.html">
<f:checkbox name="privateRealm.allowsSignup" checked="${h.defaultToTrue(instance.allowsSignup())}" title="${%Allow users to sign up}" />
</f:entry>
</j:jelly>
<j:if test="${size(h.captchaSupportDescriptors) gt 0}">
<f:entry>
<f:checkbox name="privateRealm.enableCaptcha" checked="${h.defaultToTrue(instance.isEnableCaptcha())}"
title="${%Enable captcha on sing up}" />
</f:entry>
<f:dropdownList name="privateRealm.captchaSupport" title="${%Captcha Support}">
<!-- Loop through available Captcha Support Descriptors -->
<j:forEach var="descriptor" items="${h.captchaSupportDescriptors}" varStatus="loop">
<f:dropdownListBlock title="${descriptor.displayName}" value="${loop.index}"
selected="${descriptor==instance.captchaSupport.descriptor}" staplerClass="${descriptor.clazz.name}">
<!-- Include config.jelly for this Captcha Support -->
<st:include page="${descriptor.configPage}" from="${descriptor}" optional="true" />
</f:dropdownListBlock>
</j:forEach>
</f:dropdownList>
</j:if>
</j:jelly>
Expand Up @@ -27,5 +27,5 @@ THE SOFTWARE.
-->
<?jelly escape-by-default='true'?>
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout" xmlns:t="/lib/hudson" xmlns:f="/lib/form">
<local:_entryForm host="${app}" title="${%Sign up}" action="createAccount" captcha="${true}" xmlns:local="/hudson/security/HudsonPrivateSecurityRealm" />
</j:jelly>
<local:_entryForm host="${app}" title="${%Sign up}" action="createAccount" captcha="${it.isEnableCaptcha()}" xmlns:local="/hudson/security/HudsonPrivateSecurityRealm" />
</j:jelly>

0 comments on commit 7689840

Please sign in to comment.