2020-03-23 15:35:58 +01:00
|
|
|
/*
|
|
|
|
|
* MIT License
|
2015-02-21 21:06:35 +01:00
|
|
|
*
|
2020-03-23 15:35:58 +01:00
|
|
|
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
2015-02-21 21:06:35 +01:00
|
|
|
*
|
2020-03-23 15:35:58 +01:00
|
|
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
|
|
|
* of this software and associated documentation files (the "Software"), to deal
|
|
|
|
|
* in the Software without restriction, including without limitation the rights
|
|
|
|
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
|
|
|
* copies of the Software, and to permit persons to whom the Software is
|
|
|
|
|
* furnished to do so, subject to the following conditions:
|
2015-02-21 21:06:35 +01:00
|
|
|
*
|
2020-03-23 15:35:58 +01:00
|
|
|
* The above copyright notice and this permission notice shall be included in all
|
|
|
|
|
* copies or substantial portions of the Software.
|
2015-02-21 21:06:35 +01:00
|
|
|
*
|
2020-03-23 15:35:58 +01:00
|
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
|
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
|
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
|
|
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
|
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
|
|
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
|
|
|
* SOFTWARE.
|
2015-02-21 21:06:35 +01:00
|
|
|
*/
|
2021-06-10 08:27:01 +02:00
|
|
|
|
2015-02-21 21:06:35 +01:00
|
|
|
package sonia.scm.web;
|
|
|
|
|
|
|
|
|
|
//~--- non-JDK imports --------------------------------------------------------
|
|
|
|
|
|
2021-06-10 08:27:01 +02:00
|
|
|
import com.google.common.annotations.VisibleForTesting;
|
2017-01-12 19:50:39 +01:00
|
|
|
import com.google.inject.Inject;
|
2021-06-10 08:27:01 +02:00
|
|
|
import org.apache.shiro.authc.AuthenticationToken;
|
2015-02-21 21:06:35 +01:00
|
|
|
import org.apache.shiro.authc.UsernamePasswordToken;
|
|
|
|
|
import org.apache.shiro.codec.Base64;
|
|
|
|
|
import org.slf4j.Logger;
|
|
|
|
|
import org.slf4j.LoggerFactory;
|
2021-01-26 12:58:48 +01:00
|
|
|
import sonia.scm.Priority;
|
2015-02-21 21:06:35 +01:00
|
|
|
import sonia.scm.plugin.Extension;
|
2021-06-10 08:27:01 +02:00
|
|
|
import sonia.scm.security.BearerToken;
|
|
|
|
|
import sonia.scm.security.SessionId;
|
2015-04-01 10:27:38 +02:00
|
|
|
import sonia.scm.util.HttpUtil;
|
2015-02-21 21:06:35 +01:00
|
|
|
import sonia.scm.util.Util;
|
|
|
|
|
|
|
|
|
|
import javax.servlet.http.HttpServletRequest;
|
2021-06-10 08:27:01 +02:00
|
|
|
import java.io.UnsupportedEncodingException;
|
|
|
|
|
import java.nio.charset.Charset;
|
|
|
|
|
import java.nio.charset.StandardCharsets;
|
|
|
|
|
|
|
|
|
|
//~--- JDK imports ------------------------------------------------------------
|
2015-02-21 21:06:35 +01:00
|
|
|
|
|
|
|
|
/**
|
2015-04-01 10:27:38 +02:00
|
|
|
* Creates a {@link UsernamePasswordToken} from an authorization header with
|
|
|
|
|
* basic authentication.
|
2015-02-21 21:06:35 +01:00
|
|
|
*
|
|
|
|
|
* @author Sebastian Sdorra
|
|
|
|
|
* @since 2.0.0
|
|
|
|
|
*/
|
2021-01-26 12:58:48 +01:00
|
|
|
@Priority(100)
|
2015-02-21 21:06:35 +01:00
|
|
|
@Extension
|
2021-06-10 08:27:01 +02:00
|
|
|
public class BasicWebTokenGenerator extends SchemeBasedWebTokenGenerator {
|
|
|
|
|
|
|
|
|
|
@VisibleForTesting
|
|
|
|
|
static final String BEARER_TOKEN_IDENTIFIER = "__bearer_token";
|
2015-02-21 21:06:35 +01:00
|
|
|
|
2015-04-01 10:27:38 +02:00
|
|
|
/** credential separator for basic authentication */
|
2017-01-12 19:50:39 +01:00
|
|
|
private static final String CREDENTIAL_SEPARATOR = ":";
|
2015-02-21 21:06:35 +01:00
|
|
|
|
2017-01-12 19:50:39 +01:00
|
|
|
/** default encoding to decode basic authentication header */
|
2021-06-10 08:27:01 +02:00
|
|
|
private static final Charset DEFAULT_ENCODING = StandardCharsets.ISO_8859_1;
|
|
|
|
|
|
2015-02-21 21:06:35 +01:00
|
|
|
/**
|
|
|
|
|
* the logger for BasicWebTokenGenerator
|
|
|
|
|
*/
|
|
|
|
|
private static final Logger logger =
|
|
|
|
|
LoggerFactory.getLogger(BasicWebTokenGenerator.class);
|
|
|
|
|
|
2017-01-12 19:50:39 +01:00
|
|
|
private final UserAgentParser userAgentParser;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Constructs a new BasicWebTokenGenerator.
|
|
|
|
|
*
|
|
|
|
|
* @param userAgentParser parser for user-agent header
|
|
|
|
|
*/
|
|
|
|
|
@Inject
|
|
|
|
|
public BasicWebTokenGenerator(UserAgentParser userAgentParser) {
|
|
|
|
|
this.userAgentParser = userAgentParser;
|
|
|
|
|
}
|
2015-02-21 21:06:35 +01:00
|
|
|
|
|
|
|
|
/**
|
2015-04-01 10:27:38 +02:00
|
|
|
* Creates a {@link UsernamePasswordToken} from an authorization header with
|
|
|
|
|
* basic authentication scheme.
|
2015-02-21 21:06:35 +01:00
|
|
|
*
|
|
|
|
|
*
|
2015-04-01 10:27:38 +02:00
|
|
|
* @param request http servlet request
|
|
|
|
|
* @param scheme authentication scheme
|
|
|
|
|
* @param authorization authorization payload
|
2015-02-21 21:06:35 +01:00
|
|
|
*
|
2015-04-01 10:27:38 +02:00
|
|
|
* @return {@link UsernamePasswordToken} or {@code null}
|
2015-02-21 21:06:35 +01:00
|
|
|
*/
|
|
|
|
|
@Override
|
2021-06-10 08:27:01 +02:00
|
|
|
protected AuthenticationToken createToken(HttpServletRequest request, String scheme, String authorization)
|
2015-02-21 21:06:35 +01:00
|
|
|
{
|
2021-06-10 08:27:01 +02:00
|
|
|
AuthenticationToken authToken = null;
|
2015-02-21 21:06:35 +01:00
|
|
|
|
2015-04-01 10:27:38 +02:00
|
|
|
if (HttpUtil.AUTHORIZATION_SCHEME_BASIC.equalsIgnoreCase(scheme))
|
2015-02-21 21:06:35 +01:00
|
|
|
{
|
2017-01-12 19:50:39 +01:00
|
|
|
String token = decodeAuthenticationHeader(request, authorization);
|
2015-02-21 21:06:35 +01:00
|
|
|
|
|
|
|
|
int index = token.indexOf(CREDENTIAL_SEPARATOR);
|
|
|
|
|
|
|
|
|
|
if ((index > 0) && (index < token.length()))
|
|
|
|
|
{
|
|
|
|
|
String username = token.substring(0, index);
|
|
|
|
|
String password = token.substring(index + 1);
|
|
|
|
|
|
2021-06-10 08:27:01 +02:00
|
|
|
authToken = createTokenFromCredentials(request, username, password);
|
2015-02-21 21:06:35 +01:00
|
|
|
}
|
|
|
|
|
else if (logger.isWarnEnabled())
|
|
|
|
|
{
|
|
|
|
|
logger.warn("failed to read basic auth credentials");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return authToken;
|
|
|
|
|
}
|
2021-06-10 08:27:01 +02:00
|
|
|
|
|
|
|
|
private AuthenticationToken createTokenFromCredentials(HttpServletRequest request, String username, String password) {
|
|
|
|
|
if (Util.isNotEmpty(username) && Util.isNotEmpty(password)) {
|
|
|
|
|
if (BEARER_TOKEN_IDENTIFIER.equals(username)) {
|
|
|
|
|
logger.trace("create bearer token");
|
|
|
|
|
return BearerToken.create(SessionId.from(request).orElse(null), password);
|
|
|
|
|
} else {
|
|
|
|
|
logger.trace("create username password token for {}", username);
|
|
|
|
|
return new UsernamePasswordToken(username, password);
|
|
|
|
|
}
|
|
|
|
|
} else if (logger.isWarnEnabled()) {
|
|
|
|
|
logger.warn("username or password is null/empty");
|
|
|
|
|
}
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2017-01-12 19:50:39 +01:00
|
|
|
* Decode base64 of the basic authentication header. The method will use
|
2021-06-10 08:27:01 +02:00
|
|
|
* the charset provided by the {@link UserAgent}, if the
|
|
|
|
|
* {@link UserAgentParser} is not available the method will be fall back to
|
2017-01-12 19:50:39 +01:00
|
|
|
* ISO-8859-1.
|
|
|
|
|
*
|
|
|
|
|
* @param request http request
|
|
|
|
|
* @param authentication base64 encoded basic authentication string
|
|
|
|
|
*
|
|
|
|
|
* @return decoded basic authentication header
|
|
|
|
|
*
|
|
|
|
|
* @see <a href="http://goo.gl/tZEBS3">issue 627</a>
|
|
|
|
|
* @see <a href="http://goo.gl/NhbZ2F">Stackoverflow Basic Authentication</a>
|
|
|
|
|
*
|
|
|
|
|
* @throws UnsupportedEncodingException
|
|
|
|
|
*/
|
|
|
|
|
private String decodeAuthenticationHeader(HttpServletRequest request, String authentication)
|
|
|
|
|
{
|
|
|
|
|
Charset encoding = DEFAULT_ENCODING;
|
|
|
|
|
|
|
|
|
|
if (userAgentParser != null)
|
|
|
|
|
{
|
|
|
|
|
encoding = userAgentParser.parse(request).getBasicAuthenticationCharset();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return new String(Base64.decode(authentication), encoding);
|
|
|
|
|
}
|
2015-02-21 21:06:35 +01:00
|
|
|
}
|