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.Preconditions.checkNotNull;
018import static net.openid.appauth.Preconditions.checkNullOrNotEmpty;
019
020import android.content.Intent;
021import android.net.Uri;
022import androidx.annotation.NonNull;
023import androidx.annotation.Nullable;
024import androidx.annotation.VisibleForTesting;
025
026import org.json.JSONException;
027import org.json.JSONObject;
028
029/**
030 * A response to end session request.
031 *
032 * @see EndSessionRequest
033 * @see "OpenID Connect RP-Initiated Logout 1.0 - draft 01
034 * <https://openid.net/specs/openid-connect-rpinitiated-1_0.html>"
035 */
036public class EndSessionResponse extends AuthorizationManagementResponse {
037
038    /**
039     * The extra string used to store an {@link EndSessionResponse} in an intent by
040     * {@link #toIntent()}.
041     */
042    public static final String EXTRA_RESPONSE = "net.openid.appauth.EndSessionResponse";
043
044    @VisibleForTesting
045    static final String KEY_REQUEST = "request";
046
047    @VisibleForTesting
048    static final String KEY_STATE = "state";
049
050    /**
051     * The end session request associated with this response.
052     */
053    @NonNull
054    public final EndSessionRequest request;
055
056    /**
057     * The returned state parameter, which must match the value specified in the request.
058     * AppAuth for Android ensures that this is the case.
059     */
060    @Nullable
061    public final String state;
062
063    /**
064     * Creates instances of {@link EndSessionResponse}.
065     */
066    public static final class Builder {
067        @NonNull
068        private EndSessionRequest mRequest;
069
070        @Nullable
071        private String mState;
072
073
074        public Builder(@NonNull EndSessionRequest request) {
075            setRequest(request);
076        }
077
078        @VisibleForTesting
079        Builder fromUri(@NonNull Uri uri) {
080            setState(uri.getQueryParameter(KEY_STATE));
081            return this;
082        }
083
084        public Builder setRequest(@NonNull EndSessionRequest request) {
085            mRequest = checkNotNull(request, "request cannot be null");
086            return this;
087        }
088
089        public Builder setState(@Nullable String state) {
090            mState = checkNullOrNotEmpty(state, "state must not be empty");
091            return this;
092        }
093
094        /**
095         * Builds the response object.
096         */
097        @NonNull
098        public EndSessionResponse build() {
099            return new EndSessionResponse(
100                mRequest,
101                mState);
102        }
103    }
104
105    private EndSessionResponse(
106            @NonNull EndSessionRequest request,
107            @Nullable String state) {
108        this.request = request;
109        this.state = state;
110    }
111
112    @Override
113    @Nullable
114    public String getState() {
115        return state;
116    }
117
118    /**
119     * Produces a JSON representation of the end session response for persistent storage or local
120     * transmission (e.g. between activities).
121     */
122    @Override
123    @NonNull
124    public JSONObject jsonSerialize() {
125        JSONObject json = new JSONObject();
126        JsonUtil.put(json, KEY_REQUEST, request.jsonSerialize());
127        JsonUtil.putIfNotNull(json, KEY_STATE, state);
128        return json;
129    }
130
131    /**
132     * Reads an end session response from a JSON string representation produced by
133     * {@link #jsonSerialize()}.
134     *
135     * @throws JSONException if the provided JSON does not match the expected structure.
136     */
137    @NonNull
138    public static EndSessionResponse jsonDeserialize(@NonNull JSONObject json)
139            throws JSONException {
140        if (!json.has(KEY_REQUEST)) {
141            throw new IllegalArgumentException(
142                "authorization request not provided and not found in JSON");
143        }
144
145        return new EndSessionResponse(
146                EndSessionRequest.jsonDeserialize(json.getJSONObject(KEY_REQUEST)),
147                JsonUtil.getStringIfDefined(json, KEY_STATE));
148    }
149
150    /**
151     * Reads an end session response from a JSON string representation produced by
152     * {@link #jsonSerializeString()}. This method is just a convenience wrapper for
153     * {@link #jsonDeserialize(JSONObject)}, converting the JSON string to its JSON object form.
154     *
155     * @throws JSONException if the provided JSON does not match the expected structure.
156     */
157    @NonNull
158    public static EndSessionResponse jsonDeserialize(@NonNull String jsonStr)
159            throws JSONException {
160        return jsonDeserialize(new JSONObject(jsonStr));
161    }
162
163    /**
164     * Produces an intent containing this end session response. This is used to deliver the
165     * end session response to the registered handler after a call to
166     * {@link AuthorizationService#performEndSessionRequest}.
167     */
168    @Override
169    public Intent toIntent() {
170        Intent data = new Intent();
171        data.putExtra(EXTRA_RESPONSE, this.jsonSerializeString());
172        return data;
173    }
174
175    /**
176     * Extracts an end session response from an intent produced by {@link #toIntent()}. This is
177     * used to extract the response from the intent data passed to an activity registered as the
178     * handler for {@link AuthorizationService#performEndSessionRequest}.
179     */
180    @Nullable
181    public static EndSessionResponse fromIntent(@NonNull Intent dataIntent) {
182        checkNotNull(dataIntent, "dataIntent must not be null");
183        if (!dataIntent.hasExtra(EXTRA_RESPONSE)) {
184            return null;
185        }
186
187        try {
188            return EndSessionResponse.jsonDeserialize(dataIntent.getStringExtra(EXTRA_RESPONSE));
189        } catch (JSONException ex) {
190            throw new IllegalArgumentException("Intent contains malformed auth response", ex);
191        }
192    }
193
194    static boolean containsEndSessionResponse(@NonNull Intent intent) {
195        return intent.hasExtra(EXTRA_RESPONSE);
196    }
197}