Skip to content

Commit 1c007aa

Browse files
committed
LUTECE-2184 : Add a CSRF token to portlet creation and modification
The protection is in place if portlet JspBeans call setPortletCommonData.
1 parent 32ae99a commit 1c007aa

File tree

6 files changed

+351
-3
lines changed

6 files changed

+351
-3
lines changed

src/java/fr/paris/lutece/portal/web/portlet/PortletJspBean.java

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,12 @@
3939
import fr.paris.lutece.portal.business.portlet.PortletType;
4040
import fr.paris.lutece.portal.business.portlet.PortletTypeHome;
4141
import fr.paris.lutece.portal.business.role.RoleHome;
42+
import fr.paris.lutece.portal.service.admin.AccessDeniedException;
4243
import fr.paris.lutece.portal.service.message.AdminMessage;
4344
import fr.paris.lutece.portal.service.message.AdminMessageService;
45+
import fr.paris.lutece.portal.service.security.SecurityTokenService;
4446
import fr.paris.lutece.portal.service.template.AppTemplateService;
47+
import fr.paris.lutece.portal.service.util.AppException;
4548
import fr.paris.lutece.portal.service.util.AppLogService;
4649
import fr.paris.lutece.portal.service.util.AppPropertiesService;
4750
import fr.paris.lutece.portal.web.admin.AdminFeaturesPageJspBean;
@@ -56,9 +59,12 @@
5659

5760
import javax.servlet.http.HttpServletRequest;
5861

62+
import org.springframework.web.context.request.RequestContextHolder;
63+
import org.springframework.web.context.request.ServletRequestAttributes;
64+
5965
/**
6066
* This class represents user interface Portlet. It is the base class of all user interface portlets. It is abstract and the implementation of the interface
61-
* PortletJspBeanInterface is compulsary.
67+
* PortletJspBeanInterface is compulsory.
6268
*/
6369
public abstract class PortletJspBean extends AdminFeaturesPageJspBean
6470
{
@@ -240,7 +246,11 @@ protected String setPortletCommonData( HttpServletRequest request, Portlet portl
240246

241247
return AdminMessageService.getMessageUrl( request, MESSAGE_INVALID_PAGE_ID, AdminMessage.TYPE_STOP );
242248
}
243-
249+
if ( !SecurityTokenService.getInstance( ).validate( request, TEMPLATE_CREATE_PORTLET ) )
250+
{
251+
// FIXME we wrap the AccessDeniedException so as to to break the API
252+
throw new AppException( "Invalid security token", new AccessDeniedException( "Invalid security token" ) );
253+
}
244254
int nOrder = Integer.parseInt( strOrder );
245255
int nColumn = Integer.parseInt( strColumn );
246256
int nAcceptAlias = Integer.parseInt( strAcceptAlias );
@@ -321,6 +331,7 @@ protected HtmlTemplate getCreateTemplate( String strPageId, String strPortletTyp
321331
model.put( MARK_PORTLET_COLUMNS_COMBO, getColumnsList( ) );
322332
model.put( MARK_PORTLET_STYLES_COMBO, PortletHome.getStylesList( strPortletTypeId ) );
323333
model.put( MARK_PORTLET_ROLES_COMBO, RoleHome.getRolesList( getUser( ) ) );
334+
model.put( SecurityTokenService.MARK_TOKEN, SecurityTokenService.getInstance( ).getToken( getRequest( ), TEMPLATE_CREATE_PORTLET ) );
324335

325336
HtmlTemplate template = AppTemplateService.getTemplate( TEMPLATE_CREATE_PORTLET, locale, model );
326337

@@ -362,6 +373,7 @@ protected HtmlTemplate getModifyTemplate( Portlet portlet, Map<String, Object> m
362373
putCheckBox( model, MARK_NORMAL_CHECKED, portlet.hasDeviceDisplayFlag( Portlet.FLAG_DISPLAY_ON_NORMAL_DEVICE ) );
363374
putCheckBox( model, MARK_LARGE_CHECKED, portlet.hasDeviceDisplayFlag( Portlet.FLAG_DISPLAY_ON_LARGE_DEVICE ) );
364375
putCheckBox( model, MARK_XLARGE_CHECKED, portlet.hasDeviceDisplayFlag( Portlet.FLAG_DISPLAY_ON_XLARGE_DEVICE ) );
376+
model.put( SecurityTokenService.MARK_TOKEN, SecurityTokenService.getInstance( ).getToken( getRequest( ), TEMPLATE_CREATE_PORTLET ) );
365377

366378
HtmlTemplate template = AppTemplateService.getTemplate( TEMPLATE_MODIFY_PORTLET, getLocale( ), model );
367379

@@ -395,4 +407,15 @@ protected String getPageUrl( int nIdPage )
395407
{
396408
return JSP_ADMIN_SITE + "?" + PARAMETER_PAGE_ID + "=" + nIdPage;
397409
}
410+
411+
/**
412+
* Gets the current request
413+
*
414+
* @return the current request
415+
*/
416+
private HttpServletRequest getRequest( )
417+
{
418+
ServletRequestAttributes sra = ( ServletRequestAttributes ) RequestContextHolder.getRequestAttributes( );
419+
return sra.getRequest( );
420+
}
398421
}
Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
/*
2+
* Copyright (c) 2002-2017, Mairie de Paris
3+
* All rights reserved.
4+
*
5+
* Redistribution and use in source and binary forms, with or without
6+
* modification, are permitted provided that the following conditions
7+
* are met:
8+
*
9+
* 1. Redistributions of source code must retain the above copyright notice
10+
* and the following disclaimer.
11+
*
12+
* 2. Redistributions in binary form must reproduce the above copyright notice
13+
* and the following disclaimer in the documentation and/or other materials
14+
* provided with the distribution.
15+
*
16+
* 3. Neither the name of 'Mairie de Paris' nor 'Lutece' nor the names of its
17+
* contributors may be used to endorse or promote products derived from
18+
* this software without specific prior written permission.
19+
*
20+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21+
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22+
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23+
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
24+
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
25+
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
26+
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
27+
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
28+
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
29+
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
30+
* POSSIBILITY OF SUCH DAMAGE.
31+
*
32+
* License 1.0
33+
*/
34+
package fr.paris.lutece.portal.web.portlet;
35+
36+
import java.math.BigInteger;
37+
import java.security.SecureRandom;
38+
import java.util.HashMap;
39+
import java.util.Map;
40+
import java.util.Random;
41+
42+
import org.springframework.mock.web.MockHttpServletRequest;
43+
import org.springframework.web.context.request.RequestContextHolder;
44+
import org.springframework.web.context.request.ServletRequestAttributes;
45+
46+
import fr.paris.lutece.portal.business.portlet.Portlet;
47+
import fr.paris.lutece.portal.business.user.AdminUser;
48+
import fr.paris.lutece.portal.service.admin.AccessDeniedException;
49+
import fr.paris.lutece.portal.service.admin.PasswordResetException;
50+
import fr.paris.lutece.portal.service.security.SecurityTokenService;
51+
import fr.paris.lutece.portal.service.util.AppException;
52+
import fr.paris.lutece.portal.web.constants.Parameters;
53+
import fr.paris.lutece.test.LuteceTestCase;
54+
import fr.paris.lutece.test.Utils;
55+
56+
public class PortletJspBeanTest extends LuteceTestCase
57+
{
58+
public void testGetCreateTemplate( ) throws PasswordResetException, AccessDeniedException
59+
{
60+
PortletJspBean instance = new TestPortletJspBean( );
61+
MockHttpServletRequest request = new MockHttpServletRequest( );
62+
Utils.registerAdminUserWithRigth( request, new AdminUser( ), PortletJspBean.RIGHT_MANAGE_ADMIN_SITE );
63+
instance.init( request, PortletJspBean.RIGHT_MANAGE_ADMIN_SITE );
64+
RequestContextHolder.setRequestAttributes( new ServletRequestAttributes( request ) );
65+
66+
Map<String, Object> model = new HashMap<>( );
67+
assertNotNull( instance.getCreateTemplate( "1", "ALIAS_PORTLET", model ) );
68+
assertTrue( model.containsKey( SecurityTokenService.MARK_TOKEN ) );
69+
}
70+
71+
public void testGetModifyTemplateTemplate( ) throws PasswordResetException, AccessDeniedException
72+
{
73+
PortletJspBean instance = new TestPortletJspBean( );
74+
MockHttpServletRequest request = new MockHttpServletRequest( );
75+
Utils.registerAdminUserWithRigth( request, new AdminUser( ), PortletJspBean.RIGHT_MANAGE_ADMIN_SITE );
76+
instance.init( request, PortletJspBean.RIGHT_MANAGE_ADMIN_SITE );
77+
RequestContextHolder.setRequestAttributes( new ServletRequestAttributes( request ) );
78+
79+
Map<String, Object> model = new HashMap<>( );
80+
model.put( "alias_portlet", "1" );
81+
Portlet portlet = new TestPortlet( "ALIAS_PORTLET" );
82+
assertNotNull( instance.getModifyTemplate( portlet, model ) );
83+
assertTrue( model.containsKey( SecurityTokenService.MARK_TOKEN ) );
84+
}
85+
86+
public void testSetPortletCommonData( )
87+
{
88+
PortletJspBean instance = new TestPortletJspBean( );
89+
MockHttpServletRequest request = new MockHttpServletRequest( );
90+
String strName = getRandomName( );
91+
request.setParameter( Parameters.PORTLET_NAME, strName );
92+
request.setParameter( Parameters.ORDER, "1" );
93+
request.setParameter( Parameters.COLUMN, "1" );
94+
request.setParameter( Parameters.ACCEPT_ALIAS, "1" );
95+
request.setParameter( Parameters.DISPLAY_PORTLET_TITLE, "1" );
96+
request.setParameter( Parameters.STYLE, "1" );
97+
request.setParameter( "page_id", "1" );
98+
request.setParameter( SecurityTokenService.PARAMETER_TOKEN,
99+
SecurityTokenService.getInstance( ).getToken( request, "admin/portlet/create_portlet.html" ) );
100+
Portlet portlet = new TestPortlet( "ALIAS_PORTLET" );
101+
instance.setPortletCommonData( request, portlet );
102+
103+
assertEquals( strName, portlet.getName( ) );
104+
assertEquals( 1, portlet.getOrder( ) );
105+
assertEquals( 1, portlet.getColumn( ) );
106+
assertEquals( 1, portlet.getAcceptAlias( ) );
107+
assertEquals( 1, portlet.getDisplayPortletTitle( ) );
108+
assertEquals( 1, portlet.getStyleId( ) );
109+
assertEquals( 1, portlet.getPageId( ) );
110+
}
111+
112+
public void testSetPortletCommonDataInvalidToken( )
113+
{
114+
PortletJspBean instance = new TestPortletJspBean( );
115+
MockHttpServletRequest request = new MockHttpServletRequest( );
116+
String strName = getRandomName( );
117+
request.setParameter( Parameters.PORTLET_NAME, strName );
118+
request.setParameter( Parameters.ORDER, "1" );
119+
request.setParameter( Parameters.COLUMN, "1" );
120+
request.setParameter( Parameters.ACCEPT_ALIAS, "1" );
121+
request.setParameter( Parameters.DISPLAY_PORTLET_TITLE, "1" );
122+
request.setParameter( Parameters.STYLE, "1" );
123+
request.setParameter( "page_id", "1" );
124+
request.setParameter( SecurityTokenService.PARAMETER_TOKEN,
125+
SecurityTokenService.getInstance( ).getToken( request, "admin/portlet/create_portlet.html" ) + "b" );
126+
Portlet portlet = new TestPortlet( "ALIAS_PORTLET" );
127+
try
128+
{
129+
instance.setPortletCommonData( request, portlet );
130+
fail( "Should have thrown" );
131+
}
132+
catch ( AppException e )
133+
{
134+
assertNotNull( e.getCause( ) );
135+
assertTrue( e.getCause( ) instanceof AccessDeniedException );
136+
assertEquals( "ALIAS_PORTLET", portlet.getName( ) );
137+
assertEquals( 0, portlet.getOrder( ) );
138+
assertEquals( 0, portlet.getColumn( ) );
139+
assertEquals( 0, portlet.getAcceptAlias( ) );
140+
assertEquals( 0, portlet.getDisplayPortletTitle( ) );
141+
assertEquals( 0, portlet.getStyleId( ) );
142+
assertEquals( 0, portlet.getPageId( ) );
143+
}
144+
}
145+
146+
public void testSetPortletCommonDataNoToken( )
147+
{
148+
PortletJspBean instance = new TestPortletJspBean( );
149+
MockHttpServletRequest request = new MockHttpServletRequest( );
150+
String strName = getRandomName( );
151+
request.setParameter( Parameters.PORTLET_NAME, strName );
152+
request.setParameter( Parameters.ORDER, "1" );
153+
request.setParameter( Parameters.COLUMN, "1" );
154+
request.setParameter( Parameters.ACCEPT_ALIAS, "1" );
155+
request.setParameter( Parameters.DISPLAY_PORTLET_TITLE, "1" );
156+
request.setParameter( Parameters.STYLE, "1" );
157+
request.setParameter( "page_id", "1" );
158+
159+
Portlet portlet = new TestPortlet( "ALIAS_PORTLET" );
160+
try
161+
{
162+
instance.setPortletCommonData( request, portlet );
163+
fail( "Should have thrown" );
164+
}
165+
catch ( AppException e )
166+
{
167+
assertNotNull( e.getCause( ) );
168+
assertTrue( e.getCause( ) instanceof AccessDeniedException );
169+
assertEquals( "ALIAS_PORTLET", portlet.getName( ) );
170+
assertEquals( 0, portlet.getOrder( ) );
171+
assertEquals( 0, portlet.getColumn( ) );
172+
assertEquals( 0, portlet.getAcceptAlias( ) );
173+
assertEquals( 0, portlet.getDisplayPortletTitle( ) );
174+
assertEquals( 0, portlet.getStyleId( ) );
175+
assertEquals( 0, portlet.getPageId( ) );
176+
}
177+
}
178+
179+
private String getRandomName( )
180+
{
181+
Random rand = new SecureRandom( );
182+
BigInteger bigInt = new BigInteger( 128, rand );
183+
return "junit" + bigInt.toString( 36 );
184+
}
185+
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
/*
2+
* Copyright (c) 2002-2017, Mairie de Paris
3+
* All rights reserved.
4+
*
5+
* Redistribution and use in source and binary forms, with or without
6+
* modification, are permitted provided that the following conditions
7+
* are met:
8+
*
9+
* 1. Redistributions of source code must retain the above copyright notice
10+
* and the following disclaimer.
11+
*
12+
* 2. Redistributions in binary form must reproduce the above copyright notice
13+
* and the following disclaimer in the documentation and/or other materials
14+
* provided with the distribution.
15+
*
16+
* 3. Neither the name of 'Mairie de Paris' nor 'Lutece' nor the names of its
17+
* contributors may be used to endorse or promote products derived from
18+
* this software without specific prior written permission.
19+
*
20+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21+
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22+
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23+
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
24+
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
25+
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
26+
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
27+
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
28+
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
29+
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
30+
* POSSIBILITY OF SUCH DAMAGE.
31+
*
32+
* License 1.0
33+
*/
34+
package fr.paris.lutece.portal.web.portlet;
35+
36+
import javax.servlet.http.HttpServletRequest;
37+
38+
import fr.paris.lutece.portal.business.portlet.Portlet;
39+
import fr.paris.lutece.portal.service.message.SiteMessageException;
40+
41+
final class TestPortlet extends Portlet
42+
{
43+
public TestPortlet( String strTypeId )
44+
{
45+
setPortletTypeId( strTypeId );
46+
setName( strTypeId );
47+
}
48+
49+
@Override
50+
public String getXmlDocument( HttpServletRequest request ) throws SiteMessageException
51+
{
52+
// TODO Auto-generated method stub
53+
return null;
54+
}
55+
56+
@Override
57+
public String getXml( HttpServletRequest request ) throws SiteMessageException
58+
{
59+
// TODO Auto-generated method stub
60+
return null;
61+
}
62+
63+
@Override
64+
public void remove( )
65+
{
66+
// TODO Auto-generated method stub
67+
68+
}
69+
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
/*
2+
* Copyright (c) 2002-2017, Mairie de Paris
3+
* All rights reserved.
4+
*
5+
* Redistribution and use in source and binary forms, with or without
6+
* modification, are permitted provided that the following conditions
7+
* are met:
8+
*
9+
* 1. Redistributions of source code must retain the above copyright notice
10+
* and the following disclaimer.
11+
*
12+
* 2. Redistributions in binary form must reproduce the above copyright notice
13+
* and the following disclaimer in the documentation and/or other materials
14+
* provided with the distribution.
15+
*
16+
* 3. Neither the name of 'Mairie de Paris' nor 'Lutece' nor the names of its
17+
* contributors may be used to endorse or promote products derived from
18+
* this software without specific prior written permission.
19+
*
20+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21+
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22+
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23+
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
24+
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
25+
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
26+
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
27+
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
28+
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
29+
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
30+
* POSSIBILITY OF SUCH DAMAGE.
31+
*
32+
* License 1.0
33+
*/
34+
package fr.paris.lutece.portal.web.portlet;
35+
36+
import javax.servlet.http.HttpServletRequest;
37+
38+
final class TestPortletJspBean extends PortletJspBean
39+
{
40+
private static final long serialVersionUID = 1L;
41+
42+
@Override
43+
public String getModify( HttpServletRequest request )
44+
{
45+
// TODO Auto-generated method stub
46+
return null;
47+
}
48+
49+
@Override
50+
public String getCreate( HttpServletRequest request )
51+
{
52+
// TODO Auto-generated method stub
53+
return null;
54+
}
55+
56+
@Override
57+
public String doModify( HttpServletRequest request )
58+
{
59+
// TODO Auto-generated method stub
60+
return null;
61+
}
62+
63+
@Override
64+
public String doCreate( HttpServletRequest request )
65+
{
66+
// TODO Auto-generated method stub
67+
return null;
68+
}
69+
}

0 commit comments

Comments
 (0)