integration changes to client
parent
1a1ae4c5b5
commit
b8c953281e
|
@ -10,8 +10,10 @@ import java.security.KeyPairGenerator;
|
||||||
import java.security.PrivateKey;
|
import java.security.PrivateKey;
|
||||||
import java.security.PublicKey;
|
import java.security.PublicKey;
|
||||||
import java.security.Signature;
|
import java.security.Signature;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.Enumeration;
|
import java.util.Enumeration;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
|
|
||||||
|
@ -21,14 +23,13 @@ import javax.servlet.http.HttpServletRequest;
|
||||||
import javax.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
import org.apache.commons.codec.binary.Base64;
|
import org.apache.commons.codec.binary.Base64;
|
||||||
import org.apache.commons.logging.Log;
|
|
||||||
import org.apache.commons.logging.LogFactory;
|
|
||||||
import org.apache.http.auth.AuthScope;
|
import org.apache.http.auth.AuthScope;
|
||||||
import org.apache.http.auth.UsernamePasswordCredentials;
|
import org.apache.http.auth.UsernamePasswordCredentials;
|
||||||
import org.apache.http.client.HttpClient;
|
import org.apache.http.client.HttpClient;
|
||||||
import org.apache.http.impl.client.DefaultHttpClient;
|
import org.apache.http.impl.client.DefaultHttpClient;
|
||||||
import org.mitre.openid.connect.model.IdToken;
|
import org.mitre.openid.connect.model.IdToken;
|
||||||
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
|
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
|
||||||
|
import org.springframework.security.authentication.AuthenticationServiceException;
|
||||||
import org.springframework.security.core.Authentication;
|
import org.springframework.security.core.Authentication;
|
||||||
import org.springframework.security.core.AuthenticationException;
|
import org.springframework.security.core.AuthenticationException;
|
||||||
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
|
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
|
||||||
|
@ -85,9 +86,6 @@ import com.google.gson.JsonParser;
|
||||||
public class OpenIdConnectAuthenticationFilter extends
|
public class OpenIdConnectAuthenticationFilter extends
|
||||||
AbstractAuthenticationProcessingFilter {
|
AbstractAuthenticationProcessingFilter {
|
||||||
|
|
||||||
private static Log logger = LogFactory
|
|
||||||
.getLog(OpenIdConnectAuthenticationFilter.class);
|
|
||||||
|
|
||||||
private final static int HTTP_SOCKET_TIMEOUT = 30000;
|
private final static int HTTP_SOCKET_TIMEOUT = 30000;
|
||||||
private final static String SCOPE = "openid";
|
private final static String SCOPE = "openid";
|
||||||
private final static int KEY_SIZE = 1024;
|
private final static int KEY_SIZE = 1024;
|
||||||
|
@ -259,9 +257,18 @@ public class OpenIdConnectAuthenticationFilter extends
|
||||||
}
|
}
|
||||||
|
|
||||||
// prepend the spec necessary scope
|
// prepend the spec necessary scope
|
||||||
setScope(SCOPE + ((scope != null && !scope.isEmpty()) ? " " + scope : ""));
|
setScope((scope != null && !scope.isEmpty()) ? SCOPE + " " + scope
|
||||||
|
: SCOPE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* (non-Javadoc)
|
||||||
|
*
|
||||||
|
* @see org.springframework.security.web.authentication.
|
||||||
|
* AbstractAuthenticationProcessingFilter
|
||||||
|
* #attemptAuthentication(javax.servlet.http.HttpServletRequest,
|
||||||
|
* javax.servlet.http.HttpServletResponse)
|
||||||
|
*/
|
||||||
/*
|
/*
|
||||||
* (non-Javadoc)
|
* (non-Javadoc)
|
||||||
*
|
*
|
||||||
|
@ -275,6 +282,8 @@ public class OpenIdConnectAuthenticationFilter extends
|
||||||
HttpServletResponse response) throws AuthenticationException,
|
HttpServletResponse response) throws AuthenticationException,
|
||||||
IOException, ServletException {
|
IOException, ServletException {
|
||||||
|
|
||||||
|
final boolean debug = logger.isDebugEnabled();
|
||||||
|
|
||||||
if (request.getParameter("error") != null) {
|
if (request.getParameter("error") != null) {
|
||||||
|
|
||||||
// Handle Authorization Endpoint error
|
// Handle Authorization Endpoint error
|
||||||
|
@ -313,10 +322,12 @@ public class OpenIdConnectAuthenticationFilter extends
|
||||||
httpClient.getParams().setParameter("http.socket.timeout",
|
httpClient.getParams().setParameter("http.socket.timeout",
|
||||||
new Integer(httpSocketTimeout));
|
new Integer(httpSocketTimeout));
|
||||||
|
|
||||||
UsernamePasswordCredentials credentials = new UsernamePasswordCredentials(
|
//
|
||||||
clientId, clientSecret);
|
// TODO: basic auth is untested (it wasn't working last I tested)
|
||||||
((DefaultHttpClient) httpClient).getCredentialsProvider()
|
// UsernamePasswordCredentials credentials = new UsernamePasswordCredentials(
|
||||||
.setCredentials(AuthScope.ANY, credentials);
|
// clientId, clientSecret);
|
||||||
|
// ((DefaultHttpClient) httpClient).getCredentialsProvider()
|
||||||
|
// .setCredentials(AuthScope.ANY, credentials);
|
||||||
|
|
||||||
HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory(
|
HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory(
|
||||||
httpClient);
|
httpClient);
|
||||||
|
@ -326,7 +337,18 @@ public class OpenIdConnectAuthenticationFilter extends
|
||||||
MultiValueMap<String, String> form = new LinkedMultiValueMap<String, String>();
|
MultiValueMap<String, String> form = new LinkedMultiValueMap<String, String>();
|
||||||
form.add("grant_type", "authorization_code");
|
form.add("grant_type", "authorization_code");
|
||||||
form.add("code", authorizationGrant);
|
form.add("code", authorizationGrant);
|
||||||
form.add("redirect_uri", buildRedirectURI(request));
|
form.add("redirect_uri",
|
||||||
|
buildRedirectURI(request, new String[] { "code" }));
|
||||||
|
|
||||||
|
// pass clientId and clientSecret in post of request
|
||||||
|
form.add("client_id", clientId);
|
||||||
|
form.add("client_secret", clientSecret);
|
||||||
|
|
||||||
|
|
||||||
|
if (debug) {
|
||||||
|
logger.debug("tokenEndpointURI = " + tokenEndpointURI);
|
||||||
|
logger.debug("form = " + form);
|
||||||
|
}
|
||||||
|
|
||||||
String jsonString = null;
|
String jsonString = null;
|
||||||
|
|
||||||
|
@ -341,7 +363,8 @@ public class OpenIdConnectAuthenticationFilter extends
|
||||||
+ httpClientErrorException.getStatusText() + " : "
|
+ httpClientErrorException.getStatusText() + " : "
|
||||||
+ httpClientErrorException.getMessage());
|
+ httpClientErrorException.getMessage());
|
||||||
|
|
||||||
return null;
|
throw new AuthenticationServiceException(
|
||||||
|
"Unable to obtain Access Token.");
|
||||||
}
|
}
|
||||||
|
|
||||||
JsonElement jsonRoot = new JsonParser().parse(jsonString);
|
JsonElement jsonRoot = new JsonParser().parse(jsonString);
|
||||||
|
@ -355,7 +378,9 @@ public class OpenIdConnectAuthenticationFilter extends
|
||||||
|
|
||||||
logger.error("Token Endpoint returned: " + error);
|
logger.error("Token Endpoint returned: " + error);
|
||||||
|
|
||||||
return null;
|
throw new AuthenticationServiceException(
|
||||||
|
"Unable to obtain Access Token. Token Endpoint returned: "
|
||||||
|
+ error);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
|
@ -376,7 +401,8 @@ public class OpenIdConnectAuthenticationFilter extends
|
||||||
logger.error("Problem parsing id_token: " + e);
|
logger.error("Problem parsing id_token: " + e);
|
||||||
// e.printStackTrace();
|
// e.printStackTrace();
|
||||||
|
|
||||||
return null;
|
throw new AuthenticationServiceException(
|
||||||
|
"Problem parsing id_token return from Token endpoint: " + e);
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
@ -385,7 +411,8 @@ public class OpenIdConnectAuthenticationFilter extends
|
||||||
|
|
||||||
logger.error("Token Endpoint did not return a token_id");
|
logger.error("Token Endpoint did not return a token_id");
|
||||||
|
|
||||||
return null;
|
throw new AuthenticationServiceException(
|
||||||
|
"Token Endpoint did not return a token_id");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle Check ID Endpoint interaction
|
// Handle Check ID Endpoint interaction
|
||||||
|
@ -418,7 +445,8 @@ public class OpenIdConnectAuthenticationFilter extends
|
||||||
+ httpClientErrorException.getStatusText()
|
+ httpClientErrorException.getStatusText()
|
||||||
+ " : " + httpClientErrorException.getMessage());
|
+ " : " + httpClientErrorException.getMessage());
|
||||||
|
|
||||||
return null;
|
throw new AuthenticationServiceException(
|
||||||
|
"Unable check token.");
|
||||||
}
|
}
|
||||||
|
|
||||||
jsonRoot = new JsonParser().parse(jsonString);
|
jsonRoot = new JsonParser().parse(jsonString);
|
||||||
|
@ -453,14 +481,20 @@ public class OpenIdConnectAuthenticationFilter extends
|
||||||
+ NONCE_SIGNATURE_COOKIE_NAME
|
+ NONCE_SIGNATURE_COOKIE_NAME
|
||||||
+ " failed.");
|
+ " failed.");
|
||||||
|
|
||||||
return null;
|
throw new AuthenticationServiceException(
|
||||||
|
"Possible replay attack detected! "
|
||||||
|
+ "The comparison of the nonce in the returned "
|
||||||
|
+ "ID Token to the signed session "
|
||||||
|
+ NONCE_SIGNATURE_COOKIE_NAME
|
||||||
|
+ " failed.");
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
logger.error(NONCE_SIGNATURE_COOKIE_NAME
|
logger.error(NONCE_SIGNATURE_COOKIE_NAME
|
||||||
+ " was found, but was null or empty.");
|
+ " was found, but was null or empty.");
|
||||||
|
|
||||||
return null;
|
throw new AuthenticationServiceException(NONCE_SIGNATURE_COOKIE_NAME
|
||||||
|
+ " was found, but was null or empty.");
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
@ -468,14 +502,15 @@ public class OpenIdConnectAuthenticationFilter extends
|
||||||
logger.error(NONCE_SIGNATURE_COOKIE_NAME
|
logger.error(NONCE_SIGNATURE_COOKIE_NAME
|
||||||
+ " cookie was not found.");
|
+ " cookie was not found.");
|
||||||
|
|
||||||
return null;
|
throw new AuthenticationServiceException(NONCE_SIGNATURE_COOKIE_NAME
|
||||||
|
+ " cookie was not found.");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create an Authentication object for the token, and
|
// Create an Authentication object for the token, and
|
||||||
// return.
|
// return.
|
||||||
|
|
||||||
OpenIdConnectAuthenticationToken token = new OpenIdConnectAuthenticationToken(
|
OpenIdConnectAuthenticationToken token = new OpenIdConnectAuthenticationToken(
|
||||||
idToken, userId);
|
userId, idToken);
|
||||||
|
|
||||||
Authentication authentication = this
|
Authentication authentication = this
|
||||||
.getAuthenticationManager().authenticate(token);
|
.getAuthenticationManager().authenticate(token);
|
||||||
|
@ -495,7 +530,8 @@ public class OpenIdConnectAuthenticationFilter extends
|
||||||
urlVariables.put("response_type", "code");
|
urlVariables.put("response_type", "code");
|
||||||
urlVariables.put("client_id", clientId);
|
urlVariables.put("client_id", clientId);
|
||||||
urlVariables.put("scope", scope);
|
urlVariables.put("scope", scope);
|
||||||
urlVariables.put("redirect_uri", buildRedirectURI(request));
|
urlVariables.put("redirect_uri",
|
||||||
|
buildRedirectURI(request, null));
|
||||||
|
|
||||||
// Create a string value used to associate a user agent session
|
// Create a string value used to associate a user agent session
|
||||||
// with an ID Token to mitigate replay attacks. The value is
|
// with an ID Token to mitigate replay attacks. The value is
|
||||||
|
@ -530,9 +566,15 @@ public class OpenIdConnectAuthenticationFilter extends
|
||||||
*
|
*
|
||||||
* @param request
|
* @param request
|
||||||
* the current request which is being processed by this filter
|
* the current request which is being processed by this filter
|
||||||
* @return The redirect_uri.
|
* @param ingoreParameters
|
||||||
|
* an array of parameter names to ignore.
|
||||||
|
* @return
|
||||||
*/
|
*/
|
||||||
private String buildRedirectURI(HttpServletRequest request) {
|
private String buildRedirectURI(HttpServletRequest request,
|
||||||
|
String[] ingoreParameters) {
|
||||||
|
|
||||||
|
List<String> ignore = (ingoreParameters != null) ? Arrays
|
||||||
|
.asList(ingoreParameters) : null;
|
||||||
|
|
||||||
boolean isFirst = true;
|
boolean isFirst = true;
|
||||||
|
|
||||||
|
@ -542,22 +584,25 @@ public class OpenIdConnectAuthenticationFilter extends
|
||||||
.hasMoreElements();) {
|
.hasMoreElements();) {
|
||||||
|
|
||||||
String name = (String) e.nextElement();
|
String name = (String) e.nextElement();
|
||||||
// Assume for simplicity that there is only one value
|
|
||||||
String value = request.getParameter(name);
|
|
||||||
|
|
||||||
if (value == null) {
|
if ((ignore == null) || (!ignore.contains(name))) {
|
||||||
continue;
|
// Assume for simplicity that there is only one value
|
||||||
}
|
String value = request.getParameter(name);
|
||||||
|
|
||||||
if (isFirst) {
|
if (value == null) {
|
||||||
sb.append("?");
|
continue;
|
||||||
isFirst = false;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
sb.append(name).append("=").append(value);
|
if (isFirst) {
|
||||||
|
sb.append("?");
|
||||||
|
isFirst = false;
|
||||||
|
}
|
||||||
|
|
||||||
if (e.hasMoreElements()) {
|
sb.append(name).append("=").append(value);
|
||||||
sb.append("&");
|
|
||||||
|
if (e.hasMoreElements()) {
|
||||||
|
sb.append("&");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,10 +4,22 @@ import org.springframework.beans.factory.InitializingBean;
|
||||||
import org.springframework.security.authentication.AuthenticationProvider;
|
import org.springframework.security.authentication.AuthenticationProvider;
|
||||||
import org.springframework.security.core.Authentication;
|
import org.springframework.security.core.Authentication;
|
||||||
import org.springframework.security.core.AuthenticationException;
|
import org.springframework.security.core.AuthenticationException;
|
||||||
|
import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper;
|
||||||
|
import org.springframework.security.core.authority.mapping.NullAuthoritiesMapper;
|
||||||
|
import org.springframework.security.core.userdetails.AuthenticationUserDetailsService;
|
||||||
|
import org.springframework.security.core.userdetails.UserDetails;
|
||||||
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author mjwalsh
|
||||||
|
*
|
||||||
|
*/
|
||||||
public class OpenIdConnectAuthenticationProvider implements
|
public class OpenIdConnectAuthenticationProvider implements
|
||||||
AuthenticationProvider, InitializingBean {
|
AuthenticationProvider, InitializingBean {
|
||||||
|
|
||||||
|
private AuthenticationUserDetailsService<OpenIdConnectAuthenticationToken> userDetailsService;
|
||||||
|
private GrantedAuthoritiesMapper authoritiesMapper = new NullAuthoritiesMapper();
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* (non-Javadoc)
|
* (non-Javadoc)
|
||||||
*
|
*
|
||||||
|
@ -16,7 +28,8 @@ public class OpenIdConnectAuthenticationProvider implements
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void afterPropertiesSet() throws Exception {
|
public void afterPropertiesSet() throws Exception {
|
||||||
// TODO Auto-generated method stub
|
Assert.notNull(this.userDetailsService,
|
||||||
|
"The userDetailsService must be set");
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -26,16 +39,40 @@ public class OpenIdConnectAuthenticationProvider implements
|
||||||
* authenticate(org.springframework.security.core.Authentication)
|
* authenticate(org.springframework.security.core.Authentication)
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public Authentication authenticate(Authentication authentication)
|
public Authentication authenticate(final Authentication authentication)
|
||||||
throws AuthenticationException {
|
throws AuthenticationException {
|
||||||
|
|
||||||
|
if (!supports(authentication.getClass())) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
if (authentication instanceof OpenIdConnectAuthenticationToken) {
|
if (authentication instanceof OpenIdConnectAuthenticationToken) {
|
||||||
return authentication;
|
|
||||||
|
OpenIdConnectAuthenticationToken token = (OpenIdConnectAuthenticationToken) authentication;
|
||||||
|
|
||||||
|
UserDetails userDetails = userDetailsService.loadUserDetails(token);
|
||||||
|
|
||||||
|
return new OpenIdConnectAuthenticationToken(userDetails,
|
||||||
|
authoritiesMapper.mapAuthorities(userDetails
|
||||||
|
.getAuthorities()), token.getUserId(),
|
||||||
|
token.getIdToken());
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param authoritiesMapper
|
||||||
|
*/
|
||||||
|
public void setAuthoritiesMapper(GrantedAuthoritiesMapper authoritiesMapper) {
|
||||||
|
this.authoritiesMapper = authoritiesMapper;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUserDetailsService(
|
||||||
|
AuthenticationUserDetailsService<OpenIdConnectAuthenticationToken> userDetailsService) {
|
||||||
|
this.userDetailsService = userDetailsService;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* (non-Javadoc)
|
* (non-Javadoc)
|
||||||
*
|
*
|
||||||
|
@ -48,5 +85,4 @@ public class OpenIdConnectAuthenticationProvider implements
|
||||||
return OpenIdConnectAuthenticationToken.class
|
return OpenIdConnectAuthenticationToken.class
|
||||||
.isAssignableFrom(authentication);
|
.isAssignableFrom(authentication);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package org.mitre.openid.connect.client;
|
package org.mitre.openid.connect.client;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
|
||||||
import org.mitre.openid.connect.model.IdToken;
|
import org.mitre.openid.connect.model.IdToken;
|
||||||
import org.springframework.security.authentication.AbstractAuthenticationToken;
|
import org.springframework.security.authentication.AbstractAuthenticationToken;
|
||||||
|
@ -16,25 +17,46 @@ import org.springframework.security.core.SpringSecurityCoreVersion;
|
||||||
public class OpenIdConnectAuthenticationToken extends
|
public class OpenIdConnectAuthenticationToken extends
|
||||||
AbstractAuthenticationToken {
|
AbstractAuthenticationToken {
|
||||||
|
|
||||||
|
private final Object principle;
|
||||||
private final IdToken idToken;
|
private final IdToken idToken;
|
||||||
private final String userId;
|
private final String userId;
|
||||||
|
|
||||||
private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;
|
private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param principle
|
||||||
|
* @param authorities
|
||||||
|
* @param userId
|
||||||
|
* @param idToken
|
||||||
|
*/
|
||||||
|
public OpenIdConnectAuthenticationToken(Object principle,
|
||||||
|
Collection<? extends GrantedAuthority> authorities,
|
||||||
|
String userId, IdToken idToken) {
|
||||||
|
|
||||||
|
super(authorities);
|
||||||
|
|
||||||
|
this.principle = principle;
|
||||||
|
this.userId = userId;
|
||||||
|
this.idToken = idToken;
|
||||||
|
|
||||||
|
setAuthenticated(true);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param idToken
|
* @param idToken
|
||||||
* @param userId
|
* @param userId
|
||||||
*/
|
*/
|
||||||
public OpenIdConnectAuthenticationToken(IdToken idToken, String userId) {
|
public OpenIdConnectAuthenticationToken(String userId, IdToken idToken) {
|
||||||
|
|
||||||
super(new ArrayList<GrantedAuthority>(0));
|
super(new ArrayList<GrantedAuthority>(0));
|
||||||
this.idToken = idToken;
|
|
||||||
this.userId = userId;
|
|
||||||
|
|
||||||
// what do I set for the principle? the idToken?
|
|
||||||
|
|
||||||
setAuthenticated(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
this.principle = userId;
|
||||||
|
this.userId = userId;
|
||||||
|
this.idToken = idToken;
|
||||||
|
|
||||||
|
setAuthenticated(false);
|
||||||
|
}
|
||||||
|
|
||||||
/* (non-Javadoc)
|
/* (non-Javadoc)
|
||||||
* @see org.springframework.security.core.Authentication#getCredentials()
|
* @see org.springframework.security.core.Authentication#getCredentials()
|
||||||
*/
|
*/
|
||||||
|
@ -53,12 +75,10 @@ public class OpenIdConnectAuthenticationToken extends
|
||||||
@Override
|
@Override
|
||||||
public Object getPrincipal() {
|
public Object getPrincipal() {
|
||||||
// TODO Auto-generated method stub
|
// TODO Auto-generated method stub
|
||||||
return null;
|
return principle;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getUserId() {
|
public String getUserId() {
|
||||||
return userId;
|
return userId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue