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.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}