Skip to content

Commit

Permalink
JWT and OIDC security providers now support groups claim.
Browse files Browse the repository at this point in the history
Signed-off-by: Tomas Langer <tomas.langer@oracle.com>
  • Loading branch information
tomas-langer committed Jan 28, 2020
1 parent 0d0f38c commit 4ba7e3c
Show file tree
Hide file tree
Showing 3 changed files with 49 additions and 6 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ This is a bug fix release of Helidon.
### Fixes
- Security: OIDC redirect with cookies now uses `Same-Site` policy of `Lax` by default to prevent infinite redirects.
If the default value is use, a warning is printed explaining the change.
- Security: OIDC and JWT: `groups` claim in a JWT is now honored and a `Role` is created in subject for each such group.

## [1.4.1]

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2018, 2019 Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2018, 2020 Oracle and/or its affiliates. All rights reserved.
*
* 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 @@ -35,6 +35,7 @@
import io.helidon.security.OutboundSecurityResponse;
import io.helidon.security.Principal;
import io.helidon.security.ProviderRequest;
import io.helidon.security.Role;
import io.helidon.security.SecurityEnvironment;
import io.helidon.security.SecurityResponse;
import io.helidon.security.Subject;
Expand Down Expand Up @@ -84,6 +85,7 @@ public final class JwtProvider extends SynchronousProvider implements Authentica
private final String issuer;
private final Map<OutboundTarget, JwtOutboundTarget> targetToJwtConfig = new IdentityHashMap<>();
private final Jwk defaultJwk;
private final boolean useJwtGroups;

private JwtProvider(Builder builder) {
this.optional = builder.optional;
Expand All @@ -98,6 +100,7 @@ private JwtProvider(Builder builder) {
this.issuer = builder.issuer;
this.expectedAudience = builder.expectedAudience;
this.verifySignature = builder.verifySignature;
this.useJwtGroups = builder.useJwtGroups;

if (null == atnTokenHandler) {
defaultTokenHandler = TokenHandler.builder()
Expand Down Expand Up @@ -204,12 +207,16 @@ Subject buildSubject(Jwt jwt, SignedJwt signedJwt) {
builder.addToken(Jwt.class, jwt);
builder.addToken(SignedJwt.class, signedJwt);

Optional<List<String>> scopes = jwt.scopes();

Subject.Builder subjectBuilder = Subject.builder()
.principal(principal)
.addPublicCredential(TokenCredential.class, builder.build());

if (useJwtGroups) {
Optional<List<String>> userGroups = jwt.userGroups();
userGroups.ifPresent(groups -> groups.forEach(group -> subjectBuilder.addGrant(Role.create(group))));
}

Optional<List<String>> scopes = jwt.scopes();
scopes.ifPresent(scopeList -> {
scopeList.forEach(scope -> subjectBuilder.addGrant(Grant.builder()
.name(scope)
Expand Down Expand Up @@ -599,6 +606,7 @@ public static final class Builder implements io.helidon.common.Builder<JwtProvid
private JwkKeys signKeys;
private String issuer;
private String expectedAudience;
private boolean useJwtGroups = true;

private Builder() {
}
Expand Down Expand Up @@ -789,6 +797,7 @@ public Builder config(Config config) {
config.get("sign-token").ifExists(outbound -> outboundConfig(OutboundConfig.create(outbound)));
config.get("sign-token").ifExists(this::outbound);
config.get("allow-unsigned").asBoolean().ifPresent(this::allowUnsigned);
config.get("use-jwt-groups").asBoolean().ifPresent(this::useJwtGroups);

return this;
}
Expand All @@ -802,6 +811,18 @@ public void expectedAudience(String audience) {
this.expectedAudience = audience;
}

/**
* Claim {@code groups} from JWT will be used to automatically add
* groups to current subject (may be used with {@link javax.annotation.security.RolesAllowed} annotation).
*
* @param useJwtGroups whether to use {@code groups} claim from JWT to retrieve roles
* @return updated builder instance
*/
public Builder useJwtGroups(boolean useJwtGroups) {
this.useJwtGroups = useJwtGroups;
return this;
}

private void verifyKeys(Config config) {
Resource.create(config, "jwk").ifPresent(this::verifyJwk);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2018, 2019 Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2018, 2020 Oracle and/or its affiliates. All rights reserved.
*
* 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 @@ -54,6 +54,7 @@
import io.helidon.security.OutboundSecurityResponse;
import io.helidon.security.Principal;
import io.helidon.security.ProviderRequest;
import io.helidon.security.Role;
import io.helidon.security.Security;
import io.helidon.security.SecurityEnvironment;
import io.helidon.security.SecurityLevel;
Expand Down Expand Up @@ -97,10 +98,12 @@ public final class OidcProvider extends SynchronousProvider implements Authentic
private final Pattern attemptPattern;
private final boolean propagate;
private final OidcOutboundConfig outboundConfig;
private final boolean useJwtGroups;

private OidcProvider(Builder builder, OidcOutboundConfig oidcOutboundConfig) {
this.oidcConfig = builder.oidcConfig;
this.propagate = builder.propagate;
this.useJwtGroups = builder.useJwtGroups;
this.outboundConfig = oidcOutboundConfig;

attemptPattern = Pattern.compile(".*?" + oidcConfig.redirectAttemptParam() + "=(\\d+).*");
Expand Down Expand Up @@ -495,12 +498,16 @@ private Subject buildSubject(Jwt jwt, SignedJwt signedJwt) {
builder.addToken(Jwt.class, jwt);
builder.addToken(SignedJwt.class, signedJwt);

Optional<List<String>> scopes = jwt.scopes();

Subject.Builder subjectBuilder = Subject.builder()
.principal(principal)
.addPublicCredential(TokenCredential.class, builder.build());

if (useJwtGroups) {
Optional<List<String>> userGroups = jwt.userGroups();
userGroups.ifPresent(groups -> groups.forEach(group -> subjectBuilder.addGrant(Role.create(group))));
}

Optional<List<String>> scopes = jwt.scopes();
scopes.ifPresent(scopeList -> scopeList.forEach(scope -> subjectBuilder.addGrant(Grant.builder()
.name(scope)
.type("scope")
Expand Down Expand Up @@ -543,6 +550,7 @@ public static final class Builder implements io.helidon.common.Builder<OidcProvi
// identity propagation is disabled by default. In general we should not reuse the same token
// for outbound calls, unless it is the same audience
private boolean propagate;
private boolean useJwtGroups = true;
private OutboundConfig outboundConfig;
private TokenHandler defaultOutboundHandler = TokenHandler.builder()
.tokenHeader("Authorization")
Expand Down Expand Up @@ -608,6 +616,7 @@ public Builder config(Config config) {
if (null == outboundConfig) {
config.get("outbound").ifExists(outbound -> outboundConfig(OutboundConfig.create(outbound)));
}
config.get("use-jwt-groups").asBoolean().ifPresent(this::useJwtGroups);

return this;
}
Expand Down Expand Up @@ -646,6 +655,18 @@ public Builder oidcConfig(OidcConfig config) {
this.oidcConfig = config;
return this;
}

/**
* Claim {@code groups} from JWT will be used to automatically add
* groups to current subject (may be used with {@link javax.annotation.security.RolesAllowed} annotation).
*
* @param useJwtGroups whether to use {@code groups} claim from JWT to retrieve roles
* @return updated builder instance
*/
public Builder useJwtGroups(boolean useJwtGroups) {
this.useJwtGroups = useJwtGroups;
return this;
}
}

private static final class OidcOutboundConfig {
Expand Down

0 comments on commit 4ba7e3c

Please sign in to comment.