diff --git a/vertx-auth-properties/src/main/asciidoc/index.adoc b/vertx-auth-properties/src/main/asciidoc/index.adoc index 33d363ac8..8fd7ce023 100644 --- a/vertx-auth-properties/src/main/asciidoc/index.adoc +++ b/vertx-auth-properties/src/main/asciidoc/index.adoc @@ -29,4 +29,10 @@ role.developer=do_actual_work When describing roles a wildcard `*` can be used to indicate that the role has all permissions. +The provider is usually created from a file on the file system with +{@link io.vertx.ext.auth.properties.PropertyFileAuthentication#create(io.vertx.core.Vertx, java.lang.String)}. +When the content is not stored on the file system, e.g.: it is loaded from the classpath, a remote location or preprocessed +in memory, the provider can be created from a {@link io.vertx.core.buffer.Buffer} with +{@link io.vertx.ext.auth.properties.PropertyFileAuthentication#create(io.vertx.core.Vertx, io.vertx.core.buffer.Buffer)}. + The implementation currently assumes that user/password based authentication is used. diff --git a/vertx-auth-properties/src/main/java/io/vertx/ext/auth/properties/PropertyFileAuthentication.java b/vertx-auth-properties/src/main/java/io/vertx/ext/auth/properties/PropertyFileAuthentication.java index 61e44fe5b..5b9706574 100644 --- a/vertx-auth-properties/src/main/java/io/vertx/ext/auth/properties/PropertyFileAuthentication.java +++ b/vertx-auth-properties/src/main/java/io/vertx/ext/auth/properties/PropertyFileAuthentication.java @@ -14,6 +14,7 @@ import io.vertx.codegen.annotations.VertxGen; import io.vertx.core.Vertx; +import io.vertx.core.buffer.Buffer; import io.vertx.ext.auth.authentication.AuthenticationProvider; import io.vertx.ext.auth.properties.impl.PropertyFileAuthenticationImpl; @@ -34,4 +35,17 @@ public interface PropertyFileAuthentication extends AuthenticationProvider { static PropertyFileAuthentication create(Vertx vertx, String path) { return new PropertyFileAuthenticationImpl(vertx, path); } + + /** + * Create a File authentication provider from the given property file content. Use this when the + * content is not stored on the file system, e.g.: loaded from the classpath, a remote location + * or preprocessed in memory. + * + * @param vertx the Vert.x instance + * @param buffer the property file content + * @return the authentication provider + */ + static PropertyFileAuthentication create(Vertx vertx, Buffer buffer) { + return new PropertyFileAuthenticationImpl(vertx, buffer); + } } diff --git a/vertx-auth-properties/src/main/java/io/vertx/ext/auth/properties/PropertyFileAuthorization.java b/vertx-auth-properties/src/main/java/io/vertx/ext/auth/properties/PropertyFileAuthorization.java index 55b67ab26..dd00237d0 100644 --- a/vertx-auth-properties/src/main/java/io/vertx/ext/auth/properties/PropertyFileAuthorization.java +++ b/vertx-auth-properties/src/main/java/io/vertx/ext/auth/properties/PropertyFileAuthorization.java @@ -14,6 +14,7 @@ import io.vertx.codegen.annotations.VertxGen; import io.vertx.core.Vertx; +import io.vertx.core.buffer.Buffer; import io.vertx.ext.auth.authorization.AuthorizationProvider; import io.vertx.ext.auth.properties.impl.PropertyFileAuthenticationImpl; @@ -35,4 +36,17 @@ static PropertyFileAuthorization create(Vertx vertx, String path) { return new PropertyFileAuthenticationImpl(vertx, path); } + /** + * Create a File authorization provider from the given property file content. Use this when the + * content is not stored on the file system, e.g.: loaded from the classpath, a remote location + * or preprocessed in memory. + * + * @param vertx the Vert.x instance + * @param buffer the property file content + * @return the authorization provider + */ + static PropertyFileAuthorization create(Vertx vertx, Buffer buffer) { + return new PropertyFileAuthenticationImpl(vertx, buffer); + } + } diff --git a/vertx-auth-properties/src/main/java/io/vertx/ext/auth/properties/impl/PropertyFileAuthenticationImpl.java b/vertx-auth-properties/src/main/java/io/vertx/ext/auth/properties/impl/PropertyFileAuthenticationImpl.java index 72742d231..4905876cd 100644 --- a/vertx-auth-properties/src/main/java/io/vertx/ext/auth/properties/impl/PropertyFileAuthenticationImpl.java +++ b/vertx-auth-properties/src/main/java/io/vertx/ext/auth/properties/impl/PropertyFileAuthenticationImpl.java @@ -14,6 +14,7 @@ import io.vertx.core.Future; import io.vertx.core.Vertx; +import io.vertx.core.buffer.Buffer; import io.vertx.core.internal.logging.Logger; import io.vertx.core.internal.logging.LoggerFactory; import io.vertx.ext.auth.authentication.CredentialValidationException; @@ -72,9 +73,20 @@ private void addPermission(String permission) { public PropertyFileAuthenticationImpl(Vertx vertx, String path) { Objects.requireNonNull(vertx); this.path = Objects.requireNonNull(path); + parse(vertx.fileSystem().readFileBlocking(path).toString(StandardCharsets.UTF_8)); + } + + public PropertyFileAuthenticationImpl(Vertx vertx, Buffer buffer) { + Objects.requireNonNull(vertx); + Objects.requireNonNull(buffer); + // there is no path to identify this provider + this.path = "properties"; + parse(buffer.toString(StandardCharsets.UTF_8)); + } + + private void parse(String fileContent) { final Map roles = new HashMap<>(); - String fileContent = vertx.fileSystem().readFileBlocking(path).toString(StandardCharsets.UTF_8); String[] lines = fileContent.split("\n"); for (String line : lines) { if (line.length() == 0 || line.startsWith("#")) { diff --git a/vertx-auth-properties/src/test/java/io/vertx/tests/PropertyFileBufferAuthenticationTest.java b/vertx-auth-properties/src/test/java/io/vertx/tests/PropertyFileBufferAuthenticationTest.java new file mode 100644 index 000000000..c38c90a9c --- /dev/null +++ b/vertx-auth-properties/src/test/java/io/vertx/tests/PropertyFileBufferAuthenticationTest.java @@ -0,0 +1,95 @@ +/******************************************************************************** + * Copyright (c) 2019 Stephane Bastian + * + * This program and the accompanying materials are made available under the 2 + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: 4 + * Stephane Bastian - initial API and implementation + ********************************************************************************/ +package io.vertx.tests; + +import io.vertx.core.buffer.Buffer; +import io.vertx.ext.auth.authentication.AuthenticationProvider; +import io.vertx.ext.auth.authentication.Credentials; +import io.vertx.ext.auth.authentication.UsernamePasswordCredentials; +import io.vertx.ext.auth.authorization.AuthorizationContext; +import io.vertx.ext.auth.authorization.AuthorizationProvider; +import io.vertx.ext.auth.authorization.PermissionBasedAuthorization; +import io.vertx.ext.auth.authorization.RoleBasedAuthorization; +import io.vertx.ext.auth.properties.PropertyFileAuthentication; +import io.vertx.ext.auth.properties.PropertyFileAuthorization; +import io.vertx.ext.unit.Async; +import io.vertx.ext.unit.TestContext; +import io.vertx.ext.unit.junit.RunTestOnContext; +import io.vertx.ext.unit.junit.VertxUnitRunner; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(VertxUnitRunner.class) +public class PropertyFileBufferAuthenticationTest { + + private static final Buffer CONTENT = Buffer.buffer( + "user.tim=sausages,morris_dancer,developer\n" + + "role.morris_dancer=bang_sticks\n" + + "role.developer=do_actual_work\n"); + + @Rule + public final RunTestOnContext rule = new RunTestOnContext(); + + private AuthenticationProvider authn; + private AuthorizationProvider authz; + + @Before + public void setUp() throws Exception { + authn = PropertyFileAuthentication.create(rule.vertx(), CONTENT); + authz = PropertyFileAuthorization.create(rule.vertx(), CONTENT); + } + + @Test + public void testSimpleAuthenticate(TestContext should) { + final Async test = should.async(); + Credentials authInfo = new UsernamePasswordCredentials("tim", "sausages"); + authn.authenticate(authInfo) + .onFailure(should::fail) + .onSuccess(user -> { + should.assertNotNull(user); + should.assertEquals("tim", user.principal().getString("username")); + test.complete(); + }); + } + + @Test + public void testSimpleAuthenticateFailWrongPassword(TestContext should) { + final Async test = should.async(); + Credentials authInfo = new UsernamePasswordCredentials("tim", "wrongpassword"); + authn.authenticate(authInfo) + .onSuccess(user -> should.fail("Not Expected")) + .onFailure(thr -> { + should.assertNotNull(thr); + test.complete(); + }); + } + + @Test + public void testHasRoleAndPermission(TestContext should) { + final Async test = should.async(); + Credentials authInfo = new UsernamePasswordCredentials("tim", "sausages"); + authn.authenticate(authInfo) + .onFailure(should::fail) + .onSuccess(user -> authz.getAuthorizations(user) + .onFailure(should::fail) + .onSuccess(v -> { + should.assertTrue( + RoleBasedAuthorization.create("morris_dancer").match(AuthorizationContext.create(user))); + should.assertTrue( + PermissionBasedAuthorization.create("do_actual_work").match(AuthorizationContext.create(user))); + test.complete(); + })); + } +}