/* * Copyright 2014 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.nosceon.titanite.auth.basic; import io.netty.handler.codec.http.HttpHeaders; import org.nosceon.titanite.Filter; import org.nosceon.titanite.Request; import org.nosceon.titanite.Response; import org.nosceon.titanite.auth.Auth; import java.util.Base64; import java.util.concurrent.CompletionStage; import java.util.function.Function; import static org.nosceon.titanite.Response.forbidden; import static org.nosceon.titanite.Response.unauthorized; /** * @author Johan Siebens */ public final class BasicAuthentication implements Filter { private static final String PREFIX = "Basic"; private final BasicAuthenticator<?> authenticator; private final String challenge; private final Function<Request, CompletionStage<Response>> accessDeniedHandler; public BasicAuthentication(BasicAuthenticator<?> authenticator) { this("Titanite", authenticator); } public BasicAuthentication(BasicAuthenticator<?> authenticator, Function<Request, CompletionStage<Response>> accessDeniedHandler) { this("Titanite", authenticator, accessDeniedHandler); } public BasicAuthentication(String realm, BasicAuthenticator<?> authenticator) { this(realm, authenticator, request -> forbidden().text("Access Denied\n").toFuture()); } public BasicAuthentication(String realm, BasicAuthenticator<?> authenticator, Function<Request, CompletionStage<Response>> accessDeniedHandler) { this.authenticator = authenticator; this.challenge = String.format(PREFIX + " realm=\"%s\"", realm); this.accessDeniedHandler = accessDeniedHandler; } @Override public CompletionStage<Response> apply(Request request, Function<Request, CompletionStage<Response>> handler) { String header = request.headers().getString(HttpHeaders.Names.AUTHORIZATION); Request req = request .withAttribute(Auth.UNAUTHORIZED_ATTRIBUTE_ID, unauthorizedHandler()) .withAttribute(Auth.ACCESS_DENIED_ATTRIBUTE_ID, accessDeniedHandler()); if (header != null) { final int space = header.indexOf(' '); if (space > 0) { String method = header.substring(0, space); if (PREFIX.equalsIgnoreCase(method)) { String decoded = decode(header.substring(space + 1)); int i = decoded.indexOf(':'); if (i > 0) { String username = decoded.substring(0, i); String password = decoded.substring(i + 1); return authenticator .authenticate(username, password) .thenCompose(optAuth -> optAuth .map(auth -> handler.apply(req.withAttribute(Auth.ATTRIBUTE_ID, auth))) .orElseGet(() -> handler.apply(req))); } } } } return handler.apply(req); } private Function<Request, CompletionStage<Response>> unauthorizedHandler() { return request -> unauthorized().header(HttpHeaders.Names.WWW_AUTHENTICATE, challenge).toFuture(); } private Function<Request, CompletionStage<Response>> accessDeniedHandler() { return accessDeniedHandler; } private String decode(String value) { return new String(Base64.getDecoder().decode(value)); } }