@Override public final void authenticate(final JsonObject authInfo, final Handler<AsyncResult<User>> resultHandler) { final DeviceCredentials credentials = getCredentials(Objects.requireNonNull(authInfo)); if (credentials == null) { resultHandler.handle(Future.failedFuture(new ClientErrorException(HttpURLConnection.HTTP_UNAUTHORIZED, "malformed credentials"))); } else { authenticate(credentials, s -> { if (s.succeeded()) { resultHandler.handle(Future.succeededFuture(s.result())); } else { resultHandler.handle(Future.failedFuture(s.cause())); } }); } }
/** * Retrieves credentials from the Credentials service. * * @param deviceCredentials The credentials provided by the device. * @param spanContext The {@code SpanContext} (may be {@code null}). * @return A future containing the credentials on record as retrieved from * Hono's <em>Credentials</em> API. * @throws NullPointerException if device credentials is {@code null}. */ protected final Future<CredentialsObject> getCredentialsForDevice(final DeviceCredentials deviceCredentials, final SpanContext spanContext) { Objects.requireNonNull(deviceCredentials); if (credentialsServiceClient == null) { return Future.failedFuture(new IllegalStateException("Credentials API client is not set")); } else { return getCredentialsClient(deviceCredentials.getTenantId()).compose(client -> client.get(deviceCredentials.getType(), deviceCredentials.getAuthId(), new JsonObject(), spanContext)); } }
@Override public final void authenticate( final DeviceCredentials deviceCredentials, final Handler<AsyncResult<DeviceUser>> resultHandler) { Objects.requireNonNull(deviceCredentials); Objects.requireNonNull(resultHandler); getCredentialsForDevice(deviceCredentials) .recover(t -> { if (!(t instanceof ServiceInvocationException)) { return Future.failedFuture(t); } final ServiceInvocationException e = (ServiceInvocationException) t; if (e.getErrorCode() == HttpURLConnection.HTTP_NOT_FOUND) { return Future.failedFuture(new ClientErrorException(HttpURLConnection.HTTP_UNAUTHORIZED, "bad credentials")); } else { return Future.failedFuture(t); } }).compose(credentialsOnRecord -> validateCredentials(deviceCredentials, credentialsOnRecord)) .compose(d -> Future.succeededFuture(new DeviceUser(d.getTenantId(), d.getDeviceId()))) .setHandler(resultHandler); }
/** * Verifies that the auth provider propagates the exception reported by a failed invocation * of the credentials service. * * @param ctx The vert.x test context. */ @Test public void testAuthenticateFailsWithExceptionReportedByCredentialsClient(final TestContext ctx) { final ServerErrorException reportedException = new ServerErrorException(503); when(credentialsClient.get(anyString(), anyString(), any(JsonObject.class), any())).thenReturn(Future.failedFuture(reportedException)); provider.authenticate(new JsonObject(), ctx.asyncAssertFailure(t -> { ctx.assertEquals(t, reportedException); })); }
/** * Verifies that the credentials provided by a device during the authentication * process match the credentials on record for that device. * * @param deviceCredentials The credentials provided by the device. * @param credentialsOnRecord The credentials on record. * @return A future that is succeeded with the authenticated device if the * credentials have been validated successfully. Otherwise, the * future is failed with a {@link ServiceInvocationException}. */ private Future<Device> validateCredentials( final T deviceCredentials, final CredentialsObject credentialsOnRecord) { if (!deviceCredentials.getAuthId().equals(credentialsOnRecord.getAuthId())) { return Future.failedFuture(new ServerErrorException(HttpURLConnection.HTTP_INTERNAL_ERROR)); } else if (!deviceCredentials.getType().equals(credentialsOnRecord.getType())) { return Future.failedFuture(new ServerErrorException(HttpURLConnection.HTTP_INTERNAL_ERROR)); } else if (!credentialsOnRecord.isEnabled()) { return Future.failedFuture(new ClientErrorException(HttpURLConnection.HTTP_UNAUTHORIZED)); } else { return doValidateCredentials(deviceCredentials, credentialsOnRecord); } }
/** * Verifies that the auth provider fails an authentication request with a 401 * {@code ClientErrorException} if the auth-id is unknown. * * @param ctx The vert.x test context. */ @Test public void testAuthenticateFailsWith401ForNonExistingAuthId(final TestContext ctx) { // WHEN trying to authenticate using an auth-id that is not known when(credentialsClient.get(anyString(), eq("user"), any(JsonObject.class), any())) .thenReturn(Future.failedFuture(new ClientErrorException(HttpURLConnection.HTTP_NOT_FOUND))); provider.authenticate(new JsonObject(), ctx.asyncAssertFailure(t -> { // THEN authentication fails with a 401 client error ctx.assertEquals(HttpURLConnection.HTTP_UNAUTHORIZED, ((ClientErrorException) t).getErrorCode()); })); }
@Override public final void authenticate(final JsonObject authInfo, final Handler<AsyncResult<User>> resultHandler) { final T credentials = getCredentials(Objects.requireNonNull(authInfo)); if (credentials == null) { resultHandler.handle(Future.failedFuture(new ClientErrorException(HttpURLConnection.HTTP_UNAUTHORIZED, "malformed credentials"))); } else { authenticate(credentials, TracingHelper.extractSpanContext(tracer, authInfo), s -> { if (s.succeeded()) { resultHandler.handle(Future.succeededFuture(s.result())); } else { resultHandler.handle(Future.failedFuture(s.cause())); } }); } }
/** * Verifies that credentials validation fails if the credentials on record are disabled. * * @param ctx The vert.x test context. */ @Test public void testValidateFailsIfCredentialsAreDisabled(final TestContext ctx) { // WHEN trying to authenticate a disabled device final AbstractDeviceCredentials creds = getDeviceCredentials("type", "tenant", "identity"); final CredentialsObject credentialsOnRecord = getCredentialsObject("type", "identity", "device", false) .addSecret(CredentialsObject.emptySecret(Instant.now().minusSeconds(120), null)); when(credentialsClient.get(eq("type"), eq("identity"), any(JsonObject.class), any())) .thenReturn(Future.succeededFuture(credentialsOnRecord)); provider.authenticate(creds, null, ctx.asyncAssertFailure(t -> { // THEN authentication fails with a 401 client error ctx.assertEquals(HttpURLConnection.HTTP_UNAUTHORIZED, ((ClientErrorException) t).getErrorCode()); })); }
@Override public final void authenticate( final T deviceCredentials, final SpanContext spanContext, final Handler<AsyncResult<DeviceUser>> resultHandler) { Objects.requireNonNull(deviceCredentials); Objects.requireNonNull(resultHandler); getCredentialsForDevice(deviceCredentials, spanContext) .recover(t -> { if (!(t instanceof ServiceInvocationException)) { return Future.failedFuture(t); } final ServiceInvocationException e = (ServiceInvocationException) t; if (e.getErrorCode() == HttpURLConnection.HTTP_NOT_FOUND) { return Future.failedFuture(new ClientErrorException(HttpURLConnection.HTTP_UNAUTHORIZED, "bad credentials")); } else { return Future.failedFuture(t); } }).compose(credentialsOnRecord -> validateCredentials(deviceCredentials, credentialsOnRecord)) .compose(d -> Future.succeededFuture(new DeviceUser(d.getTenantId(), d.getDeviceId()))) .setHandler(resultHandler); }
/** * Retrieves credentials from the Credentials service. * * @param deviceCredentials The credentials provided by the device. * @return A future containing the credentials on record as retrieved from * Hono's <em>Credentials</em> API. * @throws NullPointerException if device credentials is {@code null}. */ protected final Future<CredentialsObject> getCredentialsForDevice(final DeviceCredentials deviceCredentials) { Objects.requireNonNull(deviceCredentials); if (credentialsServiceClient == null) { return Future.failedFuture(new IllegalStateException("Credentials API client is not set")); } else { return getCredentialsClient(deviceCredentials.getTenantId()).compose(client -> client.get(deviceCredentials.getType(), deviceCredentials.getAuthId())); } }
/** * Verifies that the auth provider fails an authentication request with a 401 * {@code ClientErrorException} if the credentials cannot be parsed. * * @param ctx The vert.x test context. */ @Test public void testAuthenticateFailsWith401ForMalformedCredentials(final TestContext ctx) { // WHEN trying to authenticate using malformed credentials // that do not contain a tenant provider = getProvider(null, NoopTracerFactory.create()); provider.authenticate(new JsonObject(), ctx.asyncAssertFailure(t -> { // THEN authentication fails with a 401 client error ctx.assertEquals(HttpURLConnection.HTTP_UNAUTHORIZED, ((ClientErrorException) t).getErrorCode()); })); }