added expiration to device codes
parent
9cb5377ce8
commit
548dad4e29
|
@ -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
|
||||
*/
|
||||
|
|
|
@ -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);
|
||||
|
||||
}
|
||||
|
|
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;
|
||||
|
||||
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);
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
Loading…
Reference in New Issue