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.client; 019 020 021import java.net.MalformedURLException; 022import java.net.URI; 023import java.net.URISyntaxException; 024import java.net.URL; 025 026import net.jcip.annotations.Immutable; 027 028import org.apache.commons.lang3.StringUtils; 029 030import net.minidev.json.JSONObject; 031 032import com.nimbusds.jose.JWSObject; 033import com.nimbusds.jwt.JWTClaimsSet; 034import com.nimbusds.jwt.SignedJWT; 035 036import com.nimbusds.oauth2.sdk.ParseException; 037import com.nimbusds.oauth2.sdk.ProtectedResourceRequest; 038import com.nimbusds.oauth2.sdk.SerializeException; 039import com.nimbusds.oauth2.sdk.http.CommonContentTypes; 040import com.nimbusds.oauth2.sdk.http.HTTPRequest; 041import com.nimbusds.oauth2.sdk.token.BearerAccessToken; 042import com.nimbusds.oauth2.sdk.util.JSONObjectUtils; 043 044 045/** 046 * Client registration request. 047 * 048 * <p>Example HTTP request: 049 * 050 * <pre> 051 * POST /register HTTP/1.1 052 * Content-Type: application/json 053 * Accept: application/json 054 * Authorization: Bearer ey23f2.adfj230.af32-developer321 055 * Host: server.example.com 056 * 057 * { 058 * "redirect_uris" : [ "https://client.example.org/callback", 059 * "https://client.example.org/callback2" ], 060 * "client_name" : "My Example Client", 061 * "client_name#ja-Jpan-JP" : "\u30AF\u30E9\u30A4\u30A2\u30F3\u30C8\u540D", 062 * "token_endpoint_auth_method" : "client_secret_basic", 063 * "scope" : "read write dolphin", 064 * "logo_uri" : "https://client.example.org/logo.png", 065 * "jwks_uri" : "https://client.example.org/my_public_keys.jwks" 066 * } 067 * </pre> 068 * 069 * <p>Example HTTP request with a software statement: 070 * 071 * <pre> 072 * POST /register HTTP/1.1 073 * Content-Type: application/json 074 * Accept: application/json 075 * Host: server.example.com 076 * 077 * { 078 * "redirect_uris" : [ "https://client.example.org/callback", 079 * "https://client.example.org/callback2" ], 080 * "software_statement" : "eyJhbGciOiJFUzI1NiJ9.eyJpc3Mi[...omitted for brevity...]", 081 * "scope" : "read write", 082 * "example_extension_parameter" : "example_value" 083 * } 084 * 085 * </pre> 086 * 087 * <p>Related specifications: 088 * 089 * <ul> 090 * <li>OAuth 2.0 Dynamic Client Registration Protocol (RFC 7591), sections 091 * 2 and 3.1. 092 * </ul> 093 */ 094@Immutable 095public class ClientRegistrationRequest extends ProtectedResourceRequest { 096 097 098 /** 099 * The client metadata. 100 */ 101 private final ClientMetadata metadata; 102 103 104 /** 105 * The optional software statement. 106 */ 107 private final SignedJWT softwareStatement; 108 109 110 /** 111 * Creates a new client registration request. 112 * 113 * @param uri The URI of the client registration endpoint. May 114 * be {@code null} if the {@link #toHTTPRequest()} 115 * method will not be used. 116 * @param metadata The client metadata. Must not be {@code null} and 117 * must specify one or more redirection URIs. 118 * @param accessToken An OAuth 2.0 Bearer access token for the request, 119 * {@code null} if none. 120 */ 121 public ClientRegistrationRequest(final URI uri, 122 final ClientMetadata metadata, 123 final BearerAccessToken accessToken) { 124 125 this(uri, metadata, null, accessToken); 126 } 127 128 129 /** 130 * Creates a new client registration request with an optional software 131 * statement. 132 * 133 * @param uri The URI of the client registration 134 * endpoint. May be {@code null} if the 135 * {@link #toHTTPRequest()} method will not be 136 * used. 137 * @param metadata The client metadata. Must not be 138 * {@code null} and must specify one or more 139 * redirection URIs. 140 * @param softwareStatement Optional software statement, as a signed 141 * JWT with an {@code iss} claim; {@code null} 142 * if not specified. 143 * @param accessToken An OAuth 2.0 Bearer access token for the 144 * request, {@code null} if none. 145 */ 146 public ClientRegistrationRequest(final URI uri, 147 final ClientMetadata metadata, 148 final SignedJWT softwareStatement, 149 final BearerAccessToken accessToken) { 150 151 super(uri, accessToken); 152 153 if (metadata == null) 154 throw new IllegalArgumentException("The client metadata must not be null"); 155 156 this.metadata = metadata; 157 158 159 if (softwareStatement != null) { 160 161 if (softwareStatement.getState() == JWSObject.State.UNSIGNED) { 162 throw new IllegalArgumentException("The software statement JWT must be signed"); 163 } 164 165 JWTClaimsSet claimsSet; 166 167 try { 168 claimsSet = softwareStatement.getJWTClaimsSet(); 169 170 } catch (java.text.ParseException e) { 171 172 throw new IllegalArgumentException("The software statement is not a valid signed JWT: " + e.getMessage()); 173 } 174 175 if (claimsSet.getIssuer() == null) { 176 177 // http://tools.ietf.org/html/rfc7591#section-2.3 178 throw new IllegalArgumentException("The software statement JWT must contain an 'iss' claim"); 179 } 180 181 } 182 183 this.softwareStatement = softwareStatement; 184 } 185 186 187 /** 188 * Gets the associated client metadata. 189 * 190 * @return The client metadata. 191 */ 192 public ClientMetadata getClientMetadata() { 193 194 return metadata; 195 } 196 197 198 /** 199 * Gets the software statement. 200 * 201 * @return The software statement, as a signed JWT with an {@code iss} 202 * claim; {@code null} if not specified. 203 */ 204 public SignedJWT getSoftwareStatement() { 205 206 return softwareStatement; 207 } 208 209 210 @Override 211 public HTTPRequest toHTTPRequest() { 212 213 if (getEndpointURI() == null) 214 throw new SerializeException("The endpoint URI is not specified"); 215 216 URL endpointURL; 217 218 try { 219 endpointURL = getEndpointURI().toURL(); 220 221 } catch (MalformedURLException e) { 222 223 throw new SerializeException(e.getMessage(), e); 224 } 225 226 HTTPRequest httpRequest = new HTTPRequest(HTTPRequest.Method.POST, endpointURL); 227 228 if (getAccessToken() != null) { 229 httpRequest.setAuthorization(getAccessToken().toAuthorizationHeader()); 230 } 231 232 httpRequest.setContentType(CommonContentTypes.APPLICATION_JSON); 233 234 JSONObject content = metadata.toJSONObject(); 235 236 if (softwareStatement != null) { 237 238 // Signed state check done in constructor 239 content.put("software_statement", softwareStatement.serialize()); 240 } 241 242 httpRequest.setQuery(content.toString()); 243 244 return httpRequest; 245 } 246 247 248 /** 249 * Parses a client registration request from the specified HTTP POST 250 * request. 251 * 252 * @param httpRequest The HTTP request. Must not be {@code null}. 253 * 254 * @return The client registration request. 255 * 256 * @throws ParseException If the HTTP request couldn't be parsed to a 257 * client registration request. 258 */ 259 public static ClientRegistrationRequest parse(final HTTPRequest httpRequest) 260 throws ParseException { 261 262 httpRequest.ensureMethod(HTTPRequest.Method.POST); 263 264 // Get the JSON object content 265 JSONObject jsonObject = httpRequest.getQueryAsJSONObject(); 266 267 // Extract the software statement if any 268 SignedJWT stmt = null; 269 270 if (jsonObject.containsKey("software_statement")) { 271 272 try { 273 stmt = SignedJWT.parse(JSONObjectUtils.getString(jsonObject, "software_statement")); 274 275 } catch (java.text.ParseException e) { 276 277 throw new ParseException("Invalid software statement JWT: " + e.getMessage()); 278 } 279 280 // Prevent the JWT from appearing in the metadata 281 jsonObject.remove("software_statement"); 282 } 283 284 // Parse the client metadata 285 ClientMetadata metadata = ClientMetadata.parse(jsonObject); 286 287 // Parse the optional bearer access token 288 BearerAccessToken accessToken = null; 289 290 String authzHeaderValue = httpRequest.getAuthorization(); 291 292 if (StringUtils.isNotBlank(authzHeaderValue)) 293 accessToken = BearerAccessToken.parse(authzHeaderValue); 294 295 try { 296 URI endpointURI = httpRequest.getURL().toURI(); 297 298 return new ClientRegistrationRequest(endpointURI, metadata, stmt, accessToken); 299 300 } catch (URISyntaxException | IllegalArgumentException e) { 301 302 throw new ParseException(e.getMessage(), e); 303 } 304 } 305}