001 package org.apache.archiva.web.rss; 002 003 /* 004 * Licensed to the Apache Software Foundation (ASF) under one 005 * or more contributor license agreements. See the NOTICE file 006 * distributed with this work for additional information 007 * regarding copyright ownership. The ASF licenses this file 008 * to you under the Apache License, Version 2.0 (the 009 * "License"); you may not use this file except in compliance 010 * with the License. You may obtain a copy of the License at 011 * 012 * http://www.apache.org/licenses/LICENSE-2.0 013 * 014 * Unless required by applicable law or agreed to in writing, 015 * software distributed under the License is distributed on an 016 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 017 * KIND, either express or implied. See the License for the 018 * specific language governing permissions and limitations 019 * under the License. 020 */ 021 022 import com.sun.syndication.feed.synd.SyndFeed; 023 import com.sun.syndication.io.FeedException; 024 import com.sun.syndication.io.SyndFeedOutput; 025 import org.apache.archiva.metadata.repository.RepositorySession; 026 import org.apache.archiva.metadata.repository.RepositorySessionFactory; 027 import org.apache.archiva.redback.authentication.AuthenticationException; 028 import org.apache.archiva.redback.policy.AccountLockedException; 029 import org.apache.archiva.redback.policy.MustChangePasswordException; 030 import org.apache.archiva.redback.users.UserManager; 031 import org.apache.archiva.redback.users.UserNotFoundException; 032 import org.apache.archiva.rss.processor.RssFeedProcessor; 033 import org.apache.archiva.security.AccessDeniedException; 034 import org.apache.archiva.security.ArchivaSecurityException; 035 import org.apache.archiva.security.PrincipalNotFoundException; 036 import org.apache.archiva.security.ServletAuthenticator; 037 import org.apache.archiva.security.UserRepositories; 038 import org.apache.archiva.security.common.ArchivaRoleConstants; 039 import org.apache.commons.codec.Decoder; 040 import org.apache.commons.codec.DecoderException; 041 import org.apache.commons.codec.binary.Base64; 042 import org.apache.commons.lang.StringUtils; 043 import org.apache.archiva.redback.authentication.AuthenticationResult; 044 import org.apache.archiva.redback.authorization.AuthorizationException; 045 import org.apache.archiva.redback.authorization.UnauthorizedException; 046 import org.apache.archiva.redback.system.SecuritySession; 047 import org.apache.archiva.redback.integration.filter.authentication.HttpAuthenticator; 048 import org.slf4j.Logger; 049 import org.slf4j.LoggerFactory; 050 import org.springframework.web.context.WebApplicationContext; 051 import org.springframework.web.context.support.WebApplicationContextUtils; 052 053 import javax.servlet.ServletException; 054 import javax.servlet.http.HttpServlet; 055 import javax.servlet.http.HttpServletRequest; 056 import javax.servlet.http.HttpServletResponse; 057 import java.io.IOException; 058 import java.util.ArrayList; 059 import java.util.Collections; 060 import java.util.HashMap; 061 import java.util.List; 062 import java.util.Map; 063 064 /** 065 * Servlet for handling rss feed requests. 066 */ 067 public class RssFeedServlet 068 extends HttpServlet 069 { 070 public static final String MIME_TYPE = "application/rss+xml; charset=UTF-8"; 071 072 private static final String COULD_NOT_GENERATE_FEED_ERROR = "Could not generate feed"; 073 074 private static final String COULD_NOT_AUTHENTICATE_USER = "Could not authenticate user"; 075 076 private static final String USER_NOT_AUTHORIZED = "User not authorized to access feed."; 077 078 private Logger log = LoggerFactory.getLogger( RssFeedServlet.class ); 079 080 private RssFeedProcessor processor; 081 082 private WebApplicationContext wac; 083 084 private UserRepositories userRepositories; 085 086 private ServletAuthenticator servletAuth; 087 088 private HttpAuthenticator httpAuth; 089 090 /** 091 * FIXME: this could be multiple implementations and needs to be configured. 092 */ 093 private RepositorySessionFactory repositorySessionFactory; 094 095 public void init( javax.servlet.ServletConfig servletConfig ) 096 throws ServletException 097 { 098 super.init( servletConfig ); 099 wac = WebApplicationContextUtils.getRequiredWebApplicationContext( servletConfig.getServletContext() ); 100 userRepositories = wac.getBean( UserRepositories.class ); 101 servletAuth = wac.getBean( ServletAuthenticator.class ); 102 httpAuth = wac.getBean( "httpAuthenticator#basic", HttpAuthenticator.class ); 103 // TODO: what if there are other types? 104 repositorySessionFactory = wac.getBean( "repositorySessionFactory", RepositorySessionFactory.class ); 105 } 106 107 public void doGet( HttpServletRequest req, HttpServletResponse res ) 108 throws ServletException, IOException 109 { 110 String repoId = null; 111 String groupId = null; 112 String artifactId = null; 113 114 String url = StringUtils.removeEnd( req.getRequestURL().toString(), "/" ); 115 if ( StringUtils.countMatches( StringUtils.substringAfter( url, "feeds/" ), "/" ) > 0 ) 116 { 117 artifactId = StringUtils.substringAfterLast( url, "/" ); 118 groupId = StringUtils.substringBeforeLast( StringUtils.substringAfter( url, "feeds/" ), "/" ); 119 groupId = StringUtils.replaceChars( groupId, '/', '.' ); 120 } 121 else if ( StringUtils.countMatches( StringUtils.substringAfter( url, "feeds/" ), "/" ) == 0 ) 122 { 123 repoId = StringUtils.substringAfterLast( url, "/" ); 124 } 125 else 126 { 127 res.sendError( HttpServletResponse.SC_BAD_REQUEST, "Invalid request url." ); 128 return; 129 } 130 131 try 132 { 133 Map<String, String> map = new HashMap<String, String>(); 134 SyndFeed feed = null; 135 136 if ( isAllowed( req, repoId, groupId, artifactId ) ) 137 { 138 if ( repoId != null ) 139 { 140 // new artifacts in repo feed request 141 processor = wac.getBean( "rssFeedProcessor#new-artifacts", RssFeedProcessor.class ); 142 map.put( RssFeedProcessor.KEY_REPO_ID, repoId ); 143 } 144 else if ( ( groupId != null ) && ( artifactId != null ) ) 145 { 146 // TODO: this only works for guest - we could pass in the list of repos 147 // new versions of artifact feed request 148 processor = wac.getBean( "rssFeedProcessor#new-versions", RssFeedProcessor.class ); 149 map.put( RssFeedProcessor.KEY_GROUP_ID, groupId ); 150 map.put( RssFeedProcessor.KEY_ARTIFACT_ID, artifactId ); 151 } 152 } 153 else 154 { 155 res.sendError( HttpServletResponse.SC_UNAUTHORIZED, USER_NOT_AUTHORIZED ); 156 return; 157 } 158 159 RepositorySession repositorySession = repositorySessionFactory.createSession(); 160 try 161 { 162 feed = processor.process( map, repositorySession.getRepository() ); 163 } 164 finally 165 { 166 repositorySession.close(); 167 } 168 if ( feed == null ) 169 { 170 res.sendError( HttpServletResponse.SC_NO_CONTENT, "No information available." ); 171 return; 172 } 173 174 res.setContentType( MIME_TYPE ); 175 176 if ( repoId != null ) 177 { 178 feed.setLink( req.getRequestURL().toString() ); 179 } 180 else if ( ( groupId != null ) && ( artifactId != null ) ) 181 { 182 feed.setLink( req.getRequestURL().toString() ); 183 } 184 185 SyndFeedOutput output = new SyndFeedOutput(); 186 output.output( feed, res.getWriter() ); 187 } 188 catch ( UserNotFoundException unfe ) 189 { 190 log.debug( COULD_NOT_AUTHENTICATE_USER, unfe ); 191 res.sendError( HttpServletResponse.SC_UNAUTHORIZED, COULD_NOT_AUTHENTICATE_USER ); 192 } 193 catch ( AccountLockedException acce ) 194 { 195 res.sendError( HttpServletResponse.SC_UNAUTHORIZED, COULD_NOT_AUTHENTICATE_USER ); 196 } 197 catch ( AuthenticationException authe ) 198 { 199 log.debug( COULD_NOT_AUTHENTICATE_USER, authe ); 200 res.sendError( HttpServletResponse.SC_UNAUTHORIZED, COULD_NOT_AUTHENTICATE_USER ); 201 } 202 catch ( FeedException ex ) 203 { 204 log.debug( COULD_NOT_GENERATE_FEED_ERROR, ex ); 205 res.sendError( HttpServletResponse.SC_INTERNAL_SERVER_ERROR, COULD_NOT_GENERATE_FEED_ERROR ); 206 } 207 catch ( MustChangePasswordException e ) 208 { 209 res.sendError( HttpServletResponse.SC_UNAUTHORIZED, COULD_NOT_AUTHENTICATE_USER ); 210 } 211 catch ( UnauthorizedException e ) 212 { 213 log.debug( e.getMessage() ); 214 if ( repoId != null ) 215 { 216 res.setHeader( "WWW-Authenticate", 217 "Basic realm=\"Repository Archiva Managed " + repoId + " Repository" ); 218 } 219 else 220 { 221 res.setHeader( "WWW-Authenticate", "Basic realm=\"Artifact " + groupId + ":" + artifactId ); 222 } 223 224 res.sendError( HttpServletResponse.SC_UNAUTHORIZED, USER_NOT_AUTHORIZED ); 225 } 226 } 227 228 /** 229 * Basic authentication. 230 * 231 * @param req 232 * @param repositoryId TODO 233 * @param groupId TODO 234 * @param artifactId TODO 235 * @return 236 */ 237 private boolean isAllowed( HttpServletRequest req, String repositoryId, String groupId, String artifactId ) 238 throws UserNotFoundException, AccountLockedException, AuthenticationException, MustChangePasswordException, 239 UnauthorizedException 240 { 241 String auth = req.getHeader( "Authorization" ); 242 List<String> repoIds = new ArrayList<String>(); 243 244 if ( repositoryId != null ) 245 { 246 repoIds.add( repositoryId ); 247 } 248 else if ( artifactId != null && groupId != null ) 249 { 250 if ( auth != null ) 251 { 252 if ( !auth.toUpperCase().startsWith( "BASIC " ) ) 253 { 254 return false; 255 } 256 257 Decoder dec = new Base64(); 258 String usernamePassword = ""; 259 260 try 261 { 262 usernamePassword = new String( (byte[]) dec.decode( auth.substring( 6 ).getBytes() ) ); 263 } 264 catch ( DecoderException ie ) 265 { 266 log.warn( "Error decoding username and password.", ie.getMessage() ); 267 } 268 269 if ( usernamePassword == null || usernamePassword.trim().equals( "" ) ) 270 { 271 repoIds = getObservableRepos( UserManager.GUEST_USERNAME ); 272 } 273 else 274 { 275 String[] userCredentials = usernamePassword.split( ":" ); 276 repoIds = getObservableRepos( userCredentials[0] ); 277 } 278 } 279 else 280 { 281 repoIds = getObservableRepos( UserManager.GUEST_USERNAME ); 282 } 283 } 284 else 285 { 286 return false; 287 } 288 289 for ( String repoId : repoIds ) 290 { 291 try 292 { 293 AuthenticationResult result = httpAuth.getAuthenticationResult( req, null ); 294 SecuritySession securitySession = httpAuth.getSecuritySession( req.getSession( true ) ); 295 296 if ( servletAuth.isAuthenticated( req, result ) && servletAuth.isAuthorized( req, securitySession, 297 repoId, 298 ArchivaRoleConstants.OPERATION_REPOSITORY_ACCESS ) ) 299 { 300 return true; 301 } 302 } 303 catch ( AuthorizationException e ) 304 { 305 306 } 307 catch ( UnauthorizedException e ) 308 { 309 310 } 311 } 312 313 throw new UnauthorizedException( "Access denied." ); 314 } 315 316 private List<String> getObservableRepos( String principal ) 317 { 318 try 319 { 320 return userRepositories.getObservableRepositoryIds( principal ); 321 } 322 catch ( PrincipalNotFoundException e ) 323 { 324 log.warn( e.getMessage(), e ); 325 } 326 catch ( AccessDeniedException e ) 327 { 328 log.warn( e.getMessage(), e ); 329 } 330 catch ( ArchivaSecurityException e ) 331 { 332 log.warn( e.getMessage(), e ); 333 } 334 335 return Collections.emptyList(); 336 } 337 338 }