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}