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.device;
019
020
021import com.nimbusds.common.contenttype.ContentType;
022import com.nimbusds.oauth2.sdk.ErrorObject;
023import com.nimbusds.oauth2.sdk.ErrorResponse;
024import com.nimbusds.oauth2.sdk.OAuth2Error;
025import com.nimbusds.oauth2.sdk.ParseException;
026import com.nimbusds.oauth2.sdk.http.HTTPResponse;
027import com.nimbusds.oauth2.sdk.util.JSONObjectUtils;
028import net.jcip.annotations.Immutable;
029import net.minidev.json.JSONObject;
030
031import java.net.URI;
032import java.util.Collections;
033import java.util.HashSet;
034import java.util.Set;
035
036
037/**
038 * OAuth 2.0 device authorization error response.
039 *
040 * <p>Standard authorization errors:
041 *
042 * <ul>
043 *     <li>{@link OAuth2Error#INVALID_REQUEST}
044 *     <li>{@link OAuth2Error#INVALID_CLIENT}
045 *     <li>{@link OAuth2Error#INVALID_SCOPE}
046 * </ul>
047 *
048 * <p>Example HTTP response:
049 *
050 * <pre>
051 * HTTP/1.1 400 Bad Request
052 * Content-Type: application/json
053 * Cache-Control: no-store
054 * Pragma: no-cache
055 * 
056 * {
057 *  "error" : "invalid_request"
058 * }
059 * </pre>
060 *
061 * <p>Related specifications:
062 *
063 * <ul>
064 *     <li>OAuth 2.0 Device Authorization Grant (RFC 8628)
065 * </ul>
066 */
067@Immutable
068public class DeviceAuthorizationErrorResponse extends DeviceAuthorizationResponse implements ErrorResponse {
069
070        
071        /**
072         * The standard OAuth 2.0 errors for a device authorization error response.
073         */
074        private static final Set<ErrorObject> STANDARD_ERRORS;
075
076        static {
077                Set<ErrorObject> errors = new HashSet<>();
078                errors.add(OAuth2Error.INVALID_REQUEST);
079                errors.add(OAuth2Error.INVALID_CLIENT);
080                errors.add(OAuth2Error.INVALID_SCOPE);
081                STANDARD_ERRORS = Collections.unmodifiableSet(errors);
082        }
083
084
085        /**
086         * Gets the standard OAuth 2.0 errors for a device authorization error
087         * response.
088         *
089         * @return The standard errors, as a read-only set.
090         */
091        public static Set<ErrorObject> getStandardErrors() {
092
093                return STANDARD_ERRORS;
094        }
095
096
097        /**
098         * The error.
099         */
100        private final ErrorObject error;
101
102
103        /**
104         * Creates a new OAuth 2.0 device authorization error response. No
105         * OAuth 2.0 error is specified.
106         */
107        protected DeviceAuthorizationErrorResponse() {
108
109                error = null;
110        }
111
112
113        /**
114         * Creates a new OAuth 2.0 device authorization error response.
115         *
116         * @param error The error. Should match one of the
117         *              {@link #getStandardErrors standard errors} for a token
118         *              error response. Must not be {@code null}.
119         */
120        public DeviceAuthorizationErrorResponse(final ErrorObject error) {
121
122                if (error == null)
123                        throw new IllegalArgumentException("The error must not be null");
124
125                this.error = error;
126        }
127
128
129        @Override
130        public boolean indicatesSuccess() {
131
132                return false;
133        }
134
135
136        @Override
137        public ErrorObject getErrorObject() {
138
139                return error;
140        }
141
142
143        /**
144         * Returns the JSON object for this token error response.
145         *
146         * @return The JSON object for this token error response.
147         */
148        public JSONObject toJSONObject() {
149
150                JSONObject o = new JSONObject();
151
152                // No error?
153                if (error == null)
154                        return o;
155
156                o.put("error", error.getCode());
157
158                if (error.getDescription() != null)
159                        o.put("error_description", error.getDescription());
160
161                if (error.getURI() != null)
162                        o.put("error_uri", error.getURI().toString());
163
164                return o;
165        }
166
167
168        @Override
169        public HTTPResponse toHTTPResponse() {
170
171                int statusCode = (error != null && error.getHTTPStatusCode() > 0) ? error.getHTTPStatusCode()
172                                : HTTPResponse.SC_BAD_REQUEST;
173
174                HTTPResponse httpResponse = new HTTPResponse(statusCode);
175
176                if (error == null)
177                        return httpResponse;
178
179                httpResponse.setEntityContentType(ContentType.APPLICATION_JSON);
180                httpResponse.setCacheControl("no-store");
181                httpResponse.setPragma("no-cache");
182
183                httpResponse.setBody(toJSONObject().toString());
184
185                return httpResponse;
186        }
187
188
189        /**
190         * Parses an OAuth 2.0 device authorization response from the specified
191         * JSON object.
192         *
193         * @param jsonObject The JSON object to parse. Its status code must not
194         *                   be 200 (OK). Must not be {@code null}.
195         *
196         * @return The token error response.
197         *
198         * @throws ParseException If the JSON object couldn't be parsed to an
199         *                        OAuth 2.0 device authorization error
200         *                        response.
201         */
202        public static DeviceAuthorizationErrorResponse parse(final JSONObject jsonObject) throws ParseException {
203
204                // No error code?
205                if (!jsonObject.containsKey("error"))
206                        return new DeviceAuthorizationErrorResponse();
207
208                ErrorObject error;
209
210                try {
211                        // Parse code
212                        String code = JSONObjectUtils.getString(jsonObject, "error");
213                        String description = JSONObjectUtils.getString(jsonObject, "error_description", null);
214                        URI uri = JSONObjectUtils.getURI(jsonObject, "error_uri", null);
215
216                        error = new ErrorObject(code, description, HTTPResponse.SC_BAD_REQUEST, uri);
217
218                } catch (ParseException e) {
219                        throw new ParseException("Missing or invalid token error response parameter: " + e.getMessage(),
220                                        e);
221                }
222
223                return new DeviceAuthorizationErrorResponse(error);
224        }
225
226
227        /**
228         * Parses an OAuth 2.0 ciba authorization error, push error or token response -  from the
229         * specified HTTP response.
230         *
231         * @param httpResponse The HTTP response to parse. Its status code must
232         *                     not be 200 (OK). Must not be {@code null}.
233         *
234         * @return The device authorization error response.
235         *
236         * @throws ParseException If the HTTP response couldn't be parsed to an
237         *                        OAuth 2.0 device authorization error
238         *                        response.
239         */
240        public static DeviceAuthorizationErrorResponse parse(final HTTPResponse httpResponse) throws ParseException {
241
242                httpResponse.ensureStatusCodeNotOK();
243                return new DeviceAuthorizationErrorResponse(ErrorObject.parse(httpResponse));
244        }
245}