001/*
002 * oauth2-oidc-sdk
003 *
004 * Copyright 2012-2016, Connect2id Ltd and contributors.
005 *
006 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use
007 * this file except in compliance with the License. You may obtain a copy of the
008 * License at
009 *
010 *    http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software distributed
013 * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
014 * CONDITIONS OF ANY KIND, either express or implied. See the License for the
015 * specific language governing permissions and limitations under the License.
016 */
017
018package com.nimbusds.oauth2.sdk;
019
020
021import java.net.URI;
022import java.util.Collections;
023import java.util.List;
024import java.util.Map;
025
026import net.jcip.annotations.Immutable;
027
028import com.nimbusds.common.contenttype.ContentType;
029import com.nimbusds.oauth2.sdk.auth.ClientAuthentication;
030import com.nimbusds.oauth2.sdk.auth.ClientSecretBasic;
031import com.nimbusds.oauth2.sdk.http.HTTPRequest;
032import com.nimbusds.oauth2.sdk.util.MultivaluedMapUtils;
033import com.nimbusds.oauth2.sdk.util.StringUtils;
034import com.nimbusds.oauth2.sdk.util.URLUtils;
035import com.nimbusds.openid.connect.sdk.AuthenticationRequest;
036import com.nimbusds.openid.connect.sdk.op.AuthenticationRequestDetector;
037
038
039/**
040 * Pushed authorisation request.
041 *
042 * <p>Example HTTP request:
043 *
044 * <pre>
045 * POST /as/par HTTP/1.1
046 * Host: as.example.com
047 * Content-Type: application/x-www-form-urlencoded
048 * Authorization: Basic czZCaGRSa3F0Mzo3RmpmcDBaQnIxS3REUmJuZlZkbUl3
049 *
050 * response_type=code
051 * &client_id=s6BhdRkqt3&state=af0ifjsldkj
052 * &redirect_uri=https%3A%2F%2Fclient.example.org%2Fcb
053 * </pre>
054 *
055 * <p>Related specifications:
056 *
057 * <ul>
058 *     <li>OAuth 2.0 Pushed Authorization Requests (draft-ietf-oauth-par-02)
059 * </ul>
060 */
061@Immutable
062public class PushedAuthorizationRequest extends AbstractOptionallyAuthenticatedRequest {
063        
064        
065        /**
066         * The pushed authorisation request.
067         */
068        private final AuthorizationRequest authzRequest;
069        
070        
071        /**
072         * Creates a new authenticated pushed authorisation request for a
073         * confidential client.
074         *
075         * @param uri          The URI of the token endpoint. May be
076         *                     {@code null} if the {@link #toHTTPRequest}
077         *                     method will not be used.
078         * @param clientAuth   The client authentication. Must not be
079         *                     {@code null}.
080         * @param authzRequest The authorisation request. Must not be
081         *                     {@code null}.
082         */
083        public PushedAuthorizationRequest(final URI uri,
084                                          final ClientAuthentication clientAuth,
085                                          final AuthorizationRequest authzRequest) {
086                super(uri, clientAuth);
087                
088                if (clientAuth == null)
089                        throw new IllegalArgumentException("The client authentication must not be null");
090                
091                if (authzRequest == null) {
092                        throw new IllegalArgumentException("The authorization request must not be null");
093                }
094                if (authzRequest.getRequestURI() != null) {
095                        throw new IllegalArgumentException("Authorization request_uri parameter not allowed");
096                }
097                this.authzRequest = authzRequest;
098        }
099        
100        
101        /**
102         * Creates a new pushed authorisation request for a public client.
103         *
104         * @param uri          The URI of the token endpoint. May be
105         *                     {@code null} if the {@link #toHTTPRequest}
106         *                     method will not be used.
107         * @param authzRequest The authorisation request. Must not be
108         *                     {@code null}.
109         */
110        public PushedAuthorizationRequest(final URI uri,
111                                          final AuthorizationRequest authzRequest) {
112                
113                super(uri, null);
114                if (authzRequest == null) {
115                        throw new IllegalArgumentException("The authorization request must not be null");
116                }
117                if (authzRequest.getRequestURI() != null) {
118                        throw new IllegalArgumentException("Authorization request_uri parameter not allowed");
119                }
120                this.authzRequest = authzRequest;
121        }
122        
123        
124        /**
125         * Returns the pushed authorisation request.
126         *
127         * @return The pushed authorisation request.
128         */
129        public AuthorizationRequest getAuthorizationRequest() {
130                return authzRequest;
131        }
132        
133        
134        @Override
135        public HTTPRequest toHTTPRequest() {
136                
137                if (getEndpointURI() == null)
138                        throw new SerializeException("The endpoint URI is not specified");
139                
140                HTTPRequest httpRequest = new HTTPRequest(HTTPRequest.Method.POST, getEndpointURI());
141                httpRequest.setEntityContentType(ContentType.APPLICATION_URLENCODED);
142                
143                if (getClientAuthentication() != null) {
144                        getClientAuthentication().applyTo(httpRequest);
145                }
146                
147                Map<String, List<String>> params = httpRequest.getQueryParameters();
148                params.putAll(authzRequest.toParameters());
149                httpRequest.setQuery(URLUtils.serializeParameters(params));
150                
151                return httpRequest;
152        }
153        
154        
155        /**
156         * Parses a pushed authorisation request from the specified HTTP
157         * request.
158         *
159         * @param httpRequest The HTTP request. Must not be {@code null}.
160         *
161         * @return The pushed authorisation request.
162         *
163         * @throws ParseException If the HTTP request couldn't be parsed to a
164         *                        pushed authorisation request.
165         */
166        public static PushedAuthorizationRequest parse(final HTTPRequest httpRequest)
167                throws ParseException {
168                
169                // Only HTTP POST accepted
170                URI uri = httpRequest.getURI();
171                httpRequest.ensureMethod(HTTPRequest.Method.POST);
172                httpRequest.ensureEntityContentType(ContentType.APPLICATION_URLENCODED);
173                
174                // Parse client authentication, if any
175                ClientAuthentication clientAuth;
176                try {
177                        clientAuth = ClientAuthentication.parse(httpRequest);
178                } catch (ParseException e) {
179                        throw new ParseException(e.getMessage(), OAuth2Error.INVALID_REQUEST.appendDescription(": " + e.getMessage()));
180                }
181                
182                // No fragment! May use query component!
183                Map<String,List<String>> params = httpRequest.getQueryParameters();
184                
185                // Multiple conflicting client auth methods (issue #203)?
186                if (clientAuth instanceof ClientSecretBasic) {
187                        if (StringUtils.isNotBlank(MultivaluedMapUtils.getFirstValue(params, "client_assertion")) || StringUtils.isNotBlank(MultivaluedMapUtils.getFirstValue(params, "client_assertion_type"))) {
188                                String msg = "Multiple conflicting client authentication methods found: Basic and JWT assertion";
189                                throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg));
190                        }
191                }
192                
193                // client_id not required in authZ params if auth is present
194                if (! params.containsKey("client_id") && clientAuth != null) {
195                        params.put("client_id", Collections.singletonList(clientAuth.getClientID().getValue()));
196                }
197                
198                // Parse the authZ request, allow for OpenID
199                AuthorizationRequest authzRequest;
200                if (AuthenticationRequestDetector.isLikelyOpenID(params)) {
201                        authzRequest = AuthenticationRequest.parse(params);
202                } else {
203                        authzRequest = AuthorizationRequest.parse(params);
204                }
205                
206                if (authzRequest.getRequestURI() != null) {
207                        String msg = "Authorization request_uri parameter not allowed";
208                        throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg));
209                }
210                
211                if (clientAuth != null) {
212                        return new PushedAuthorizationRequest(uri, clientAuth, authzRequest);
213                } else {
214                        return new PushedAuthorizationRequest(uri, authzRequest);
215                }
216        }
217}