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}