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-2015 ForgeRock AS.
015 */
016
017package org.forgerock.openig.filter.oauth2.challenge;
018
019import static java.lang.String.format;
020
021import java.util.Collections;
022import java.util.Set;
023
024import org.forgerock.http.protocol.Response;
025import org.forgerock.http.protocol.Status;
026
027/**
028 * Builds an error {@link Response} when the request is missing some required scope.
029 * <p>
030 * Example:
031 * <pre>
032 *     {@code
033 *     HTTP/1.1 403 Forbidden
034 *     WWW-Authenticate: Bearer realm="example",
035 *                              error="insufficient_scope",
036 *                              error_description="....",
037 *                              scope="openid profile email"
038 *     }
039 * </pre>
040 */
041public class InsufficientScopeChallengeHandler extends AuthenticateChallengeHandler {
042
043    private static final String INSUFFICIENT_SCOPE_DESCRIPTION = "The request requires higher privileges than "
044            + "provided by the access token.";
045
046    private final Set<String> scopes;
047
048    /**
049     * Builds a new InsufficientScopeChallengeHandler with a default description, no error URI page and no scopes.
050     *
051     * @param realm
052     *         mandatory realm value.
053     */
054    public InsufficientScopeChallengeHandler(final String realm) {
055        this(realm, Collections.<String>emptySet());
056    }
057
058    /**
059     * Builds a new InsufficientScopeChallengeHandler with a default description and no error URI page.
060     *
061     * @param realm
062     *         mandatory realm value.
063     * @param scopes
064     *         List of required scopes (will be omitted if empty)
065     */
066    public InsufficientScopeChallengeHandler(final String realm, final Set<String> scopes) {
067        this(realm, scopes, null);
068    }
069
070    /**
071     * Builds a new InsufficientScopeChallengeHandler with a default description.
072     *
073     * @param realm
074     *         mandatory realm value.
075     * @param scopes
076     *         List of required scopes (will be omitted if empty)
077     * @param insufficientScopeUri
078     *         error uri page (will be omitted if {@literal null})
079     */
080    public InsufficientScopeChallengeHandler(final String realm,
081                                             final Set<String> scopes,
082                                             final String insufficientScopeUri) {
083        this(realm, INSUFFICIENT_SCOPE_DESCRIPTION, scopes, insufficientScopeUri);
084    }
085
086    /**
087     * Builds a new InsufficientScopeChallengeHandler.
088     *
089     * @param realm
090     *         mandatory realm value.
091     * @param description
092     *         error description (will be omitted if {@literal null})
093     * @param scopes
094     *         List of required scopes (will be omitted if empty)
095     * @param insufficientScopeUri
096     *         error uri page (will be omitted if {@literal null})
097     */
098    public InsufficientScopeChallengeHandler(final String realm,
099                                             final String description,
100                                             final Set<String> scopes,
101                                             final String insufficientScopeUri) {
102        super(realm, "insufficient_scope", description, insufficientScopeUri);
103        this.scopes = scopes;
104    }
105
106    @Override
107    protected Response createResponse() {
108        Response response = new Response();
109        response.setStatus(Status.FORBIDDEN);
110        return response;
111    }
112
113    @Override
114    protected void appendExtraAttributes(final StringBuilder sb) {
115        StringBuilder scopeAttr = buildSpaceSeparatedScopeList();
116        if (scopeAttr.length() != 0) {
117            sb.append(format(", scope=\"%s\"", scopeAttr.toString()));
118        }
119    }
120
121    private StringBuilder buildSpaceSeparatedScopeList() {
122        StringBuilder scopeAttr = new StringBuilder();
123        for (String scope : scopes) {
124            if (scopeAttr.length() != 0) {
125                scopeAttr.append(" ");
126            }
127            scopeAttr.append(scope);
128        }
129        return scopeAttr;
130    }
131
132
133}