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.URL;
024
025import net.jcip.annotations.Immutable;
026import net.minidev.json.JSONObject;
027
028import com.nimbusds.jwt.JWT;
029import com.nimbusds.jwt.JWTParser;
030import com.nimbusds.jwt.PlainJWT;
031import com.nimbusds.oauth2.sdk.auth.PKITLSClientAuthentication;
032import com.nimbusds.oauth2.sdk.auth.SelfSignedTLSClientAuthentication;
033import com.nimbusds.oauth2.sdk.auth.TLSClientAuthentication;
034import com.nimbusds.oauth2.sdk.http.CommonContentTypes;
035import com.nimbusds.oauth2.sdk.http.HTTPRequest;
036import com.nimbusds.oauth2.sdk.id.ClientID;
037import com.nimbusds.oauth2.sdk.util.JSONObjectUtils;
038
039
040/**
041 * Request object POST request.
042 *
043 * <p>Example request object POST request:
044 *
045 * <pre>
046 * POST /requests HTTP/1.1
047 * Host: c2id.com
048 * Content-Type: application/jws
049 * Content-Length: 1288
050 *
051 * eyJhbGciOiJSUzI1NiIsImtpZCI6ImsyYmRjIn0.ew0KICJpc3MiOiA
052 * (... abbreviated for brevity ...)
053 * zCYIb_NMXvtTIVc1jpspnTSD7xMbpL-2QgwUsAlMGzw
054 * </pre>
055 *
056 * <p>Related specifications:
057 *
058 * <ul>
059 *     <li>Financial-grade API - Part 2: Read and Write API Security Profile,
060 *         section 7.
061 *     <li>The OAuth 2.0 Authorization Framework: JWT Secured Authorization
062 *         Request (JAR) (draft-ietf-oauth-jwsreq-17).
063 * </ul>
064 */
065@Immutable
066public final class RequestObjectPOSTRequest extends AbstractOptionallyAuthenticatedRequest {
067        
068        
069        /**
070         * The request object as JWT, {@code null} for a
071         * {@link #requestJSONObject plain JSON object}.
072         */
073        private final JWT requestObject;
074        
075        
076        /**
077         * The request parameters as plain JSON object, {@code null} for
078         * {@link #requestObject JWT}.
079         */
080        private final JSONObject requestJSONObject;
081        
082        
083        /**
084         * Creates a new request object POST request.
085         *
086         * @param uri           The URI of the request object endpoint. May be
087         *                      {@code null} if the {@link #toHTTPRequest}
088         *                      method will not be used.
089         * @param requestObject The request object. Must not be {@code null}.
090         */
091        public RequestObjectPOSTRequest(final URI uri,
092                                        final JWT requestObject) {
093                
094                super(uri, null);
095                
096                if (requestObject == null) {
097                        throw new IllegalArgumentException("The request object must not be null");
098                }
099                
100                if (requestObject instanceof PlainJWT) {
101                        throw new IllegalArgumentException("The request object must not be an unsecured JWT (alg=none)");
102                }
103                
104                this.requestObject = requestObject;
105                
106                requestJSONObject = null;
107        }
108        
109        
110        /**
111         * Creates a new request object POST request where the parameters are
112         * submitted as plain JSON object, and the client authenticates by
113         * means of mutual TLS. TLS also ensures the integrity and
114         * confidentiality of the request parameters. This method is not
115         * standard.
116         *
117         * @param uri               The URI of the request object endpoint. May
118         *                          be {@code null} if the
119         *                          {@link #toHTTPRequest} method will not be
120         *                          used.
121         * @param tlsClientAuth     The mutual TLS client authentication. Must
122         *                          not be {@code null}.
123         * @param requestJSONObject The request parameters as plain JSON
124         *                          object. Must not be {@code null}.
125         */
126        public RequestObjectPOSTRequest(final URI uri,
127                                        final TLSClientAuthentication tlsClientAuth,
128                                        final JSONObject requestJSONObject) {
129                
130                super(uri, tlsClientAuth);
131                
132                if (tlsClientAuth == null) {
133                        throw new IllegalArgumentException("The mutual TLS client authentication must not be null");
134                }
135                
136                if (requestJSONObject == null) {
137                        throw new IllegalArgumentException("The request JSON object must not be null");
138                }
139                
140                this.requestJSONObject = requestJSONObject;
141                
142                requestObject = null;
143        }
144        
145        
146        /**
147         * Returns the request object as JWT.
148         *
149         * @return The request object as JWT, {@code null} if the request
150         *         parameters are specified as {@link #getRequestJSONObject()
151         *         plain JSON object} instead.
152         */
153        public JWT getRequestObject() {
154                
155                return requestObject;
156        }
157        
158        
159        /**
160         * Returns the request object as plain JSON object.
161         *
162         * @return The request parameters as plain JSON object, {@code null}
163         *         if the request object is specified as a
164         *         {@link #getRequestObject() JWT}.
165         */
166        public JSONObject getRequestJSONObject() {
167                
168                return requestJSONObject;
169        }
170        
171        
172        /**
173         * Returns the mutual TLS client authentication.
174         *
175         * @return The mutual TLS client authentication.
176         */
177        public TLSClientAuthentication getTLSClientAuthentication() {
178                
179                return (TLSClientAuthentication) getClientAuthentication();
180        }
181        
182        
183        @Override
184        public HTTPRequest toHTTPRequest() {
185                
186                if (getEndpointURI() == null)
187                        throw new SerializeException("The endpoint URI is not specified");
188                
189                URL url;
190                try {
191                        url = getEndpointURI().toURL();
192                } catch (MalformedURLException e) {
193                        throw new SerializeException(e.getMessage(), e);
194                }
195                
196                HTTPRequest httpRequest = new HTTPRequest(HTTPRequest.Method.POST, url);
197                
198                if (getRequestObject() != null) {
199                        httpRequest.setContentType(CommonContentTypes.APPLICATION_JWT);
200                        httpRequest.setQuery(getRequestObject().serialize());
201                } else if (getRequestJSONObject() != null) {
202                        httpRequest.setContentType(CommonContentTypes.APPLICATION_JSON);
203                        httpRequest.setQuery(getRequestJSONObject().toJSONString());
204                        getTLSClientAuthentication().applyTo(httpRequest);
205                }
206                
207                return httpRequest;
208        }
209        
210        
211        /**
212         * Parses a request object POST request from the specified HTTP
213         * request.
214         *
215         * @param httpRequest The HTTP request. Must not be {@code null}.
216         *
217         * @return The request object POST request.
218         *
219         * @throws ParseException If the HTTP request couldn't be parsed to a
220         *                        request object POST request.
221         */
222        public static RequestObjectPOSTRequest parse(final HTTPRequest httpRequest)
223                throws ParseException {
224                
225                // Only HTTP POST accepted
226                httpRequest.ensureMethod(HTTPRequest.Method.POST);
227                
228                if (httpRequest.getContentType() == null) {
229                        throw new ParseException("Missing Content-Type");
230                }
231                
232                if (
233                        CommonContentTypes.APPLICATION_JOSE.match(httpRequest.getContentType()) ||
234                        CommonContentTypes.APPLICATION_JWT.match(httpRequest.getContentType())) {
235                        
236                        // Signed or signed and encrypted request object
237                        
238                        JWT requestObject;
239                        try {
240                                requestObject = JWTParser.parse(httpRequest.getQuery());
241                        } catch (java.text.ParseException e) {
242                                throw new ParseException("Invalid request object JWT: " + e.getMessage());
243                        }
244                        
245                        if (requestObject instanceof PlainJWT) {
246                                throw new ParseException("The request object is an unsecured JWT (alg=none)");
247                        }
248                        
249                        return new RequestObjectPOSTRequest(httpRequest.getURI(), requestObject);
250                        
251                } else if (CommonContentTypes.APPLICATION_JSON.match(httpRequest.getContentType())) {
252                        
253                        JSONObject jsonObject = httpRequest.getQueryAsJSONObject();
254                        
255                        if (jsonObject.get("client_id") == null) {
256                                throw new ParseException("Missing client_id in JSON object");
257                        }
258                        
259                        ClientID clientID = new ClientID(JSONObjectUtils.getString(jsonObject, "client_id"));
260                        
261                        TLSClientAuthentication tlsClientAuth;
262                        if (httpRequest.getClientX509CertificateSubjectDN() != null) {
263                                tlsClientAuth = new PKITLSClientAuthentication(clientID, httpRequest.getClientX509CertificateSubjectDN());
264                        } else if (httpRequest.getClientX509Certificate() != null) {
265                                tlsClientAuth = new SelfSignedTLSClientAuthentication(clientID, httpRequest.getClientX509Certificate());
266                        } else {
267                                throw new ParseException("Missing mutual TLS client authentication");
268                        }
269                        
270                        return new RequestObjectPOSTRequest(httpRequest.getURI(), tlsClientAuth, jsonObject);
271                        
272                } else {
273                        
274                        throw new ParseException("Unexpected Content-Type: " + httpRequest.getContentType());
275                }
276        }
277}