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.getClientX509Certificate() != null && httpRequest.getClientX509CertificateSubjectDN() != null && 263 httpRequest.getClientX509CertificateSubjectDN().equals(httpRequest.getClientX509CertificateRootDN())) { 264 tlsClientAuth = new SelfSignedTLSClientAuthentication(clientID, httpRequest.getClientX509Certificate()); 265 } else if (httpRequest.getClientX509Certificate() != null) { 266 tlsClientAuth = new PKITLSClientAuthentication(clientID, httpRequest.getClientX509Certificate()); 267 } else { 268 throw new ParseException("Missing mutual TLS client authentication"); 269 } 270 271 return new RequestObjectPOSTRequest(httpRequest.getURI(), tlsClientAuth, jsonObject); 272 273 } else { 274 275 throw new ParseException("Unexpected Content-Type: " + httpRequest.getContentType()); 276 } 277 } 278}