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