[xwiki-devs] [proposal] Migrating the captcha plugin to a component
Hello folks, I have a proposed method for migrating the captcha plugin to a component. I have posted a patch here: http://jira.xwiki.org/jira/browse/XWIKI-4741 which contains the core modifications which I am proposing. Though I have a patch, this is still in the design phase and I welcome anyone to suggest changes. ComponentRole: Captcha /** * Check if the solution to the captcha is correct. * * @param answer The provided solution. * @return true if the solution is correct. */ boolean isAnswerCorrect(String answer); Implementing components: DefaultImageCaptchaAction DefaultSoundCaptchaAction The new DefaultImageCaptcha component can be used to solve captchas which were downloaded through the old struts based jcaptcha plugin. Both components also implement org.xwiki.action.Action and when execute() is called, they send a captcha to the response. The components depend on the request and response in the container being servlet request/response because com.octo.captcha.module.web.image.ImageToJpegHelper and com.octo.captcha.module.web.sound.SoundToWavHelper require HttpServletRequest and HttpServletResponse. Also to get the ID of the session in a way that is compatible with the old struts based method of getting captchas, an HttpServletRequest is needed. I am not happy with this design as it demands the use of the if instanceof then cast hack. I would like to hear any ideas about how to clean this up. There is also a velocity initializer which provides a captcha service with: /** * Check if the solution to the captcha is correct. * * @param captchaName The name of the Captcha you are checking the answer against * @param answer The provided solution * @return true if the solution is correct * @throws UnsupportedOperationException If there is no captcha by the name captchaName */ public boolean isAnswerCorrect(String captchaName, String answer) throws UnsupportedOperationException and /** @return a List of the names of all registered classes implementing {@link org.xwiki.captcha.Captcha}. */ public List<String> listCaptchaNames() In my opinion the html should all be in the application side and the core code should not even know it's URL. I am working on an application and would be interested to hear what requirements you think it should meet. Sorry for the long mail, I am anxious to hear your ideas about this. Caleb James DeLisle
Hi, On Fri, Jan 15, 2010 at 2:35 PM, Caleb James DeLisle < [email protected]> wrote:
Hello folks,
I have a proposed method for migrating the captcha plugin to a component. I have posted a patch here: http://jira.xwiki.org/jira/browse/XWIKI-4741 which contains the core modifications which I am proposing. Though I have a patch, this is still in the design phase and I welcome anyone to suggest changes.
ComponentRole: Captcha /** * Check if the solution to the captcha is correct. * * @param answer The provided solution. * @return true if the solution is correct. */ boolean isAnswerCorrect(String answer);
May be I have understood wrong, but it seems only your velocity API (below) provides a method signature like: boolean isAnswerCorrect(String captchaId, String answer); - Why not have this in component API? For an example, recaptcha works like this. It needs both a challengeId as well as it's answer for it to validate the response. - This methods should probably declare an exception (e.g. unable to contact recaptcha servers?)
Implementing components: DefaultImageCaptchaAction DefaultSoundCaptchaAction
The new DefaultImageCaptcha component can be used to solve captchas which were downloaded through the old struts based jcaptcha plugin. Both components also implement org.xwiki.action.Action and when execute() is called, they send a captcha to the response.
The components depend on the request and response in the container being servlet request/response because com.octo.captcha.module.web.image.ImageToJpegHelper and com.octo.captcha.module.web.sound.SoundToWavHelper require HttpServletRequest and HttpServletResponse. Also to get the ID of the session in a way that is compatible with the old struts based method of getting captchas, an HttpServletRequest is needed. I am not happy with this design as it demands the use of the if instanceof then cast hack. I would like to hear any ideas about how to clean this up.
There is also a velocity initializer which provides a captcha service with:
/** * Check if the solution to the captcha is correct. * * @param captchaName The name of the Captcha you are checking the answer against * @param answer The provided solution * @return true if the solution is correct * @throws UnsupportedOperationException If there is no captcha by the name captchaName */ public boolean isAnswerCorrect(String captchaName, String answer) throws UnsupportedOperationException
If this is a velocity API (it sounds like that), it should not throw an exception, there is no way to catch it from velocity. It should probably return false in case of an error (and set an error message inside the context). I'm a bit unclear about your proposal and in doubt whether I have understood your proposal wrong. Please correct me if that is the case. Thanks. - Asiri
and
/** @return a List of the names of all registered classes implementing {@link org.xwiki.captcha.Captcha}. */ public List<String> listCaptchaNames()
In my opinion the html should all be in the application side and the core code should not even know it's URL. I am working on an application and would be interested to hear what requirements you think it should meet.
Sorry for the long mail, I am anxious to hear your ideas about this.
Caleb James DeLisle
_______________________________________________ devs mailing list [email protected] http://lists.xwiki.org/mailman/listinfo/devs
Asiri Rathnayake wrote:
Hi,
On Fri, Jan 15, 2010 at 2:35 PM, Caleb James DeLisle < [email protected]> wrote:
Hello folks,
I have a proposed method for migrating the captcha plugin to a component. I have posted a patch here: http://jira.xwiki.org/jira/browse/XWIKI-4741 which contains the core modifications which I am proposing. Though I have a patch, this is still in the design phase and I welcome anyone to suggest changes.
ComponentRole: Captcha /** * Check if the solution to the captcha is correct. * * @param answer The provided solution. * @return true if the solution is correct. */ boolean isAnswerCorrect(String answer);
May be I have understood wrong, but it seems only your velocity API (below) provides a method signature like:
boolean isAnswerCorrect(String captchaId, String answer);
Not exactly, I think you are thinking of: $captchaservice.isAnswerCorrect($sessionId, $answer) while captchaName is the hint for getting a component from the component manager. eg: $captchaservice.isAnswerCorrect("defaultImageCaptcha", $answer)
- Why not have this in component API? For an example, recaptcha works like this. It needs both a challengeId as well as it's answer for it to validate the response.
I have the component set up to automatically get the id from the servlet so that an application can just reference an image from the old struts based image captcha generator and the next answer given by the same web browser will fit that captcha. here is a working example: {{velocity}} #if($captchaservice.isAnswerCorrect("defaultImageCaptcha", $request.getParameter("captchaAnswer"))) #set($captchaAnswerIsCorrect = true) #else {{html}} <img src="$doc.getURL('jcaptcha')"/ > <form name="input" action="$doc.getURL()" method="get"> Fill in the letters in the image above: <br/> <input type="text" name="captchaAnswer" /> <input type="submit" value="I'm Human" /> </form> {{/html}} #end #if($captchaAnswerIsCorrect) = You win. #end {{/velocity}} The fact that the last call to the struts based jcaptcha component was from same session means that the answer provided will work. Perhaps it would be best to offer a way to check the answer with the sessionId either A: $captchaservice.isAnswerCorrect("defaultImageCaptcha", $sessionId, $answer) or B: #set($captcha = $captchaservice.getCaptcha("defaultImageCaptcha")) $captcha.isAnswerCorrect($sessionId, $answer) Also an easy way to get the session id using $request in Velocity? Something like: A: #set($sessionId = $captchaservice.getSessionId($request)) or B: #set($sessionId = $captcha.getSessionId($request))
- This methods should probably declare an exception (e.g. unable to contact recaptcha servers?)
I hadn't thought of recaptcha being an implementation of captcha but it makes perfect sense. does the all generic "throws Exception" seem right to you?
Implementing components: DefaultImageCaptchaAction DefaultSoundCaptchaAction
The new DefaultImageCaptcha component can be used to solve captchas which were downloaded through the old struts based jcaptcha plugin. Both components also implement org.xwiki.action.Action and when execute() is called, they send a captcha to the response.
The components depend on the request and response in the container being servlet request/response because com.octo.captcha.module.web.image.ImageToJpegHelper and com.octo.captcha.module.web.sound.SoundToWavHelper require HttpServletRequest and HttpServletResponse. Also to get the ID of the session in a way that is compatible with the old struts based method of getting captchas, an HttpServletRequest is needed. I am not happy with this design as it demands the use of the if instanceof then cast hack. I would like to hear any ideas about how to clean this up.
There is also a velocity initializer which provides a captcha service with:
/** * Check if the solution to the captcha is correct. * * @param captchaName The name of the Captcha you are checking the answer against * @param answer The provided solution * @return true if the solution is correct * @throws UnsupportedOperationException If there is no captcha by the name captchaName */ public boolean isAnswerCorrect(String captchaName, String answer) throws UnsupportedOperationException
If this is a velocity API (it sounds like that), it should not throw an exception, there is no way to catch it from velocity. It should probably return false in case of an error (and set an error message inside the context).
You are right, I wanted to be easy on the application programmer by letting them know that they haven't just gotten the captcha wrong, but throwing exceptions to velocity will open a can of worms.
I'm a bit unclear about your proposal and in doubt whether I have understood your proposal wrong. Please correct me if that is the case.
It is probably that my proposal is unclear, I think your understanding is correct though. Thank you for looking at it, Caleb
Thanks.
- Asiri
and
/** @return a List of the names of all registered classes implementing {@link org.xwiki.captcha.Captcha}. */ public List<String> listCaptchaNames()
In my opinion the html should all be in the application side and the core code should not even know it's URL. I am working on an application and would be interested to hear what requirements you think it should meet.
Sorry for the long mail, I am anxious to hear your ideas about this.
Caleb James DeLisle
_______________________________________________ devs mailing list [email protected] http://lists.xwiki.org/mailman/listinfo/devs
_______________________________________________ devs mailing list [email protected] http://lists.xwiki.org/mailman/listinfo/devs
Hi, Not exactly, I think you are thinking of:
$captchaservice.isAnswerCorrect($sessionId, $answer)
Yes. I believe here sessionId is generated by the captcha service for each captcha displayed? Or is it something else? I'm asking because recaptcha generates a unque captchaId for each captcha it generates. Then you need to handover this captchaId + user's answer to the server to verify the answer.
while captchaName is the hint for getting a component from the component manager. eg: $captchaservice.isAnswerCorrect("defaultImageCaptcha", $answer)
Ok, I understand now.
The fact that the last call to the struts based jcaptcha component was from same session means that the answer provided will work.
Perhaps it would be best to offer a way to check the answer with the sessionId either A: $captchaservice.isAnswerCorrect("defaultImageCaptcha", $sessionId, $answer)
or B: #set($captcha = $captchaservice.getCaptcha("defaultImageCaptcha")) $captcha.isAnswerCorrect($sessionId, $answer)
Both sounds good. Although I like the second form.
Also an easy way to get the session id using $request in Velocity? Something like:
A: #set($sessionId = $captchaservice.getSessionId($request))
or B: #set($sessionId = $captcha.getSessionId($request))
I don't know whether binding velocity APIs to HTTPRequest is a good idea, but it will surely make things easy. We could even have something like: #set($captcha = $captchaservice.getCaptcha("defaultImageCaptcha")) $captcha.isAnswerCorrect($request) So we leave all the parameter resolution to the captcha component itself. Thanks. - Asiri
Asiri Rathnayake wrote:
Hi,
Not exactly, I think you are thinking of:
$captchaservice.isAnswerCorrect($sessionId, $answer)
Yes. I believe here sessionId is generated by the captcha service for each captcha displayed? Or is it something else?
I'm asking because recaptcha generates a unque captchaId for each captcha it generates. Then you need to handover this captchaId + user's answer to the server to verify the answer.
while captchaName is the hint for getting a component from the component manager. eg: $captchaservice.isAnswerCorrect("defaultImageCaptcha", $answer)
Ok, I understand now.
The fact that the last call to the struts based jcaptcha component was from same session means that the answer provided will work.
Perhaps it would be best to offer a way to check the answer with the sessionId either A: $captchaservice.isAnswerCorrect("defaultImageCaptcha", $sessionId, $answer)
or B: #set($captcha = $captchaservice.getCaptcha("defaultImageCaptcha")) $captcha.isAnswerCorrect($sessionId, $answer)
Both sounds good. Although I like the second form.
Agreed, I am thinking I can create an implementation of Captcha which wraps another implementation but never throws any exceptions so it is velocity friendly.
Also an easy way to get the session id using $request in Velocity? Something like:
A: #set($sessionId = $captchaservice.getSessionId($request))
or B: #set($sessionId = $captcha.getSessionId($request))
I don't know whether binding velocity APIs to HTTPRequest is a good idea, but it will surely make things easy. We could even have something like:
#set($captcha = $captchaservice.getCaptcha("defaultImageCaptcha")) $captcha.isAnswerCorrect($request)
So we leave all the parameter resolution to the captcha component itself.
As easy as it is, I get nervous thinking about hardcoding the name of the parameter in the core, suppose we wanted to use it in a case where that parameter was taken? Anyway defining hardcoded strings gives me a bad feeling. How about: #set($captcha = $captchaservice.getCaptcha("defaultImageCaptcha")) #set($servletId = $captcha.getServletId($request)) $captcha.isAnswerCorrect($answer) captcha.getServletId can take an HttpServletRequest object and the XWikiRequest (in velocity that is $request) will work because XWikiRequest extends and wraps the HttpServletRequest object. Thanks, Caleb
Thanks.
- Asiri _______________________________________________ devs mailing list [email protected] http://lists.xwiki.org/mailman/listinfo/devs
Whoops I made a mistake in my example: How about: #set($captcha = $captchaservice.getCaptcha("defaultImageCaptcha")) #set($servletId = $captcha.getServletId($request)) $captcha.isAnswerCorrect($answer) should be: $captcha.isAnswerCorrect($servletId, $answer) Caleb Caleb James DeLisle wrote:
Asiri Rathnayake wrote:
Hi,
Not exactly, I think you are thinking of:
$captchaservice.isAnswerCorrect($sessionId, $answer)
Yes. I believe here sessionId is generated by the captcha service for each captcha displayed? Or is it something else?
I'm asking because recaptcha generates a unque captchaId for each captcha it generates. Then you need to handover this captchaId + user's answer to the server to verify the answer.
while captchaName is the hint for getting a component from the component manager. eg: $captchaservice.isAnswerCorrect("defaultImageCaptcha", $answer)
Ok, I understand now.
The fact that the last call to the struts based jcaptcha component was from same session means that the answer provided will work.
Perhaps it would be best to offer a way to check the answer with the sessionId either A: $captchaservice.isAnswerCorrect("defaultImageCaptcha", $sessionId, $answer)
or B: #set($captcha = $captchaservice.getCaptcha("defaultImageCaptcha")) $captcha.isAnswerCorrect($sessionId, $answer)
Both sounds good. Although I like the second form.
Agreed, I am thinking I can create an implementation of Captcha which wraps another implementation but never throws any exceptions so it is velocity friendly.
Also an easy way to get the session id using $request in Velocity? Something like:
A: #set($sessionId = $captchaservice.getSessionId($request))
or B: #set($sessionId = $captcha.getSessionId($request))
I don't know whether binding velocity APIs to HTTPRequest is a good idea, but it will surely make things easy. We could even have something like:
#set($captcha = $captchaservice.getCaptcha("defaultImageCaptcha")) $captcha.isAnswerCorrect($request)
So we leave all the parameter resolution to the captcha component itself.
As easy as it is, I get nervous thinking about hardcoding the name of the parameter in the core, suppose we wanted to use it in a case where that parameter was taken? Anyway defining hardcoded strings gives me a bad feeling.
How about: #set($captcha = $captchaservice.getCaptcha("defaultImageCaptcha")) #set($servletId = $captcha.getServletId($request)) $captcha.isAnswerCorrect($answer)
captcha.getServletId can take an HttpServletRequest object and the XWikiRequest (in velocity that is $request) will work because XWikiRequest extends and wraps the HttpServletRequest object.
Thanks,
Caleb
Thanks.
- Asiri _______________________________________________ devs mailing list [email protected] http://lists.xwiki.org/mailman/listinfo/devs
_______________________________________________ devs mailing list [email protected] http://lists.xwiki.org/mailman/listinfo/devs
Hi Caleb, On Fri, Jan 15, 2010 at 4:21 PM, Caleb James DeLisle < [email protected]> wrote:
Whoops I made a mistake in my example: How about: #set($captcha = $captchaservice.getCaptcha("defaultImageCaptcha")) #set($servletId = $captcha.getServletId($request)) $captcha.isAnswerCorrect($answer)
should be:
$captcha.isAnswerCorrect($servletId, $answer)
Why is it called servletId? shouldn't it be captchaId? Or may be I have misunderstood you.
$captchaservice.isAnswerCorrect($sessionId, $answer)
Yes. I believe here sessionId is generated by the captcha service for each captcha displayed? Or is it something else?
I'm asking because recaptcha generates a unque captchaId for each captcha it generates. Then you need to handover this captchaId + user's answer to the server to verify the answer.
May be my above question got commented out on your mail reader. Thanks. - Asiri
I'm assuming you are referring to $captcha.getServletId($request) I said getServletId because I couldn't think of a way a request could yield any other type of id, maybe you can think of one? I do want to use captchaId for: $captcha.isAnswerCorrect($servletId, $answer) the method would look like: boolean isAnswerCorrect(String captchaId, String answer) In most implementations the servletId would be used as the captchaId. On the one hand I feel like getServletId should be a function of the $captchaservice but as you can see here: http://www.docjar.com/html/api/com/octo/captcha/module/config/CaptchaModuleC... If a configuration is set to ID_GENERATED=true then it uses a different method (this is the function called by the struts jcaptcha action) Caleb Asiri Rathnayake wrote:
Hi Caleb,
On Fri, Jan 15, 2010 at 4:21 PM, Caleb James DeLisle < [email protected]> wrote:
Whoops I made a mistake in my example: How about: #set($captcha = $captchaservice.getCaptcha("defaultImageCaptcha")) #set($servletId = $captcha.getServletId($request)) $captcha.isAnswerCorrect($answer)
should be:
$captcha.isAnswerCorrect($servletId, $answer)
Why is it called servletId? shouldn't it be captchaId? Or may be I have misunderstood you.
$captchaservice.isAnswerCorrect($sessionId, $answer) Yes. I believe here sessionId is generated by the captcha service for each captcha displayed? Or is it something else?
I'm asking because recaptcha generates a unque captchaId for each captcha it generates. Then you need to handover this captchaId + user's answer to the server to verify the answer.
May be my above question got commented out on your mail reader.
Thanks.
- Asiri _______________________________________________ devs mailing list [email protected] http://lists.xwiki.org/mailman/listinfo/devs
Update: I have changed a few things in my copy of the code. I renamed the Captcha interface CaptchaVerifier because it is a component for verifying captchas of a certain type, not an individual captcha puzzle. I also named the id which is gotten from the request "userId" because it may be generated on a per-captcha basis and passed by a parameter depending on jcaptcha settings.
From velocity the commands look like: #set($cv = $captchaservice.getCaptchaVerifier("defaultImageCaptcha")) #set($userId = $cv.getUserId($request)) #if($cv.isAnswerCorrect($userId, $answer)) ## Do whatever the captcha is protecting #else ## Show captcha image and ask for answer, submit button redirects to ## same page with answer given as parameter. #end
isAnswerCorrect throws Exception but in Velocity getCaptchaVerifier returns a wrapper of the CaptchaVerifier which catches any Exception and returns false. It also switches null for "" in getUserId. I have tried to build JCaptcha 2.0 from source but I am unable to as I use jdk 1.6 which fails with warnings for including com.sun.* classes Any ideas about what these functions should be named would be great: getUserId getCaptchaVerifier Thanks, Caleb Caleb James DeLisle wrote:
I'm assuming you are referring to $captcha.getServletId($request) I said getServletId because I couldn't think of a way a request could yield any other type of id, maybe you can think of one?
I do want to use captchaId for: $captcha.isAnswerCorrect($servletId, $answer)
the method would look like: boolean isAnswerCorrect(String captchaId, String answer)
In most implementations the servletId would be used as the captchaId. On the one hand I feel like getServletId should be a function of the $captchaservice but as you can see here: http://www.docjar.com/html/api/com/octo/captcha/module/config/CaptchaModuleC... If a configuration is set to ID_GENERATED=true then it uses a different method (this is the function called by the struts jcaptcha action)
Caleb
Asiri Rathnayake wrote:
Hi Caleb,
On Fri, Jan 15, 2010 at 4:21 PM, Caleb James DeLisle < [email protected]> wrote:
Whoops I made a mistake in my example: How about: #set($captcha = $captchaservice.getCaptcha("defaultImageCaptcha")) #set($servletId = $captcha.getServletId($request)) $captcha.isAnswerCorrect($answer)
should be:
$captcha.isAnswerCorrect($servletId, $answer)
Why is it called servletId? shouldn't it be captchaId? Or may be I have misunderstood you.
$captchaservice.isAnswerCorrect($sessionId, $answer) Yes. I believe here sessionId is generated by the captcha service for each captcha displayed? Or is it something else?
I'm asking because recaptcha generates a unque captchaId for each captcha it generates. Then you need to handover this captchaId + user's answer to the server to verify the answer. May be my above question got commented out on your mail reader.
Thanks.
- Asiri _______________________________________________ devs mailing list [email protected] http://lists.xwiki.org/mailman/listinfo/devs
_______________________________________________ devs mailing list [email protected] http://lists.xwiki.org/mailman/listinfo/devs
Hi, On Sat, Jan 16, 2010 at 11:21 PM, Caleb James DeLisle < [email protected]> wrote:
Update: I have changed a few things in my copy of the code. I renamed the Captcha interface CaptchaVerifier because it is a component for verifying captchas of a certain type, not an individual captcha puzzle.
I also named the id which is gotten from the request "userId" because it may be generated on a per-captcha basis and passed by a parameter depending on jcaptcha settings.
From velocity the commands look like: #set($cv = $captchaservice.getCaptchaVerifier("defaultImageCaptcha")) #set($userId = $cv.getUserId($request)) #if($cv.isAnswerCorrect($userId, $answer)) ## Do whatever the captcha is protecting #else ## Show captcha image and ask for answer, submit button redirects to ## same page with answer given as parameter. #end
isAnswerCorrect throws Exception but in Velocity getCaptchaVerifier returns a wrapper of the CaptchaVerifier which catches any Exception and returns false. It also switches null for "" in getUserId.
Sounds great! Once you commit I'll try to implement a recaptcha version of the CaptchaVerifier. This will be a good test case :) PS: I hope I will have enough time ;)
I have tried to build JCaptcha 2.0 from source but I am unable to as I use jdk 1.6 which fails with warnings for including com.sun.* classes
Any ideas about what these functions should be named would be great: getUserId getCaptchaVerifier
I thought getCaptchaId() would make sense in case of recaptcha but getUserId() might be more generic as you describe. Thanks. - Asiri
Thanks,
Caleb
Caleb James DeLisle wrote:
I'm assuming you are referring to $captcha.getServletId($request) I said getServletId because I couldn't think of a way a request could yield any other type of id, maybe you can think of one?
I do want to use captchaId for: $captcha.isAnswerCorrect($servletId, $answer)
the method would look like: boolean isAnswerCorrect(String captchaId, String answer)
In most implementations the servletId would be used as the captchaId. On the one hand I feel like getServletId should be a function of the $captchaservice but as you can see here:
http://www.docjar.com/html/api/com/octo/captcha/module/config/CaptchaModuleC...
If a configuration is set to ID_GENERATED=true then it uses a different method (this is the function called by the struts jcaptcha action)
Caleb
Asiri Rathnayake wrote:
Hi Caleb,
On Fri, Jan 15, 2010 at 4:21 PM, Caleb James DeLisle < [email protected]> wrote:
Whoops I made a mistake in my example: How about: #set($captcha = $captchaservice.getCaptcha("defaultImageCaptcha")) #set($servletId = $captcha.getServletId($request)) $captcha.isAnswerCorrect($answer)
should be:
$captcha.isAnswerCorrect($servletId, $answer)
Why is it called servletId? shouldn't it be captchaId? Or may be I have misunderstood you.
$captchaservice.isAnswerCorrect($sessionId, $answer) Yes. I believe here sessionId is generated by the captcha service for each captcha displayed? Or is it something else?
I'm asking because recaptcha generates a unque captchaId for each captcha it generates. Then you need to handover this captchaId + user's answer to the server to verify the answer. May be my above question got commented out on your mail reader.
Thanks.
- Asiri _______________________________________________ devs mailing list [email protected] http://lists.xwiki.org/mailman/listinfo/devs
_______________________________________________ devs mailing list [email protected] http://lists.xwiki.org/mailman/listinfo/devs
_______________________________________________ devs mailing list [email protected] http://lists.xwiki.org/mailman/listinfo/devs
participants (2)
-
Asiri Rathnayake -
Caleb James DeLisle