001/* 002 * Copyright 2016 The AppAuth for Android Authors. All Rights Reserved. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 005 * in compliance with the License. You may obtain a copy of the License at 006 * 007 * http://www.apache.org/licenses/LICENSE-2.0 008 * 009 * Unless required by applicable law or agreed to in writing, software distributed under the 010 * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 011 * express or implied. See the License for the specific language governing permissions and 012 * limitations under the License. 013 */ 014 015package net.openid.appauth; 016 017import static net.openid.appauth.AdditionalParamsProcessor.builtInParams; 018import static net.openid.appauth.AdditionalParamsProcessor.checkAdditionalParams; 019import static net.openid.appauth.Preconditions.checkCollectionNotEmpty; 020import static net.openid.appauth.Preconditions.checkNotEmpty; 021import static net.openid.appauth.Preconditions.checkNotNull; 022 023import android.net.Uri; 024import androidx.annotation.NonNull; 025import androidx.annotation.Nullable; 026 027import org.json.JSONException; 028import org.json.JSONObject; 029 030import java.util.ArrayList; 031import java.util.Arrays; 032import java.util.Collections; 033import java.util.List; 034import java.util.Map; 035import java.util.Set; 036 037public class RegistrationRequest { 038 /** 039 * OpenID Connect 'application_type'. 040 */ 041 public static final String APPLICATION_TYPE_NATIVE = "native"; 042 043 static final String PARAM_REDIRECT_URIS = "redirect_uris"; 044 static final String PARAM_RESPONSE_TYPES = "response_types"; 045 static final String PARAM_GRANT_TYPES = "grant_types"; 046 static final String PARAM_APPLICATION_TYPE = "application_type"; 047 static final String PARAM_SUBJECT_TYPE = "subject_type"; 048 static final String PARAM_JWKS_URI = "jwks_uri"; 049 static final String PARAM_JWKS = "jwks"; 050 static final String PARAM_TOKEN_ENDPOINT_AUTHENTICATION_METHOD = "token_endpoint_auth_method"; 051 052 private static final Set<String> BUILT_IN_PARAMS = builtInParams( 053 PARAM_REDIRECT_URIS, 054 PARAM_RESPONSE_TYPES, 055 PARAM_GRANT_TYPES, 056 PARAM_APPLICATION_TYPE, 057 PARAM_SUBJECT_TYPE, 058 PARAM_JWKS_URI, 059 PARAM_JWKS, 060 PARAM_TOKEN_ENDPOINT_AUTHENTICATION_METHOD 061 ); 062 063 static final String KEY_ADDITIONAL_PARAMETERS = "additionalParameters"; 064 static final String KEY_CONFIGURATION = "configuration"; 065 066 /** 067 * Instructs the authorization server to generate a pairwise subject identifier. 068 * 069 * @see "OpenID Connect Core 1.0, Section 8 070 * <https://openid.net/specs/openid-connect-core-1_0.html#rfc.section.8>" 071 */ 072 public static final String SUBJECT_TYPE_PAIRWISE = "pairwise"; 073 074 /** 075 * Instructs the authorization server to generate a public subject identifier. 076 * 077 * @see "OpenID Connect Core 1.0, Section 8 078 * <https://openid.net/specs/openid-connect-core-1_0.html#rfc.section.8>" 079 */ 080 public static final String SUBJECT_TYPE_PUBLIC = "public"; 081 082 /** 083 * The service's {@link AuthorizationServiceConfiguration configuration}. 084 * This configuration specifies how to connect to a particular OAuth provider. 085 * Configurations may be 086 * {@link 087 * AuthorizationServiceConfiguration#AuthorizationServiceConfiguration(Uri, Uri, Uri, Uri) 088 * created manually}, or 089 * {@link AuthorizationServiceConfiguration#fetchFromUrl(Uri, 090 * AuthorizationServiceConfiguration.RetrieveConfigurationCallback) 091 * via an OpenID Connect Discovery Document}. 092 */ 093 @NonNull 094 public final AuthorizationServiceConfiguration configuration; 095 096 /** 097 * The client's redirect URI's. 098 * 099 * @see "The OAuth 2.0 Authorization Framework (RFC 6749), Section 3.1.2 100 * <https://tools.ietf.org/html/rfc6749#section-3.1.2>" 101 */ 102 @NonNull 103 public final List<Uri> redirectUris; 104 105 /** 106 * The application type to register, will always be 'native'. 107 */ 108 @NonNull 109 public final String applicationType; 110 111 /** 112 * The response types to use. 113 * 114 * @see "OpenID Connect Core 1.0, Section 3 115 * <https://openid.net/specs/openid-connect-core-1_0.html#rfc.section.3>" 116 */ 117 @Nullable 118 public final List<String> responseTypes; 119 120 /** 121 * The grant types to use. 122 * 123 * @see "OpenID Connect Dynamic Client Registration 1.0, Section 2 124 * <https://openid.net/specs/openid-connect-discovery-1_0.html#rfc.section.2>" 125 */ 126 @Nullable 127 public final List<String> grantTypes; 128 129 /** 130 * The subject type to use. 131 * 132 * @see "OpenID Connect Core 1.0, Section 8 <https://openid.net/specs/openid-connect-core-1_0.html#rfc.section.8>" 133 */ 134 @Nullable 135 public final String subjectType; 136 137 /** 138 * URL for the Client's JSON Web Key Set [JWK] document. 139 * 140 * @see "OpenID Connect Dynamic Client Registration 1.0, Client Metadata 141 * <https://openid.net/specs/openid-connect-registration-1_0.html#ClientMetadata>" 142 */ 143 @Nullable 144 public final Uri jwksUri; 145 146 /** 147 * Client's JSON Web Key Set [JWK] document. 148 * 149 * @see "OpenID Connect Dynamic Client Registration 1.0, Client Metadata 150 * <https://openid.net/specs/openid-connect-registration-1_0.html#ClientMetadata>" 151 */ 152 @Nullable 153 public final JSONObject jwks; 154 155 /** 156 * The client authentication method to use at the token endpoint. 157 * 158 * @see "OpenID Connect Core 1.0, Section 9 <https://openid.net/specs/openid-connect-core-1_0.html#rfc.section.9>" 159 */ 160 @Nullable 161 public final String tokenEndpointAuthenticationMethod; 162 163 /** 164 * Additional parameters to be passed as part of the request. 165 */ 166 @NonNull 167 public final Map<String, String> additionalParameters; 168 169 170 /** 171 * Creates instances of {@link RegistrationRequest}. 172 */ 173 public static final class Builder { 174 @NonNull 175 private AuthorizationServiceConfiguration mConfiguration; 176 @NonNull 177 private List<Uri> mRedirectUris = new ArrayList<>(); 178 179 @Nullable 180 private List<String> mResponseTypes; 181 182 @Nullable 183 private List<String> mGrantTypes; 184 185 @Nullable 186 private String mSubjectType; 187 188 @Nullable 189 private Uri mJwksUri; 190 191 @Nullable 192 private JSONObject mJwks; 193 194 @Nullable 195 private String mTokenEndpointAuthenticationMethod; 196 197 @NonNull 198 private Map<String, String> mAdditionalParameters = Collections.emptyMap(); 199 200 201 /** 202 * Creates a registration request builder with the specified mandatory properties. 203 */ 204 public Builder( 205 @NonNull AuthorizationServiceConfiguration configuration, 206 @NonNull List<Uri> redirectUri) { 207 setConfiguration(configuration); 208 setRedirectUriValues(redirectUri); 209 } 210 211 /** 212 * Specifies the authorization service configuration for the request, which must not 213 * be null or empty. 214 */ 215 @NonNull 216 public Builder setConfiguration(@NonNull AuthorizationServiceConfiguration configuration) { 217 mConfiguration = checkNotNull(configuration); 218 return this; 219 } 220 221 /** 222 * Specifies the redirect URI's. 223 * 224 * @see <a href="https://tools.ietf.org/html/rfc6749#section-3.1.2"> "The OAuth 2.0 225 * Authorization Framework" (RFC 6749), Section 3.1.2</a> 226 */ 227 @NonNull 228 public Builder setRedirectUriValues(@NonNull Uri... redirectUriValues) { 229 return setRedirectUriValues(Arrays.asList(redirectUriValues)); 230 } 231 232 /** 233 * Specifies the redirect URI's. 234 * 235 * @see "The OAuth 2.0 Authorization Framework (RFC 6749), Section 3.1.2 236 * <https://tools.ietf.org/html/rfc6749#section-3.1.2>" 237 */ 238 @NonNull 239 public Builder setRedirectUriValues(@NonNull List<Uri> redirectUriValues) { 240 checkCollectionNotEmpty(redirectUriValues, "redirectUriValues cannot be null"); 241 mRedirectUris = redirectUriValues; 242 return this; 243 } 244 245 /** 246 * Specifies the response types. 247 * 248 * @see "OpenID Connect Core 1.0, Section 3 249 * <https://openid.net/specs/openid-connect-core-1_0.html#rfc.section.3>" 250 */ 251 @NonNull 252 public Builder setResponseTypeValues(@Nullable String... responseTypeValues) { 253 return setResponseTypeValues(Arrays.asList(responseTypeValues)); 254 } 255 256 /** 257 * Specifies the response types. 258 * 259 * @see "OpenID Connect Core 1.0, Section X 260 * <https://openid.net/specs/openid-connect-core-1_0.html#rfc.section.X>" 261 */ 262 @NonNull 263 public Builder setResponseTypeValues(@Nullable List<String> responseTypeValues) { 264 mResponseTypes = responseTypeValues; 265 return this; 266 } 267 268 /** 269 * Specifies the grant types. 270 * 271 * @see "OpenID Connect Dynamic Client Registration 1.0, Section 2 272 * <https://openid.net/specs/openid-connect-discovery-1_0.html#rfc.section.2>" 273 */ 274 @NonNull 275 public Builder setGrantTypeValues(@Nullable String... grantTypeValues) { 276 return setGrantTypeValues(Arrays.asList(grantTypeValues)); 277 } 278 279 /** 280 * Specifies the grant types. 281 * 282 * @see "OpenID Connect Dynamic Client Registration 1.0, Section 2 283 * <https://openid.net/specs/openid-connect-discovery-1_0.html#rfc.section.2>" 284 */ 285 @NonNull 286 public Builder setGrantTypeValues(@Nullable List<String> grantTypeValues) { 287 mGrantTypes = grantTypeValues; 288 return this; 289 } 290 291 /** 292 * Specifies the subject types. 293 * 294 * @see "OpenID Connect Core 1.0, Section 8 295 * <https://openid.net/specs/openid-connect-core-1_0.html#rfc.section.8>" 296 */ 297 @NonNull 298 public Builder setSubjectType(@Nullable String subjectType) { 299 mSubjectType = subjectType; 300 return this; 301 } 302 303 /** 304 * Specifies the URL for the Client's JSON Web Key Set. 305 * 306 * @see "OpenID Connect Dynamic Client Registration 1.0, Client Metadata 307 * <https://openid.net/specs/openid-connect-registration-1_0.html#ClientMetadata>" 308 */ 309 @NonNull 310 public Builder setJwksUri(@Nullable Uri jwksUri) { 311 mJwksUri = jwksUri; 312 return this; 313 } 314 315 /** 316 * Specifies the client's JSON Web Key Set. 317 * 318 * @see "OpenID Connect Dynamic Client Registration 1.0, Client Metadata 319 * <https://openid.net/specs/openid-connect-registration-1_0.html#ClientMetadata>" 320 */ 321 @NonNull 322 public Builder setJwks(@Nullable JSONObject jwks) { 323 mJwks = jwks; 324 return this; 325 } 326 327 /** 328 * Specifies the client authentication method to use at the token endpoint. 329 * 330 * @see "OpenID Connect Core 1.0, Section 9 331 * <https://openid.net/specs/openid-connect-core-1_0.html#rfc.section.9>" 332 */ 333 @NonNull 334 public Builder setTokenEndpointAuthenticationMethod( 335 @Nullable String tokenEndpointAuthenticationMethod) { 336 this.mTokenEndpointAuthenticationMethod = tokenEndpointAuthenticationMethod; 337 return this; 338 } 339 340 /** 341 * Specifies additional parameters. Replaces any previously provided set of parameters. 342 * Parameter keys and values cannot be null or empty. 343 */ 344 @NonNull 345 public Builder setAdditionalParameters(@Nullable Map<String, String> additionalParameters) { 346 mAdditionalParameters = checkAdditionalParams(additionalParameters, BUILT_IN_PARAMS); 347 return this; 348 } 349 350 /** 351 * Constructs the registration request. At a minimum, the redirect URI must have been 352 * set before calling this method. 353 */ 354 @NonNull 355 public RegistrationRequest build() { 356 return new RegistrationRequest( 357 mConfiguration, 358 Collections.unmodifiableList(mRedirectUris), 359 mResponseTypes == null 360 ? mResponseTypes : Collections.unmodifiableList(mResponseTypes), 361 mGrantTypes == null ? mGrantTypes : Collections.unmodifiableList(mGrantTypes), 362 mSubjectType, 363 mJwksUri, 364 mJwks, 365 mTokenEndpointAuthenticationMethod, 366 Collections.unmodifiableMap(mAdditionalParameters)); 367 } 368 } 369 370 private RegistrationRequest( 371 @NonNull AuthorizationServiceConfiguration configuration, 372 @NonNull List<Uri> redirectUris, 373 @Nullable List<String> responseTypes, 374 @Nullable List<String> grantTypes, 375 @Nullable String subjectType, 376 @Nullable Uri jwksUri, 377 @Nullable JSONObject jwks, 378 @Nullable String tokenEndpointAuthenticationMethod, 379 @NonNull Map<String, String> additionalParameters) { 380 this.configuration = configuration; 381 this.redirectUris = redirectUris; 382 this.responseTypes = responseTypes; 383 this.grantTypes = grantTypes; 384 this.subjectType = subjectType; 385 this.jwksUri = jwksUri; 386 this.jwks = jwks; 387 this.tokenEndpointAuthenticationMethod = tokenEndpointAuthenticationMethod; 388 this.additionalParameters = additionalParameters; 389 this.applicationType = APPLICATION_TYPE_NATIVE; 390 } 391 392 /** 393 * Converts the registration request to JSON for transmission to an authorization service. 394 * For local persistence and transmission, use {@link #jsonSerialize()}. 395 */ 396 @NonNull 397 public String toJsonString() { 398 JSONObject json = jsonSerializeParams(); 399 for (Map.Entry<String, String> param : additionalParameters.entrySet()) { 400 JsonUtil.put(json, param.getKey(), param.getValue()); 401 } 402 return json.toString(); 403 } 404 405 /** 406 * Produces a JSON representation of the registration request for persistent storage or 407 * local transmission (e.g. between activities). 408 */ 409 @NonNull 410 public JSONObject jsonSerialize() { 411 JSONObject json = jsonSerializeParams(); 412 JsonUtil.put(json, KEY_CONFIGURATION, configuration.toJson()); 413 JsonUtil.put(json, KEY_ADDITIONAL_PARAMETERS, 414 JsonUtil.mapToJsonObject(additionalParameters)); 415 return json; 416 } 417 418 /** 419 * Produces a JSON string representation of the registration request for persistent storage or 420 * local transmission (e.g. between activities). This method is just a convenience wrapper 421 * for {@link #jsonSerialize()}, converting the JSON object to its string form. 422 */ 423 @NonNull 424 public String jsonSerializeString() { 425 return jsonSerialize().toString(); 426 } 427 428 private JSONObject jsonSerializeParams() { 429 JSONObject json = new JSONObject(); 430 JsonUtil.put(json, PARAM_REDIRECT_URIS, JsonUtil.toJsonArray(redirectUris)); 431 JsonUtil.put(json, PARAM_APPLICATION_TYPE, applicationType); 432 433 if (responseTypes != null) { 434 JsonUtil.put(json, PARAM_RESPONSE_TYPES, JsonUtil.toJsonArray(responseTypes)); 435 } 436 if (grantTypes != null) { 437 JsonUtil.put(json, PARAM_GRANT_TYPES, JsonUtil.toJsonArray(grantTypes)); 438 } 439 JsonUtil.putIfNotNull(json, PARAM_SUBJECT_TYPE, subjectType); 440 441 JsonUtil.putIfNotNull(json, PARAM_JWKS_URI, jwksUri); 442 JsonUtil.putIfNotNull(json, PARAM_JWKS, jwks); 443 444 JsonUtil.putIfNotNull(json, PARAM_TOKEN_ENDPOINT_AUTHENTICATION_METHOD, 445 tokenEndpointAuthenticationMethod); 446 return json; 447 } 448 449 /** 450 * Reads a registration request from a JSON string representation produced by 451 * {@link #jsonSerialize()}. 452 * @throws JSONException if the provided JSON does not match the expected structure. 453 */ 454 public static RegistrationRequest jsonDeserialize(@NonNull JSONObject json) 455 throws JSONException { 456 checkNotNull(json, "json must not be null"); 457 458 return new RegistrationRequest( 459 AuthorizationServiceConfiguration.fromJson(json.getJSONObject(KEY_CONFIGURATION)), 460 JsonUtil.getUriList(json, PARAM_REDIRECT_URIS), 461 JsonUtil.getStringListIfDefined(json, PARAM_RESPONSE_TYPES), 462 JsonUtil.getStringListIfDefined(json, PARAM_GRANT_TYPES), 463 JsonUtil.getStringIfDefined(json, PARAM_SUBJECT_TYPE), 464 JsonUtil.getUriIfDefined(json, PARAM_JWKS_URI), 465 JsonUtil.getJsonObjectIfDefined(json, PARAM_JWKS), 466 JsonUtil.getStringIfDefined(json, PARAM_TOKEN_ENDPOINT_AUTHENTICATION_METHOD), 467 JsonUtil.getStringMap(json, KEY_ADDITIONAL_PARAMETERS)); 468 } 469 470 /** 471 * Reads a registration request from a JSON string representation produced by 472 * {@link #jsonSerializeString()}. This method is just a convenience wrapper for 473 * {@link #jsonDeserialize(JSONObject)}, converting the JSON string to its JSON object form. 474 * @throws JSONException if the provided JSON does not match the expected structure. 475 */ 476 public static RegistrationRequest jsonDeserialize(@NonNull String jsonStr) 477 throws JSONException { 478 checkNotEmpty(jsonStr, "jsonStr must not be empty or null"); 479 return jsonDeserialize(new JSONObject(jsonStr)); 480 } 481}