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 java.util.Collections;
022import java.util.LinkedHashMap;
023import java.util.List;
024import java.util.Map;
025
026import net.jcip.annotations.Immutable;
027
028import com.nimbusds.oauth2.sdk.AuthorizationGrant;
029import com.nimbusds.oauth2.sdk.GrantType;
030import com.nimbusds.oauth2.sdk.OAuth2Error;
031import com.nimbusds.oauth2.sdk.ParseException;
032import com.nimbusds.oauth2.sdk.util.MultivaluedMapUtils;
033
034
035/**
036 * Device code grant for the OAuth 2.0 Device Authorization Grant.
037 *
038 * <p>
039 * Related specifications:
040 *
041 * <ul>
042 *     <li>OAuth 2.0 Device Authorization Grant (draft-ietf-oauth-device-flow-15)
043 * </ul>
044 */
045@Immutable
046public class DeviceCodeGrant extends AuthorizationGrant {
047
048        
049        /**
050         * The grant type.
051         */
052        public static final GrantType GRANT_TYPE = GrantType.DEVICE_CODE;
053
054
055        /**
056         * The device code received from the authorisation server.
057         */
058        private final DeviceCode deviceCode;
059
060
061        /**
062         * Creates a new device code grant.
063         * 
064         * @param deviceCode The device code. Must not be {@code null}.
065         */
066        public DeviceCodeGrant(final DeviceCode deviceCode) {
067
068                super(GRANT_TYPE);
069
070                if (deviceCode == null)
071                        throw new IllegalArgumentException("The device code must not be null");
072
073                this.deviceCode = deviceCode;
074        }
075
076
077        /**
078         * Returns the device code received from the authorisation server.
079         * 
080         * @return The device code received from the authorisation server.
081         */
082        public DeviceCode getDeviceCode() {
083
084                return deviceCode;
085        }
086
087
088        @Override
089        public Map<String, List<String>> toParameters() {
090
091                Map<String, List<String>> params = new LinkedHashMap<>();
092                params.put("grant_type", Collections.singletonList(GRANT_TYPE.getValue()));
093                params.put("device_code", Collections.singletonList(deviceCode.getValue()));
094                return params;
095        }
096
097
098        /**
099         * Parses a device code grant from the specified request body
100         * parameters.
101         *
102         * <p>Example:
103         *
104         * <pre>
105         * grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Adevice_code
106         * &amp;device_code=GmRhmhcxhwAzkoEqiMEg_DnyEysNkuNhszIySk9eS
107         * </pre>
108         *
109         * @param params The parameters.
110         *
111         * @return The device code grant.
112         *
113         * @throws ParseException If parsing failed.
114         */
115        public static DeviceCodeGrant parse(final Map<String, List<String>> params) throws ParseException {
116
117                // Parse grant type
118                String grantTypeString = MultivaluedMapUtils.getFirstValue(params, "grant_type");
119
120                if (grantTypeString == null) {
121                        String msg = "Missing \"grant_type\" parameter";
122                        throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg));
123                }
124
125                if (!GrantType.parse(grantTypeString).equals(GRANT_TYPE)) {
126                        String msg = "The \"grant_type\" must be " + GRANT_TYPE;
127                        throw new ParseException(msg, OAuth2Error.UNSUPPORTED_GRANT_TYPE.appendDescription(": " + msg));
128                }
129
130                // Parse authorisation code
131                String deviceCodeString = MultivaluedMapUtils.getFirstValue(params, "device_code");
132
133                if (deviceCodeString == null || deviceCodeString.trim().isEmpty()) {
134                        String msg = "Missing or empty \"device_code\" parameter";
135                        throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg));
136                }
137
138                DeviceCode deviceCode = new DeviceCode(deviceCodeString);
139
140                return new DeviceCodeGrant(deviceCode);
141        }
142
143
144        @Override
145        public boolean equals(Object o) {
146
147                if (this == o)
148                        return true;
149                if (!(o instanceof DeviceCodeGrant))
150                        return false;
151
152                DeviceCodeGrant deviceCodeGrant = (DeviceCodeGrant) o;
153                return deviceCode.equals(deviceCodeGrant.deviceCode);
154        }
155
156
157        @Override
158        public int hashCode() {
159
160                return deviceCode.hashCode();
161        }
162}