Hi Francesco,
I'll try something - not sure if next week or the one after.In the meantime 
I've quickly developed this class (attached).
It really needs more polishing: - I'd like to do a bridge to 
RestServiceExceptionMapper so we have a central place for error handlig (but 
it's JAX-RS and I'm at servlet layer here). - I'd need to add types and codes 
in ClientExceptionType - I'd need to add the same customisation for 
syncopeAccessDeniedHandler
Thanks,Adrian
    Le vendredi 15 septembre 2017 à 15:28:56 UTC+2, Francesco Chicchiriccò 
<[email protected]> a écrit :  
 
  On 15/09/2017 15:17, Adrian Gonzalez wrote:
  
  Hello, 
  I'm using Syncope 2.0.5. I'm calling /self REST API with invalid credentials 
(withcontent-type: application/json .) /self returns HTML code and not a JSON 
message body. 
  i.e.  ```
 curl -X GET \   http://localhost:9080/syncope/rest/users/self \   -H 
'authorization: Basic YmVsbGluaTpiZWxsaW5pNjY1' \   -H 'cache-control: 
no-cache' \   -H 'content-type: application/json' \   -H 'postman-token: 
477ebc5a-6350-4ba5-a8a2-4d6ecea31712'  ```
 
  Returns an HTML error page i.e; <!doctype html><html 
lang="en"><head><title>HTTP Status 401 – Unauthorized</title>...<b>Message</b> 
User bellini not authenticated</p><p><b>Description</b> The request has not 
been applied because it lacks valid authentication credentials for the target 
resource.</p><hr class="line" /><h3>Apache Tomcat/8.5.20</h3></body></html> 
  Shouldn't it be valid json (since I requested json ?) 
  The pb is that when I use the syncope client REST API, I get:  2017-09-15 
11:17:13.625 -ERROR [http-apr-9080-exec-6] 
org.apache.cxf.jaxrs.utils.JAXRSUtils    : No message body reader has been 
found for class java.util.List, ContentType: text/html;charset=utf-8 2017-09-15 
11:17:13.626 -DEBUG [http-apr-9080-exec-6] 
o.a.s.c.lib.RestClientExceptionMapper    : Could not read 
org.apache.syncope.common.lib.to.ErrorTO list, attempting to read headers... 
javax.ws.rs.client.ResponseProcessingException: No message body reader has been 
found for class java.util.List, ContentType: text/html;charset=utf-8  
atorg.apache.cxf.jaxrs.impl.ResponseImpl.reportMessageHandlerProblem(ResponseImpl.java:439)
  atorg.apache.cxf.jaxrs.impl.ResponseImpl.doReadEntity(ResponseImpl.java:390)  
at org.apache.cxf.jaxrs.impl.ResponseImpl.readEntity(ResponseImpl.java:326)  at 
org.apache.cxf.jaxrs.impl.ResponseImpl.readEntity(ResponseImpl.java:314)  
atorg.apache.syncope.client.lib.RestClientExceptionMapper.checkSyncopeClientCompositeException(RestClientExceptionMapper.java:98)
  
atorg.apache.syncope.client.lib.RestClientExceptionMapper.fromResponse(RestClientExceptionMapper.java:53)
  
atorg.apache.syncope.client.lib.RestClientExceptionMapper.fromResponse(RestClientExceptionMapper.java:42)
  
atorg.apache.cxf.jaxrs.client.ClientProxyImpl.checkResponse(ClientProxyImpl.java:313)
  
atorg.apache.cxf.jaxrs.client.ClientProxyImpl.handleResponse(ClientProxyImpl.java:876)
  
atorg.apache.cxf.jaxrs.client.ClientProxyImpl.doChainedInvocation(ClientProxyImpl.java:789)
  
atorg.apache.cxf.jaxrs.client.ClientProxyImpl.invoke(ClientProxyImpl.java:235)  
at com.sun.proxy.$Proxy641.read(Unknown Source)  at 
org.apache.syncope.client.lib.SyncopeClient.self(SyncopeClient.java:132) 
  Should we modifythe 
org.apache.syncope.core.spring.security.SyncopeBasicAuthenticationEntryPoint ? 
(not super because we'd need to handle manually XML and JSON formatting)  
 
 Hi Adrian,
 your proposal makes sense but I remember (not well enough, unfortunately) that 
there were some related issues when attempting to change the 
AuthenticationEntryPoint due to some Spring Security internals.
 
 Anyway, your contribution is more than welcome, as usual!
 
 Regards.
 -- 
Francesco Chicchiriccò

Tirasa - Open Source Excellence
http://www.tirasa.net/

Member at The Apache Software Foundation
Syncope, Cocoon, Olingo, CXF, OpenJPA, PonyMail
http://home.apache.org/~ilgrosso/   
import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE;
import static org.springframework.http.MediaType.APPLICATION_XML_VALUE;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;

import org.apache.syncope.common.lib.to.ErrorTO;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.security.core.AuthenticationException;

import com.fasterxml.jackson.databind.ObjectMapper;

/**
 * Extend Syncope authenticationEntryPoint to handle JSON or XML exceptions.
 */
public class SyncopeBasicAuthenticationEntryPoint
        extends org.apache.syncope.core.spring.security.SyncopeBasicAuthenticationEntryPoint {

    private static final Logger LOGGER = LoggerFactory.getLogger(SyncopeBasicAuthenticationEntryPoint.class);

    private ObjectMapper objectMapper;

    private String realmName;

    private JAXBContext jc;

    public SyncopeBasicAuthenticationEntryPoint() {
        this(new ObjectMapper());
        try {
            this.jc = JAXBContext.newInstance(ErrorTO.class);
        } catch (JAXBException e) {
            throw new IllegalStateException(String.format("Error creating JAXB ctx for %s: %s", ErrorTO.class.getName(), e), e);
        }
    }

    public SyncopeBasicAuthenticationEntryPoint(ObjectMapper objectMapper) {
        if (objectMapper == null) {
            throw new IllegalArgumentException("objectMapper parameter is required");
        }
        this.objectMapper = objectMapper;
    }

    @Override
    public void commence(final HttpServletRequest request, final HttpServletResponse response,
            final AuthenticationException authException) throws IOException, ServletException {

        if (APPLICATION_JSON_VALUE.equals(request.getContentType())) {
            handleJsonException(request, response, authException);
        } else if (APPLICATION_XML_VALUE.equals(request.getContentType())) {
            handleXmlException(request, response, authException);
        } else {
            super.commence(request, response, authException);
        }
    }

    private void handleXmlException(HttpServletRequest request, HttpServletResponse response,
            AuthenticationException authException) throws IOException {
        ErrorTO errorTO = getErrorTO(authException);
        addHeadersToResponse(errorTO, authException, response);
        Marshaller marshaller = null;
        try {
            marshaller = jc.createMarshaller();
            marshaller.marshal(errorTO, response.getOutputStream());
        } catch (JAXBException e) {
            throw new RuntimeException(String.format("Error serializing %s to XML: %s", errorTO, e), e);
        }
    }

    private void handleJsonException(HttpServletRequest request, HttpServletResponse response,
            AuthenticationException authException) throws IOException {
        ErrorTO errorTO = getErrorTO(authException);
        addHeadersToResponse(errorTO, authException, response);
        objectMapper.writeValue(response.getOutputStream(), errorTO);
    }

    /**
     * Adds the same headers as Spring Security BasicAuthenticationEntryPoint
     */
    private void addHeadersToResponse(ErrorTO errorTO, AuthenticationException authException, HttpServletResponse response) {
        response.addHeader("X-Application-Error-Info", authException.getMessage());
        response.addHeader("WWW-Authenticate", "Basic realm=\"" + realmName + "\"");
        response.setStatus(errorTO.getStatus());
    }

    /**
     * Code similar to RestServiceExceptionMapper but not tied to
     * jax-rs classes (perhaps we can do a bridge ?).
     */
    private ErrorTO getErrorTO(AuthenticationException e) {
        ErrorTO errorTO = new ErrorTO();
        errorTO.setStatus(HttpStatus.UNAUTHORIZED.value());
        return errorTO;
    }

    @Override
    public void setRealmName(String realmName) {
        super.setRealmName(realmName);
        this.realmName = realmName;
    }
}

Reply via email to