001/******************************************************************************* 002 * Copyright 2017 The MIT Internet Trust Consortium 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 *******************************************************************************/ 016 017package org.mitre.oauth2.token; 018 019import java.util.Date; 020 021import org.mitre.oauth2.exception.AuthorizationPendingException; 022import org.mitre.oauth2.exception.DeviceCodeExpiredException; 023import org.mitre.oauth2.model.DeviceCode; 024import org.mitre.oauth2.service.DeviceCodeService; 025import org.mitre.oauth2.web.DeviceEndpoint; 026import org.springframework.beans.factory.annotation.Autowired; 027import org.springframework.security.oauth2.common.exceptions.InvalidGrantException; 028import org.springframework.security.oauth2.provider.ClientDetails; 029import org.springframework.security.oauth2.provider.ClientDetailsService; 030import org.springframework.security.oauth2.provider.OAuth2Authentication; 031import org.springframework.security.oauth2.provider.OAuth2RequestFactory; 032import org.springframework.security.oauth2.provider.TokenRequest; 033import org.springframework.security.oauth2.provider.token.AbstractTokenGranter; 034import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices; 035import org.springframework.stereotype.Component; 036 037/** 038 * Implements https://tools.ietf.org/html/draft-ietf-oauth-device-flow 039 * 040 * @see DeviceEndpoint 041 * 042 * @author jricher 043 * 044 */ 045@Component("deviceTokenGranter") 046public class DeviceTokenGranter extends AbstractTokenGranter { 047 048 public static final String GRANT_TYPE = "urn:ietf:params:oauth:grant-type:device_code"; 049 050 @Autowired 051 private DeviceCodeService deviceCodeService; 052 053 /** 054 * @param tokenServices 055 * @param clientDetailsService 056 * @param requestFactory 057 * @param grantType 058 */ 059 protected DeviceTokenGranter(AuthorizationServerTokenServices tokenServices, ClientDetailsService clientDetailsService, OAuth2RequestFactory requestFactory) { 060 super(tokenServices, clientDetailsService, requestFactory, GRANT_TYPE); 061 } 062 063 /* (non-Javadoc) 064 * @see org.springframework.security.oauth2.provider.token.AbstractTokenGranter#getOAuth2Authentication(org.springframework.security.oauth2.provider.ClientDetails, org.springframework.security.oauth2.provider.TokenRequest) 065 */ 066 @Override 067 protected OAuth2Authentication getOAuth2Authentication(ClientDetails client, TokenRequest tokenRequest) { 068 069 String deviceCode = tokenRequest.getRequestParameters().get("device_code"); 070 071 // look up the device code and consume it 072 DeviceCode dc = deviceCodeService.findDeviceCode(deviceCode, client); 073 074 if (dc != null) { 075 076 // make sure the code hasn't expired yet 077 if (dc.getExpiration() != null && dc.getExpiration().before(new Date())) { 078 079 deviceCodeService.clearDeviceCode(deviceCode, client); 080 081 throw new DeviceCodeExpiredException("Device code has expired " + deviceCode); 082 083 } else if (!dc.isApproved()) { 084 085 // still waiting for approval 086 throw new AuthorizationPendingException("Authorization pending for code " + deviceCode); 087 088 } else { 089 // inherit the (approved) scopes from the original request 090 tokenRequest.setScope(dc.getScope()); 091 092 OAuth2Authentication auth = new OAuth2Authentication(getRequestFactory().createOAuth2Request(client, tokenRequest), dc.getAuthenticationHolder().getUserAuth()); 093 094 deviceCodeService.clearDeviceCode(deviceCode, client); 095 096 return auth; 097 } 098 } else { 099 throw new InvalidGrantException("Invalid device code: " + deviceCode); 100 } 101 102 } 103 104 105 106 107}