Skip to content

Commit

Permalink
Global context and its use in Security (#2549)
Browse files Browse the repository at this point in the history
* Updated security clients to use security from global context if not available otherwise
* Excluded outbound security support in tracing TCK

Signed-off-by: Tomas Langer <tomas.langer@oracle.com>
  • Loading branch information
tomas-langer authored Dec 2, 2020
1 parent fa59ea9 commit 5f9e0d7
Show file tree
Hide file tree
Showing 9 changed files with 98 additions and 12 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2019, 2020 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -171,15 +171,27 @@ class Builder implements io.helidon.common.Builder<Context> {
private static final AtomicLong CHILD_CONTEXT_COUNTER = new AtomicLong(1);
private Context parent;
private String id;
private boolean notGlobal = true;

@Override
public Context build() {
if (null == id) {
if (id == null) {
id = generateId();
}

if (notGlobal && (parent == null)) {
// only configure a parent for non-global context. Global context is the only one that has a null parent
parent = Contexts.globalContext();
}

return new ListContext(this);
}

Builder global() {
notGlobal = false;
return this;
}

private String generateId() {
if (null == parent) {
return String.valueOf(PARENT_CONTEXT_COUNTER.getAndIncrement());
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2019, 2020 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -21,11 +21,17 @@
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ScheduledExecutorService;

import io.helidon.common.LazyValue;

/**
* Support for handling {@link io.helidon.common.context.Context} across thread boundaries.
*/
public final class Contexts {
private static final ThreadLocal<Stack<Context>> REGISTRY = ThreadLocal.withInitial(Stack::new);
private static final LazyValue<Context> GLOBAL_CONTEXT = LazyValue.create(() -> Context.builder()
.id("helidon")
.global()
.build());

private Contexts() {
}
Expand Down Expand Up @@ -57,6 +63,19 @@ public static Optional<Context> context() {
return Optional.ofNullable(contextStack.peek());
}

/**
* Global context is always present and statically shared in this JVM.
* This is similar to Singleton scope when using an injection engine.
* Global context is also used as a parent for newly created contexts, unless you specify a parent using
* {@link Context.Builder#parent(Context)}.
* Registering any instance in this context will make it available to any component in this JVM.
*
* @return global context instance, never null
*/
public static Context globalContext() {
return GLOBAL_CONTEXT.get();
}

/**
* Wrap an executor service to correctly propagate context to its threads.
*
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2019, 2020 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -179,6 +179,9 @@ void testContextSubmitCallableWithThrow() throws Exception {

@Test
void testMultipleContexts() {
Context global = Contexts.globalContext();
global.register("global", TEST_STRING);

Context topLevel = Context.create();
topLevel.register("topLevel", TEST_STRING);
topLevel.register("first", TEST_STRING);
Expand All @@ -189,16 +192,19 @@ void testMultipleContexts() {

Contexts.runInContext(topLevel, () -> {
Context myContext = Contexts.context().get();
assertThat(myContext.get("global", String.class), is(TEST_STRING_OPTIONAL));
assertThat(myContext.get("topLevel", String.class), is(TEST_STRING_OPTIONAL));
assertThat(myContext.get("first", String.class), is(TEST_STRING_OPTIONAL));

Contexts.runInContext(firstLevel, () -> {
Context firstLevelContext = Contexts.context().get();
assertThat(myContext.get("global", String.class), is(TEST_STRING_OPTIONAL));
assertThat(firstLevelContext.get("topLevel", String.class), is(TEST_STRING_OPTIONAL));
assertThat(firstLevelContext.get("first", String.class), is(Optional.of(TEST_STRING + "_1")));
assertThat(firstLevelContext.get("second", String.class), is(TEST_STRING_OPTIONAL));
});

assertThat(myContext.get("global", String.class), is(TEST_STRING_OPTIONAL));
assertThat(myContext.get("topLevel", String.class), is(TEST_STRING_OPTIONAL));
assertThat(myContext.get("first", String.class), is(TEST_STRING_OPTIONAL));
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,9 +70,11 @@
import javax.ws.rs.PUT;

import io.helidon.common.Errors;
import io.helidon.common.context.Contexts;
import io.helidon.config.Config;
import io.helidon.config.ConfigValue;
import io.helidon.metrics.MetricsSupport;
import io.helidon.metrics.RegistryFactory;
import io.helidon.microprofile.cdi.RuntimeStart;
import io.helidon.microprofile.server.ServerCdiExtension;
import io.helidon.webserver.Routing;
Expand Down Expand Up @@ -719,6 +721,9 @@ void registerMetrics(@Observes @Priority(LIBRARY_BEFORE + 10) @Initialized(Appli
vendorMetricsAdded.add(routeName);
}
});

// registry factory is available in global
Contexts.globalContext().register(RegistryFactory.getInstance());
}

private static boolean chooseRestEndpointsSetting(Config metricsConfig) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,9 @@ private void registerSecurity(@Observes @Priority(LIBRARY_BEFORE) @Initialized(A
// we need an effectively final instance to use in lambda
Security security = tmpSecurity;

// security is available in global
Contexts.globalContext().register(security);

JaxRsCdiExtension jaxrs = bm.getExtension(JaxRsCdiExtension.class);
ServerCdiExtension server = bm.getExtension(ServerCdiExtension.class);

Expand Down
7 changes: 6 additions & 1 deletion microprofile/tests/tck/tck-opentracing/pom.xml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright (c) 2019, 2020 Oracle and/or its affiliates. All rights reserved.
Copyright (c) 2019, 2020 Oracle and/or its affiliates.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -45,6 +45,11 @@
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-json-binding</artifactId>
</exclusion>
<exclusion>
<!-- we must remove security tracing -->
<groupId>io.helidon.security.integration</groupId>
<artifactId>helidon-security-integration-jersey-client</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,9 @@ private void prepareTracer(@Observes @Priority(PLATFORM_BEFORE + 11) @Initialize
.config(config)
.build();

// tracer is available in global
Contexts.globalContext().register(tracer);

server.serverBuilder()
.tracer(tracer);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicLong;
import java.util.logging.Level;
import java.util.logging.Logger;

Expand All @@ -30,10 +31,12 @@
import javax.ws.rs.client.ClientRequestFilter;
import javax.ws.rs.core.MultivaluedMap;

import io.helidon.common.context.Context;
import io.helidon.common.context.Contexts;
import io.helidon.security.EndpointConfig;
import io.helidon.security.OutboundSecurityClientBuilder;
import io.helidon.security.OutboundSecurityResponse;
import io.helidon.security.Security;
import io.helidon.security.SecurityContext;
import io.helidon.security.SecurityEnvironment;
import io.helidon.security.SecurityResponse;
Expand All @@ -51,6 +54,7 @@
public class ClientSecurityFilter implements ClientRequestFilter {

private static final Logger LOGGER = Logger.getLogger(ClientSecurityFilter.class.getName());
private static final AtomicLong CONTEXT_COUNTER = new AtomicLong();

/**
* Create an instance of this filter (used by Jersey or for unit tests, do not use explicitly in your production code).
Expand All @@ -76,9 +80,29 @@ private void doFilter(ClientRequestContext requestContext) {
if (securityContext.isPresent()) {
outboundSecurity(requestContext, securityContext.get());
} else {
LOGGER.finest("Security not propagated, as security context is not available "
+ "neither in context, nor as the property \""
LOGGER.finest("Security context not available, using empty one. You can define it using "
+ "property \""
+ ClientSecurity.PROPERTY_CONTEXT + "\" on request");

// use current context, or create a new one if we run outside of Helidon context
Context context = Contexts.context()
.orElseGet(() -> Context.builder()
.id("security-" + CONTEXT_COUNTER.incrementAndGet())
.build());

// create a new security context for current request (not authenticated)
Optional<SecurityContext> newSecurityContext = context.get(Security.class)
.map(it -> it.createContext(context.id()));

if (newSecurityContext.isPresent()) {
// run in the context we obtained above with the new security context
// we may still propagate security information (such as when we explicitly configure outbound
// security in outbound target of a provider
Contexts.runInContext(context, () -> outboundSecurity(requestContext, newSecurityContext.get()));
} else {
// we cannot do anything - security is not available in global or current context, cannot propagate
LOGGER.finest("Security is not available in global or current context, cannot propagate identity.");
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import java.util.logging.Logger;

import io.helidon.common.context.Context;
import io.helidon.common.context.Contexts;
import io.helidon.common.reactive.Single;
import io.helidon.security.EndpointConfig;
import io.helidon.security.OutboundSecurityClientBuilder;
Expand All @@ -47,9 +48,14 @@ public class WebClientSecurity implements WebClientService {

private static final String PROVIDER_NAME = "io.helidon.security.rest.client.security.providerName";

private Security security;
private final Security security;

private WebClientSecurity() {
this(null);
}

private WebClientSecurity(Security security) {
this.security = security;
}

/**
Expand All @@ -58,7 +64,11 @@ private WebClientSecurity() {
* @return client security service
*/
public static WebClientSecurity create() {
return new WebClientSecurity();
Context context = Contexts.context().orElseGet(Contexts::globalContext);

return context.get(Security.class)
.map(WebClientSecurity::new) // if available, use constructor with Security parameter
.orElseGet(WebClientSecurity::new); // else use constructor without Security parameter
}

/**
Expand All @@ -68,9 +78,8 @@ public static WebClientSecurity create() {
* @return client security service
*/
public static WebClientSecurity create(Security security) {
WebClientSecurity clientSecurity = create();
clientSecurity.security = security;
return clientSecurity;
// if we have one more configuration parameter, we need to switch to builder based pattern
return new WebClientSecurity(security);
}

@Override
Expand Down

0 comments on commit 5f9e0d7

Please sign in to comment.