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.oauth2.sdk.auth.ClientAuthentication;
032import com.nimbusds.oauth2.sdk.auth.ClientSecretBasic;
033import com.nimbusds.oauth2.sdk.http.CommonContentTypes;
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
062 *         (draft-lodderstedt-oauth-par-00)
063 * </ul>
064 */
065@Immutable
066public class PushedAuthorizationRequest extends AbstractOptionallyAuthenticatedRequest {
067        
068        
069        /**
070         * The pushed authorisation request.
071         */
072        private final AuthorizationRequest authzRequest;
073        
074        
075        /**
076         * Creates a new authenticated pushed authorisation request for a
077         * confidential client.
078         *
079         * @param uri          The URI of the token endpoint. May be
080         *                     {@code null} if the {@link #toHTTPRequest}
081         *                     method will not be used.
082         * @param clientAuth   The client authentication. Must not be
083         *                     {@code null}.
084         * @param authzRequest The authorisation request. Must not be
085         *                     {@code null}.
086         */
087        public PushedAuthorizationRequest(final URI uri,
088                                          final ClientAuthentication clientAuth,
089                                          final AuthorizationRequest authzRequest) {
090                super(uri, clientAuth);
091                
092                if (clientAuth == null)
093                        throw new IllegalArgumentException("The client authentication must not be null");
094                
095                if (authzRequest == null) {
096                        throw new IllegalArgumentException("The authorization request must not be null");
097                }
098                if (authzRequest.getRequestURI() != null) {
099                        throw new IllegalArgumentException("Authorization request_uri parameter not allowed");
100                }
101                this.authzRequest = authzRequest;
102        }
103        
104        
105        /**
106         * Creates a new pushed authorisation request for a public client.
107         *
108         * @param uri          The URI of the token endpoint. May be
109         *                     {@code null} if the {@link #toHTTPRequest}
110         *                     method will not be used.
111         * @param authzRequest The authorisation request. Must not be
112         *                     {@code null}.
113         */
114        public PushedAuthorizationRequest(final URI uri,
115                                          final AuthorizationRequest authzRequest) {
116                
117                super(uri, null);
118                if (authzRequest == null) {
119                        throw new IllegalArgumentException("The authorization request must not be null");
120                }
121                if (authzRequest.getRequestURI() != null) {
122                        throw new IllegalArgumentException("Authorization request_uri parameter not allowed");
123                }
124                this.authzRequest = authzRequest;
125        }
126        
127        
128        /**
129         * Returns the pushed authorisation request.
130         *
131         * @return The pushed authorisation request.
132         */
133        public AuthorizationRequest getAuthorizationRequest() {
134                return authzRequest;
135        }
136        
137        
138        @Override
139        public HTTPRequest toHTTPRequest() {
140                
141                if (getEndpointURI() == null)
142                        throw new SerializeException("The endpoint URI is not specified");
143                
144                URL url;
145                try {
146                        url = getEndpointURI().toURL();
147                } catch (MalformedURLException e) {
148                        throw new SerializeException(e.getMessage(), e);
149                }
150                
151                HTTPRequest httpRequest = new HTTPRequest(HTTPRequest.Method.POST, url);
152                httpRequest.setContentType(CommonContentTypes.APPLICATION_URLENCODED);
153                
154                if (getClientAuthentication() != null) {
155                        getClientAuthentication().applyTo(httpRequest);
156                }
157                
158                Map<String, List<String>> params = httpRequest.getQueryParameters();
159                params.putAll(authzRequest.toParameters());
160                httpRequest.setQuery(URLUtils.serializeParameters(params));
161                
162                return httpRequest;
163        }
164        
165        
166        /**
167         * Parses a pushed authorisation request from the specified HTTP
168         * request.
169         *
170         * @param httpRequest The HTTP request. Must not be {@code null}.
171         *
172         * @return The pushed authorisation request.
173         *
174         * @throws ParseException If the HTTP request couldn't be parsed to a
175         *                        pushed authorisation request.
176         */
177        public static PushedAuthorizationRequest parse(final HTTPRequest httpRequest)
178                throws ParseException {
179                
180                // Only HTTP POST accepted
181                URI uri;
182                try {
183                        uri = httpRequest.getURL().toURI();
184                } catch (URISyntaxException e) {
185                        throw new ParseException(e.getMessage(), e);
186                }
187                
188                httpRequest.ensureMethod(HTTPRequest.Method.POST);
189                httpRequest.ensureContentType(CommonContentTypes.APPLICATION_URLENCODED);
190                
191                // Parse client authentication, if any
192                ClientAuthentication clientAuth;
193                try {
194                        clientAuth = ClientAuthentication.parse(httpRequest);
195                } catch (ParseException e) {
196                        throw new ParseException(e.getMessage(), OAuth2Error.INVALID_REQUEST.appendDescription(": " + e.getMessage()));
197                }
198                
199                // No fragment! May use query component!
200                Map<String,List<String>> params = httpRequest.getQueryParameters();
201                
202                // Multiple conflicting client auth methods (issue #203)?
203                if (clientAuth instanceof ClientSecretBasic) {
204                        if (StringUtils.isNotBlank(MultivaluedMapUtils.getFirstValue(params, "client_assertion")) || StringUtils.isNotBlank(MultivaluedMapUtils.getFirstValue(params, "client_assertion_type"))) {
205                                String msg = "Multiple conflicting client authentication methods found: Basic and JWT assertion";
206                                throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg));
207                        }
208                }
209                
210                // client_id not required in authZ params if auth is present
211                if (! params.containsKey("client_id") && clientAuth != null) {
212                        params.put("client_id", Collections.singletonList(clientAuth.getClientID().getValue()));
213                }
214                
215                // Parse the authZ request, allow for OpenID
216                AuthorizationRequest authzRequest;
217                if (AuthenticationRequestDetector.isLikelyOpenID(params)) {
218                        authzRequest = AuthenticationRequest.parse(params);
219                } else {
220                        authzRequest = AuthorizationRequest.parse(params);
221                }
222                
223                if (authzRequest.getRequestURI() != null) {
224                        String msg = "Authorization request_uri parameter not allowed";
225                        throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg));
226                }
227                
228                if (clientAuth != null) {
229                        return new PushedAuthorizationRequest(uri, clientAuth, authzRequest);
230                } else {
231                        return new PushedAuthorizationRequest(uri, authzRequest);
232                }
233        }
234}