From f781bfffa461e330f46051f7be7379779f1d1f4b Mon Sep 17 00:00:00 2001 From: "Maria Odea B. Ching" Date: Sat, 3 May 2008 02:38:07 +0000 Subject: [PATCH] [MRM-773] -added basic http authentication to rss feed servlet -added commons-codec as dependency which is used for decoding the username and password -updated tests git-svn-id: https://svn.apache.org/repos/asf/archiva/trunk@652981 13f79535-47bb-0310-9956-ffa450edef68 --- .../archiva-web/archiva-webapp/pom.xml | 5 + .../maven/archiva/web/rss/RssFeedServlet.java | 167 ++++++++++++++++-- .../archiva/web/rss/RssFeedServletTest.java | 81 +++++++-- .../archiva/web/rss/SecuritySystemStub.java | 164 +++++++++++++++++ .../archiva/web/rss/UserRepositoriesStub.java | 63 +++++++ .../archiva/web/rss/RssFeedServletTest.xml | 12 ++ 6 files changed, 465 insertions(+), 27 deletions(-) create mode 100644 archiva-modules/archiva-web/archiva-webapp/src/test/java/org/apache/maven/archiva/web/rss/SecuritySystemStub.java create mode 100644 archiva-modules/archiva-web/archiva-webapp/src/test/java/org/apache/maven/archiva/web/rss/UserRepositoriesStub.java diff --git a/archiva-modules/archiva-web/archiva-webapp/pom.xml b/archiva-modules/archiva-web/archiva-webapp/pom.xml index d2da4cca7..65739251b 100644 --- a/archiva-modules/archiva-web/archiva-webapp/pom.xml +++ b/archiva-modules/archiva-web/archiva-webapp/pom.xml @@ -299,6 +299,11 @@ commons-fileupload commons-fileupload + + commons-codec + commons-codec + 1.3 + diff --git a/archiva-modules/archiva-web/archiva-webapp/src/main/java/org/apache/maven/archiva/web/rss/RssFeedServlet.java b/archiva-modules/archiva-web/archiva-webapp/src/main/java/org/apache/maven/archiva/web/rss/RssFeedServlet.java index 4cca3aa94..dad2b095c 100644 --- a/archiva-modules/archiva-web/archiva-webapp/src/main/java/org/apache/maven/archiva/web/rss/RssFeedServlet.java +++ b/archiva-modules/archiva-web/archiva-webapp/src/main/java/org/apache/maven/archiva/web/rss/RssFeedServlet.java @@ -20,7 +20,10 @@ package org.apache.maven.archiva.web.rss; */ import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; +import java.util.List; import java.util.Map; import javax.servlet.ServletException; @@ -28,8 +31,23 @@ import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import org.apache.archiva.rss.RssFeedGenerator; import org.apache.archiva.rss.processor.RssFeedProcessor; +import org.apache.commons.codec.Decoder; +import org.apache.commons.codec.DecoderException; +import org.apache.commons.codec.binary.Base64; +import org.apache.maven.archiva.security.AccessDeniedException; +import org.apache.maven.archiva.security.ArchivaRoleConstants; +import org.apache.maven.archiva.security.ArchivaSecurityException; +import org.apache.maven.archiva.security.PrincipalNotFoundException; +import org.apache.maven.archiva.security.UserRepositories; +import org.codehaus.plexus.redback.authentication.AuthenticationDataSource; +import org.codehaus.plexus.redback.authentication.AuthenticationException; +import org.codehaus.plexus.redback.authentication.PasswordBasedAuthenticationDataSource; +import org.codehaus.plexus.redback.authorization.AuthorizationException; +import org.codehaus.plexus.redback.policy.AccountLockedException; +import org.codehaus.plexus.redback.system.SecuritySession; +import org.codehaus.plexus.redback.system.SecuritySystem; +import org.codehaus.plexus.redback.users.UserNotFoundException; import org.codehaus.plexus.spring.PlexusToSpringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -53,17 +71,29 @@ public class RssFeedServlet private static final String COULD_NOT_GENERATE_FEED_ERROR = "Could not generate feed"; - private Logger log = LoggerFactory.getLogger( RssFeedGenerator.class ); + private static final String COULD_NOT_AUTHENTICATE_USER = "Could not authenticate user"; + + private static final String USER_NOT_AUTHORIZED = "User not authorized to access feed."; + + private Logger log = LoggerFactory.getLogger( RssFeedServlet.class ); private RssFeedProcessor processor; private WebApplicationContext wac; + private SecuritySystem securitySystem; + + private UserRepositories userRepositories; + public void init( javax.servlet.ServletConfig servletConfig ) throws ServletException { super.init( servletConfig ); wac = WebApplicationContextUtils.getRequiredWebApplicationContext( servletConfig.getServletContext() ); + securitySystem = + (SecuritySystem) wac.getBean( PlexusToSpringUtils.buildSpringId( SecuritySystem.class.getName() ) ); + userRepositories = + (UserRepositories) wac.getBean( PlexusToSpringUtils.buildSpringId( UserRepositories.class.getName() ) ); } public void doGet( HttpServletRequest req, HttpServletResponse res ) @@ -74,17 +104,21 @@ public class RssFeedServlet { Map map = new HashMap(); SyndFeed feed = null; - - if ( req.getParameter( "repoId" ) != null ) + String repoId = req.getParameter( "repoId" ); + String groupId = req.getParameter( "groupId" ); + String artifactId = req.getParameter( "artifactId" ); + + if ( repoId != null ) { - if ( isAuthorized() ) + + if ( isAuthorized( req ) ) { - // new artifacts in repo feed request + // new artifacts in repo feed request processor = (RssFeedProcessor) wac.getBean( PlexusToSpringUtils.buildSpringId( RssFeedProcessor.class.getName(), "new-artifacts" ) ); - map.put( RssFeedProcessor.KEY_REPO_ID, req.getParameter( "repoId" ) ); + map.put( RssFeedProcessor.KEY_REPO_ID, repoId ); } else { @@ -92,17 +126,17 @@ public class RssFeedServlet return; } } - else if ( ( req.getParameter( "groupId" ) != null ) && ( req.getParameter( "artifactId" ) != null ) ) + else if ( ( groupId != null ) && ( artifactId != null ) ) { - if ( isAuthorized() ) + if ( isAuthorized( req ) ) { - // new versions of artifact feed request + // new versions of artifact feed request processor = (RssFeedProcessor) wac.getBean( PlexusToSpringUtils.buildSpringId( RssFeedProcessor.class.getName(), "new-versions" ) ); - map.put( RssFeedProcessor.KEY_GROUP_ID, req.getParameter( "groupId" ) ); - map.put( RssFeedProcessor.KEY_ARTIFACT_ID, req.getParameter( "artifactId" ) ); + map.put( RssFeedProcessor.KEY_GROUP_ID, groupId ); + map.put( RssFeedProcessor.KEY_ARTIFACT_ID, artifactId ); } else { @@ -122,16 +156,115 @@ public class RssFeedServlet SyndFeedOutput output = new SyndFeedOutput(); output.output( feed, res.getWriter() ); } + catch ( AuthorizationException ae ) + { + log.error( USER_NOT_AUTHORIZED, ae ); + res.sendError( HttpServletResponse.SC_UNAUTHORIZED, USER_NOT_AUTHORIZED ); + } + catch ( UserNotFoundException unfe ) + { + log.error( COULD_NOT_AUTHENTICATE_USER, unfe ); + res.sendError( HttpServletResponse.SC_UNAUTHORIZED, COULD_NOT_AUTHENTICATE_USER ); + } + catch ( AccountLockedException acce ) + { + log.error( COULD_NOT_AUTHENTICATE_USER, acce ); + res.sendError( HttpServletResponse.SC_UNAUTHORIZED, COULD_NOT_AUTHENTICATE_USER ); + } + catch ( AuthenticationException authe ) + { + log.error( COULD_NOT_AUTHENTICATE_USER, authe ); + res.sendError( HttpServletResponse.SC_UNAUTHORIZED, COULD_NOT_AUTHENTICATE_USER ); + } catch ( FeedException ex ) { - String msg = COULD_NOT_GENERATE_FEED_ERROR; - log.error( msg, ex ); - res.sendError( HttpServletResponse.SC_INTERNAL_SERVER_ERROR, msg ); + log.error( COULD_NOT_GENERATE_FEED_ERROR, ex ); + res.sendError( HttpServletResponse.SC_INTERNAL_SERVER_ERROR, COULD_NOT_GENERATE_FEED_ERROR ); } } - private boolean isAuthorized() + /** + * Basic authentication. + * + * @param req + * @return + */ + private boolean isAuthorized( HttpServletRequest req ) + throws UserNotFoundException, AccountLockedException, AuthenticationException, AuthorizationException { - return true; + String auth = req.getHeader( "Authorization" ); + + if ( auth == null ) + { + return false; + } + + if ( !auth.toUpperCase().startsWith( "BASIC " ) ) + { + return false; + } + + Decoder dec = new Base64(); + String usernamePassword = ""; + + try + { + usernamePassword = new String( ( byte[] ) dec.decode( auth.substring( 6 ).getBytes() ) ); + } + catch ( DecoderException ie ) + { + log.error( "Error decoding username and password.", ie.getMessage() ); + } + + String[] userCredentials = usernamePassword.split( ":" ); + String username = userCredentials[0]; + String password = userCredentials[1]; + + AuthenticationDataSource dataSource = new PasswordBasedAuthenticationDataSource( username, password ); + SecuritySession session = null; + + List repoIds = new ArrayList(); + if ( req.getParameter( "repoId" ) != null ) + { + repoIds.add( req.getParameter( "repoId" ) ); + } + else + { + repoIds = getObservableRepos( username ); + } + + session = securitySystem.authenticate( dataSource ); + + for ( String repoId : repoIds ) + { + if ( securitySystem.isAuthorized( session, ArchivaRoleConstants.OPERATION_REPOSITORY_ACCESS, repoId ) ) + { + return true; + } + } + + return false; + } + + private List getObservableRepos( String principal ) + { + try + { + return userRepositories.getObservableRepositoryIds( principal ); + } + catch ( PrincipalNotFoundException e ) + { + log.warn( e.getMessage(), e ); + } + catch ( AccessDeniedException e ) + { + log.warn( e.getMessage(), e ); + } + catch ( ArchivaSecurityException e ) + { + log.warn( e.getMessage(), e ); + } + + return Collections.emptyList(); } } diff --git a/archiva-modules/archiva-web/archiva-webapp/src/test/java/org/apache/maven/archiva/web/rss/RssFeedServletTest.java b/archiva-modules/archiva-web/archiva-webapp/src/test/java/org/apache/maven/archiva/web/rss/RssFeedServletTest.java index 4eef8f532..e5fc9bc73 100644 --- a/archiva-modules/archiva-web/archiva-webapp/src/test/java/org/apache/maven/archiva/web/rss/RssFeedServletTest.java +++ b/archiva-modules/archiva-web/archiva-webapp/src/test/java/org/apache/maven/archiva/web/rss/RssFeedServletTest.java @@ -19,9 +19,19 @@ package org.apache.maven.archiva.web.rss; * under the License. */ +import java.io.IOException; + import javax.servlet.http.HttpServletResponse; + +import org.apache.commons.codec.Encoder; +import org.apache.commons.codec.binary.Base64; import org.codehaus.plexus.spring.PlexusInSpringTestCase; + +import sun.misc.BASE64Encoder; + +import com.meterware.httpunit.GetMethodWebRequest; import com.meterware.httpunit.HttpException; +import com.meterware.httpunit.WebRequest; import com.meterware.httpunit.WebResponse; import com.meterware.servletunit.ServletRunner; import com.meterware.servletunit.ServletUnitClient; @@ -60,9 +70,15 @@ public class RssFeedServletTest (RssFeedServlet) client.newInvocation( "http://localhost/rss/rss_feeds?repoId=test-repo" ).getServlet(); assertNotNull( servlet ); - WebResponse response = client.getResponse( "http://localhost/rss/rss_feeds?repoId=test-repo" ); + WebRequest request = new GetMethodWebRequest( "http://localhost/rss/rss_feeds?repoId=test-repo" ); + + BASE64Encoder encoder = new BASE64Encoder(); + String userPass = "user1:password1"; + String encodedUserPass = encoder.encode( userPass.getBytes() ); + request.setHeaderField( "Authorization", "BASIC " + encodedUserPass ); + + WebResponse response = client.getResponse( request ); assertEquals( RssFeedServlet.MIME_TYPE, response.getHeaderField( "CONTENT-TYPE" ) ); - assertNotNull( "Should have recieved a response", response ); assertEquals( "Should have been an OK response code.", HttpServletResponse.SC_OK, response.getResponseCode() ); } @@ -75,9 +91,15 @@ public class RssFeedServletTest "http://localhost/rss/rss_feeds?groupId=org.apache.archiva&artifactId=artifact-two" ).getServlet(); assertNotNull( servlet ); - WebResponse response = client.getResponse( "http://localhost/rss/rss_feeds?groupId=org.apache.archiva&artifactId=artifact-two" ); + WebRequest request = new GetMethodWebRequest( "http://localhost/rss/rss_feeds?groupId=org.apache.archiva&artifactId=artifact-two" ); + + BASE64Encoder encoder = new BASE64Encoder(); + String userPass = "user1:password1"; + String encodedUserPass = encoder.encode( userPass.getBytes() ); + request.setHeaderField( "Authorization", "BASIC " + encodedUserPass ); + + WebResponse response = client.getResponse( request ); assertEquals( RssFeedServlet.MIME_TYPE, response.getHeaderField( "CONTENT-TYPE" ) ); - assertNotNull( "Should have recieved a response", response ); assertEquals( "Should have been an OK response code.", HttpServletResponse.SC_OK, response.getResponseCode() ); } @@ -99,20 +121,59 @@ public class RssFeedServletTest assertEquals( "Should have been a bad request response code.", HttpServletResponse.SC_BAD_REQUEST, he.getResponseCode() ); } } + + public void testInvalidAuthenticationRequest() + throws Exception + { + RssFeedServlet servlet = + (RssFeedServlet) client.newInvocation( + "http://localhost/rss/rss_feeds?repoId=unauthorized-repo" ).getServlet(); + assertNotNull( servlet ); - public void testUnAuthorizedRequest() + + WebRequest request = new GetMethodWebRequest( "http://localhost/rss/rss_feeds?repoId=unauthorized-repo" ); + + Encoder encoder = new Base64(); + String userPass = "unauthUser:unauthPass"; + String encodedUserPass = new String( ( byte[] ) encoder.encode( userPass.getBytes() ) ); + request.setHeaderField( "Authorization", "BASIC " + encodedUserPass ); + + try + { + WebResponse response = client.getResponse( request ); + } + catch ( HttpException he ) + { + assertEquals( "Should have been a unauthorized response.", HttpServletResponse.SC_UNAUTHORIZED, he.getResponseCode() ); + } + } + + public void testUnauthorizedRequest() throws Exception { RssFeedServlet servlet = (RssFeedServlet) client.newInvocation( - "http://localhost/rss/rss_feeds" ).getServlet(); + "http://localhost/rss/rss_feeds?repoId=unauthorized-repo" ).getServlet(); assertNotNull( servlet ); - //WebResponse response = client.getResponse( "http://localhost/rss/rss_feeds" ); - //assertNotNull( "Should have recieved a response", response ); - //assertEquals( "Should have been a bad request response code.", HttpServletResponse.SC_BAD_REQUEST, response.getResponseCode() ); + + WebRequest request = new GetMethodWebRequest( "http://localhost/rss/rss_feeds?repoId=unauthorized-repo" ); + + BASE64Encoder encoder = new BASE64Encoder(); + String userPass = "user1:password1"; + String encodedUserPass = encoder.encode( userPass.getBytes() ); + request.setHeaderField( "Authorization", "BASIC " + encodedUserPass ); + + try + { + WebResponse response = client.getResponse( request ); + } + catch ( HttpException he ) + { + assertEquals( "Should have been a unauthorized response.", HttpServletResponse.SC_UNAUTHORIZED, he.getResponseCode() ); + } } - + @Override protected String getPlexusConfigLocation() { diff --git a/archiva-modules/archiva-web/archiva-webapp/src/test/java/org/apache/maven/archiva/web/rss/SecuritySystemStub.java b/archiva-modules/archiva-web/archiva-webapp/src/test/java/org/apache/maven/archiva/web/rss/SecuritySystemStub.java new file mode 100644 index 000000000..a01f60eef --- /dev/null +++ b/archiva-modules/archiva-web/archiva-webapp/src/test/java/org/apache/maven/archiva/web/rss/SecuritySystemStub.java @@ -0,0 +1,164 @@ +package org.apache.maven.archiva.web.rss; + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.codehaus.plexus.redback.authentication.AuthenticationDataSource; +import org.codehaus.plexus.redback.authentication.AuthenticationException; +import org.codehaus.plexus.redback.authentication.AuthenticationResult; +import org.codehaus.plexus.redback.authorization.AuthorizationException; +import org.codehaus.plexus.redback.authorization.AuthorizationResult; +import org.codehaus.plexus.redback.keys.KeyManager; +import org.codehaus.plexus.redback.policy.AccountLockedException; +import org.codehaus.plexus.redback.policy.UserSecurityPolicy; +import org.codehaus.plexus.redback.system.DefaultSecuritySession; +import org.codehaus.plexus.redback.system.SecuritySession; +import org.codehaus.plexus.redback.system.SecuritySystem; +import org.codehaus.plexus.redback.users.User; +import org.codehaus.plexus.redback.users.UserManager; +import org.codehaus.plexus.redback.users.UserNotFoundException; +import org.codehaus.plexus.redback.users.jdo.JdoUser; + +/** + * SecuritySystem stub used for testing. + * + * @author Maria Odea Ching + * @version $Id$ + */ +public class SecuritySystemStub + implements SecuritySystem +{ + Map users = new HashMap(); + + List repoIds = new ArrayList(); + + public SecuritySystemStub() + { + users.put( "user1", "password1" ); + users.put( "user2", "password2" ); + users.put( "user3", "password3" ); + + repoIds.add( "test-repo" ); + } + + public SecuritySession authenticate( AuthenticationDataSource source ) + throws AuthenticationException, UserNotFoundException, AccountLockedException + { + AuthenticationResult result = null; + SecuritySession session = null; + + if ( users.get( source.getPrincipal() ) != null ) + { + result = new AuthenticationResult( true, source.getPrincipal(), null ); + + User user = new JdoUser(); + user.setUsername( source.getPrincipal() ); + user.setPassword( users.get( source.getPrincipal() ) ); + + session = new DefaultSecuritySession( result, user ); + } + else + { + result = new AuthenticationResult( false, source.getPrincipal(), null ); + session = new DefaultSecuritySession( result ); + } + return session; + } + + public AuthorizationResult authorize( SecuritySession arg0, Object arg1 ) + throws AuthorizationException + { + // TODO Auto-generated method stub + return null; + } + + public AuthorizationResult authorize( SecuritySession arg0, Object arg1, Object arg2 ) + throws AuthorizationException + { + // TODO Auto-generated method stub + return null; + } + + public String getAuthenticatorId() + { + // TODO Auto-generated method stub + return null; + } + + public String getAuthorizerId() + { + // TODO Auto-generated method stub + return null; + } + + public KeyManager getKeyManager() + { + // TODO Auto-generated method stub + return null; + } + + public UserSecurityPolicy getPolicy() + { + // TODO Auto-generated method stub + return null; + } + + public String getUserManagementId() + { + // TODO Auto-generated method stub + return null; + } + + public UserManager getUserManager() + { + // TODO Auto-generated method stub + return null; + } + + public boolean isAuthenticated( AuthenticationDataSource arg0 ) + throws AuthenticationException, UserNotFoundException, AccountLockedException + { + // TODO Auto-generated method stub + return false; + } + + public boolean isAuthorized( SecuritySession arg0, Object arg1 ) + throws AuthorizationException + { + // TODO Auto-generated method stub + return false; + } + + public boolean isAuthorized( SecuritySession arg0, Object arg1, Object arg2 ) + throws AuthorizationException + { + if ( repoIds.contains( arg2 ) ) + { + return true; + } + + return false; + } + +} diff --git a/archiva-modules/archiva-web/archiva-webapp/src/test/java/org/apache/maven/archiva/web/rss/UserRepositoriesStub.java b/archiva-modules/archiva-web/archiva-webapp/src/test/java/org/apache/maven/archiva/web/rss/UserRepositoriesStub.java new file mode 100644 index 000000000..6876fa4a5 --- /dev/null +++ b/archiva-modules/archiva-web/archiva-webapp/src/test/java/org/apache/maven/archiva/web/rss/UserRepositoriesStub.java @@ -0,0 +1,63 @@ +package org.apache.maven.archiva.web.rss; + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + +import java.util.ArrayList; +import java.util.List; + +import org.apache.maven.archiva.security.AccessDeniedException; +import org.apache.maven.archiva.security.ArchivaSecurityException; +import org.apache.maven.archiva.security.PrincipalNotFoundException; +import org.apache.maven.archiva.security.UserRepositories; + +/** + * UserRepositories stub used for testing. + * + * @author Maria Odea Ching + * @version $Id$ + */ +public class UserRepositoriesStub + implements UserRepositories +{ + + public void createMissingRepositoryRoles( String repoId ) + throws ArchivaSecurityException + { + // TODO Auto-generated method stub + + } + + public List getObservableRepositoryIds( String principal ) + throws PrincipalNotFoundException, AccessDeniedException, ArchivaSecurityException + { + List repoIds = new ArrayList(); + repoIds.add( "test-repo" ); + + return repoIds; + } + + public boolean isAuthorizedToUploadArtifacts( String principal, String repoId ) + throws PrincipalNotFoundException, ArchivaSecurityException + { + // TODO Auto-generated method stub + return false; + } + +} diff --git a/archiva-modules/archiva-web/archiva-webapp/src/test/resources/org/apache/maven/archiva/web/rss/RssFeedServletTest.xml b/archiva-modules/archiva-web/archiva-webapp/src/test/resources/org/apache/maven/archiva/web/rss/RssFeedServletTest.xml index 52729b668..138ffec62 100644 --- a/archiva-modules/archiva-web/archiva-webapp/src/test/resources/org/apache/maven/archiva/web/rss/RssFeedServletTest.xml +++ b/archiva-modules/archiva-web/archiva-webapp/src/test/resources/org/apache/maven/archiva/web/rss/RssFeedServletTest.xml @@ -34,5 +34,17 @@ jdo org.apache.maven.archiva.web.rss.ArtifactDAOStub + + + org.codehaus.plexus.redback.system.SecuritySystem + default + org.apache.maven.archiva.web.rss.SecuritySystemStub + + + + org.apache.maven.archiva.security.UserRepositories + default + org.apache.maven.archiva.web.rss.UserRepositoriesStub + -- 2.39.5