use of org.openremote.container.security.AuthContext in project openremote by openremote.
the class AssetStorageService method configure.
@SuppressWarnings("unchecked")
@Override
public void configure() throws Exception {
// If any asset was modified in the database, publish events
from(PERSISTENCE_TOPIC).routeId("AssetPersistenceChanges").filter(isPersistenceEventForEntityType(ServerAsset.class)).process(exchange -> publishModificationEvents(exchange.getIn().getBody(PersistenceEvent.class)));
// React if a client wants to read attribute state
from(CLIENT_EVENT_TOPIC).routeId("FromClientReadRequests").filter(body().isInstanceOf(ReadAssetAttributesEvent.class)).process(exchange -> {
ReadAssetAttributesEvent event = exchange.getIn().getBody(ReadAssetAttributesEvent.class);
LOG.fine("Handling from client: " + event);
if (event.getAssetId() == null || event.getAssetId().isEmpty())
return;
String sessionKey = getSessionKey(exchange);
AuthContext authContext = exchange.getIn().getHeader(Constants.AUTH_CONTEXT, AuthContext.class);
// Superuser can get all
if (authContext.isSuperUser()) {
ServerAsset asset = find(event.getAssetId(), true);
if (asset != null)
replyWithAttributeEvents(sessionKey, asset, event.getAttributeNames());
return;
}
// User must have role
if (!authContext.hasResourceRole(ClientRole.READ_ASSETS.getValue(), Constants.KEYCLOAK_CLIENT_ID)) {
return;
}
ServerAsset asset = find(event.getAssetId(), true, identityService.getIdentityProvider().isRestrictedUser(authContext.getUserId()) ? RESTRICTED_READ : PRIVATE_READ);
if (asset != null) {
replyWithAttributeEvents(sessionKey, asset, event.getAttributeNames());
}
});
}
use of org.openremote.container.security.AuthContext in project openremote by openremote.
the class ClientEventService method init.
@Override
public void init(Container container) throws Exception {
timerService = container.getService(TimerService.class);
messageBrokerService = container.getService(MessageBrokerService.class);
identityService = container.getService(ManagerIdentityService.class);
gatewayService = container.getService(GatewayService.class);
eventSubscriptions = new EventSubscriptions(container.getService(TimerService.class));
messageBrokerService.getContext().getTypeConverterRegistry().addTypeConverters(new EventTypeConverters());
// TODO: Remove prefix and just use event type then use a subscription wrapper to pass subscription ID around
messageBrokerService.getContext().addRoutes(new RouteBuilder() {
@Override
public void configure() throws Exception {
from("websocket://" + WEBSOCKET_EVENTS).routeId("FromClientWebsocketEvents").process(exchange -> exchange.getIn().setHeader(HEADER_CONNECTION_TYPE, HEADER_CONNECTION_TYPE_WEBSOCKET)).to(ClientEventService.CLIENT_EVENT_QUEUE).end();
from(ClientEventService.CLIENT_EVENT_QUEUE).routeId("ClientEvents").choice().when(header(ConnectionConstants.SESSION_OPEN)).process(exchange -> {
String sessionKey = getSessionKey(exchange);
sessionKeyInfoMap.put(sessionKey, createSessionInfo(sessionKey, exchange));
passToInterceptors(exchange);
}).stop().when(or(header(ConnectionConstants.SESSION_CLOSE), header(ConnectionConstants.SESSION_CLOSE_ERROR))).process(exchange -> {
String sessionKey = getSessionKey(exchange);
sessionKeyInfoMap.remove(sessionKey);
eventSubscriptions.cancelAll(sessionKey);
passToInterceptors(exchange);
}).stop().end().process(exchange -> {
// Do basic formatting of exchange
EventRequestResponseWrapper<?> requestResponse = null;
if (exchange.getIn().getBody() instanceof EventRequestResponseWrapper) {
requestResponse = exchange.getIn().getBody(EventRequestResponseWrapper.class);
} else if (exchange.getIn().getBody() instanceof String && exchange.getIn().getBody(String.class).startsWith(EventRequestResponseWrapper.MESSAGE_PREFIX)) {
requestResponse = exchange.getIn().getBody(EventRequestResponseWrapper.class);
}
if (requestResponse != null) {
SharedEvent event = requestResponse.getEvent();
exchange.getIn().setHeader(HEADER_REQUEST_RESPONSE_MESSAGE_ID, requestResponse.getMessageId());
exchange.getIn().setBody(event);
}
if (exchange.getIn().getBody() instanceof String) {
String bodyStr = exchange.getIn().getBody(String.class);
if (bodyStr.startsWith(EventSubscription.SUBSCRIBE_MESSAGE_PREFIX)) {
exchange.getIn().setBody(exchange.getIn().getBody(EventSubscription.class));
} else if (bodyStr.startsWith(CancelEventSubscription.MESSAGE_PREFIX)) {
exchange.getIn().setBody(exchange.getIn().getBody(CancelEventSubscription.class));
} else if (bodyStr.startsWith(SharedEvent.MESSAGE_PREFIX)) {
exchange.getIn().setBody(exchange.getIn().getBody(SharedEvent.class));
}
}
if (exchange.getIn().getBody() instanceof SharedEvent) {
SharedEvent event = exchange.getIn().getBody(SharedEvent.class);
// If there is no timestamp in event, set to system time
if (event.getTimestamp() <= 0) {
event.setTimestamp(timerService.getCurrentTimeMillis());
}
}
}).process(exchange -> passToInterceptors(exchange)).choice().when(body().isInstanceOf(EventSubscription.class)).process(exchange -> {
String sessionKey = getSessionKey(exchange);
EventSubscription<?> subscription = exchange.getIn().getBody(EventSubscription.class);
AuthContext authContext = exchange.getIn().getHeader(Constants.AUTH_CONTEXT, AuthContext.class);
boolean restrictedUser = identityService.getIdentityProvider().isRestrictedUser(authContext);
boolean anonymousUser = authContext == null;
String username = authContext == null ? "anonymous" : authContext.getUsername();
String realm = exchange.getIn().getHeader(Constants.REALM_PARAM_NAME, String.class);
if (authorizeEventSubscription(realm, authContext, subscription)) {
eventSubscriptions.createOrUpdate(sessionKey, restrictedUser, anonymousUser, subscription);
subscription.setSubscribed(true);
sendToSession(sessionKey, subscription);
} else {
LOG.warning("Unauthorized subscription from '" + username + "' in realm '" + realm + "': " + subscription);
sendToSession(sessionKey, new UnauthorizedEventSubscription<>(subscription));
}
}).stop().when(body().isInstanceOf(CancelEventSubscription.class)).process(exchange -> {
String sessionKey = getSessionKey(exchange);
eventSubscriptions.cancel(sessionKey, exchange.getIn().getBody(CancelEventSubscription.class));
}).stop().when(body().isInstanceOf(SharedEvent.class)).choice().when(// Inbound messages from clients
header(HEADER_CONNECTION_TYPE).isNotNull()).to(ClientEventService.CLIENT_EVENT_TOPIC).stop().when(// Outbound message to clients
header(HEADER_CONNECTION_TYPE).isNull()).split(method(eventSubscriptions, "splitForSubscribers")).process(exchange -> {
String sessionKey = getSessionKey(exchange);
sendToSession(sessionKey, exchange.getIn().getBody());
}).stop().endChoice().otherwise().process(exchange -> LOG.info("Unsupported message body: " + exchange.getIn().getBody())).end();
}
});
// Add pending internal subscriptions
if (!pendingInternalSubscriptions.isEmpty()) {
pendingInternalSubscriptions.forEach(subscription -> eventSubscriptions.createOrUpdate(INTERNAL_SESSION_KEY, false, false, subscription));
}
pendingInternalSubscriptions = null;
}
use of org.openremote.container.security.AuthContext in project openremote by openremote.
the class NotificationService method configure.
@Override
public void configure() throws Exception {
from(NOTIFICATION_QUEUE).routeId("NotificationQueueProcessor").doTry().process(exchange -> {
Notification notification = exchange.getIn().getBody(Notification.class);
if (notification == null) {
throw new NotificationProcessingException(MISSING_NOTIFICATION, "Notification must be set");
}
LOG.finest("Processing: " + notification.getName());
if (notification.getMessage() == null) {
throw new NotificationProcessingException(MISSING_MESSAGE, "Notification message must be set");
}
Notification.Source source = exchange.getIn().getHeader(HEADER_SOURCE, () -> null, Notification.Source.class);
if (source == null) {
throw new NotificationProcessingException(MISSING_SOURCE);
}
// Validate handler and message
NotificationHandler handler = notificationHandlerMap.get(notification.getMessage().getType());
if (handler == null) {
throw new NotificationProcessingException(UNSUPPORTED_MESSAGE_TYPE, "No handler for message type: " + notification.getMessage().getType());
}
if (!handler.isValid()) {
throw new NotificationProcessingException(NOTIFICATION_HANDLER_CONFIG_ERROR, "Handler is not valid: " + handler.getTypeName());
}
if (!handler.isMessageValid(notification.getMessage())) {
throw new NotificationProcessingException(INVALID_MESSAGE);
}
// Validate access and map targets to handler compatible targets
String realm = null;
String userId = null;
String assetId = null;
AtomicReference<String> sourceId = new AtomicReference<>("");
boolean isSuperUser = false;
boolean isRestrictedUser = false;
switch(source) {
case INTERNAL:
isSuperUser = true;
break;
case CLIENT:
AuthContext authContext = exchange.getIn().getHeader(Constants.AUTH_CONTEXT, AuthContext.class);
if (authContext == null) {
// Anonymous clients cannot send notifications
throw new NotificationProcessingException(INSUFFICIENT_ACCESS);
}
realm = authContext.getAuthenticatedRealm();
userId = authContext.getUserId();
sourceId.set(userId);
isSuperUser = authContext.isSuperUser();
isRestrictedUser = identityService.getIdentityProvider().isRestrictedUser(authContext);
break;
case GLOBAL_RULESET:
isSuperUser = true;
break;
case TENANT_RULESET:
realm = exchange.getIn().getHeader(Notification.HEADER_SOURCE_ID, String.class);
sourceId.set(realm);
break;
case ASSET_RULESET:
assetId = exchange.getIn().getHeader(Notification.HEADER_SOURCE_ID, String.class);
sourceId.set(assetId);
Asset<?> asset = assetStorageService.find(assetId, false);
realm = asset.getRealm();
break;
}
LOG.info("Sending " + notification.getMessage().getType() + " notification '" + notification.getName() + "': '" + source + ":" + sourceId.get() + "' -> " + notification.getTargets());
// Check access permissions
checkAccess(source, sourceId.get(), notification.getTargets(), realm, userId, isSuperUser, isRestrictedUser, assetId);
// Get the list of notification targets
List<Notification.Target> mappedTargetsList = handler.getTargets(source, sourceId.get(), notification.getTargets(), notification.getMessage());
if (mappedTargetsList == null || mappedTargetsList.isEmpty()) {
throw new NotificationProcessingException(MISSING_TARGETS, "Notification targets must be set");
}
// Filter targets based on repeat frequency
if (!TextUtil.isNullOrEmpty(notification.getName()) && (!TextUtil.isNullOrEmpty(notification.getRepeatInterval()) || notification.getRepeatFrequency() != null)) {
mappedTargetsList = mappedTargetsList.stream().filter(target -> okToSendNotification(source, sourceId.get(), target, notification)).collect(Collectors.toList());
}
// Send message to each applicable target
AtomicBoolean success = new AtomicBoolean(true);
mappedTargetsList.forEach(target -> {
boolean targetSuccess = persistenceService.doReturningTransaction(em -> {
// commit the notification first to get the ID
SentNotification sentNotification = new SentNotification().setName(notification.getName()).setType(notification.getMessage().getType()).setSource(source).setSourceId(sourceId.get()).setTarget(target.getType()).setTargetId(target.getId()).setMessage(notification.getMessage()).setSentOn(Date.from(timerService.getNow()));
sentNotification = em.merge(sentNotification);
long id = sentNotification.getId();
try {
NotificationSendResult result = handler.sendMessage(id, source, sourceId.get(), target, notification.getMessage());
if (result.isSuccess()) {
LOG.info("Notification sent '" + id + "': " + target);
} else {
LOG.warning("Notification failed '" + id + "': " + target + ", reason=" + result.getMessage());
sentNotification.setError(TextUtil.isNullOrEmpty(result.getMessage()) ? "Unknown error" : result.getMessage());
}
// Merge the sent notification again with the message included just in case the handler modified the message
sentNotification.setMessage(notification.getMessage());
em.merge(sentNotification);
} catch (Exception e) {
LOG.log(Level.SEVERE, "Notification handler threw an exception whilst sending notification '" + id + "'", e);
sentNotification.setError(TextUtil.isNullOrEmpty(e.getMessage()) ? "Unknown error" : e.getMessage());
em.merge(sentNotification);
}
return sentNotification.getError() == null;
});
if (!targetSuccess) {
success.set(false);
}
});
exchange.getOut().setBody(success.get());
}).endDoTry().doCatch(NotificationProcessingException.class).process(handleNotificationProcessingException(LOG));
}
use of org.openremote.container.security.AuthContext in project openremote by openremote.
the class DefaultWebsocketComponent method deploy.
@Override
protected void deploy() throws Exception {
WebSocketDeploymentInfo webSocketDeploymentInfo = new WebSocketDeploymentInfo();
getConsumers().forEach((key, value) -> {
String endpointPath = WEBSOCKET_PATH + "/" + key;
LOG.info("Deploying websocket endpoint: " + endpointPath);
webSocketDeploymentInfo.addEndpoint(ServerEndpointConfig.Builder.create(WebsocketAdapter.class, endpointPath).configurator(new DefaultContainerConfigurator() {
@SuppressWarnings("unchecked")
@Override
public <T> T getEndpointInstance(Class<T> endpointClass) throws InstantiationException {
return (T) new WebsocketAdapter(value);
}
@Override
public void modifyHandshake(ServerEndpointConfig config, HandshakeRequest request, HandshakeResponse response) {
String realm = Optional.ofNullable(request.getHeaders().get(Constants.REALM_PARAM_NAME)).map(realms -> realms.isEmpty() ? null : realms.get(0)).orElse(null);
Principal principal = request.getUserPrincipal();
AuthContext authContext = null;
if (principal instanceof KeycloakPrincipal) {
KeycloakPrincipal<?> keycloakPrincipal = (KeycloakPrincipal<?>) principal;
authContext = new AccessTokenAuthContext(keycloakPrincipal.getKeycloakSecurityContext().getRealm(), keycloakPrincipal.getKeycloakSecurityContext().getToken());
} else if (principal instanceof BasicAuthContext) {
authContext = (BasicAuthContext) principal;
} else if (principal != null) {
LOG.info("Unsupported user principal type: " + principal);
}
config.getUserProperties().put(ConnectionConstants.HANDSHAKE_AUTH, authContext);
config.getUserProperties().put(ConnectionConstants.HANDSHAKE_REALM, realm);
super.modifyHandshake(config, request, response);
}
}).build());
});
// We use the I/O thread to handle received websocket frames, as we expect to quickly hand them over to
// an internal asynchronous message queue for processing, so we don't need a separate worker thread
// pool for websocket frame processing
webSocketDeploymentInfo.setDispatchToWorkerThread(false);
// Make the shit Undertow/Websocket JSR client bootstrap happy - this is the pool that would be used
// when Undertow acts as a WebSocket client, which we don't do... and I'm not even sure it can do that...
webSocketDeploymentInfo.setWorker(Xnio.getInstance().createWorker(OptionMap.builder().set(Options.WORKER_TASK_MAX_THREADS, 1).set(Options.WORKER_NAME, "WebsocketInternalClient").set(Options.THREAD_DAEMON, true).getMap()));
boolean directBuffers = Boolean.getBoolean("io.undertow.websockets.direct-buffers");
webSocketDeploymentInfo.setBuffers(new DefaultByteBufferPool(directBuffers, 1024, 100, 12));
String deploymentName = "WebSocket Deployment";
deploymentInfo = new DeploymentInfo().setDeploymentName(deploymentName).setContextPath(WEBSOCKET_PATH).addServletContextAttribute(WebSocketDeploymentInfo.ATTRIBUTE_NAME, webSocketDeploymentInfo).setClassLoader(WebsocketComponent.class.getClassLoader());
// Require authentication, but authorize specific roles later in Camel
WebResourceCollection resourceCollection = new WebResourceCollection();
resourceCollection.addUrlPattern("/*");
SecurityConstraint constraint = new SecurityConstraint();
constraint.setEmptyRoleSemantic(SecurityInfo.EmptyRoleSemantic.PERMIT);
constraint.addWebResourceCollection(resourceCollection);
deploymentInfo.addSecurityConstraints(constraint);
HttpHandler handler = WebService.addServletDeployment(container, deploymentInfo, true);
websocketHttpHandler = pathStartsWithHandler(deploymentName, WEBSOCKET_PATH, handler);
// Give web socket handler higher priority than any other handlers already added
webService.getRequestHandlers().add(0, websocketHttpHandler);
}
use of org.openremote.container.security.AuthContext in project openremote by openremote.
the class UserResourceImpl method query.
@Override
public User[] query(RequestParams requestParams, UserQuery query) {
AuthContext authContext = getAuthContext();
boolean isAdmin = authContext.hasResourceRole(ClientRole.READ_ADMIN.getValue(), authContext.getClientId());
boolean isRestricted = !isAdmin && authContext.hasResourceRole(ClientRole.READ_USERS.getValue(), authContext.getClientId());
if (!isAdmin && !isRestricted) {
throw new ForbiddenException("Insufficient permissions to read users");
}
if (query == null) {
query = new UserQuery();
}
if (isRestricted) {
if (query.select == null) {
query.select = new UserQuery.Select();
}
query.select.basic(true);
}
if (!authContext.isSuperUser()) {
// Force realm to match users
query.tenant(new TenantPredicate(authContext.getAuthenticatedRealm()));
// Hide system service accounts from non super users
if (query.select == null) {
query.select = new UserQuery.Select();
}
query.select.excludeSystemUsers = true;
}
try {
return identityService.getIdentityProvider().queryUsers(query);
} catch (ClientErrorException ex) {
throw new WebApplicationException(ex.getCause(), ex.getResponse().getStatus());
} catch (Exception ex) {
throw new WebApplicationException(ex);
}
}
Aggregations