functioning device code flow
parent
3326eee934
commit
a5b4115169
|
@ -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
|
||||
|
|
|
@ -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');
|
||||
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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">×</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"/>
|
||||
<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">×</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="text-align: left;">
|
||||
<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" />
|
||||
|
||||
<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/>
|
|
@ -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/>
|
|
@ -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"/>
|
||||
<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" />
|
||||
|
||||
<input name="deny" value="${deny_label}" type="submit" class="btn btn-secondary btn-large" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<o:footer/>
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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";
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
|
||||
|
|
Loading…
Reference in New Issue