@Override public Future<Boolean> isAuthorized(final HonoUser user, final ResourceIdentifier resource, final Activity intent) { Objects.requireNonNull(user); Objects.requireNonNull(resource); Objects.requireNonNull(intent); if (user.isExpired()) { return Future.failedFuture(new ClientErrorException(HttpURLConnection.HTTP_FORBIDDEN, "user information expired")); } else { return Future.succeededFuture(user.getAuthorities().isAuthorized(resource, intent)); } }
final Map<String, String[]> permissions = getPermissionsFromAuthorities(clientPrincipal.getAuthorities()); final Map<Symbol, Object> properties = new HashMap<>(); final boolean isLegacy = isLegacyClient(connection); if (isLegacy) { properties.put(PROPERTY_AUTH_IDENTITY, clientPrincipal.getName()); } else { properties.put(PROPERTY_AUTH_IDENTITY, Collections.singletonMap("sub", clientPrincipal.getName())); connection.setOfferedCapabilities(new Symbol[] { CAPABILITY_ADDRESS_AUTHZ }); LOG.debug("transfering {} permissions of client [container: {}, user: {}] in open frame [legacy format: {}]", permissions.size(), connection.getRemoteContainer(), clientPrincipal.getName(), isLegacy);
private void processMessage(final Message<JsonObject> message) { final JsonObject body = message.body(); authenticate(body, validation -> { if (validation.succeeded()) { message.reply(AuthenticationConstants.getAuthenticationReply(validation.result().getToken())); } else { message.fail(ERROR_CODE_AUTHENTICATION_FAILED, validation.cause().getMessage()); } }); }
final Duration delay = Duration.between(Instant.now(), clientPrincipal.getExpirationTime()); final WeakReference<ProtonConnection> conRef = new WeakReference<>(connection); vertx.setTimer(delay.toMillis(), timerId -> { connection.open(); LOG.info("client connected [container: {}, user: {}, token valid until: {}]", connection.getRemoteContainer(), clientPrincipal.getName(), clientPrincipal.getExpirationTime().toString());
@Override public void process(final Handler<Boolean> completionHandler) { final String[] remoteMechanisms = sasl.getRemoteMechanisms(); if (remoteMechanisms.length == 0) { LOG.debug("client provided an empty list of SASL mechanisms [hostname: {}, state: {}]", sasl.getHostname(), sasl.getState().name()); completionHandler.handle(false); } else { final String chosenMechanism = remoteMechanisms[0]; LOG.debug("client wants to authenticate using SASL [mechanism: {}, host: {}, state: {}]", chosenMechanism, sasl.getHostname(), sasl.getState().name()); final Future<HonoUser> authTracker = Future.future(); authTracker.setHandler(s -> { if (s.succeeded()) { final HonoUser user = s.result(); LOG.debug("authentication of client [authorization ID: {}] succeeded", user.getName()); Constants.setClientPrincipal(protonConnection, user); succeeded = true; sasl.done(SaslOutcome.PN_SASL_OK); } else { LOG.debug("authentication failed: " + s.cause().getMessage()); sasl.done(SaslOutcome.PN_SASL_AUTH); } completionHandler.handle(Boolean.TRUE); }); final byte[] saslResponse = new byte[sasl.pending()]; sasl.recv(saslResponse, 0, saslResponse.length); verify(chosenMechanism, saslResponse, authTracker.completer()); } }
@Override public void process(final Handler<Boolean> completionHandler) { final String[] remoteMechanisms = sasl.getRemoteMechanisms(); if (remoteMechanisms.length == 0) { LOG.debug("client provided an empty list of SASL mechanisms [hostname: {}, state: {}]", sasl.getHostname(), sasl.getState().name()); completionHandler.handle(false); } else { final String chosenMechanism = remoteMechanisms[0]; LOG.debug("client wants to authenticate using SASL [mechanism: {}, host: {}, state: {}]", chosenMechanism, sasl.getHostname(), sasl.getState().name()); final Future<HonoUser> authTracker = Future.future(); authTracker.setHandler(s -> { if (s.succeeded()) { final HonoUser user = s.result(); LOG.debug("authentication of client [authorization ID: {}] succeeded", user.getName()); Constants.setClientPrincipal(protonConnection, user); succeeded = true; sasl.done(SaslOutcome.PN_SASL_OK); } else { LOG.debug("authentication failed: " + s.cause().getMessage()); sasl.done(SaslOutcome.PN_SASL_AUTH); } completionHandler.handle(Boolean.TRUE); }); final byte[] saslResponse = new byte[sasl.pending()]; sasl.recv(saslResponse, 0, saslResponse.length); verify(chosenMechanism, saslResponse, authTracker.completer()); } }
/** * Processes a peer's AMQP <em>open</em> frame. * <p> * This default implementation * <ol> * <li>adds a unique connection identifier to the connection's attachments * under key {@link Constants#KEY_CONNECTION_ID}</li> * <li>invokes {@link #processDesiredCapabilities(ProtonConnection, Symbol[])}</li> * <li>sets a timer that closes the connection once the client's token * has expired</li> * <li>sends the AMQP <em>open</em> frame to the peer</li> * </ol> * * @param connection The connection to open. */ protected void processRemoteOpen(final ProtonConnection connection) { final HonoUser clientPrincipal = Constants.getClientPrincipal(connection); LOG.debug("client [container: {}, user: {}] connected", connection.getRemoteContainer(), clientPrincipal.getName()); // attach an ID so that we can later inform downstream components when connection is closed connection.attachments().set(Constants.KEY_CONNECTION_ID, String.class, UUID.randomUUID().toString()); processDesiredCapabilities(connection, connection.getRemoteDesiredCapabilities()); final Duration delay = Duration.between(Instant.now(), clientPrincipal.getExpirationTime()); final WeakReference<ProtonConnection> conRef = new WeakReference<>(connection); vertx.setTimer(delay.toMillis(), timerId -> { if (conRef.get() != null) { closeExpiredConnection(conRef.get()); } }); connection.open(); }
/** * Closes an expired client connection. * <p> * A connection is considered expired if the {@link HonoUser#isExpired()} method * of the user principal attached to the connection returns {@code true}. * * @param con The client connection. */ protected final void closeExpiredConnection(final ProtonConnection con) { if (!con.isDisconnected()) { final HonoUser clientPrincipal = Constants.getClientPrincipal(con); if (clientPrincipal != null) { LOG.debug("client's [{}] access token has expired, closing connection", clientPrincipal.getName()); con.disconnectHandler(null); con.closeHandler(null); con.setCondition(ProtonHelper.condition(AmqpError.UNAUTHORIZED_ACCESS, "access token expired")); con.close(); con.disconnect(); publishConnectionClosedEvent(con); } } }
@Override public Future<Boolean> isAuthorized(final HonoUser user, final ResourceIdentifier resource, final String operation) { Objects.requireNonNull(user); Objects.requireNonNull(resource); Objects.requireNonNull(operation); if (user.isExpired()) { return Future.failedFuture(new ClientErrorException(HttpURLConnection.HTTP_FORBIDDEN, "user information expired")); } else { return Future.succeededFuture(user.getAuthorities().isAuthorized(resource, operation)); } } }
private void processMessage(final Message<JsonObject> message) { final JsonObject body = message.body(); authenticate(body, validation -> { if (validation.succeeded()) { message.reply(AuthenticationConstants.getAuthenticationReply(validation.result().getToken())); } else { message.fail(ERROR_CODE_AUTHENTICATION_FAILED, validation.cause().getMessage()); } }); }
/** * Closes an expired client connection. * <p> * A connection is considered expired if the {@link HonoUser#isExpired()} method * of the user principal attached to the connection returns {@code true}. * * @param con The client connection. */ protected final void closeExpiredConnection(final ProtonConnection con) { if (!con.isDisconnected()) { final HonoUser clientPrincipal = Constants.getClientPrincipal(con); if (clientPrincipal != null) { LOG.debug("client's [{}] access token has expired, closing connection", clientPrincipal.getName()); con.disconnectHandler(null); con.closeHandler(null); con.setCondition(ProtonHelper.condition(AmqpError.UNAUTHORIZED_ACCESS, "access token expired")); con.close(); con.disconnect(); publishConnectionClosedEvent(con); } } }
@Override public Future<Boolean> isAuthorized(final HonoUser user, final ResourceIdentifier resource, final String operation) { Objects.requireNonNull(user); Objects.requireNonNull(resource); Objects.requireNonNull(operation); if (user.isExpired()) { return Future.failedFuture(new ClientErrorException(HttpURLConnection.HTTP_FORBIDDEN, "user information expired")); } else { return Future.succeededFuture(user.getAuthorities().isAuthorized(resource, operation)); } } }
@Override public final void onLinkAttach(final ProtonConnection con, final ProtonSender sender, final ResourceIdentifier targetResource) { if (ProtonQoS.AT_LEAST_ONCE.equals(sender.getRemoteQoS())) { final HonoUser user = Constants.getClientPrincipal(con); sender.setQoS(ProtonQoS.AT_LEAST_ONCE).open(); logger.debug("transferring token to client..."); final Message tokenMsg = ProtonHelper.message(user.getToken()); MessageHelper.addProperty(tokenMsg, AuthenticationConstants.APPLICATION_PROPERTY_TYPE, AuthenticationConstants.TYPE_AMQP_JWT); sender.send(tokenMsg, disposition -> { if (disposition.remotelySettled()) { logger.debug("successfully transferred auth token to client"); } else { logger.debug("failed to transfer auth token to client"); } sender.close(); }); } else { onLinkDetach(sender, ProtonHelper.condition(AmqpError.INVALID_FIELD, "supports AT_LEAST_ONCE delivery mode only")); } }
logger.debug("client [{}] is {}authorized to {}:{}", clientPrincipal.getName(), authorized ? "" : "not ", targetAddress, message.getSubject());
@Override public Future<Boolean> isAuthorized(final HonoUser user, final ResourceIdentifier resource, final Activity intent) { Objects.requireNonNull(user); Objects.requireNonNull(resource); Objects.requireNonNull(intent); if (user.isExpired()) { return Future.failedFuture(new ClientErrorException(HttpURLConnection.HTTP_FORBIDDEN, "user information expired")); } else { return Future.succeededFuture(user.getAuthorities().isAuthorized(resource, intent)); } }
logger.debug("client [{}] is {}authorized to {}:{}", clientPrincipal.getName(), authorized ? "" : "not ", targetAddress, message.getSubject());
} else { final HonoUser user = Constants.getClientPrincipal(con); if (Constants.SUBJECT_ANONYMOUS.equals(user.getName())) { con.setCondition(ProtonHelper.condition(AmqpError.UNAUTHORIZED_ACCESS, "client must authenticate using SASL")).close(); } else {
endpoint.onLinkAttach(con, sender, targetResource); } else { LOG.debug("subject [{}] is not authorized to READ from [{}]", user.getName(), targetResource); sender.setCondition(ProtonHelper.condition(AmqpError.UNAUTHORIZED_ACCESS.toString(), "unauthorized")); sender.close();
endpoint.onLinkAttach(con, sender, targetResource); } else { LOG.debug("subject [{}] is not authorized to READ from [{}]", user.getName(), targetResource); sender.setCondition(ProtonHelper.condition(AmqpError.UNAUTHORIZED_ACCESS.toString(), "unauthorized")); sender.close();
endpoint.onLinkAttach(con, receiver, targetResource); } else { LOG.debug("subject [{}] is not authorized to WRITE to [{}]", user.getName(), targetResource); receiver.setCondition(ProtonHelper.condition(AmqpError.UNAUTHORIZED_ACCESS.toString(), "unauthorized")); receiver.close();