added expiration to device codes

pull/1161/merge
Justin Richer 2017-03-11 15:29:08 -05:00
parent 9cb5377ce8
commit 548dad4e29
8 changed files with 6554 additions and 18 deletions

View File

@ -17,6 +17,7 @@
package org.mitre.oauth2.model;
import java.util.Date;
import java.util.Map;
import java.util.Set;
@ -33,6 +34,7 @@ import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.MapKeyColumn;
import javax.persistence.Table;
import javax.persistence.Temporal;
/**
* @author jricher
@ -46,6 +48,7 @@ public class DeviceCode {
private String deviceCode;
private String userCode;
private Set<String> scope;
private Date expiration;
private String clientId;
private Map<String, String> requestParameters;
private boolean approved;
@ -61,7 +64,6 @@ public class DeviceCode {
this.scope = scope;
this.clientId = clientId;
this.requestParameters = params;
this.setApproved(false);
}
/**
@ -133,6 +135,17 @@ public class DeviceCode {
this.scope = scope;
}
@Basic
@Temporal(javax.persistence.TemporalType.TIMESTAMP)
@Column(name = "expiration")
public Date getExpiration() {
return expiration;
}
public void setExpiration(Date expiration) {
this.expiration = expiration;
}
/**
* @return the clientId
*/

View File

@ -17,6 +17,10 @@
package org.mitre.oauth2.service;
import java.util.Map;
import java.util.Set;
import org.mitre.oauth2.model.ClientDetailsEntity;
import org.mitre.oauth2.model.DeviceCode;
import org.springframework.security.oauth2.provider.ClientDetails;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
@ -28,11 +32,6 @@ import org.springframework.security.oauth2.provider.OAuth2Request;
*/
public interface DeviceCodeService {
/**
* @param dc
*/
public DeviceCode save(DeviceCode dc);
/**
* @param userCode
* @return
@ -52,4 +51,14 @@ public interface DeviceCodeService {
*/
public DeviceCode consumeDeviceCode(String deviceCode, ClientDetails client);
/**
* @param deviceCode
* @param userCode
* @param requestedScopes
* @param client
* @param parameters
* @return
*/
public DeviceCode createNewDeviceCode(String deviceCode, String userCode, Set<String> requestedScopes, ClientDetailsEntity client, Map<String, String> parameters);
}

View File

@ -0,0 +1,48 @@
/*******************************************************************************
* 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 DeviceCodeExpiredException extends OAuth2Exception {
/**
* @param msg
*/
public DeviceCodeExpiredException(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 "expired_token";
}
}

View File

@ -0,0 +1,102 @@
/*******************************************************************************
* 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.service.impl;
import java.util.Date;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.mitre.oauth2.model.AuthenticationHolderEntity;
import org.mitre.oauth2.model.ClientDetailsEntity;
import org.mitre.oauth2.model.DeviceCode;
import org.mitre.oauth2.service.DeviceCodeService;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.provider.ClientDetails;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.stereotype.Service;
/**
* @author jricher
*
*/
@Service
public class InMemoryDeviceCodeService implements DeviceCodeService {
private Set<DeviceCode> codes = new HashSet<>();
/* (non-Javadoc)
* @see org.mitre.oauth2.service.DeviceCodeService#save(org.mitre.oauth2.model.DeviceCode)
*/
@Override
public DeviceCode createNewDeviceCode(String deviceCode, String userCode, Set<String> requestedScopes, ClientDetailsEntity client, Map<String, String> parameters) {
DeviceCode dc = new DeviceCode(deviceCode, userCode, requestedScopes, client.getClientId(), parameters);
if (client.getDeviceCodeValiditySeconds() != null) {
dc.setExpiration(new Date(System.currentTimeMillis() + client.getDeviceCodeValiditySeconds() * 1000L));
}
dc.setApproved(false);
codes.add(dc);
return dc;
}
/* (non-Javadoc)
* @see org.mitre.oauth2.service.DeviceCodeService#lookUpByUserCode(java.lang.String)
*/
@Override
public DeviceCode lookUpByUserCode(String userCode) {
for (DeviceCode dc : codes) {
if (dc.getUserCode().equals(userCode)) {
return dc;
}
}
return null;
}
/* (non-Javadoc)
* @see org.mitre.oauth2.service.DeviceCodeService#approveDeviceCode(org.mitre.oauth2.model.DeviceCode)
*/
@Override
public DeviceCode approveDeviceCode(DeviceCode dc, OAuth2Authentication auth) {
dc.setApproved(true);
AuthenticationHolderEntity authHolder = new AuthenticationHolderEntity();
authHolder.setAuthentication(auth);
dc.setAuthenticationHolder(authHolder);
return dc;
}
/* (non-Javadoc)
* @see org.mitre.oauth2.service.DeviceCodeService#consumeDeviceCode(java.lang.String, org.springframework.security.oauth2.provider.ClientDetails)
*/
@Override
public DeviceCode consumeDeviceCode(String deviceCode, ClientDetails client) {
for (DeviceCode dc : codes) {
if (dc.getDeviceCode().equals(deviceCode) && dc.getClientId().equals(client.getClientId())) {
codes.remove(dc);
return dc;
}
}
return null;
}
}

View File

@ -17,7 +17,10 @@
package org.mitre.oauth2.token;
import java.util.Date;
import org.mitre.oauth2.exception.AuthorizationPendingException;
import org.mitre.oauth2.exception.DeviceCodeExpiredException;
import org.mitre.oauth2.model.DeviceCode;
import org.mitre.oauth2.service.DeviceCodeService;
import org.mitre.oauth2.web.DeviceEndpoint;
@ -71,18 +74,23 @@ public class DeviceTokenGranter extends AbstractTokenGranter {
if (dc != null) {
if (dc.isApproved()) {
// make sure the code hasn't expired yet
if (dc.getExpiration() != null && dc.getExpiration().before(new Date())) {
// TODO: return an error
throw new DeviceCodeExpiredException("Device code has expired " + deviceCode);
} else if (!dc.isApproved()) {
// still waiting for approval
throw new AuthorizationPendingException("Authorization pending for code " + deviceCode);
} else {
// 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

@ -18,6 +18,7 @@
package org.mitre.oauth2.web;
import java.util.Collection;
import java.util.Date;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
@ -144,17 +145,15 @@ public class DeviceEndpoint {
// create a user code, should be random but small and typable
String userCode = randomGenerator.generate();
// TODO: expiration
DeviceCode dc = deviceCodeService.createNewDeviceCode(deviceCode, userCode, requestedScopes, client, parameters);
model.put(JsonEntityView.ENTITY, ImmutableMap.of(
"device_code", deviceCode,
"user_code", userCode,
"verification_uri", config.getIssuer() + URL
"verification_uri", config.getIssuer() + URL,
"expires_in", client.getDeviceCodeValiditySeconds()
));
DeviceCode dc = new DeviceCode(deviceCode, userCode, requestedScopes, clientId, parameters);
deviceCodeService.save(dc);
return JsonEntityView.VIEWNAME;
@ -177,6 +176,24 @@ public class DeviceEndpoint {
// look up the request based on the user code
DeviceCode dc = deviceCodeService.lookUpByUserCode(userCode);
// we couldn't find the device code
if (dc == null) {
// TODO: return error
return "error";
}
// make sure the code hasn't expired yet
if (dc.getExpiration() != null && dc.getExpiration().before(new Date())) {
// TODO: return an error
return "error";
}
// make sure the device code hasn't already been approved
if (dc.isApproved()) {
// TODO: return an error
return "error";
}
ClientDetailsEntity client = clientService.loadClientByClientId(dc.getClientId());
model.put("client", client);
@ -210,16 +227,30 @@ public class DeviceEndpoint {
@PreAuthorize("hasRole('ROLE_USER')")
@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) {
public String approveDevice(@RequestParam("user_code") String userCode, @RequestParam(value = "user_oauth_approval") Boolean approve, ModelMap model, Authentication auth, HttpSession session) {
AuthorizationRequest authorizationRequest = (AuthorizationRequest) session.getAttribute("authorizationRequest");
DeviceCode dc = (DeviceCode) session.getAttribute("deviceCode");
// make sure the form that was submitted is the one that we were expecting
if (!dc.getUserCode().equals(userCode)) {
// TODO: return an error
return "error";
}
// make sure the code hasn't expired yet
if (dc.getExpiration() != null && dc.getExpiration().before(new Date())) {
// TODO: return an error
return "error";
}
// user did not approve
if (!approve) {
// TODO: return an error
return "error";
}
// create an OAuth request for storage
OAuth2Request o2req = oAuth2RequestFactory.createOAuth2Request(authorizationRequest);
OAuth2Authentication o2Auth = new OAuth2Authentication(o2req, auth);