Skip to content

Commit

Permalink
Add OidcSession interface
Browse files Browse the repository at this point in the history
  • Loading branch information
sberyozkin committed Dec 14, 2021
1 parent 5f817a1 commit d1a1316
Show file tree
Hide file tree
Showing 13 changed files with 322 additions and 117 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
import io.quarkus.oidc.runtime.OidcIdentityProvider;
import io.quarkus.oidc.runtime.OidcJsonWebTokenProducer;
import io.quarkus.oidc.runtime.OidcRecorder;
import io.quarkus.oidc.runtime.OidcSessionImpl;
import io.quarkus.oidc.runtime.OidcTokenCredentialProducer;
import io.quarkus.oidc.runtime.TenantConfigBean;
import io.quarkus.runtime.TlsConfig;
Expand Down Expand Up @@ -87,7 +88,8 @@ public void additionalBeans(BuildProducer<AdditionalBeanBuildItem> additionalBea
.addBeanClass(OidcConfigurationMetadataProducer.class)
.addBeanClass(OidcIdentityProvider.class)
.addBeanClass(DefaultTenantConfigResolver.class)
.addBeanClass(DefaultTokenStateManager.class);
.addBeanClass(DefaultTokenStateManager.class)
.addBeanClass(OidcSessionImpl.class);
additionalBeans.produce(builder.build());
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package io.quarkus.oidc;

import java.time.Instant;

import org.eclipse.microprofile.jwt.JsonWebToken;

import io.smallrye.mutiny.Uni;

public interface OidcSession {

/**
* Return the tenant identifier of the current session
*
* @return tenant id
*/
String getTenantId();

/**
* Return an {@linkplain:Instant} indicating how long will it take for the current session to expire.
*
* @return
*/
Instant expiresIn();

/**
* Perform a local logout without a redirect to the OpenId Connect provider.
*
* @return Uni<Void>
*/
Uni<Void> logout();

/**
* Return the ID token the current session depends upon.
*
* @return id token
*/
JsonWebToken getIdToken();

}
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
package io.quarkus.oidc.runtime;

import java.util.function.Function;

import io.netty.handler.codec.http.HttpHeaderNames;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.quarkus.oidc.AccessTokenCredential;
Expand All @@ -21,18 +19,13 @@ public class BearerAuthenticationMechanism extends AbstractOidcAuthenticationMec
HttpHeaderNames.WWW_AUTHENTICATE, OidcConstants.BEARER_SCHEME);

public Uni<SecurityIdentity> authenticate(RoutingContext context,
IdentityProviderManager identityProviderManager) {
return resolver.resolveConfig(context).chain(new Function<OidcTenantConfig, Uni<? extends SecurityIdentity>>() {
@Override
public Uni<? extends SecurityIdentity> apply(OidcTenantConfig oidcTenantConfig) {
String token = extractBearerToken(context, oidcTenantConfig);
// if a bearer token is provided try to authenticate
if (token != null) {
return authenticate(identityProviderManager, context, new AccessTokenCredential(token));
}
return Uni.createFrom().nullItem();
}
});
IdentityProviderManager identityProviderManager, OidcTenantConfig oidcTenantConfig) {
String token = extractBearerToken(context, oidcTenantConfig);
// if a bearer token is provided try to authenticate
if (token != null) {
return authenticate(identityProviderManager, context, new AccessTokenCredential(token));
}
return Uni.createFrom().nullItem();
}

public Uni<ChallengeData> getChallenge(RoutingContext context) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,58 +45,50 @@ public class CodeAuthenticationMechanism extends AbstractOidcAuthenticationMecha

static final String AMP = "&";
static final String EQ = "=";
static final String UNDERSCORE = "_";
static final String COOKIE_DELIM = "|";
static final Pattern COOKIE_PATTERN = Pattern.compile("\\" + COOKIE_DELIM);
static final String SESSION_COOKIE_NAME = "q_session";
static final String SESSION_MAX_AGE_PARAM = "session-max-age";
static final Uni<Void> VOID_UNI = Uni.createFrom().voidItem();
static final Integer MAX_COOKIE_VALUE_LENGTH = 4096;

private static final Logger LOG = Logger.getLogger(CodeAuthenticationMechanism.class);

private static final String STATE_COOKIE_NAME = "q_auth";
private static final String POST_LOGOUT_COOKIE_NAME = "q_post_logout";

private final BlockingTaskRunner<String> createTokenStateRequestContext = new BlockingTaskRunner<String>();
private final BlockingTaskRunner<AuthorizationCodeTokens> getTokenStateRequestContext = new BlockingTaskRunner<AuthorizationCodeTokens>();
private final BlockingTaskRunner<Void> deleteTokensRequestContext = new BlockingTaskRunner<Void>();

public Uni<SecurityIdentity> authenticate(RoutingContext context,
IdentityProviderManager identityProviderManager) {
return resolver.resolveConfig(context).chain(new Function<OidcTenantConfig, Uni<? extends SecurityIdentity>>() {
@Override
public Uni<? extends SecurityIdentity> apply(OidcTenantConfig oidcTenantConfig) {

final Cookie sessionCookie = context.request().getCookie(getSessionCookieName(oidcTenantConfig));

// if session already established, try to re-authenticate
if (sessionCookie != null) {
Uni<TenantConfigContext> resolvedContext = resolver.resolveContext(context);
return resolvedContext.onItem()
.transformToUni(new Function<TenantConfigContext, Uni<? extends SecurityIdentity>>() {
@Override
public Uni<SecurityIdentity> apply(TenantConfigContext tenantContext) {
return reAuthenticate(sessionCookie, context, identityProviderManager, tenantContext);
}
});
}
IdentityProviderManager identityProviderManager, OidcTenantConfig oidcTenantConfig) {
final Cookie sessionCookie = context.request().getCookie(getSessionCookieName(oidcTenantConfig));

// if session already established, try to re-authenticate
if (sessionCookie != null) {
context.put(OidcUtils.SESSION_COOKIE_NAME, sessionCookie.getName());
Uni<TenantConfigContext> resolvedContext = resolver.resolveContext(context);
return resolvedContext.onItem()
.transformToUni(new Function<TenantConfigContext, Uni<? extends SecurityIdentity>>() {
@Override
public Uni<SecurityIdentity> apply(TenantConfigContext tenantContext) {
return reAuthenticate(sessionCookie, context, identityProviderManager, tenantContext);
}
});
}

final String code = context.request().getParam("code");
if (code == null) {
return Uni.createFrom().optional(Optional.empty());
}
final String code = context.request().getParam("code");
if (code == null) {
return Uni.createFrom().optional(Optional.empty());
}

// start a new session by starting the code flow dance
Uni<TenantConfigContext> resolvedContext = resolver.resolveContext(context);
return resolvedContext.onItem()
.transformToUni(new Function<TenantConfigContext, Uni<? extends SecurityIdentity>>() {
@Override
public Uni<SecurityIdentity> apply(TenantConfigContext tenantContext) {
return performCodeFlow(identityProviderManager, context, tenantContext, code);
}
});

// start a new session by starting the code flow dance
Uni<TenantConfigContext> resolvedContext = resolver.resolveContext(context);
return resolvedContext.onItem()
.transformToUni(new Function<TenantConfigContext, Uni<? extends SecurityIdentity>>() {
@Override
public Uni<SecurityIdentity> apply(TenantConfigContext tenantContext) {
return performCodeFlow(identityProviderManager, context, tenantContext, code);
}
});
}
});
}

private Uni<SecurityIdentity> reAuthenticate(Cookie sessionCookie,
Expand Down Expand Up @@ -188,7 +180,7 @@ public Uni<ChallengeData> apply(TenantConfigContext tenantContext) {
}

public Uni<ChallengeData> getChallengeInternal(RoutingContext context, TenantConfigContext configContext) {
return removeSessionCookie(context, configContext, getSessionCookieName(configContext.oidcConfig))
return removeSessionCookie(context, configContext.oidcConfig)
.chain(new Function<Void, Uni<? extends ChallengeData>>() {

@Override
Expand Down Expand Up @@ -271,7 +263,7 @@ private Uni<SecurityIdentity> performCodeFlow(IdentityProviderManager identityPr
userPath = pair[1];
}
}
removeCookie(context, configContext, getStateCookieName(configContext));
OidcUtils.removeCookie(context, configContext.oidcConfig, stateCookie.getName());
}
} else {
// State cookie must be available to minimize the risk of CSRF
Expand Down Expand Up @@ -371,7 +363,7 @@ private Uni<Void> processSuccessfulAuthentication(RoutingContext context,
TenantConfigContext configContext,
AuthorizationCodeTokens tokens,
SecurityIdentity securityIdentity) {
return removeSessionCookie(context, configContext, getSessionCookieName(configContext.oidcConfig))
return removeSessionCookie(context, configContext.oidcConfig)
.chain(new Function<Void, Uni<? extends Void>>() {

@Override
Expand Down Expand Up @@ -458,7 +450,7 @@ private String generateCodeFlowState(RoutingContext context, TenantConfigContext
}

private String generatePostLogoutState(RoutingContext context, TenantConfigContext configContext) {
removeCookie(context, configContext, getPostLogoutCookieName(configContext));
OidcUtils.removeCookie(context, configContext.oidcConfig, getPostLogoutCookieName(configContext));
return createCookie(context, configContext.oidcConfig, getPostLogoutCookieName(configContext),
UUID.randomUUID().toString(),
60 * 30).getValue();
Expand All @@ -472,22 +464,14 @@ static ServerCookie createCookie(RoutingContext context, OidcTenantConfig oidcCo
cookie.setMaxAge(maxAge);
LOG.debugf(name + " cookie 'max-age' parameter is set to %d", maxAge);
Authentication auth = oidcConfig.getAuthentication();
setCookiePath(context, auth, cookie);
OidcUtils.setCookiePath(context, auth, cookie);
if (auth.cookieDomain.isPresent()) {
cookie.setDomain(auth.getCookieDomain().get());
}
context.response().addCookie(cookie);
return cookie;
}

static void setCookiePath(RoutingContext context, Authentication auth, ServerCookie cookie) {
if (auth.cookiePathHeader.isPresent() && context.request().headers().contains(auth.cookiePathHeader.get())) {
cookie.setPath(context.request().getHeader(auth.cookiePathHeader.get()));
} else {
cookie.setPath(auth.getCookiePath());
}
}

private String buildUri(RoutingContext context, boolean forceHttps, String path) {
String authority = URI.create(context.request().absoluteURI()).getAuthority();
return buildUri(context, forceHttps, authority, path);
Expand All @@ -512,38 +496,6 @@ private String buildUri(RoutingContext context, boolean forceHttps, String autho
.toString();
}

private Uni<Void> removeSessionCookie(RoutingContext context, TenantConfigContext configContext, String cookieName) {
String cookieValue = removeCookie(context, configContext, cookieName);
if (cookieValue != null) {
return resolver.getTokenStateManager().deleteTokens(context, configContext.oidcConfig, cookieValue,
deleteTokensRequestContext);
} else {
return VOID_UNI;
}
}

private String removeCookie(RoutingContext context, TenantConfigContext configContext, String cookieName) {
ServerCookie cookie = (ServerCookie) context.cookieMap().get(cookieName);
String cookieValue = null;
if (cookie != null) {
cookieValue = cookie.getValue();
removeCookie(context, cookie, configContext.oidcConfig);
}
return cookieValue;
}

static void removeCookie(RoutingContext context, ServerCookie cookie, OidcTenantConfig oidcConfig) {
if (cookie != null) {
cookie.setValue("");
cookie.setMaxAge(0);
Authentication auth = oidcConfig.getAuthentication();
setCookiePath(context, auth, cookie);
if (auth.cookieDomain.isPresent()) {
cookie.setDomain(auth.cookieDomain.get());
}
}
}

private boolean isLogout(RoutingContext context, TenantConfigContext configContext) {
Optional<String> logoutPath = configContext.oidcConfig.logout.path;

Expand Down Expand Up @@ -660,7 +612,7 @@ private boolean isForceHttps(TenantConfigContext configContext) {

private Uni<Void> buildLogoutRedirectUriUni(RoutingContext context, TenantConfigContext configContext,
String idToken) {
return removeSessionCookie(context, configContext, getSessionCookieName(configContext.oidcConfig))
return removeSessionCookie(context, configContext.oidcConfig)
.map(new Function<Void, Void>() {
@Override
public Void apply(Void t) {
Expand All @@ -670,23 +622,28 @@ public Void apply(Void t) {
}

private static String getStateCookieName(TenantConfigContext configContext) {
return STATE_COOKIE_NAME + getCookieSuffix(configContext.oidcConfig);
return OidcUtils.STATE_COOKIE_NAME + getCookieSuffix(configContext.oidcConfig);
}

private static String getPostLogoutCookieName(TenantConfigContext configContext) {
return POST_LOGOUT_COOKIE_NAME + getCookieSuffix(configContext.oidcConfig);
return OidcUtils.POST_LOGOUT_COOKIE_NAME + getCookieSuffix(configContext.oidcConfig);
}

private static String getSessionCookieName(OidcTenantConfig oidcConfig) {
return SESSION_COOKIE_NAME + getCookieSuffix(oidcConfig);
return OidcUtils.SESSION_COOKIE_NAME + getCookieSuffix(oidcConfig);
}

private Uni<Void> removeSessionCookie(RoutingContext context, OidcTenantConfig oidcConfig) {
String cookieName = getSessionCookieName(oidcConfig);
return OidcUtils.removeSessionCookie(context, oidcConfig, cookieName, resolver.getTokenStateManager());
}

static String getCookieSuffix(OidcTenantConfig oidcConfig) {
String tenantId = oidcConfig.tenantId.get();
String tenantIdSuffix = !"Default".equals(tenantId) ? "_" + tenantId : "";
String tenantIdSuffix = !"Default".equals(tenantId) ? UNDERSCORE + tenantId : "";

return oidcConfig.authentication.cookieSuffix.isPresent()
? (tenantIdSuffix + "_" + oidcConfig.authentication.cookieSuffix.get())
? (tenantIdSuffix + UNDERSCORE + oidcConfig.authentication.cookieSuffix.get())
: tenantIdSuffix;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@
@ApplicationScoped
public class DefaultTokenStateManager implements TokenStateManager {

private static final String SESSION_AT_COOKIE_NAME = CodeAuthenticationMechanism.SESSION_COOKIE_NAME + "_at";
private static final String SESSION_RT_COOKIE_NAME = CodeAuthenticationMechanism.SESSION_COOKIE_NAME + "_rt";
private static final String SESSION_AT_COOKIE_NAME = OidcUtils.SESSION_COOKIE_NAME + "_at";
private static final String SESSION_RT_COOKIE_NAME = OidcUtils.SESSION_COOKIE_NAME + "_rt";

@Override
public Uni<String> createTokenState(RoutingContext routingContext, OidcTenantConfig oidcConfig,
Expand Down Expand Up @@ -101,9 +101,9 @@ public Uni<AuthorizationCodeTokens> getTokens(RoutingContext routingContext, Oid
public Uni<Void> deleteTokens(RoutingContext routingContext, OidcTenantConfig oidcConfig, String tokenState,
OidcRequestContext<Void> requestContext) {
if (oidcConfig.tokenStateManager.splitTokens) {
CodeAuthenticationMechanism.removeCookie(routingContext, getAccessTokenCookie(routingContext, oidcConfig),
OidcUtils.removeCookie(routingContext, getAccessTokenCookie(routingContext, oidcConfig),
oidcConfig);
CodeAuthenticationMechanism.removeCookie(routingContext, getRefreshTokenCookie(routingContext, oidcConfig),
OidcUtils.removeCookie(routingContext, getRefreshTokenCookie(routingContext, oidcConfig),
oidcConfig);
}
return CodeAuthenticationMechanism.VOID_UNI;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,20 +39,22 @@ public void init() {
@Override
public Uni<SecurityIdentity> authenticate(RoutingContext context,
IdentityProviderManager identityProviderManager) {
setTenantIdAttribute(context);
return resolve(context).chain(new Function<OidcTenantConfig, Uni<? extends SecurityIdentity>>() {
@Override
public Uni<? extends SecurityIdentity> apply(OidcTenantConfig oidcConfig) {
if (!oidcConfig.tenantEnabled) {
return Uni.createFrom().nullItem();
}
return isWebApp(context, oidcConfig) ? codeAuth.authenticate(context, identityProviderManager)
: bearerAuth.authenticate(context, identityProviderManager);
return isWebApp(context, oidcConfig) ? codeAuth.authenticate(context, identityProviderManager, oidcConfig)
: bearerAuth.authenticate(context, identityProviderManager, oidcConfig);
}
});
}

@Override
public Uni<ChallengeData> getChallenge(RoutingContext context) {
setTenantIdAttribute(context);
return resolve(context).chain(new Function<OidcTenantConfig, Uni<? extends ChallengeData>>() {
@Override
public Uni<? extends ChallengeData> apply(OidcTenantConfig oidcTenantConfig) {
Expand Down Expand Up @@ -95,4 +97,26 @@ public HttpCredentialTransport getCredentialTransport() {
//if OIDC is present we don't really want another bearer mechanism
return new HttpCredentialTransport(HttpCredentialTransport.Type.AUTHORIZATION, OidcConstants.BEARER_SCHEME);
}

private static void setTenantIdAttribute(RoutingContext context) {
for (String cookieName : context.cookieMap().keySet()) {
if (cookieName.startsWith(OidcUtils.SESSION_COOKIE_NAME)) {
setTenantIdAttribute(context, OidcUtils.SESSION_COOKIE_NAME, cookieName);
} else if (cookieName.startsWith(OidcUtils.STATE_COOKIE_NAME)) {
setTenantIdAttribute(context, OidcUtils.STATE_COOKIE_NAME, cookieName);
}
}
}

private static void setTenantIdAttribute(RoutingContext context, String cookiePrefix, String cookieName) {
if (cookieName.equals(cookiePrefix)) {
context.put(OidcUtils.TENANT_ID_ATTRIBUTE, OidcUtils.DEFAULT_TENANT_ID);
} else {
String suffix = cookieName.substring(cookiePrefix.length() + 1);
// It can be either be a tenant_id or tenand_id and cookie suffix property
int index = suffix.indexOf("_");
String tenantId = index == -1 ? suffix : suffix.substring(0, index);
context.put(OidcUtils.TENANT_ID_ATTRIBUTE, tenantId);
}
}
}
Loading

0 comments on commit d1a1316

Please sign in to comment.