001package 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 022import com.rometools.rome.feed.synd.SyndFeed; 023import com.rometools.rome.io.FeedException; 024import com.rometools.rome.io.SyndFeedOutput; 025import org.apache.archiva.metadata.repository.RepositorySessionFactory; 026import org.apache.archiva.redback.authentication.AuthenticationException; 027import org.apache.archiva.redback.authentication.AuthenticationResult; 028import org.apache.archiva.redback.authorization.AuthorizationException; 029import org.apache.archiva.redback.authorization.UnauthorizedException; 030import org.apache.archiva.redback.integration.filter.authentication.HttpAuthenticator; 031import org.apache.archiva.redback.policy.AccountLockedException; 032import org.apache.archiva.redback.policy.MustChangePasswordException; 033import org.apache.archiva.redback.system.SecuritySession; 034import org.apache.archiva.redback.users.UserManager; 035import org.apache.archiva.redback.users.UserNotFoundException; 036import org.apache.archiva.rss.processor.RssFeedProcessor; 037import org.apache.archiva.security.AccessDeniedException; 038import org.apache.archiva.security.ArchivaSecurityException; 039import org.apache.archiva.security.PrincipalNotFoundException; 040import org.apache.archiva.security.ServletAuthenticator; 041import org.apache.archiva.security.UserRepositories; 042import org.apache.archiva.security.common.ArchivaRoleConstants; 043import org.apache.commons.codec.Decoder; 044import org.apache.commons.codec.DecoderException; 045import org.apache.commons.codec.binary.Base64; 046import org.apache.commons.lang3.StringUtils; 047import org.slf4j.Logger; 048import org.slf4j.LoggerFactory; 049import org.springframework.web.context.WebApplicationContext; 050import org.springframework.web.context.support.WebApplicationContextUtils; 051 052import javax.servlet.ServletConfig; 053import javax.servlet.ServletException; 054import javax.servlet.http.HttpServlet; 055import javax.servlet.http.HttpServletRequest; 056import javax.servlet.http.HttpServletResponse; 057import java.io.IOException; 058import java.util.ArrayList; 059import java.util.Collections; 060import java.util.HashMap; 061import java.util.List; 062import java.util.Map; 063 064/** 065 * Servlet for handling rss feed requests. 066 */ 067public 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 WebApplicationContext wac; 081 082 private UserRepositories userRepositories; 083 084 private ServletAuthenticator servletAuth; 085 086 private HttpAuthenticator httpAuth; 087 088 private RssFeedProcessor newArtifactsprocessor; 089 090 private RssFeedProcessor newVersionsprocessor; 091 092 /** 093 * FIXME: this could be multiple implementations and needs to be configured. 094 */ 095 private RepositorySessionFactory repositorySessionFactory; 096 097 @Override 098 public void init( ServletConfig servletConfig ) 099 throws ServletException 100 { 101 super.init( servletConfig ); 102 wac = WebApplicationContextUtils.getRequiredWebApplicationContext( servletConfig.getServletContext() ); 103 userRepositories = wac.getBean( UserRepositories.class ); 104 servletAuth = wac.getBean( ServletAuthenticator.class ); 105 httpAuth = wac.getBean( "httpAuthenticator#basic", HttpAuthenticator.class ); 106 // TODO: what if there are other types? 107 repositorySessionFactory = wac.getBean( "repositorySessionFactory", RepositorySessionFactory.class ); 108 109 newArtifactsprocessor = wac.getBean( "rssFeedProcessor#new-artifacts", RssFeedProcessor.class ); 110 newVersionsprocessor = wac.getBean( "rssFeedProcessor#new-versions", RssFeedProcessor.class ); 111 } 112 113 @Override 114 public void doGet( HttpServletRequest req, HttpServletResponse res ) 115 throws ServletException, IOException 116 { 117 118 119 String repoId = null; 120 String groupId = null; 121 String artifactId = null; 122 123 String url = StringUtils.removeEnd( req.getRequestURL().toString(), "/" ); 124 125 if ( StringUtils.countMatches( StringUtils.substringAfter( url, "feeds/" ), "/" ) > 0 ) 126 { 127 artifactId = StringUtils.substringAfterLast( url, "/" ); 128 groupId = StringUtils.substringBeforeLast( StringUtils.substringAfter( url, "feeds/" ), "/" ); 129 groupId = StringUtils.replaceChars( groupId, '/', '.' ); 130 } 131 else if ( StringUtils.countMatches( StringUtils.substringAfter( url, "feeds/" ), "/" ) == 0 ) 132 { 133 // we receive feeds?babla=ded which is not correct 134 if ( StringUtils.countMatches( url, "feeds?" ) > 0 ) 135 { 136 res.sendError( HttpServletResponse.SC_BAD_REQUEST, "Invalid request url." ); 137 return; 138 } 139 repoId = StringUtils.substringAfterLast( url, "/" ); 140 } 141 else 142 { 143 res.sendError( HttpServletResponse.SC_BAD_REQUEST, "Invalid request url." ); 144 return; 145 } 146 147 RssFeedProcessor processor = null; 148 149 try 150 { 151 Map<String, String> map = new HashMap<>(); 152 SyndFeed feed = null; 153 154 if ( isAllowed( req, repoId, groupId, artifactId ) ) 155 { 156 if ( repoId != null ) 157 { 158 // new artifacts in repo feed request 159 processor = newArtifactsprocessor; 160 map.put( RssFeedProcessor.KEY_REPO_ID, repoId ); 161 } 162 else if ( ( groupId != null ) && ( artifactId != null ) ) 163 { 164 // TODO: this only works for guest - we could pass in the list of repos 165 // new versions of artifact feed request 166 processor = newVersionsprocessor; 167 map.put( RssFeedProcessor.KEY_GROUP_ID, groupId ); 168 map.put( RssFeedProcessor.KEY_ARTIFACT_ID, artifactId ); 169 } 170 } 171 else 172 { 173 res.sendError( HttpServletResponse.SC_UNAUTHORIZED, USER_NOT_AUTHORIZED ); 174 return; 175 } 176 177 feed = processor.process( map ); 178 179 if ( feed == null ) 180 { 181 res.sendError( HttpServletResponse.SC_NO_CONTENT, "No information available." ); 182 return; 183 } 184 185 res.setContentType( MIME_TYPE ); 186 187 if ( repoId != null ) 188 { 189 feed.setLink( req.getRequestURL().toString() ); 190 } 191 else if ( ( groupId != null ) && ( artifactId != null ) ) 192 { 193 feed.setLink( req.getRequestURL().toString() ); 194 } 195 196 SyndFeedOutput output = new SyndFeedOutput(); 197 output.output( feed, res.getWriter() ); 198 } 199 catch ( UserNotFoundException unfe ) 200 { 201 log.debug( COULD_NOT_AUTHENTICATE_USER, unfe ); 202 res.sendError( HttpServletResponse.SC_UNAUTHORIZED, COULD_NOT_AUTHENTICATE_USER ); 203 } 204 catch ( AccountLockedException acce ) 205 { 206 res.sendError( HttpServletResponse.SC_UNAUTHORIZED, COULD_NOT_AUTHENTICATE_USER ); 207 } 208 catch ( AuthenticationException authe ) 209 { 210 log.debug( COULD_NOT_AUTHENTICATE_USER, authe ); 211 res.sendError( HttpServletResponse.SC_UNAUTHORIZED, COULD_NOT_AUTHENTICATE_USER ); 212 } 213 catch ( FeedException ex ) 214 { 215 log.debug( COULD_NOT_GENERATE_FEED_ERROR, ex ); 216 res.sendError( HttpServletResponse.SC_INTERNAL_SERVER_ERROR, COULD_NOT_GENERATE_FEED_ERROR ); 217 } 218 catch ( MustChangePasswordException e ) 219 { 220 res.sendError( HttpServletResponse.SC_UNAUTHORIZED, COULD_NOT_AUTHENTICATE_USER ); 221 } 222 catch ( UnauthorizedException e ) 223 { 224 log.debug( e.getMessage() ); 225 if ( repoId != null ) 226 { 227 res.setHeader( "WWW-Authenticate", 228 "Basic realm=\"Repository Archiva Managed " + repoId + " Repository" ); 229 } 230 else 231 { 232 res.setHeader( "WWW-Authenticate", "Basic realm=\"Artifact " + groupId + ":" + artifactId ); 233 } 234 235 res.sendError( HttpServletResponse.SC_UNAUTHORIZED, USER_NOT_AUTHORIZED ); 236 } 237 } 238 239 /** 240 * Basic authentication. 241 * 242 * @param req 243 * @param repositoryId TODO 244 * @param groupId TODO 245 * @param artifactId TODO 246 * @return 247 */ 248 private boolean isAllowed( HttpServletRequest req, String repositoryId, String groupId, String artifactId ) 249 throws UserNotFoundException, AccountLockedException, AuthenticationException, MustChangePasswordException, 250 UnauthorizedException 251 { 252 String auth = req.getHeader( "Authorization" ); 253 List<String> repoIds = new ArrayList<>(); 254 255 if ( repositoryId != null ) 256 { 257 repoIds.add( repositoryId ); 258 } 259 else if ( artifactId != null && groupId != null ) 260 { 261 if ( auth != null ) 262 { 263 if ( !auth.toUpperCase().startsWith( "BASIC " ) ) 264 { 265 return false; 266 } 267 268 Decoder dec = new Base64(); 269 String usernamePassword = ""; 270 271 try 272 { 273 usernamePassword = new String( (byte[]) dec.decode( auth.substring( 6 ).getBytes() ) ); 274 } 275 catch ( DecoderException ie ) 276 { 277 log.warn( "Error decoding username and password: {}", ie.getMessage() ); 278 } 279 280 if ( usernamePassword == null || usernamePassword.trim().equals( "" ) ) 281 { 282 repoIds = getObservableRepos( UserManager.GUEST_USERNAME ); 283 } 284 else 285 { 286 String[] userCredentials = usernamePassword.split( ":" ); 287 repoIds = getObservableRepos( userCredentials[0] ); 288 } 289 } 290 else 291 { 292 repoIds = getObservableRepos( UserManager.GUEST_USERNAME ); 293 } 294 } 295 else 296 { 297 return false; 298 } 299 300 for ( String repoId : repoIds ) 301 { 302 try 303 { 304 AuthenticationResult result = httpAuth.getAuthenticationResult( req, null ); 305 SecuritySession securitySession = httpAuth.getSecuritySession( req.getSession( true ) ); 306 307 if ( servletAuth.isAuthenticated( req, result ) // 308 && servletAuth.isAuthorized( req, // 309 securitySession, // 310 repoId, // 311 ArchivaRoleConstants.OPERATION_REPOSITORY_ACCESS ) ) 312 { 313 return true; 314 } 315 } 316 catch ( AuthorizationException e ) 317 { 318 log.debug( "AuthorizationException for repoId: {}", repoId ); 319 } 320 catch ( UnauthorizedException e ) 321 { 322 log.debug( "UnauthorizedException for repoId: {}", repoId ); 323 } 324 } 325 326 throw new UnauthorizedException( "Access denied." ); 327 } 328 329 private List<String> getObservableRepos( String principal ) 330 { 331 try 332 { 333 return userRepositories.getObservableRepositoryIds( principal ); 334 } 335 catch ( PrincipalNotFoundException e ) 336 { 337 log.warn( e.getMessage(), e ); 338 } 339 catch ( AccessDeniedException e ) 340 { 341 log.warn( e.getMessage(), e ); 342 } 343 catch ( ArchivaSecurityException e ) 344 { 345 log.warn( e.getMessage(), e ); 346 } 347 348 return Collections.emptyList(); 349 } 350 351}