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