Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions vertx-auth-sql-client/src/main/asciidoc/index.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,18 @@ The out of the box config assumes certain queries for authentication and authori
{@link io.vertx.ext.auth.sqlclient.SqlAuthorizationOptions#setPermissionsQuery(String)} and
{@link io.vertx.ext.auth.sqlclient.SqlAuthorizationOptions#setRolesQuery(String)}, if you want to use them with a different database schema.

When the authentication query returns more columns than the password, the extra columns can be mapped to attributes of the
authenticated {@link io.vertx.ext.auth.User} by providing a mapping function:

[source,$lang]
----
{@link examples.AuthSqlExamples#example10}
----

The password is always expected in the first column, any other column is available to the mapping function and the returned
JSON object is merged into the user attributes. This avoids a second query to load user data, such as its id, right after
authentication.

The basic data definition for the storage should look like this:

[source,sql]
Expand Down
15 changes: 15 additions & 0 deletions vertx-auth-sql-client/src/main/java/examples/AuthSqlExamples.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package examples;

import io.vertx.core.Vertx;
import io.vertx.core.json.JsonObject;
import io.vertx.ext.auth.User;
import io.vertx.ext.auth.prng.VertxContextPRNG;
import io.vertx.ext.auth.authentication.AuthenticationProvider;
Expand Down Expand Up @@ -46,6 +47,20 @@ public void example5(Vertx vertx, SqlClient sqlClient) {
SqlAuthentication.create(sqlClient, options);
}

public void example10(Vertx vertx, SqlClient sqlClient) {

SqlAuthenticationOptions options = new SqlAuthenticationOptions()
// the password is expected in the first column, any other
// column is available to the attribute mapper
.setAuthenticationQuery(
"SELECT password, email FROM users WHERE username = ?");

AuthenticationProvider authenticationProvider =
SqlAuthentication.create(sqlClient, options, row ->
new JsonObject()
.put("email", row.getString("email")));
}

public void example6(AuthenticationProvider authProvider) {

Credentials authInfo = new UsernamePasswordCredentials(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,14 @@
package io.vertx.ext.auth.sqlclient;

import io.vertx.codegen.annotations.VertxGen;
import io.vertx.core.json.JsonObject;
import io.vertx.ext.auth.authentication.AuthenticationProvider;
import io.vertx.ext.auth.sqlclient.impl.SqlAuthenticationImpl;
import io.vertx.sqlclient.Row;
import io.vertx.sqlclient.SqlClient;

import java.util.Map;
import java.util.function.Function;

/**
* Factory interface for creating {@link io.vertx.ext.auth.authentication.AuthenticationProvider} instances that use the Vert.x SQL client.
Expand Down Expand Up @@ -52,6 +55,24 @@ static SqlAuthentication create(SqlClient client, SqlAuthenticationOptions optio
return new SqlAuthenticationImpl(client, options);
}

/**
* Create a JDBC auth provider implementation that enriches the authenticated user with
* attributes extracted from the authentication query row.
* <p>
* The authentication query is expected to return the password in the first column, any other
* column is available to the given {@code attributeMapper}. The JSON object returned by the
* mapper is merged into the {@link io.vertx.ext.auth.User#attributes()} of the authenticated
* user.
*
* @param client the JDBC client instance
* @param options authentication options
* @param attributeMapper maps the authenticated row to extra user attributes, may return {@code null}
* @return the auth provider
*/
static SqlAuthentication create(SqlClient client, SqlAuthenticationOptions options, Function<Row, JsonObject> attributeMapper) {
return new SqlAuthenticationImpl(client, options, attributeMapper);
}

/**
* Hashes a password to be stored.
* <p>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,12 @@
import io.vertx.sqlclient.SqlClient;
import io.vertx.sqlclient.Tuple;

import io.vertx.core.json.JsonObject;

import java.util.Collections;
import java.util.Map;
import java.util.Objects;
import java.util.function.Function;

/**
* @author <a href="http://tfox.org">Tim Fox</a>
Expand All @@ -39,11 +42,17 @@ public class SqlAuthenticationImpl implements SqlAuthentication {

private final SqlClient client;
private final SqlAuthenticationOptions options;
private final Function<Row, JsonObject> attributeMapper;
private final HashingStrategy strategy = HashingStrategy.load();

public SqlAuthenticationImpl(SqlClient client, SqlAuthenticationOptions options) {
this(client, options, null);
}

public SqlAuthenticationImpl(SqlClient client, SqlAuthenticationOptions options, Function<Row, JsonObject> attributeMapper) {
this.client = Objects.requireNonNull(client);
this.options = Objects.requireNonNull(options);
this.attributeMapper = attributeMapper;
}

@Override
Expand Down Expand Up @@ -77,6 +86,12 @@ public Future<User> authenticate(Credentials credentials) {
User user = User.fromName(authInfo.getUsername());
// metadata "amr"
user.principal().put("amr", Collections.singletonList("pwd"));
if (attributeMapper != null) {
JsonObject attributes = attributeMapper.apply(row);
if (attributes != null) {
user.attributes().mergeIn(attributes);
}
}
return Future.succeededFuture(user);
} else {
return Future.failedFuture("Invalid username/password");
Expand Down
24 changes: 24 additions & 0 deletions vertx-auth-sql-client/src/test/java/io/vertx/tests/MySQLTest.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package io.vertx.tests;

import io.vertx.core.json.JsonObject;
import io.vertx.ext.auth.User;
import io.vertx.ext.auth.authentication.AuthenticationProvider;
import io.vertx.ext.auth.authentication.Credentials;
Expand All @@ -8,6 +9,7 @@
import io.vertx.ext.auth.authorization.PermissionBasedAuthorization;
import io.vertx.ext.auth.authorization.RoleBasedAuthorization;
import io.vertx.ext.auth.sqlclient.SqlAuthentication;
import io.vertx.ext.auth.sqlclient.SqlAuthenticationOptions;
import io.vertx.ext.auth.sqlclient.SqlAuthorization;
import io.vertx.ext.unit.Async;
import io.vertx.ext.unit.TestContext;
Expand Down Expand Up @@ -111,6 +113,28 @@ public void testAuthenticateBadUser(TestContext should) {
});
}

@Test
public void testAuthenticateWithAttributeMapper(TestContext should) {
final Async test = should.async();

AuthenticationProvider authn = SqlAuthentication.create(mysql,
new SqlAuthenticationOptions()
.setAuthenticationQuery("SELECT password, email FROM users WHERE username = ?"),
row -> new JsonObject().put("email", row.getString("email")));

Credentials authInfo = new UsernamePasswordCredentials("lopus", "secret");

authn.authenticate(authInfo)
.onComplete(authenticate -> {
should.assertTrue(authenticate.succeeded());
final User user = authenticate.result();
should.assertNotNull(user);
should.assertEquals("lopus", user.principal().getString("username"));
should.assertEquals("lopus@vertx.io", user.attributes().getString("email"));
test.complete();
});
}

@Test
public void testAuthoriseHasRole(TestContext should) {
final Async test = should.async();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
CREATE TABLE `users`
(
username VARCHAR(255) NOT NULL,
password VARCHAR(255) NOT NULL
password VARCHAR(255) NOT NULL,
email VARCHAR(255)
);

CREATE TABLE `users_roles`
Expand Down Expand Up @@ -30,7 +31,8 @@ ALTER TABLE users_roles

insert into users
values ('lopus',
'$pbkdf2$1drH02tXcgS5ipJIf8v/AlL/qm3CjAgAp7Qt3hyJx/c$/lONU4cTa3ayMRJbHIup47nX/1HhysyzDA0dpoFpsf727LoGH2OZ+SyFCGtv/pIEZK3mQtJv+yjzD+W0quF6xg');
'$pbkdf2$1drH02tXcgS5ipJIf8v/AlL/qm3CjAgAp7Qt3hyJx/c$/lONU4cTa3ayMRJbHIup47nX/1HhysyzDA0dpoFpsf727LoGH2OZ+SyFCGtv/pIEZK3mQtJv+yjzD+W0quF6xg',
'lopus@vertx.io');

insert into roles_perms
values ('dev', 'commit_code');
Expand Down