001package org.apache.archiva.redback.rest.services.interceptors;
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 * Unless required by applicable law or agreed to in writing,
014 * software distributed under the License is distributed on an
015 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
016 * KIND, either express or implied.  See the License for the
017 * specific language governing permissions and limitations
018 * under the License.
019 */
020
021import org.apache.archiva.redback.authentication.AuthenticationException;
022import org.apache.archiva.redback.authentication.AuthenticationFailureCause;
023import org.apache.archiva.redback.authentication.AuthenticationResult;
024import org.apache.archiva.redback.authentication.BearerTokenAuthenticationDataSource;
025import org.apache.archiva.redback.authentication.jwt.BearerError;
026import org.apache.archiva.redback.authentication.jwt.JwtAuthenticator;
027import org.apache.archiva.redback.authorization.RedbackAuthorization;
028import org.apache.archiva.redback.integration.filter.authentication.HttpAuthenticationException;
029import org.apache.archiva.redback.policy.AccountLockedException;
030import org.apache.archiva.redback.policy.MustChangePasswordException;
031import org.apache.archiva.redback.rbac.RBACManager;
032import org.apache.archiva.redback.rest.services.RedbackAuthenticationThreadLocal;
033import org.apache.archiva.redback.rest.services.RedbackRequestInformation;
034import org.apache.archiva.redback.system.SecuritySession;
035import org.apache.archiva.redback.system.SecuritySystem;
036import org.apache.archiva.redback.users.User;
037import org.apache.archiva.redback.users.UserManager;
038import org.apache.archiva.redback.users.UserManagerException;
039import org.apache.archiva.redback.users.UserNotFoundException;
040import org.apache.commons.lang3.StringUtils;
041import org.slf4j.Logger;
042import org.slf4j.LoggerFactory;
043import org.springframework.stereotype.Service;
044
045import javax.annotation.Priority;
046import javax.inject.Inject;
047import javax.inject.Named;
048import javax.servlet.http.HttpServletRequest;
049import javax.servlet.http.HttpServletResponse;
050import javax.ws.rs.container.ContainerRequestContext;
051import javax.ws.rs.container.ContainerRequestFilter;
052import javax.ws.rs.container.ResourceInfo;
053import javax.ws.rs.core.Context;
054import javax.ws.rs.core.Response;
055import javax.ws.rs.core.SecurityContext;
056import javax.ws.rs.core.UriInfo;
057import javax.ws.rs.ext.Provider;
058import java.io.IOException;
059import java.util.List;
060import java.util.function.Function;
061import java.util.regex.Pattern;
062import java.util.stream.Collectors;
063import java.util.stream.Stream;
064
065/**
066 * Interceptor that checks for the Bearer Header value and tries to verify the token.
067 *
068 * @author Martin Stockhammer <martin_s@apache.org>
069 * @since 3.0
070 */
071@Service( "bearerAuthInterceptor#rest" )
072@Provider
073@Priority( Priorities.AUTHENTICATION )
074public class BearerAuthInterceptor extends AbstractInterceptor
075    implements ContainerRequestFilter
076{
077
078    private static final Logger log = LoggerFactory.getLogger( BearerAuthInterceptor.class );
079
080    @Inject
081    @Named( value = "userManager#default" )
082    private UserManager userManager;
083
084    @Inject
085    @Named( value = "rbacManager#default" )
086    RBACManager rbacManager;
087
088    @Inject
089    @Named( value = "securitySystem" )
090    SecuritySystem securitySystem;
091
092    @Inject
093    JwtAuthenticator jwtAuthenticator;
094
095    @Context
096    private ResourceInfo resourceInfo;
097
098    protected void setUserManager( UserManager userManager )
099    {
100        this.userManager = userManager;
101    }
102
103    protected void setJwtAuthenticator( JwtAuthenticator jwtAuthenticator )
104    {
105        this.jwtAuthenticator = jwtAuthenticator;
106    }
107
108    protected void setResourceInfo( ResourceInfo resourceInfo )
109    {
110        this.resourceInfo = resourceInfo;
111    }
112
113    @Override
114    public void filter( ContainerRequestContext requestContext ) throws IOException
115    {
116        log.debug( "Intercepting request for bearer token" );
117        log.debug( "Request {}", requestContext.getUriInfo( ).getPath( ) );
118        final String requestPath = requestContext.getUriInfo( ).getPath( );
119        if (ignoreAuth( requestPath )) {
120            return;
121        }
122
123        // If no redback resource info, we deny the request
124        RedbackAuthorization redbackAuthorization = getRedbackAuthorization( resourceInfo );
125        if ( redbackAuthorization == null )
126        {
127
128            log.warn( "Request path {} doesn't contain any information regarding permissions. Denying access.",
129                requestContext.getUriInfo( ).getRequestUri( ) );
130            // here we failed to authenticate so 403 as there is no detail on karma for this
131            // it must be marked as it's exposed
132            requestContext.abortWith( Response.status( Response.Status.FORBIDDEN ).build( ) );
133            return;
134        }
135        String bearerHeader = StringUtils.defaultIfEmpty( requestContext.getHeaderString( "Authorization" ), "" ).trim( );
136        if ( !"".equals( bearerHeader ) )
137        {
138            log.debug( "Found Bearer token in header" );
139            String bearerToken = bearerHeader.replaceFirst( "\\s*Bearer\\s+(\\S+)\\s*", "$1" );
140            final HttpServletRequest request = getHttpServletRequest( );
141            BearerTokenAuthenticationDataSource source = new BearerTokenAuthenticationDataSource( "", bearerToken );
142
143            if ( redbackAuthorization.noRestriction( ) )
144            {
145                log.debug( "No restriction for method {}#{}", resourceInfo.getResourceClass( ), resourceInfo.getResourceMethod( ) );
146                // maybe session exists so put it in threadLocal
147                // some services need the current user if logged
148                // maybe there is some authz in the request so try it but not fail so catch Exception !
149                try
150                {
151                    SecuritySession securitySession = securitySystem.authenticate( source );
152                    AuthenticationResult authenticationResult = securitySession.getAuthenticationResult( );
153
154                    if ( ( authenticationResult == null ) || ( !authenticationResult.isAuthenticated( ) ) )
155                    {
156                        return;
157                    }
158
159                    User user = authenticationResult.getUser( ) == null ? userManager.findUser(
160                        authenticationResult.getPrincipal( ) ) : authenticationResult.getUser( );
161                    RedbackRequestInformation redbackRequestInformation =
162                        new RedbackRequestInformation( securitySession, user, request.getRemoteAddr( ) );
163
164                    RedbackAuthenticationThreadLocal.set( redbackRequestInformation );
165                    requestContext.setProperty( AUTHENTICATION_RESULT, authenticationResult );
166                    requestContext.setProperty( SECURITY_SESSION, securitySession );
167                    RedbackSecurityContext securityContext = new RedbackSecurityContext(requestContext.getUriInfo(), user, securitySession );
168
169                    if (rbacManager!=null)
170                    {
171                        List<String> roleNames = rbacManager.getAssignedRoles( user.getUsername( ) ).stream( )
172                            .flatMap( role -> Stream.concat( Stream.of( role.getName( ) ), role.getChildRoleNames( ).stream( ) ) )
173                            .collect( Collectors.toList( ) );
174                        securityContext.setRoles( roleNames );
175                    }
176                    requestContext.setSecurityContext( securityContext );
177                }
178                catch ( Exception e )
179                {
180                    log.debug( "Authentication failed {}", e.getMessage( ), e );
181                    // ignore here
182                }
183                return;
184            }
185            HttpServletResponse response = getHttpServletResponse( );
186            try
187            {
188                SecuritySession securitySession = securitySystem.authenticate( source );
189                AuthenticationResult authenticationResult = securitySession.getAuthenticationResult( );
190
191                if ( ( authenticationResult == null ) || ( !authenticationResult.isAuthenticated( ) ) )
192                {
193                    String error;
194                    String message;
195                    if ( authenticationResult.getAuthenticationFailureCauses( ).size( ) > 0 )
196                    {
197                        AuthenticationFailureCause cause = authenticationResult.getAuthenticationFailureCauses( ).get( 0 );
198                        error = BearerError.get( cause.getCause( ) ).getError( );
199                        message = cause.getMessage( );
200                    }
201                    else
202                    {
203                        error = "invalid_token";
204                        message = "Unknown error";
205                    }
206                    response.setHeader( "WWW-Authenticate", "Bearer realm=\"" + request.getRemoteHost( ) + "\",error=\""
207                        + error + "\",error_description=\"" + message + "\"" );
208                    requestContext.abortWith( Response.status( Response.Status.UNAUTHORIZED ).build( ) );
209                    return;
210                }
211
212                User user = authenticationResult.getUser( ) == null
213                    ? userManager.findUser( authenticationResult.getPrincipal( ) )
214                    : authenticationResult.getUser( );
215
216                RedbackRequestInformation redbackRequestInformation =
217                    new RedbackRequestInformation( user, request.getRemoteAddr( ) );
218                redbackRequestInformation.setSecuritySession( securitySession );
219                RedbackAuthenticationThreadLocal.set( redbackRequestInformation );
220                // message.put( AuthenticationResult.class, authenticationResult );
221                requestContext.setProperty( AUTHENTICATION_RESULT, authenticationResult );
222                requestContext.setProperty( SECURITY_SESSION, securitySession );
223                RedbackSecurityContext securityContext = new RedbackSecurityContext(requestContext.getUriInfo(), user, securitySession );
224                requestContext.setSecurityContext( securityContext );
225                return;
226            }
227            catch ( AuthenticationException e )
228            {
229                response.setHeader( "WWW-Authenticate", "Bearer realm=\"" + request.getRemoteHost( )
230                    + "\",error=\"invalid_token\",error_description=\"" + e.getMessage( ) + "\"" );
231                requestContext.abortWith( Response.status( Response.Status.UNAUTHORIZED ).build( ) );
232            }
233            catch ( UserNotFoundException e )
234            {
235                response.setHeader( "WWW-Authenticate", "Bearer realm=\"" + request.getRemoteHost( )
236                    + "\",error=\"invalid_token\",error_description=\"user not found\"" );
237                requestContext.abortWith( Response.status( Response.Status.UNAUTHORIZED ).build( ) );
238            }
239            catch ( UserManagerException e )
240            {
241                log.error( "Error from user manager " + e.getMessage( ) );
242                requestContext.abortWith( Response.status( Response.Status.INTERNAL_SERVER_ERROR ).build( ) );
243            }
244            catch ( AccountLockedException e )
245            {
246                response.setHeader( "WWW-Authenticate", "Bearer realm=\"" + request.getRemoteHost( )
247                    + "\",error=\"invalid_token\",error_description=\"account locked\"" );
248                requestContext.abortWith( Response.status( Response.Status.UNAUTHORIZED ).build( ) );
249            }
250            catch ( MustChangePasswordException e )
251            {
252                response.setHeader( "WWW-Authenticate", "Bearer realm=\"" + request.getRemoteHost( )
253                    + "\",error=\"invalid_token\",error_description=\"password change required\"" );
254                requestContext.abortWith( Response.status( Response.Status.UNAUTHORIZED ).build( ) );
255            }
256
257
258        } else {
259            log.debug( "No Bearer token found" );
260        }
261    }
262}