/**
* Copyright 2011-2014 the original author or authors.
*/
package com.jetdrone.vertx.yoke.middleware;
import com.jetdrone.vertx.yoke.Middleware;
import org.jetbrains.annotations.NotNull;
import io.vertx.core.Handler;
import io.vertx.core.json.JsonObject;
import java.util.Base64;
/**
* # BasicAuth
*
* Enfore basic authentication by providing a AuthHandler.handler(user, pass), which must return true in order to gain
* access. Populates request.user. The final alternative is simply passing username / password strings.
*/
public class BasicAuth extends Middleware {
/**
* Realm name for the application
*/
private final String realm;
/**
* AuthHandler for validating this instance authentication requests.
*/
private final AuthHandler authHandler;
/**
* Creates a new BasicAuth middleware with a master username / password and a given realm.
* <pre>
* Yoke yoke = new Yoke(...);
* yoke.use("/admin", new BasicAuth("admin", "s3cr37",
* "MyApp Auth Required"));
* </pre>
*
* @param username the security principal user name
* @param password the security principal password
* @param realm the security realm
*/
public BasicAuth(@NotNull final String username, @NotNull final String password, @NotNull String realm) {
this.realm = realm;
authHandler = new AuthHandler() {
@Override
public void handle(String _username, String _password, Handler<JsonObject> result) {
boolean success = username.equals(_username) && password.equals(_password);
if (success) {
result.handle(new JsonObject().put("username", _username));
} else {
result.handle(null);
}
}
};
}
/**
* Creates a new BasicAuth middleware with a master username / password. By default the realm will be `Authentication required`.
*
* <pre>
* Yoke yoke = new Yoke(...);
* yoke.use("/admin", new BasicAuth("admin", "s3cr37"));
* </pre>
*
* @param username the security principal user name
* @param password the security principal password
*/
public BasicAuth(@NotNull String username, @NotNull String password) {
this (username, password, "Authentication required");
}
/**
* Creates a new BasicAuth middleware with a AuthHandler and a given realm.
*
* <pre>
* Yoke yoke = new Yoke(...);
* yoke.use("/admin", new AuthHandler() {
* public void handle(String user, String password, Handler next) {
* // a better example would be fetching user from a DB
* if ("user".equals(user) && "pass".equals(password)) {
* next.handle(true);
* } else {
* next.handle(false);
* }
* }
* }, "My App Auth");
* </pre>
*
* @param authHandler the authentication handler
* @param realm the security realm
*/
public BasicAuth(@NotNull String realm, @NotNull AuthHandler authHandler) {
this.realm = realm;
this.authHandler = authHandler;
}
/**
* Creates a new BasicAuth middleware with a AuthHandler.
*
* <pre>
* Yoke yoke = new Yoke(...);
* yoke.use("/admin", new AuthHandler() {
* public void handle(String user, String password, Handler next) {
* // a better example would be fetching user from a DB
* if ("user".equals(user) && "pass".equals(password)) {
* next.handle(true);
* } else {
* next.handle(false);
* }
* }
* });
* </pre>
* @param authHandler the authentication handler
*/
public BasicAuth(@NotNull AuthHandler authHandler) {
this("Authentication required", authHandler);
}
/**
* Handle all forbidden errors, in this case we need to add a special header to the response
*
* @param request yoke request
* @param next middleware to be called next
*/
private void handle401(final YokeRequest request, final Handler<Object> next) {
YokeResponse response = request.response();
response.putHeader("WWW-Authenticate", "Basic realm=\"" + getRealm(request) + "\"");
response.setStatusCode(401);
next.handle("No authorization token");
}
@Override
public void handle(@NotNull final YokeRequest request, @NotNull final Handler<Object> next) {
String authorization = request.getHeader("authorization");
if (authorization == null) {
handle401(request, next);
} else {
final String user;
final String pass;
final String scheme;
try {
String[] parts = authorization.split(" ");
scheme = parts[0];
String[] credentials = new String(Base64.getDecoder().decode(parts[1])).split(":");
user = credentials[0];
// when the header is: "user:"
pass = credentials.length > 1 ? credentials[1] : null;
} catch (ArrayIndexOutOfBoundsException e) {
handle401(request, next);
return;
} catch (IllegalArgumentException | NullPointerException e) {
// IllegalArgumentException includes PatternSyntaxException
next.handle(e);
return;
}
if (!"Basic".equals(scheme)) {
next.handle(400);
} else {
authHandler.handle(user, pass, new Handler<JsonObject>() {
@Override
public void handle(JsonObject json) {
if (json != null) {
request.put("user", user);
next.handle(null);
} else {
handle401(request, next);
}
}
});
}
}
}
/**
* Get the realm for this instance
*
* The usecase is a multitenant app where I want different realms for paths like /foo/homepage and /bar/homepage.
*
* @param request http yoke request
* @return realm name
*/
public String getRealm(@NotNull YokeRequest request) {
return realm;
}
}