functioning device code flow

pull/1161/merge
Justin Richer 2017-03-09 20:08:47 -05:00
parent 3326eee934
commit a5b4115169
11 changed files with 493 additions and 15 deletions

View File

@ -19,6 +19,8 @@ package org.mitre.oauth2.service;
import org.mitre.oauth2.model.DeviceCode;
import org.springframework.security.oauth2.provider.ClientDetails;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.OAuth2Request;
/**
* @author jricher
@ -39,8 +41,9 @@ public interface DeviceCodeService {
/**
* @param dc
* @param o2Auth
*/
public DeviceCode approveDeviceCode(DeviceCode dc);
public DeviceCode approveDeviceCode(DeviceCode dc, OAuth2Authentication o2Auth);
/**
* @param deviceCode

View File

@ -28,6 +28,7 @@ INSERT INTO client_redirect_uri_TEMP (owner_id, redirect_uri) VALUES
INSERT INTO client_grant_type_TEMP (owner_id, grant_type) VALUES
('client', 'authorization_code'),
('client', 'urn:ietf:params:oauth:grant_type:redelegate'),
('client', 'urn:ietf:params:oauth:grant-type:device_code'),
('client', 'implicit'),
('client', 'refresh_token');

View File

@ -138,6 +138,20 @@
<security:csrf disabled="true"/>
</security:http>
<security:http pattern="/#{T(org.mitre.oauth2.web.DeviceEndpoint).URL}/**"
use-expressions="true"
entry-point-ref="oauthAuthenticationEntryPoint"
create-session="stateless"
authentication-manager-ref="clientAuthenticationManager">
<security:http-basic entry-point-ref="oauthAuthenticationEntryPoint" />
<!-- include this only if you need to authenticate clients via request parameters -->
<security:custom-filter ref="clientAssertionEndpointFilter" after="PRE_AUTH_FILTER" /> <!-- this one has to go first -->
<security:custom-filter ref="clientCredentialsEndpointFilter" after="BASIC_AUTH_FILTER" />
<security:custom-filter ref="corsFilter" after="SECURITY_CONTEXT_FILTER" />
<security:access-denied-handler ref="oauthAccessDeniedHandler" />
<security:csrf disabled="true"/>
</security:http>
<security:http pattern="/#{T(org.mitre.oauth2.web.IntrospectionEndpoint).URL}**"
use-expressions="true"
entry-point-ref="oauthAuthenticationEntryPoint"

View File

@ -47,6 +47,7 @@
<oauth:client-credentials/>
<oauth:custom-grant token-granter-ref="chainedTokenGranter" />
<oauth:custom-grant token-granter-ref="jwtAssertionTokenGranter" />
<oauth:custom-grant token-granter-ref="deviceTokenGranter" />
</oauth:authorization-server>

View File

@ -0,0 +1,286 @@
<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="utf-8"%>
<%@ page import="org.springframework.security.core.AuthenticationException"%>
<%@ page import="org.springframework.security.oauth2.common.exceptions.UnapprovedClientAuthenticationException"%>
<%@ page import="org.springframework.security.web.WebAttributes"%>
<%@ taglib prefix="authz" uri="http://www.springframework.org/security/tags"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt"%>
<%@ taglib prefix="o" tagdir="/WEB-INF/tags"%>
<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
<%@ taglib prefix="spring" uri="http://www.springframework.org/tags"%>
<spring:message code="approve.title" var="title"/>
<o:header title="${title}"/>
<o:topbar pageName="Approve" />
<div class="container main">
<% if (session.getAttribute(WebAttributes.AUTHENTICATION_EXCEPTION) != null && !(session.getAttribute(WebAttributes.AUTHENTICATION_EXCEPTION) instanceof UnapprovedClientAuthenticationException)) { %>
<div class="alert-message error">
<a href="#" class="close">&times;</a>
<p><strong><spring:message code="approve.error.not_granted"/></strong>
(<%= ((AuthenticationException) session.getAttribute(WebAttributes.AUTHENTICATION_EXCEPTION)).getMessage() %>)
</p>
</div>
<% } %>
<c:remove scope="session" var="SPRING_SECURITY_LAST_EXCEPTION" />
<div class="well" style="text-align: center">
<h1><spring:message code="approve.required_for"/>&nbsp;
<c:choose>
<c:when test="${empty client.clientName}">
<em><c:out value="${client.clientId}" /></em>
</c:when>
<c:otherwise>
<em><c:out value="${client.clientName}" /></em>
</c:otherwise>
</c:choose>
</h1>
<form name="confirmationForm"
action="${pageContext.request.contextPath.endsWith('/') ? pageContext.request.contextPath : pageContext.request.contextPath.concat('/') }device-user/approve" method="post">
<div class="row">
<div class="span5 offset1 well-small" style="text-align: left">
<c:if test="${ client.dynamicallyRegistered }">
<c:choose>
<c:when test="${ gras }">
<!-- client is "generally recognized as safe, display a more muted block -->
<div>
<p class="alert alert-info">
<i class="icon-globe"></i>
<spring:message code="approve.dynamically_registered"/>
</p>
</div>
</c:when>
<c:otherwise>
<!-- client is dynamically registered -->
<div class="alert alert-block <c:out value="${ count eq 0 ? 'alert-error' : 'alert-warn' }" />">
<h4>
<i class="icon-globe"></i> <spring:message code="approve.caution.title"/>:
</h4>
<p>
<spring:message code="approve.dynamically_registered" arguments="${ client.createdAt }"/>
</p>
<p>
<c:choose>
<c:when test="${count == 0}">
<spring:message code="approve.caution.message.none" arguments="${count}"/>
</c:when>
<c:when test="${count == 1}">
<spring:message code="approve.caution.message.singular" arguments="${count}"/>
</c:when>
<c:otherwise>
<spring:message code="approve.caution.message.plural" arguments="${count}"/>
</c:otherwise>
</c:choose>
</p>
</div>
</c:otherwise>
</c:choose>
</c:if>
<c:if test="${ not empty client.logoUri }">
<ul class="thumbnails">
<li class="span5">
<a class="thumbnail" data-toggle="modal" data-target="#logoModal"><img src="api/clients/${ client.id }/logo" /></a>
</li>
</ul>
<!-- Modal -->
<div id="logoModal" class="modal hide fade" tabindex="-1" role="dialog" aria-labelledby="logoModalLabel" aria-hidden="true">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
<h3 id="logoModalLabel">
<c:choose>
<c:when test="${empty client.clientName}">
<em><c:out value="${client.clientId}" /></em>
</c:when>
<c:otherwise>
<em><c:out value="${client.clientName}" /></em>
</c:otherwise>
</c:choose>
</h3>
</div>
<div class="modal-body">
<img src="api/clients/${ client.id }/logo" />
<c:if test="${ not empty client.clientUri }">
<a href="<c:out value="${ client.clientUri }" />"><c:out value="${ client.clientUri }" /></a>
</c:if>
</div>
<div class="modal-footer">
<button class="btn" data-dismiss="modal" aria-hidden="true">Close</button>
</div>
</div>
</c:if>
<c:if test="${ (not empty client.clientDescription) || (not empty client.clientUri) || (not empty client.policyUri) || (not empty client.tosUri) || (not empty contacts) }">
<div class="muted moreInformationContainer">
<c:out value="${client.clientDescription}" />
<c:if test="${ (not empty client.clientUri) || (not empty client.policyUri) || (not empty client.tosUri) || (not empty contacts) }">
<div id="toggleMoreInformation" style="cursor: pointer;">
<small><i class="icon-chevron-right"></i> <spring:message code="approve.more_information"/></small>
</div>
<div id="moreInformation" class="hide">
<ul>
<c:if test="${ not empty client.clientUri }">
<li><spring:message code="approve.home_page"/>: <a href="<c:out value="${ client.clientUri }" />"><c:out value="${ client.clientUri }" /></a></li>
</c:if>
<c:if test="${ not empty client.policyUri }">
<li><spring:message code="Policy"/>: <a href="<c:out value="${ client.policyUri }" />"><c:out value="${ client.policyUri }" /></a></li>
</c:if>
<c:if test="${ not empty client.tosUri }">
<li><spring:message code="approve.terms"/>: <a href="<c:out value="${ client.tosUri }" />"><c:out value="${ client.tosUri }" /></a></li>
</c:if>
<c:if test="${ (not empty contacts) }">
<li><spring:message code="approve.contacts"/>: <c:out value="${ contacts }" /></li>
</c:if>
</ul>
</div>
</c:if>
</div>
</c:if>
<c:if test="${ client.subjectType == 'PAIRWISE' }">
<div class="alert alert-success">
<spring:message code="approve.pairwise"/>
</div>
</c:if>
</div>
<div class="span4">
<fieldset style="text-align: left" class="well">
<legend style="margin-bottom: 0;"><spring:message code="approve.access_to"/>:</legend>
<c:if test="${ empty client.scope }">
<div class="alert alert-block alert-error">
<h4>
<i class="icon-info-sign"></i> <spring:message code="approve.warning"/>:
</h4>
<p>
<spring:message code="approve.no_scopes"/>
</p>
</div>
</c:if>
<c:forEach var="scope" items="${ scopes }">
<c:if test="${ not empty scope.icon }">
<i class="icon-${ fn:escapeXml(scope.icon) }"></i>
</c:if>
<c:choose>
<c:when test="${ not empty scope.description }">
<c:out value="${ scope.description }" />
</c:when>
<c:otherwise>
<c:out value="${ scope.value }" />
</c:otherwise>
</c:choose>
<c:if test="${ not empty claims[scope.value] }">
<span class="claim-tooltip" data-toggle="popover"
data-html="true"
data-placement="right"
data-trigger="hover"
data-title="These values will be sent:"
data-content="<div style=&quot;text-align: left;&quot;>
<ul>
<c:forEach var="claim" items="${ claims[scope.value] }">
<li>
<b><c:out value="${ claim.key }" /></b>:
<c:out value="${ claim.value }" />
</li>
</c:forEach>
</ul>
</div>
"
>
<i class="icon-question-sign"></i>
</span>
</c:if>
</c:forEach>
</fieldset>
</div>
</div>
<div class="row">
<h3>
<spring:message code="approve.do_authorize"/>
"<c:choose>
<c:when test="${empty client.clientName}">
<c:out value="${client.clientId}" />
</c:when>
<c:otherwise>
<c:out value="${client.clientName}" />
</c:otherwise>
</c:choose>"?
</h3>
<spring:message code="approve.label.authorize" var="authorize_label"/>
<spring:message code="approve.label.deny" var="deny_label"/>
<input id="user_oauth_approval" name="user_oauth_approval" value="true" type="hidden" />
<input type="hidden" name="user_code" value="${ dc.userCode }" />
<input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}" />
<input name="authorize" value="${authorize_label}" type="submit"
onclick="$('#user_oauth_approval').attr('value',true)" class="btn btn-success btn-large" />
&nbsp;
<input name="deny" value="${deny_label}" type="submit" onclick="$('#user_oauth_approval').attr('value',false)"
class="btn btn-secondary btn-large" />
</div>
</form>
</div>
</div>
<script type="text/javascript">
<!--
$(document).ready(function() {
$('.claim-tooltip').popover();
$('.claim-tooltip').on('click', function(e) {
e.preventDefault();
$(this).popover('show');
});
$(document).on('click', '#toggleMoreInformation', function(event) {
event.preventDefault();
if ($('#moreInformation').is(':visible')) {
// hide it
$('.moreInformationContainer', this.el).removeClass('alert').removeClass('alert-info').addClass('muted');
$('#moreInformation').hide('fast');
$('#toggleMoreInformation i').attr('class', 'icon-chevron-right');
} else {
// show it
$('.moreInformationContainer', this.el).addClass('alert').addClass('alert-info').removeClass('muted');
$('#moreInformation').show('fast');
$('#toggleMoreInformation i').attr('class', 'icon-chevron-down');
}
});
var creationDate = "<c:out value="${ client.createdAt }" />";
var displayCreationDate = $.t('approve.dynamically-registered-unkown');
var hoverCreationDate = "";
if (creationDate != null && moment(creationDate).isValid()) {
creationDate = moment(creationDate);
if (moment().diff(creationDate, 'months') < 6) {
displayCreationDate = creationDate.fromNow();
} else {
displayCreationDate = "on " + creationDate.format("LL");
}
hoverCreationDate = creationDate.format("LLL");
}
$('#registrationTime').html(displayCreationDate);
$('#registrationTime').attr('title', hoverCreationDate);
});
//-->
</script>
<o:footer/>

View File

@ -0,0 +1,33 @@
<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="utf-8"%>
<%@ page import="org.springframework.security.core.AuthenticationException"%>
<%@ page import="org.springframework.security.oauth2.common.exceptions.UnapprovedClientAuthenticationException"%>
<%@ page import="org.springframework.security.web.WebAttributes"%>
<%@ taglib prefix="authz" uri="http://www.springframework.org/security/tags"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt"%>
<%@ taglib prefix="o" tagdir="/WEB-INF/tags"%>
<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
<%@ taglib prefix="spring" uri="http://www.springframework.org/tags"%>
<spring:message code="approve.title" var="title"/>
<o:header title="${title}"/>
<o:topbar pageName="Approve" />
<div class="container main">
<div class="well" style="text-align: center">
<h1><c:choose>
<c:when test="${empty client.clientName}">
<em><c:out value="${client.clientId}" /></em>
</c:when>
<c:otherwise>
<em><c:out value="${client.clientName}" /></em>
</c:otherwise>
</c:choose>
<spring:message code="device.has-been-approved" />
</h1>
<div><spring:message code="device.approved" /></div>
</div>
</div>
<o:footer/>

View File

@ -0,0 +1,48 @@
<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="utf-8"%>
<%@ page import="org.springframework.security.core.AuthenticationException"%>
<%@ page import="org.springframework.security.oauth2.common.exceptions.UnapprovedClientAuthenticationException"%>
<%@ page import="org.springframework.security.web.WebAttributes"%>
<%@ taglib prefix="authz" uri="http://www.springframework.org/security/tags"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt"%>
<%@ taglib prefix="o" tagdir="/WEB-INF/tags"%>
<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
<%@ taglib prefix="spring" uri="http://www.springframework.org/tags"%>
<spring:message code="device.request_code.title" var="title"/>
<o:header title="${title}"/>
<o:topbar pageName="Approve" />
<div class="container main">
<div class="well" style="text-align: center">
<h1><spring:message code="device.request_code.header"/>&nbsp;
<c:choose>
<c:when test="${empty client.clientName}">
<em><c:out value="${client.clientId}" /></em>
</c:when>
<c:otherwise>
<em><c:out value="${client.clientName}" /></em>
</c:otherwise>
</c:choose>
</h1>
<form action="${ config.issuer }${ config.issuer.endsWith('/') ? '' : '/' }device-user/verify" method="POST">
<div class="row">
<div class="span12">
<spring:message code="approve.label.authorize" var="authorize_label"/>
<spring:message code="approve.label.deny" var="deny_label"/>
<input type="text" name="user_code" />
<input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}" />
<input name="approve" value="${authorize_label}" type="submit" class="btn btn-success btn-large" />
&nbsp;
<input name="deny" value="${deny_label}" type="submit" class="btn btn-secondary btn-large" />
</div>
</div>
</form>
</div>
</div>
<o:footer/>

View File

@ -487,6 +487,13 @@
"password": "Password",
"login-button": "Login",
"error": "The system was unable to log you in. Please try again."
},
"device": {
"request_code": {
"title": "Enter Code",
"header": "Enter code for ",
"description": "Enter the code displayed on your device into the box below and press submit",
"submit": "Submit"
}
}
}

View File

@ -0,0 +1,50 @@
/*******************************************************************************
* Copyright 2017 The MITRE Corporation
* and the MIT Internet Trust Consortium
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*******************************************************************************/
package org.mitre.oauth2.exception;
import org.springframework.security.oauth2.common.exceptions.OAuth2Exception;
/**
* @author jricher
*
*/
public class AuthorizationPendingException extends OAuth2Exception {
/**
* @param msg
*/
public AuthorizationPendingException(String msg) {
super(msg);
}
/**
*
*/
private static final long serialVersionUID = -7078098692596870940L;
/* (non-Javadoc)
* @see org.springframework.security.oauth2.common.exceptions.OAuth2Exception#getOAuth2ErrorCode()
*/
@Override
public String getOAuth2ErrorCode() {
return "authorization_pending";
}
}

View File

@ -17,6 +17,7 @@
package org.mitre.oauth2.token;
import org.mitre.oauth2.exception.AuthorizationPendingException;
import org.mitre.oauth2.model.DeviceCode;
import org.mitre.oauth2.service.DeviceCodeService;
import org.mitre.oauth2.web.DeviceEndpoint;
@ -29,6 +30,7 @@ import org.springframework.security.oauth2.provider.OAuth2RequestFactory;
import org.springframework.security.oauth2.provider.TokenRequest;
import org.springframework.security.oauth2.provider.token.AbstractTokenGranter;
import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices;
import org.springframework.stereotype.Component;
/**
* Implements https://tools.ietf.org/html/draft-ietf-oauth-device-flow
@ -38,6 +40,7 @@ import org.springframework.security.oauth2.provider.token.AuthorizationServerTok
* @author jricher
*
*/
@Component("deviceTokenGranter")
public class DeviceTokenGranter extends AbstractTokenGranter {
public static final String GRANT_TYPE = "urn:ietf:params:oauth:grant-type:device_code";
@ -67,12 +70,20 @@ public class DeviceTokenGranter extends AbstractTokenGranter {
DeviceCode dc = deviceCodeService.consumeDeviceCode(deviceCode, client);
if (dc != null) {
// inherit the (approved) scopes from the original request
tokenRequest.setScope(dc.getScope());
OAuth2Authentication auth = new OAuth2Authentication(getRequestFactory().createOAuth2Request(client, tokenRequest), dc.getAuthenticationHolder().getUserAuth());
if (dc.isApproved()) {
return auth;
// inherit the (approved) scopes from the original request
tokenRequest.setScope(dc.getScope());
OAuth2Authentication auth = new OAuth2Authentication(getRequestFactory().createOAuth2Request(client, tokenRequest), dc.getAuthenticationHolder().getUserAuth());
return auth;
} else {
// still waiting for approval
throw new AuthorizationPendingException("Authorization pending for code " + deviceCode);
}
} else {
throw new InvalidGrantException("Invalid device code: " + deviceCode);
}

View File

@ -23,6 +23,8 @@ import java.util.Map;
import java.util.Set;
import java.util.UUID;
import javax.servlet.http.HttpSession;
import org.mitre.oauth2.model.ClientDetailsEntity;
import org.mitre.oauth2.model.DeviceCode;
import org.mitre.oauth2.model.SystemScope;
@ -40,10 +42,15 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.common.exceptions.InvalidClientException;
import org.springframework.security.oauth2.common.exceptions.OAuth2Exception;
import org.springframework.security.oauth2.common.util.OAuth2Utils;
import org.springframework.security.oauth2.common.util.RandomValueStringGenerator;
import org.springframework.security.oauth2.provider.AuthorizationRequest;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.OAuth2Request;
import org.springframework.security.oauth2.provider.OAuth2RequestFactory;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.RequestMapping;
@ -64,7 +71,8 @@ import com.google.common.collect.Sets;
@Controller
public class DeviceEndpoint {
public static final String URL = "/device";
public static final String URL = "device";
public static final String USER_URL = "device-user";
public static final Logger logger = LoggerFactory.getLogger(DeviceEndpoint.class);
@ -80,9 +88,12 @@ public class DeviceEndpoint {
@Autowired
private DeviceCodeService deviceCodeService;
@Autowired
private OAuth2RequestFactory oAuth2RequestFactory;
private RandomValueStringGenerator randomGenerator = new RandomValueStringGenerator();
@RequestMapping(value = URL, method = RequestMethod.POST, consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
@RequestMapping(value = "/" + URL, method = RequestMethod.POST, consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
public String requestDeviceCode(@RequestParam("client_id") String clientId, @RequestParam(name="scope", required=false) String scope, Map<String, String> parameters, ModelMap model) {
ClientDetailsEntity client;
@ -150,7 +161,7 @@ public class DeviceEndpoint {
}
@PreAuthorize("hasRole('ROLE_USER')")
@RequestMapping(value = URL, method = RequestMethod.GET)
@RequestMapping(value = "/" + USER_URL, method = RequestMethod.GET)
public String requestUserCode(ModelMap model) {
// print out a page that asks the user to enter their user code
@ -160,8 +171,8 @@ public class DeviceEndpoint {
}
@PreAuthorize("hasRole('ROLE_USER')")
@RequestMapping(value = URL + "/verify", method = RequestMethod.POST)
public String readUserCode(@RequestParam("userCode") String userCode, ModelMap model) {
@RequestMapping(value = "/" + USER_URL + "/verify", method = RequestMethod.POST)
public String readUserCode(@RequestParam("user_code") String userCode, ModelMap model, HttpSession session) {
// look up the request based on the user code
DeviceCode dc = deviceCodeService.lookUpByUserCode(userCode);
@ -189,17 +200,30 @@ public class DeviceEndpoint {
model.put("scopes", sortedScopes);
AuthorizationRequest authorizationRequest = oAuth2RequestFactory.createAuthorizationRequest(dc.getRequestParameters());
session.setAttribute("authorizationRequest", authorizationRequest);
session.setAttribute("deviceCode", dc);
return "approveDevice";
}
@PreAuthorize("hasRole('ROLE_USER')")
@RequestMapping(value = URL + "/approve", method = RequestMethod.POST)
public String approveDevice(@RequestParam("userCode") String userCode, @RequestParam(value = "approve", required = false) String approve, ModelMap model) {
@RequestMapping(value = "/" + USER_URL + "/approve", method = RequestMethod.POST)
public String approveDevice(@RequestParam("user_code") String userCode, @RequestParam(value = "user_oauth_approval") String approve, ModelMap model, Authentication auth, HttpSession session) {
AuthorizationRequest authorizationRequest = (AuthorizationRequest) session.getAttribute("authorizationRequest");
DeviceCode dc = (DeviceCode) session.getAttribute("deviceCode");
DeviceCode dc = deviceCodeService.lookUpByUserCode(userCode);
if (!dc.getUserCode().equals(userCode)) {
// TODO: return an error
return "error";
}
DeviceCode approvedCode = deviceCodeService.approveDeviceCode(dc);
OAuth2Request o2req = oAuth2RequestFactory.createOAuth2Request(authorizationRequest);
OAuth2Authentication o2Auth = new OAuth2Authentication(o2req, auth);
DeviceCode approvedCode = deviceCodeService.approveDeviceCode(dc, o2Auth);
ClientDetailsEntity client = clientService.loadClientByClientId(dc.getClientId());