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