added expiration to device codes
parent
9cb5377ce8
commit
548dad4e29
|
@ -17,6 +17,7 @@
|
||||||
|
|
||||||
package org.mitre.oauth2.model;
|
package org.mitre.oauth2.model;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
|
@ -33,6 +34,7 @@ import javax.persistence.JoinColumn;
|
||||||
import javax.persistence.ManyToOne;
|
import javax.persistence.ManyToOne;
|
||||||
import javax.persistence.MapKeyColumn;
|
import javax.persistence.MapKeyColumn;
|
||||||
import javax.persistence.Table;
|
import javax.persistence.Table;
|
||||||
|
import javax.persistence.Temporal;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author jricher
|
* @author jricher
|
||||||
|
@ -46,6 +48,7 @@ public class DeviceCode {
|
||||||
private String deviceCode;
|
private String deviceCode;
|
||||||
private String userCode;
|
private String userCode;
|
||||||
private Set<String> scope;
|
private Set<String> scope;
|
||||||
|
private Date expiration;
|
||||||
private String clientId;
|
private String clientId;
|
||||||
private Map<String, String> requestParameters;
|
private Map<String, String> requestParameters;
|
||||||
private boolean approved;
|
private boolean approved;
|
||||||
|
@ -61,7 +64,6 @@ public class DeviceCode {
|
||||||
this.scope = scope;
|
this.scope = scope;
|
||||||
this.clientId = clientId;
|
this.clientId = clientId;
|
||||||
this.requestParameters = params;
|
this.requestParameters = params;
|
||||||
this.setApproved(false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -133,6 +135,17 @@ public class DeviceCode {
|
||||||
this.scope = scope;
|
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
|
* @return the clientId
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -17,6 +17,10 @@
|
||||||
|
|
||||||
package org.mitre.oauth2.service;
|
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.mitre.oauth2.model.DeviceCode;
|
||||||
import org.springframework.security.oauth2.provider.ClientDetails;
|
import org.springframework.security.oauth2.provider.ClientDetails;
|
||||||
import org.springframework.security.oauth2.provider.OAuth2Authentication;
|
import org.springframework.security.oauth2.provider.OAuth2Authentication;
|
||||||
|
@ -28,11 +32,6 @@ import org.springframework.security.oauth2.provider.OAuth2Request;
|
||||||
*/
|
*/
|
||||||
public interface DeviceCodeService {
|
public interface DeviceCodeService {
|
||||||
|
|
||||||
/**
|
|
||||||
* @param dc
|
|
||||||
*/
|
|
||||||
public DeviceCode save(DeviceCode dc);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param userCode
|
* @param userCode
|
||||||
* @return
|
* @return
|
||||||
|
@ -52,4 +51,14 @@ public interface DeviceCodeService {
|
||||||
*/
|
*/
|
||||||
public DeviceCode consumeDeviceCode(String deviceCode, ClientDetails client);
|
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);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -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";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -17,7 +17,10 @@
|
||||||
|
|
||||||
package org.mitre.oauth2.token;
|
package org.mitre.oauth2.token;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
import org.mitre.oauth2.exception.AuthorizationPendingException;
|
import org.mitre.oauth2.exception.AuthorizationPendingException;
|
||||||
|
import org.mitre.oauth2.exception.DeviceCodeExpiredException;
|
||||||
import org.mitre.oauth2.model.DeviceCode;
|
import org.mitre.oauth2.model.DeviceCode;
|
||||||
import org.mitre.oauth2.service.DeviceCodeService;
|
import org.mitre.oauth2.service.DeviceCodeService;
|
||||||
import org.mitre.oauth2.web.DeviceEndpoint;
|
import org.mitre.oauth2.web.DeviceEndpoint;
|
||||||
|
@ -71,18 +74,23 @@ public class DeviceTokenGranter extends AbstractTokenGranter {
|
||||||
|
|
||||||
if (dc != null) {
|
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
|
// inherit the (approved) scopes from the original request
|
||||||
tokenRequest.setScope(dc.getScope());
|
tokenRequest.setScope(dc.getScope());
|
||||||
|
|
||||||
OAuth2Authentication auth = new OAuth2Authentication(getRequestFactory().createOAuth2Request(client, tokenRequest), dc.getAuthenticationHolder().getUserAuth());
|
OAuth2Authentication auth = new OAuth2Authentication(getRequestFactory().createOAuth2Request(client, tokenRequest), dc.getAuthenticationHolder().getUserAuth());
|
||||||
|
|
||||||
return auth;
|
return auth;
|
||||||
} else {
|
|
||||||
|
|
||||||
// still waiting for approval
|
|
||||||
throw new AuthorizationPendingException("Authorization pending for code " + deviceCode);
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
throw new InvalidGrantException("Invalid device code: " + deviceCode);
|
throw new InvalidGrantException("Invalid device code: " + deviceCode);
|
||||||
|
|
|
@ -18,6 +18,7 @@
|
||||||
package org.mitre.oauth2.web;
|
package org.mitre.oauth2.web;
|
||||||
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
import java.util.Date;
|
||||||
import java.util.LinkedHashSet;
|
import java.util.LinkedHashSet;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
@ -144,17 +145,15 @@ public class DeviceEndpoint {
|
||||||
// create a user code, should be random but small and typable
|
// create a user code, should be random but small and typable
|
||||||
String userCode = randomGenerator.generate();
|
String userCode = randomGenerator.generate();
|
||||||
|
|
||||||
// TODO: expiration
|
DeviceCode dc = deviceCodeService.createNewDeviceCode(deviceCode, userCode, requestedScopes, client, parameters);
|
||||||
|
|
||||||
model.put(JsonEntityView.ENTITY, ImmutableMap.of(
|
model.put(JsonEntityView.ENTITY, ImmutableMap.of(
|
||||||
"device_code", deviceCode,
|
"device_code", deviceCode,
|
||||||
"user_code", userCode,
|
"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;
|
return JsonEntityView.VIEWNAME;
|
||||||
|
|
||||||
|
@ -177,6 +176,24 @@ public class DeviceEndpoint {
|
||||||
// look up the request based on the user code
|
// look up the request based on the user code
|
||||||
DeviceCode dc = deviceCodeService.lookUpByUserCode(userCode);
|
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());
|
ClientDetailsEntity client = clientService.loadClientByClientId(dc.getClientId());
|
||||||
|
|
||||||
model.put("client", client);
|
model.put("client", client);
|
||||||
|
@ -210,16 +227,30 @@ public class DeviceEndpoint {
|
||||||
|
|
||||||
@PreAuthorize("hasRole('ROLE_USER')")
|
@PreAuthorize("hasRole('ROLE_USER')")
|
||||||
@RequestMapping(value = "/" + USER_URL + "/approve", method = RequestMethod.POST)
|
@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");
|
AuthorizationRequest authorizationRequest = (AuthorizationRequest) session.getAttribute("authorizationRequest");
|
||||||
DeviceCode dc = (DeviceCode) session.getAttribute("deviceCode");
|
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)) {
|
if (!dc.getUserCode().equals(userCode)) {
|
||||||
// TODO: return an error
|
// TODO: return an error
|
||||||
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";
|
||||||
|
}
|
||||||
|
|
||||||
|
// user did not approve
|
||||||
|
if (!approve) {
|
||||||
|
// TODO: return an error
|
||||||
|
return "error";
|
||||||
|
}
|
||||||
|
|
||||||
|
// create an OAuth request for storage
|
||||||
OAuth2Request o2req = oAuth2RequestFactory.createOAuth2Request(authorizationRequest);
|
OAuth2Request o2req = oAuth2RequestFactory.createOAuth2Request(authorizationRequest);
|
||||||
OAuth2Authentication o2Auth = new OAuth2Authentication(o2req, auth);
|
OAuth2Authentication o2Auth = new OAuth2Authentication(o2req, auth);
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue