You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

RssFeedServlet.java 13KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342
  1. package org.apache.maven.archiva.web.rss;
  2. /*
  3. * Licensed to the Apache Software Foundation (ASF) under one
  4. * or more contributor license agreements. See the NOTICE file
  5. * distributed with this work for additional information
  6. * regarding copyright ownership. The ASF licenses this file
  7. * to you under the Apache License, Version 2.0 (the
  8. * "License"); you may not use this file except in compliance
  9. * with the License. You may obtain a copy of the License at
  10. *
  11. * http://www.apache.org/licenses/LICENSE-2.0
  12. *
  13. * Unless required by applicable law or agreed to in writing,
  14. * software distributed under the License is distributed on an
  15. * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
  16. * KIND, either express or implied. See the License for the
  17. * specific language governing permissions and limitations
  18. * under the License.
  19. */
  20. import java.io.IOException;
  21. import java.util.ArrayList;
  22. import java.util.Collections;
  23. import java.util.HashMap;
  24. import java.util.List;
  25. import java.util.Map;
  26. import javax.servlet.ServletException;
  27. import javax.servlet.http.HttpServlet;
  28. import javax.servlet.http.HttpServletRequest;
  29. import javax.servlet.http.HttpServletResponse;
  30. import org.apache.archiva.rss.processor.RssFeedProcessor;
  31. import org.apache.commons.codec.Decoder;
  32. import org.apache.commons.codec.DecoderException;
  33. import org.apache.commons.codec.binary.Base64;
  34. import org.apache.commons.lang.StringUtils;
  35. import org.apache.maven.archiva.database.ArchivaDatabaseException;
  36. import org.apache.maven.archiva.security.AccessDeniedException;
  37. import org.apache.maven.archiva.security.ArchivaRoleConstants;
  38. import org.apache.maven.archiva.security.ArchivaSecurityException;
  39. import org.apache.maven.archiva.security.ArchivaXworkUser;
  40. import org.apache.maven.archiva.security.PrincipalNotFoundException;
  41. import org.apache.maven.archiva.security.ServletAuthenticator;
  42. import org.apache.maven.archiva.security.UserRepositories;
  43. import org.codehaus.plexus.redback.authentication.AuthenticationException;
  44. import org.codehaus.plexus.redback.authentication.AuthenticationResult;
  45. import org.codehaus.plexus.redback.authorization.AuthorizationException;
  46. import org.codehaus.plexus.redback.authorization.UnauthorizedException;
  47. import org.codehaus.plexus.redback.policy.AccountLockedException;
  48. import org.codehaus.plexus.redback.policy.MustChangePasswordException;
  49. import org.codehaus.plexus.redback.system.SecuritySession;
  50. import org.codehaus.plexus.redback.users.UserNotFoundException;
  51. import org.codehaus.plexus.redback.struts2.filter.authentication.HttpAuthenticator;
  52. import org.codehaus.plexus.spring.PlexusToSpringUtils;
  53. import org.slf4j.Logger;
  54. import org.slf4j.LoggerFactory;
  55. import org.springframework.web.context.WebApplicationContext;
  56. import org.springframework.web.context.support.WebApplicationContextUtils;
  57. import com.sun.syndication.feed.synd.SyndFeed;
  58. import com.sun.syndication.io.FeedException;
  59. import com.sun.syndication.io.SyndFeedOutput;
  60. /**
  61. * Servlet for handling rss feed requests.
  62. *
  63. * @author <a href="mailto:oching@apache.org">Maria Odea Ching</a>
  64. * @version
  65. */
  66. public class RssFeedServlet
  67. extends HttpServlet
  68. {
  69. public static final String MIME_TYPE = "application/rss+xml; charset=UTF-8";
  70. private static final String COULD_NOT_GENERATE_FEED_ERROR = "Could not generate feed";
  71. private static final String COULD_NOT_AUTHENTICATE_USER = "Could not authenticate user";
  72. private static final String USER_NOT_AUTHORIZED = "User not authorized to access feed.";
  73. private Logger log = LoggerFactory.getLogger( RssFeedServlet.class );
  74. private RssFeedProcessor processor;
  75. private WebApplicationContext wac;
  76. private UserRepositories userRepositories;
  77. private ServletAuthenticator servletAuth;
  78. private HttpAuthenticator httpAuth;
  79. private ArchivaXworkUser archivaXworkUser;
  80. public void init( javax.servlet.ServletConfig servletConfig )
  81. throws ServletException
  82. {
  83. super.init( servletConfig );
  84. wac = WebApplicationContextUtils.getRequiredWebApplicationContext( servletConfig.getServletContext() );
  85. userRepositories =
  86. (UserRepositories) wac.getBean( PlexusToSpringUtils.buildSpringId( UserRepositories.class.getName() ) );
  87. servletAuth =
  88. (ServletAuthenticator) wac.getBean( PlexusToSpringUtils.buildSpringId( ServletAuthenticator.class.getName() ) );
  89. httpAuth =
  90. (HttpAuthenticator) wac.getBean( PlexusToSpringUtils.buildSpringId( HttpAuthenticator.ROLE, "basic" ) );
  91. archivaXworkUser = (ArchivaXworkUser) wac.getBean( PlexusToSpringUtils.buildSpringId( ArchivaXworkUser.class ) );
  92. }
  93. public void doGet( HttpServletRequest req, HttpServletResponse res )
  94. throws ServletException, IOException
  95. {
  96. String repoId = null;
  97. String groupId = null;
  98. String artifactId = null;
  99. String url = StringUtils.removeEnd( req.getRequestURL().toString(), "/" );
  100. if( StringUtils.countMatches( StringUtils.substringAfter( url, "feeds/" ), "/" ) > 0 )
  101. {
  102. artifactId = StringUtils.substringAfterLast( url, "/" );
  103. groupId = StringUtils.substringBeforeLast( StringUtils.substringAfter( url, "feeds/" ), "/");
  104. groupId = StringUtils.replaceChars( groupId, '/', '.' );
  105. }
  106. else if( StringUtils.countMatches( StringUtils.substringAfter( url, "feeds/" ), "/" ) == 0 )
  107. {
  108. repoId = StringUtils.substringAfterLast( url, "/" );
  109. }
  110. else
  111. {
  112. res.sendError( HttpServletResponse.SC_BAD_REQUEST, "Invalid request url." );
  113. return;
  114. }
  115. try
  116. {
  117. Map<String, String> map = new HashMap<String, String>();
  118. SyndFeed feed = null;
  119. if ( isAllowed( req, repoId, groupId, artifactId ) )
  120. {
  121. if ( repoId != null )
  122. {
  123. // new artifacts in repo feed request
  124. processor =
  125. (RssFeedProcessor) wac.getBean( PlexusToSpringUtils.buildSpringId(
  126. RssFeedProcessor.class.getName(),
  127. "new-artifacts" ) );
  128. map.put( RssFeedProcessor.KEY_REPO_ID, repoId );
  129. }
  130. else if ( ( groupId != null ) && ( artifactId != null ) )
  131. {
  132. // new versions of artifact feed request
  133. processor =
  134. (RssFeedProcessor) wac.getBean( PlexusToSpringUtils.buildSpringId(
  135. RssFeedProcessor.class.getName(),
  136. "new-versions" ) );
  137. map.put( RssFeedProcessor.KEY_GROUP_ID, groupId );
  138. map.put( RssFeedProcessor.KEY_ARTIFACT_ID, artifactId );
  139. }
  140. }
  141. else
  142. {
  143. res.sendError( HttpServletResponse.SC_UNAUTHORIZED, USER_NOT_AUTHORIZED );
  144. return;
  145. }
  146. feed = processor.process( map );
  147. if( feed == null )
  148. {
  149. res.sendError( HttpServletResponse.SC_NO_CONTENT, "No information available." );
  150. return;
  151. }
  152. res.setContentType( MIME_TYPE );
  153. if ( repoId != null )
  154. {
  155. feed.setLink( req.getRequestURL().toString() );
  156. }
  157. else if ( ( groupId != null ) && ( artifactId != null ) )
  158. {
  159. feed.setLink( req.getRequestURL().toString() );
  160. }
  161. SyndFeedOutput output = new SyndFeedOutput();
  162. output.output( feed, res.getWriter() );
  163. }
  164. catch ( ArchivaDatabaseException e )
  165. {
  166. log.debug( COULD_NOT_GENERATE_FEED_ERROR, e );
  167. res.sendError( HttpServletResponse.SC_INTERNAL_SERVER_ERROR, COULD_NOT_GENERATE_FEED_ERROR );
  168. }
  169. catch ( UserNotFoundException unfe )
  170. {
  171. log.debug( COULD_NOT_AUTHENTICATE_USER, unfe );
  172. res.sendError( HttpServletResponse.SC_UNAUTHORIZED, COULD_NOT_AUTHENTICATE_USER );
  173. }
  174. catch ( AccountLockedException acce )
  175. {
  176. res.sendError( HttpServletResponse.SC_UNAUTHORIZED, COULD_NOT_AUTHENTICATE_USER );
  177. }
  178. catch ( AuthenticationException authe )
  179. {
  180. log.debug( COULD_NOT_AUTHENTICATE_USER, authe );
  181. res.sendError( HttpServletResponse.SC_UNAUTHORIZED, COULD_NOT_AUTHENTICATE_USER );
  182. }
  183. catch ( FeedException ex )
  184. {
  185. log.debug( COULD_NOT_GENERATE_FEED_ERROR, ex );
  186. res.sendError( HttpServletResponse.SC_INTERNAL_SERVER_ERROR, COULD_NOT_GENERATE_FEED_ERROR );
  187. }
  188. catch ( MustChangePasswordException e )
  189. {
  190. res.sendError( HttpServletResponse.SC_UNAUTHORIZED, COULD_NOT_AUTHENTICATE_USER );
  191. }
  192. catch ( UnauthorizedException e )
  193. {
  194. log.debug( e.getMessage() );
  195. if ( repoId != null )
  196. {
  197. res.setHeader("WWW-Authenticate", "Basic realm=\"Repository Archiva Managed " + repoId + " Repository" );
  198. }
  199. else
  200. {
  201. res.setHeader("WWW-Authenticate", "Basic realm=\"Artifact " + groupId + ":" + artifactId );
  202. }
  203. res.sendError( HttpServletResponse.SC_UNAUTHORIZED, USER_NOT_AUTHORIZED );
  204. }
  205. }
  206. /**
  207. * Basic authentication.
  208. *
  209. * @param req
  210. * @param repositoryId TODO
  211. * @param groupId TODO
  212. * @param artifactId TODO
  213. * @return
  214. */
  215. private boolean isAllowed( HttpServletRequest req, String repositoryId, String groupId, String artifactId )
  216. throws UserNotFoundException, AccountLockedException, AuthenticationException, MustChangePasswordException,
  217. UnauthorizedException
  218. {
  219. String auth = req.getHeader( "Authorization" );
  220. List<String> repoIds = new ArrayList<String>();
  221. if ( repositoryId != null )
  222. {
  223. repoIds.add( repositoryId );
  224. }
  225. else if ( artifactId != null && groupId != null )
  226. {
  227. if ( auth != null )
  228. {
  229. if ( !auth.toUpperCase().startsWith( "BASIC " ) )
  230. {
  231. return false;
  232. }
  233. Decoder dec = new Base64();
  234. String usernamePassword = "";
  235. try
  236. {
  237. usernamePassword = new String( (byte[]) dec.decode( auth.substring( 6 ).getBytes() ) );
  238. }
  239. catch ( DecoderException ie )
  240. {
  241. log.warn( "Error decoding username and password.", ie.getMessage() );
  242. }
  243. if ( usernamePassword == null || usernamePassword.trim().equals( "" ) )
  244. {
  245. repoIds = getObservableRepos( archivaXworkUser.getGuest() );
  246. }
  247. else
  248. {
  249. String[] userCredentials = usernamePassword.split( ":" );
  250. repoIds = getObservableRepos( userCredentials[0] );
  251. }
  252. }
  253. else
  254. {
  255. repoIds = getObservableRepos( archivaXworkUser.getGuest() );
  256. }
  257. }
  258. else
  259. {
  260. return false;
  261. }
  262. for ( String repoId : repoIds )
  263. {
  264. try
  265. {
  266. AuthenticationResult result = httpAuth.getAuthenticationResult( req, null );
  267. SecuritySession securitySession = httpAuth.getSecuritySession();
  268. if ( servletAuth.isAuthenticated( req, result ) &&
  269. servletAuth.isAuthorized( req, securitySession, repoId, false ) )
  270. {
  271. return true;
  272. }
  273. }
  274. catch ( AuthorizationException e )
  275. {
  276. }
  277. catch ( UnauthorizedException e )
  278. {
  279. }
  280. }
  281. throw new UnauthorizedException( "Access denied." );
  282. }
  283. private List<String> getObservableRepos( String principal )
  284. {
  285. try
  286. {
  287. return userRepositories.getObservableRepositoryIds( principal );
  288. }
  289. catch ( PrincipalNotFoundException e )
  290. {
  291. log.warn( e.getMessage(), e );
  292. }
  293. catch ( AccessDeniedException e )
  294. {
  295. log.warn( e.getMessage(), e );
  296. }
  297. catch ( ArchivaSecurityException e )
  298. {
  299. log.warn( e.getMessage(), e );
  300. }
  301. return Collections.emptyList();
  302. }
  303. }