Here's a better candidate for the default implementation of
CookieAuthenticator.challenge:

    @Override public void challenge(Response response, boolean stale) {
        Reference ref = response.getRequest().getResourceRef();
        String redirectQueryName = getRedirectQueryName();
        String redirectQueryValue =
ref.getQueryAsForm().getFirstValue(redirectQueryName, "");

        if ("".equals(redirectQueryValue)) {
            redirectQueryValue = new Reference(getLoginFormPath())
                .addQueryParameter(redirectQueryName, ref.toString())
                .toString();
        }

        response.redirectSeeOther(redirectQueryValue);
    }

If the redirect URI already contains a redirect query value we use it
directly, because it means we're creating a challenge after a failed login
attempt, so by using the redirect query value, we're restarting the whole
process. Otherwise, this is a first login attempt, so we create a new
reference to the login path with a redirect query.

For example, suppose the user tries to access a resource at /foo that
requires authentication/authorization. Since /foo does not contain a
targetUri query parameter, the if-test succeeds and this will result in a
challenge redirection to /login?targetUri=/foo. If that login attempt
fails, the authentication machinery now tries to authenticate
/login?targetUri=/foo. This time, redirectQueryValue will be /foo, the if
clause will be skipped, and the redirection will be to /foo, back to our
starting point.

This is slightly brittle because of the possibility that other resources
might use the redirect query name in queries, too, which would confuse this
logic. Consider using a name other than the default "targetUri" if this
worries you.

--tim

On Sun, Dec 9, 2012 at 4:44 PM, Tim Peierls <[email protected]> wrote:

> No one answered the question below, and I'd still like to get an answer,
> but in the meantime I got CookieAuthenticator to work by overriding
> challenge(Response, boolean) as follows.
>
>     @Override public void challenge(Response response, boolean stale) {
>         response.redirectSeeOther(
>             new Reference(getLoginFormPath())
>                 .addQueryParameter(
>                     getRedirectQueryName(),
>                     response.getRequest().getResourceRef().toString()
>                 )
>                 .toString() // Interpret URI relative to this host.
>         );
>     }
>
> This would seem to be a good candidate for a default implementation of
> CookieAuthenticator.challenge(Response, boolean).
>
> Here's the rest of the story: I have an AuthApplication to which /auth
> requests are routed in my main component.
>
>     // MainComponent initialization:
>     host.attach("/auth", authApplication);
>
> That application routes /loginForm to a login form resource and routes
> everything else to an instance of my CookieAuthenticator extension around a
> Restlet that does nothing. (If you leave out this vacuous Restlet, the
> machinery complains about a Filter with no target.)
>
>     // In AuthApplication.createInboundRoot:
>     Authenticator authenticator = ... my CookieAuthenticator extension ...;
>     authenticator.setNext(new Restlet() {
>         @Override public void handle(Request request, Response response) {
>             getLogger().debug("Null restlet");
>         }
>     });
>
>     router.attach("/loginForm", LoginFormServerResource.class);
>     router.attachDefault(authenticator);
>
> LoginFormServerResource is a straighforward use of Freemarker's
> TemplateRepresentation:
>
>     @Get public Representation showLoginForm() {
>         ImmutableMap<String, String> dataModel = ImmutableMap.of(
>             "targetUri", getQuery().getFirstValue("targetUri")
>         );
>         return new TemplateRepresentation(
>             LOGIN_FORM_TEMPLATE, freemarkerConfiguration,
>             dataModel, MediaType.TEXT_HTML
>         );
>     }
>
> The template has a form element with an action to post to the login
> resource, using the default values of CookieAuthenticator's
> identifierFormName and secretFormName properties ("login", "password"). A
> stripped-down example:
>
>     <form action="/auth/login?targetUri=${targetUri}" method="POST">
>         <input type="text" id="login" name="login" size="15" />
>         <input type="password" id="password" name="password" size="15" />
>         <input type="submit" value="Login" />
>     </form>
>
> Because I've made authentication/authorization a separate Application, I
> had to set the cookie path to "/" in my CookieAuthenticator extension in
> order to have the credentials in the cookie apply to the entire URI-space
> of my Restlet Component.
>
>     @Override protected CookieSetting getCredentialsCookie(Request
> request, Response response) {
>         CookieSetting credentialsCookie =
> super.getCredentialsCookie(request, response);
>         credentialsCookie.setPath("/");
>         return credentialsCookie;
>     }
>
> --tim
>
>
> On Mon, Dec 3, 2012 at 3:38 PM, Tim Peierls <[email protected]> wrote:
>
>> The class javadoc for CookieAuthenticator says:
>>
>> When the credentials are missing or stale, the challenge(Response,
>>> boolean)<http://www.restlet.org/documentation/2.1/jee/ext/org/restlet/ext/crypto/CookieAuthenticator.html#challenge(org.restlet.Response,%20boolean)>
>>>  method
>>> is invoked by the parent class, and its default behavior is to redirect the
>>> user's browser to the 
>>> getLoginFormPath()<http://www.restlet.org/documentation/2.1/jee/ext/org/restlet/ext/crypto/CookieAuthenticator.html#getLoginFormPath()>
>>>  URI,
>>> adding the URI of the target resource as a query parameter of name
>>> getRedirectQueryName()<http://www.restlet.org/documentation/2.1/jee/ext/org/restlet/ext/crypto/CookieAuthenticator.html#getRedirectQueryName()>
>>> .
>>
>>
>> But the javadoc for CookieAuthenticator.challenge(Response, boolean) says
>> it must be overridden to return a login form representation, and in fact
>> the implementation of challenge(Response, boolean) is to call
>> super.challenge(response, stale), in both stable and unstable versions. The
>> supertype version (ChallengeAuthenticator.challenge) sets the status to
>> unauthorized and creates a challenge request from the challenge scheme and
>> puts it in the response. This fails because the HTTP_Cookie scheme is not
>> meant to be used in this way.
>>
>> My question is whether the class comment is the intent, and the current
>> implementation of CookieAuthenticator.challenge is incomplete, or if the
>> class comment is out of date. If the latter is the case, then can someone
>> give an example of how to override challenge?
>>
>> --tim
>>
>
>

------------------------------------------------------
http://restlet.tigris.org/ds/viewMessage.do?dsForumId=4447&dsMessageId=3034586

Reply via email to