001/*
002 * The contents of this file are subject to the terms of the Common Development and
003 * Distribution License (the License). You may not use this file except in compliance with the
004 * License.
005 *
006 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
007 * specific language governing permission and limitations under the License.
008 *
009 * When distributing Covered Software, include this CDDL Header Notice in each file and include
010 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
011 * Header, with the fields enclosed by brackets [] replaced by your own identifying
012 * information: "Portions copyright [year] [name of copyright owner]".
013 *
014 * Copyright 2014 ForgeRock AS.
015 */
016
017package org.forgerock.openig.filter.oauth2.challenge;
018
019import static java.lang.String.*;
020import static org.forgerock.util.Reject.*;
021
022import java.io.IOException;
023
024import org.forgerock.openig.handler.Handler;
025import org.forgerock.openig.handler.HandlerException;
026import org.forgerock.openig.http.Exchange;
027import org.forgerock.openig.http.Response;
028
029/**
030 * This handler build an authentication challenge to be returned in the {@link Response} {@literal Authorization} HTTP
031 * header.
032 * <p>
033 * It has to be sub-classed in order to create a {@link Response} with the appropriate status code and reason phrase.
034 */
035public abstract class AuthenticateChallengeHandler implements Handler {
036
037    /**
038     * Authorization HTTP Header name.
039     */
040    public static final String WWW_AUTHENTICATE = "WWW-Authenticate";
041
042    private final String realm;
043    private final String error;
044    private final String description;
045    private final String uri;
046
047    /**
048     * Creates a new AuthenticateChallengeHandler. The realm must not be {@literal null}.
049     *
050     * @param realm
051     *         mandatory realm value.
052     * @param error
053     *         error code (will be omitted if {@literal null})
054     * @param description
055     *         error description (will be omitted if {@literal null})
056     * @param uri
057     *         error uri page (will be omitted if {@literal null})
058     */
059    protected AuthenticateChallengeHandler(final String realm,
060                                           final String error,
061                                           final String description,
062                                           final String uri) {
063        this.realm = checkNotNull(realm, "OAuth2 Challenge needs a realm");
064        this.error = error;
065        this.description = description;
066        this.uri = uri;
067    }
068
069    @Override
070    public void handle(final Exchange exchange) throws HandlerException, IOException {
071        exchange.response = createResponse();
072        exchange.response.getHeaders().putSingle(WWW_AUTHENTICATE,
073                                            format("Bearer %s", buildChallenge()));
074    }
075
076    /**
077     * Creates a {@link Response} with the appropriate status code and reason. This method is called each time the
078     * {@link #handle(Exchange)} method is invoked.
079     *
080     * @return a new initialized {@link Response} instance
081     */
082    protected abstract Response createResponse();
083
084    private String buildChallenge() {
085        StringBuilder sb = new StringBuilder();
086
087        appendRealm(sb);
088        appendError(sb);
089        appendErrorDescription(sb);
090        appendErrorUri(sb);
091        appendExtraAttributes(sb);
092
093        return sb.toString();
094    }
095
096    private void appendRealm(final StringBuilder sb) {
097        sb.append(format("realm=\"%s\"", realm));
098    }
099
100    private void appendError(final StringBuilder sb) {
101        if (error != null) {
102            sb.append(format(", error=\"%s\"", error));
103        }
104    }
105
106    private void appendErrorDescription(final StringBuilder sb) {
107        if (description != null) {
108            sb.append(format(", error_description=\"%s\"", description));
109        }
110    }
111
112    /**
113     * Permits sub-classes to append extra attributes to the challenge.
114     * @param sb Challenge value
115     */
116    protected void appendExtraAttributes(final StringBuilder sb) {
117        // For sub-classes to add extra attributes
118    }
119
120    private void appendErrorUri(final StringBuilder sb) {
121        if (uri != null) {
122            sb.append(format(", error_uri=\"%s\"", uri));
123        }
124    }
125
126}