-
Notifications
You must be signed in to change notification settings - Fork 3.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Support passing groups in OAuth access token claim #10262
Changes from all commits
6a9256b
576f658
27117b2
1287cba
6632601
b0c9db1
c1b94b5
791afaa
b1474b1
0e004f5
d842185
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -13,15 +13,20 @@ | |
*/ | ||
package io.trino.server.security.oauth2; | ||
|
||
import com.google.common.collect.ImmutableSet; | ||
import io.trino.server.security.AbstractBearerAuthenticator; | ||
import io.trino.server.security.AuthenticationException; | ||
import io.trino.server.security.UserMapping; | ||
import io.trino.server.security.UserMappingException; | ||
import io.trino.spi.security.BasicPrincipal; | ||
import io.trino.spi.security.Identity; | ||
|
||
import javax.inject.Inject; | ||
import javax.ws.rs.container.ContainerRequestContext; | ||
|
||
import java.net.URI; | ||
import java.security.Principal; | ||
import java.util.List; | ||
import java.util.Map; | ||
import java.util.Optional; | ||
import java.util.UUID; | ||
|
||
|
@@ -36,23 +41,33 @@ public class OAuth2Authenticator | |
{ | ||
private final OAuth2Service service; | ||
private final String principalField; | ||
private final Optional<String> groupsField; | ||
private final UserMapping userMapping; | ||
|
||
@Inject | ||
public OAuth2Authenticator(OAuth2Service service, OAuth2Config config) | ||
{ | ||
super(createUserMapping(config.getUserMappingPattern(), config.getUserMappingFile())); | ||
this.service = requireNonNull(service, "service is null"); | ||
this.principalField = config.getPrincipalField(); | ||
groupsField = requireNonNull(config.getGroupsField(), "groupsField is null"); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: we can skip this rnn here and in other places as groups field is an empty |
||
userMapping = createUserMapping(config.getUserMappingPattern(), config.getUserMappingFile()); | ||
} | ||
|
||
@Override | ||
protected Optional<Principal> extractPrincipalFromToken(String token) | ||
protected Optional<Identity> createIdentity(String token) | ||
throws UserMappingException | ||
{ | ||
try { | ||
return service.convertTokenToClaims(token) | ||
.map(claims -> claims.get(principalField)) | ||
.map(String.class::cast) | ||
.map(BasicPrincipal::new); | ||
Optional<Map<String, Object>> claims = service.convertTokenToClaims(token); | ||
if (claims.isEmpty()) { | ||
return Optional.empty(); | ||
} | ||
String principal = (String) claims.get().get(principalField); | ||
Identity.Builder builder = Identity.forUser(userMapping.mapUser(principal)); | ||
builder.withPrincipal(new BasicPrincipal(principal)); | ||
groupsField.flatMap(field -> Optional.ofNullable((List<String>) claims.get().get(field))) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No need for There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
.ifPresent(groups -> builder.withGroups(ImmutableSet.copyOf(groups))); | ||
return Optional.of(builder.build()); | ||
} | ||
catch (ChallengeFailedException e) { | ||
return Optional.empty(); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -49,6 +49,7 @@ public class OAuth2Config | |
private String clientSecret; | ||
private Set<String> scopes = ImmutableSet.of(OPENID_SCOPE); | ||
private String principalField = "sub"; | ||
private Optional<String> groupsField = Optional.empty(); | ||
private List<String> additionalAudiences = Collections.emptyList(); | ||
private Duration challengeTimeout = new Duration(15, TimeUnit.MINUTES); | ||
private Optional<String> userMappingPattern = Optional.empty(); | ||
|
@@ -222,6 +223,19 @@ public OAuth2Config setPrincipalField(String principalField) | |
return this; | ||
} | ||
|
||
public Optional<String> getGroupsField() | ||
{ | ||
return groupsField; | ||
} | ||
|
||
@Config("http-server.authentication.oauth2.groups-field") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please add changes to docs too There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's already done: * - ``http-server.authentication.oauth2.groups-field``
- The field of the access token used for Trino groups. The corresponding claim value must be an array. Would like to add something? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No. Thanks. |
||
@ConfigDescription("Groups field in the claim") | ||
public OAuth2Config setGroupsField(String groupsField) | ||
{ | ||
this.groupsField = Optional.ofNullable(groupsField); | ||
return this; | ||
} | ||
|
||
@MinDuration("1ms") | ||
@NotNull | ||
public Duration getChallengeTimeout() | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -28,6 +28,7 @@ | |
import javax.ws.rs.container.ContainerRequestContext; | ||
import javax.ws.rs.core.Response; | ||
|
||
import java.util.List; | ||
import java.util.Map; | ||
import java.util.Optional; | ||
|
||
|
@@ -50,6 +51,7 @@ public class OAuth2WebUiAuthenticationFilter | |
private final String principalField; | ||
private final OAuth2Service service; | ||
private final UserMapping userMapping; | ||
private final Optional<String> groupsField; | ||
|
||
@Inject | ||
public OAuth2WebUiAuthenticationFilter(OAuth2Service service, OAuth2Config oauth2Config) | ||
|
@@ -58,6 +60,7 @@ public OAuth2WebUiAuthenticationFilter(OAuth2Service service, OAuth2Config oauth | |
requireNonNull(oauth2Config, "oauth2Config is null"); | ||
this.userMapping = UserMapping.createUserMapping(oauth2Config.getUserMappingPattern(), oauth2Config.getUserMappingFile()); | ||
this.principalField = oauth2Config.getPrincipalField(); | ||
groupsField = requireNonNull(oauth2Config.getGroupsField(), "groupsField is null"); | ||
} | ||
|
||
@Override | ||
|
@@ -101,9 +104,11 @@ public void filter(ContainerRequestContext request) | |
return; | ||
} | ||
String principalName = (String) principal; | ||
setAuthenticatedIdentity(request, Identity.forUser(userMapping.mapUser(principalName)) | ||
.withPrincipal(new BasicPrincipal(principalName)) | ||
.build()); | ||
Identity.Builder builder = Identity.forUser(userMapping.mapUser(principalName)); | ||
builder.withPrincipal(new BasicPrincipal(principalName)); | ||
groupsField.flatMap(field -> Optional.ofNullable((List<String>) claims.get().get(field))) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Again There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
.ifPresent(groups -> builder.withGroups(ImmutableSet.copyOf(groups))); | ||
setAuthenticatedIdentity(request, builder.build()); | ||
} | ||
catch (UserMappingException e) { | ||
sendErrorMessage(request, UNAUTHORIZED, firstNonNull(e.getMessage(), "Unauthorized")); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can continue the chain and avoid the if statement here by using
map
.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm afraid it would not work. User mapping throws
UserMappingException
which is checked exception.