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}