Skip to content

Commit

Permalink
Aggiunta gestione regole di hardening al servizio pubblico di POST Pe…
Browse files Browse the repository at this point in the history
…ndenze delle API-Pagamento V2.

Effettuate correzioni al servizio di login delle API-User:
Il servizio di Login ora consente di loggarsi senza specificare una url dove venire ridiretti.
Il servizio di logout ora consente di effettuare l'operazione e di essere rediretti ad una url.
  • Loading branch information
pintorig committed Aug 5, 2021
1 parent b62d202 commit f27a10b
Show file tree
Hide file tree
Showing 11 changed files with 661 additions and 18 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -1885,3 +1885,88 @@ When method get
Then status 200


@test23
Scenario: Caricamento di un tracciato in formato CSV valido con versamento con soglia 60 giorni

* set patchValue.richiesta = encodeBase64InputStream(read('msg/freemarker-request.ftl'))
* set patchValue.risposta = encodeBase64InputStream(read('msg/freemarker-response.ftl'))

Given url backofficeBaseurl
And path '/configurazioni'
And headers basicAutenticationHeader
And request
"""
[
{
op: "REPLACE",
path: "/tracciatoCsv",
value: #(patchValue)
}
]
"""
When method patch
Then status 200

* call read('classpath:configurazione/v1/operazioni-resetCache.feature')

* def idPendenza = getCurrentTimeMillis()
* def numeroAvviso = buildNumeroAvviso(dominio, applicazione)
* def tracciato = karate.readAsString('classpath:test/api/backoffice/v1/tracciati/post/msg/tracciato-pendenze-v9.csv')
* def tracciato = replace(tracciato,"{idA2A}", idA2A);
* def tracciato = replace(tracciato,"{idPendenza}", idPendenza);
* def tracciato = replace(tracciato,"{idDominio}", idDominio);
* def tracciato = replace(tracciato,"{numeroAvviso}", numeroAvviso);
* def tracciato = replace(tracciato,"{ibanAccredito}", ibanAccredito);
* def tracciato = replace(tracciato,"{ibanAppoggio}", ibanAccreditoPostale);
* def tracciato = replace(tracciato,"{tipoPendenza}", codEntrataSegreteria);
* def tracciato = replace(tracciato,"{importo}", importo);
* def tracciato = replace(tracciato,"{importo_voce}", importo_voce);

Given url backofficeBaseurl
And path 'pendenze', 'tracciati', idDominio, codEntrataSegreteria
And headers { 'Content-Type' : 'text/csv' }
And headers basicAutenticationHeader
And request tracciato
When method post
Then status 201

* def idTracciato = response.id

Given url backofficeBaseurl
And path 'operazioni', 'elaborazioneTracciatiPendenze'
And headers basicAutenticationHeader
When method get

Given url backofficeBaseurl
And path 'pendenze', 'tracciati', idTracciato
And headers basicAutenticationHeader
And retry until response.stato == 'ESEGUITO'
When method get
Then match response contains { descrizioneStato: '##null' }
Then match response.numeroOperazioniTotali == 1
Then match response.numeroOperazioniEseguite == 1
Then match response.numeroOperazioniFallite == 0
Then match response.numeroAvvisiTotali == 1
Then match response.numeroAvvisiStampati == 1
Then match response.numeroAvvisiFalliti == 0
Then match response.stampaAvvisi == true

Given url backofficeBaseurl
And path 'pendenze', 'tracciati', idTracciato, 'stampe'
And headers basicAutenticationHeader
When method get
Then status 200

Given url backofficeBaseurl
And path 'pendenze', 'tracciati', idTracciato, 'richiesta'
And headers basicAutenticationHeader
When method get
Then status 200

Given url backofficeBaseurl
And path 'pendenze', 'tracciati', idTracciato, 'esito'
And headers basicAutenticationHeader
When method get
Then status 200


Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ public static GovpayConfig newInstance(InputStream is) throws Exception {
private String templateProspettoRiscossioni;

private Properties apiUserLoginRedirectURLs;
private Properties apiUserLogoutRedirectURLs;

private Integer batchCaricamentoTracciatiNumeroVersamentiDaCaricarePerThread;
private Integer batchCaricamentoTracciatiNumeroAvvisiDaStamparePerThread;
Expand Down Expand Up @@ -202,6 +203,7 @@ public GovpayConfig(InputStream is) throws Exception {
this.templateProspettoRiscossioni = null;

this.apiUserLoginRedirectURLs = new Properties();
this.apiUserLogoutRedirectURLs = new Properties();

this.batchCaricamentoTracciatiNumeroVersamentiDaCaricarePerThread = 100;
this.batchCaricamentoTracciatiNumeroAvvisiDaStamparePerThread = 100;
Expand Down Expand Up @@ -617,6 +619,9 @@ public void readProperties() throws Exception {
Map<String, String> redirectURLs = getProperties("it.govpay.login-redirect.",this.props, false, log);
this.apiUserLoginRedirectURLs.putAll(redirectURLs);

Map<String, String> logoutRedirectURLs = getProperties("it.govpay.logout-redirect.",this.props, false, log);
this.apiUserLogoutRedirectURLs.putAll(logoutRedirectURLs);

String dimensioneMassimaListaRisultatiString = getProperty("it.govpay.api.find.maxRisultatiPerPagina", this.props, false, log);
try{
this.dimensioneMassimaListaRisultati = Integer.parseInt(dimensioneMassimaListaRisultatiString);
Expand Down Expand Up @@ -967,6 +972,10 @@ public String getTemplateProspettoRiscossioni() {
public Properties getApiUserLoginRedirectURLs() {
return apiUserLoginRedirectURLs;
}

public Properties getApiUserLogoutRedirectURLs() {
return apiUserLogoutRedirectURLs;
}

public Properties getAutenticazioneSSLHeaderProperties() {
return autenticazioneSSLHeaderProperties;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -253,11 +253,16 @@
<b:constructor-arg index="0" value="/rs/public/v2/pagamenti" />
<b:constructor-arg index="1" value="POST" />
</b:bean>
<b:bean id="pendenzaPostRequestMatcherV2" class="it.govpay.rs.v1.authentication.hardening.matcher.HardeningAntPathRequestMatcher">
<b:constructor-arg index="0" value="/rs/public/v2/pendenze/**" />
<b:constructor-arg index="1" value="POST" />
</b:bean>
<http auto-config="false" use-expressions="true" create-session="stateless" entry-point-ref="http403ForbiddenEntryPointPublic" pattern="/rs/public/v2/**">
<csrf disabled="true"/>
<intercept-url pattern="/rs/public/v2/avvisi/**" access="isAnonymous()" method="GET" request-matcher-ref="avvisiGetRequestMatcherV2" />
<intercept-url pattern="/rs/public/v2/pagamenti" access="isAnonymous()" method="POST" request-matcher-ref="pagamentoPostRequestMatcherV2" />
<intercept-url pattern="/rs/public/v2/pendenze/**" access="isAnonymous()" method="POST" />
<intercept-url pattern="/rs/public/v2/pendenze/**" access="isAnonymous()" method="POST" request-matcher-ref="pendenzaPostRequestMatcherV2" />
<intercept-url pattern="/rs/public/v2/pendenze/**" access="isAnonymous()" method="GET" />
<intercept-url pattern="/rs/public/v2/pagamenti/**" access="isAnonymous()" method="GET" />
<intercept-url pattern="/rs/public/v2/domini/**" access="isAnonymous()" method="GET" />
<intercept-url pattern="/rs/public/v2/rpp/**" access="isAnonymous()" />
Expand Down
10 changes: 10 additions & 0 deletions wars/api-user/src/main/java/it/govpay/user/v1/Login.java
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,16 @@ public Response login(@Context UriInfo uriInfo, @Context HttpHeaders httpHeaders
return this.controller.login(this.getUser(), uriInfo, httpHeaders, urlID);
}

@GET
@Path("/")


public Response loginSenzaRedirect(@Context UriInfo uriInfo, @Context HttpHeaders httpHeaders){
this.controller.setContext(this.getContext());
this.controller.setRequestResponse(this.request, this.response);
return this.controller.login(this.getUser(), uriInfo, httpHeaders, null);
}

}


14 changes: 12 additions & 2 deletions wars/api-user/src/main/java/it/govpay/user/v1/Logout.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.Response;
Expand All @@ -28,13 +29,22 @@ public Logout() throws ServiceException {



@GET
@Path("/{urlID}")


public Response logout(@Context UriInfo uriInfo, @Context HttpHeaders httpHeaders, @PathParam("urlID") String urlID){
this.controller.setRequestResponse(this.request, this.response);
return this.controller.logout(this.getUser(), uriInfo, httpHeaders, urlID);
}

@GET
@Path("/")


public Response logout(@Context UriInfo uriInfo, @Context HttpHeaders httpHeaders){
public Response logoutSenzaRedirect(@Context UriInfo uriInfo, @Context HttpHeaders httpHeaders){
this.controller.setRequestResponse(this.request, this.response);
return this.controller.logout(this.getUser(), uriInfo, httpHeaders);
return this.controller.logout(this.getUser(), uriInfo, httpHeaders, null);
}

}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
package it.govpay.user.v1.authentication.handler;

import java.io.IOException;
import java.util.Map;
import java.util.Properties;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.lang.StringUtils;
import org.openspcoop2.utils.LoggerWrapperFactory;
import org.slf4j.Logger;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.Assert;

import it.govpay.core.utils.GovpayConfig;
import it.govpay.user.v1.authentication.matcher.LogoutRequestMatcher;
import it.govpay.user.v1.authentication.matcher.LogoutRequestMatcher.Matcher;
import it.govpay.user.v1.authentication.matcher.LogoutRequestMatcher.SpringAntMatcher;
import it.govpay.user.v1.authentication.matcher.LogoutRequestMatcher.SubpathMatcher;

/**
* Implementa il gestore del logout effettuato con successo fornendo la funzionalita' di redirect alla url indicata dall'eventuale URL-ID passato come parametro
* Se non viene passato il parametro URL-ID allora la procedura si conclude con un 200OK
*
* @author pintori
*
*/
public class RedirectLogoutSuccessHandler implements LogoutSuccessHandler{

private static Logger log = LoggerWrapperFactory.getLogger(RedirectLogoutSuccessHandler.class);

private final String pattern;
private final boolean caseSensitive;
private final Matcher matcher;

public RedirectLogoutSuccessHandler(String pattern) {
this(pattern, true);
}

public RedirectLogoutSuccessHandler(String pattern, boolean caseSensitive) {
Assert.hasText(pattern, "Pattern cannot be null or empty");
this.caseSensitive = caseSensitive;
if (pattern.equals(LogoutRequestMatcher.MATCH_ALL) || pattern.equals("**")) {
pattern = LogoutRequestMatcher.MATCH_ALL;
this.matcher = null;
}
else {
// If the pattern ends with {@code /**} and has no other wildcards or path
// variables, then optimize to a sub-path match
if (pattern.endsWith(LogoutRequestMatcher.MATCH_ALL)
&& (pattern.indexOf('?') == -1 && pattern.indexOf('{') == -1
&& pattern.indexOf('}') == -1)
&& pattern.indexOf("*") == pattern.length() - 2) {
this.matcher = new SubpathMatcher(
pattern.substring(0, pattern.length() - 3), this.caseSensitive);
}
else {
this.matcher = new SpringAntMatcher(pattern, this.caseSensitive);
}
}

this.pattern = pattern;
}

public boolean doMatches(HttpServletRequest request) {
if (this.pattern.equals(LogoutRequestMatcher.MATCH_ALL)) {
log.debug("Request '" + LogoutRequestMatcher.getRequestPath(request) + "' matched by universal pattern '/**'");
return true;
}

String url = LogoutRequestMatcher.getRequestPath(request);

log.debug("Checking match of request : '" + url + "'; against '" + this.pattern + "'");

return this.matcher.matches(url);
}

@Override
public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {

log.debug("Esecuzione del logout in corso...");
String url = LogoutRequestMatcher.getRequestPath(request);
log.debug("Url invocata ["+url+"]");
String urlID = null;
if(doMatches(request)) {
String [] tokensPath = org.springframework.util.StringUtils.tokenizeToStringArray(url, AntPathMatcher.DEFAULT_PATH_SEPARATOR, false, true);
String [] tokensPattern = org.springframework.util.StringUtils.tokenizeToStringArray(this.pattern, AntPathMatcher.DEFAULT_PATH_SEPARATOR, false, true);
// il pattern e il path invocato matchano grazie al confronto fatto con il metodo domatch, mi preoccupo di estrarre solo la parte finale
String tokenTrovato = null;
for (int i = 0; i < tokensPattern.length; i++) {
if(this.pattern.endsWith(LogoutRequestMatcher.MATCH_ALL)) {
// skip di tutti i token fino al doppio **
if(!tokensPattern[i].equals("**")) {
continue;
}

if(i < tokensPath.length) {
tokenTrovato = tokensPath[i];
}
}
}
if(tokenTrovato != null) {
urlID = url.substring(url.indexOf(tokenTrovato));
}
}

log.debug("urlID ["+urlID+"]");

if(StringUtils.isBlank(urlID)) {
response.setStatus(HttpServletResponse.SC_OK);
log.debug("Esecuzione del logout completata con status 200 OK.");
} else {

Properties props = GovpayConfig.getInstance().getApiUserLogoutRedirectURLs();

String redirectURL = props.getProperty(urlID);

if(StringUtils.isBlank(redirectURL)) {
response.setStatus(HttpServletResponse.SC_OK);
log.debug("Esecuzione del logout completata con status 200 OK.");
} else {
response.setStatus(HttpServletResponse.SC_SEE_OTHER);
response.setHeader("Location", redirectURL);
log.debug("Esecuzione del logout completata con status 303 SeeOther, Location ["+redirectURL+"].");
}
}
}
}

0 comments on commit f27a10b

Please sign in to comment.