Fix #4812: UserResources patch operation resetting inheritedRoles and AuthenticationMechanism (#4814)

This commit is contained in:
Sriharsha Chintalapani 2022-05-09 23:59:04 -07:00 committed by GitHub
parent ced532812a
commit a774a67ffa
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 45 additions and 30 deletions

View File

@ -44,7 +44,7 @@ import org.openmetadata.catalog.util.JsonUtils;
@Slf4j
public class UserRepository extends EntityRepository<User> {
static final String USER_PATCH_FIELDS = "profile,roles,teams";
static final String USER_PATCH_FIELDS = "profile,roles,teams,inheritedRoles,authenticationMechanism";
static final String USER_UPDATE_FIELDS = "profile,roles,teams";
public UserRepository(CollectionDAO dao) {
@ -78,7 +78,11 @@ public class UserRepository extends EntityRepository<User> {
@Override
public void restorePatchAttributes(User original, User updated) {
// Patch can't make changes to following fields. Ignore the changes
updated.withId(original.getId()).withName(original.getName()).withInheritedRoles(original.getInheritedRoles());
updated
.withId(original.getId())
.withName(original.getName())
.withInheritedRoles(original.getInheritedRoles())
.withAuthenticationMechanism(original.getAuthenticationMechanism());
}
private List<EntityReference> getTeamDefaultRoles(User user) throws IOException {

View File

@ -67,6 +67,7 @@ import org.openmetadata.catalog.security.Authorizer;
import org.openmetadata.catalog.security.SecurityUtil;
import org.openmetadata.catalog.security.jwt.JWTTokenGenerator;
import org.openmetadata.catalog.teams.authn.JWTAuthMechanism;
import org.openmetadata.catalog.teams.authn.JWTTokenExpiry;
import org.openmetadata.catalog.type.EntityHistory;
import org.openmetadata.catalog.type.Include;
import org.openmetadata.catalog.util.EntityUtil.Fields;
@ -364,14 +365,14 @@ public class UserResource extends EntityResource<User, UserRepository> {
responseCode = "200",
description = "The user ",
content =
@Content(mediaType = "application/json", schema = @Schema(implementation = JWTAuthMechanism.class))),
@Content(mediaType = "application/json", schema = @Schema(implementation = JWTTokenExpiry.class))),
@ApiResponse(responseCode = "400", description = "Bad request")
})
public JWTAuthMechanism generateToken(
@Context UriInfo uriInfo,
@Context SecurityContext securityContext,
@PathParam("id") String id,
JWTAuthMechanism jwtAuthMechanism)
JWTTokenExpiry jwtTokenExpiry)
throws IOException {
User user = dao.get(uriInfo, id, Fields.EMPTY_FIELDS);
@ -379,8 +380,7 @@ public class UserResource extends EntityResource<User, UserRepository> {
throw new IllegalArgumentException("Generating JWT token is only supported for bot users");
}
SecurityUtil.authorizeAdmin(authorizer, securityContext, ADMIN);
String token = jwtTokenGenerator.generateJWTToken(user, jwtAuthMechanism.getJWTTokenExpiry());
jwtAuthMechanism.setJWTToken(token);
JWTAuthMechanism jwtAuthMechanism = jwtTokenGenerator.generateJWTToken(user, jwtTokenExpiry);
AuthenticationMechanism authenticationMechanism =
new AuthenticationMechanism().withConfig(jwtAuthMechanism).withAuthType(AuthenticationMechanism.AuthType.JWT);
user.setAuthenticationMechanism(authenticationMechanism);

View File

@ -18,6 +18,7 @@ import java.util.List;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.openmetadata.catalog.entity.teams.User;
import org.openmetadata.catalog.teams.authn.JWTAuthMechanism;
import org.openmetadata.catalog.teams.authn.JWTTokenExpiry;
@Slf4j
@ -67,19 +68,24 @@ public class JWTTokenGenerator {
}
}
public String generateJWTToken(User user, JWTTokenExpiry expiry) {
public JWTAuthMechanism generateJWTToken(User user, JWTTokenExpiry expiry) {
try {
JWTAuthMechanism jwtAuthMechanism = new JWTAuthMechanism().withJWTTokenExpiry(expiry);
Algorithm algorithm = Algorithm.RSA256(null, privateKey);
Date expires = getExpiryDate(expiry);
return JWT.create()
.withIssuer(issuer)
.withKeyId(kid)
.withClaim("sub", user.getName())
.withClaim("email", user.getEmail())
.withClaim("isBot", true)
.withIssuedAt(new Date(System.currentTimeMillis()))
.withExpiresAt(expires)
.sign(algorithm);
String token =
JWT.create()
.withIssuer(issuer)
.withKeyId(kid)
.withClaim("sub", user.getName())
.withClaim("email", user.getEmail())
.withClaim("isBot", true)
.withIssuedAt(new Date(System.currentTimeMillis()))
.withExpiresAt(expires)
.sign(algorithm);
jwtAuthMechanism.setJWTToken(token);
jwtAuthMechanism.setJWTTokenExpiresAt(expires != null ? expires.getTime() : null);
return jwtAuthMechanism;
} catch (Exception e) {
throw new JWTCreationException("Failed to generate JWT Token. Please check your OpenMetadata Configuration.", e);
}

View File

@ -32,6 +32,10 @@
"name": "Unlimited"
}
]
},
"JWTTokenExpiresAt": {
"description": "JWT Auth Token expiration time.",
"$ref": "../../../type/basic.json#/definitions/timestamp"
}
},
"additionalProperties": false,

View File

@ -63,7 +63,6 @@ import java.util.Map;
import java.util.TimeZone;
import java.util.UUID;
import java.util.function.Predicate;
import javax.ws.rs.client.WebTarget;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
@ -88,7 +87,6 @@ import org.openmetadata.catalog.resources.databases.TableResourceTest;
import org.openmetadata.catalog.resources.locations.LocationResourceTest;
import org.openmetadata.catalog.resources.teams.UserResource.UserList;
import org.openmetadata.catalog.security.AuthenticationException;
import org.openmetadata.catalog.security.jwt.JWKSResponse;
import org.openmetadata.catalog.teams.authn.JWTAuthMechanism;
import org.openmetadata.catalog.teams.authn.JWTTokenExpiry;
import org.openmetadata.catalog.type.ChangeDescription;
@ -696,7 +694,10 @@ public class UserResourceTest extends EntityResourceTest<User, CreateUser> {
authHeaders("ingestion-bot-jwt@email.com"));
JWTAuthMechanism authMechanism = new JWTAuthMechanism().withJWTTokenExpiry(JWTTokenExpiry.Seven);
TestUtils.put(
getResource(String.format("users/generateToken/%s", user.getId())), authMechanism, OK, ADMIN_AUTH_HEADERS);
getResource(String.format("users/generateToken/%s", user.getId())),
JWTTokenExpiry.Seven,
OK,
ADMIN_AUTH_HEADERS);
user = getEntity(user.getId(), ADMIN_AUTH_HEADERS);
assertNull(user.getAuthenticationMechanism());
JWTAuthMechanism jwtAuthMechanism =
@ -717,8 +718,6 @@ public class UserResourceTest extends EntityResourceTest<User, CreateUser> {
}
private DecodedJWT decodedJWT(String token) throws MalformedURLException, JwkException, HttpResponseException {
WebTarget target = getConfigResource("jwks");
JWKSResponse auth = TestUtils.get(target, JWKSResponse.class, TEST_AUTH_HEADERS);
DecodedJWT jwt;
try {
jwt = JWT.decode(token);

View File

@ -22,6 +22,7 @@ import org.junit.jupiter.api.TestInstance;
import org.openmetadata.catalog.entity.teams.User;
import org.openmetadata.catalog.security.jwt.JWTTokenConfiguration;
import org.openmetadata.catalog.security.jwt.JWTTokenGenerator;
import org.openmetadata.catalog.teams.authn.JWTAuthMechanism;
import org.openmetadata.catalog.teams.authn.JWTTokenExpiry;
@Slf4j
@ -44,29 +45,30 @@ public class JWTTokenGeneratorTest {
}
@Test
void testGenerateJWTToken() throws NoSuchAlgorithmException, InvalidKeySpecException, IOException {
void testGenerateJWTToken() {
User user =
new User()
.withEmail("ingestion-bot@open-metadata.org")
.withName("ingestion-bot")
.withDisplayName("ingestion-bot");
String token = jwtTokenGenerator.generateJWTToken(user, JWTTokenExpiry.Seven);
DecodedJWT jwt = decodedJWT(token);
JWTAuthMechanism jwtAuthMechanism = jwtTokenGenerator.generateJWTToken(user, JWTTokenExpiry.Seven);
DecodedJWT jwt = decodedJWT(jwtAuthMechanism.getJWTToken());
assertEquals(jwt.getClaims().get("sub").asString(), "ingestion-bot");
Date date = jwt.getExpiresAt();
long daysBetween = ((date.getTime() - jwt.getIssuedAt().getTime()) / (1000 * 60 * 60 * 24));
assertTrue(daysBetween >= 6);
token = jwtTokenGenerator.generateJWTToken(user, JWTTokenExpiry.Ninety);
jwt = decodedJWT(token);
jwtAuthMechanism = jwtTokenGenerator.generateJWTToken(user, JWTTokenExpiry.Ninety);
jwt = decodedJWT(jwtAuthMechanism.getJWTToken());
date = jwt.getExpiresAt();
daysBetween = ((date.getTime() - jwt.getIssuedAt().getTime()) / (1000 * 60 * 60 * 24));
assertTrue(daysBetween >= 89);
token = jwtTokenGenerator.generateJWTToken(user, JWTTokenExpiry.Unlimited);
jwt = decodedJWT(token);
jwtAuthMechanism = jwtTokenGenerator.generateJWTToken(user, JWTTokenExpiry.Unlimited);
jwt = decodedJWT(jwtAuthMechanism.getJWTToken());
assertNull(jwt.getExpiresAt());
assertNull(jwtAuthMechanism.getJWTTokenExpiresAt());
}
private DecodedJWT decodedJWT(String token) throws IOException, NoSuchAlgorithmException, InvalidKeySpecException {
private DecodedJWT decodedJWT(String token) {
RSAPublicKey publicKey = jwtTokenGenerator.getPublicKey();
Algorithm algorithm = Algorithm.RSA256(publicKey, null);
JWTVerifier verifier = JWT.require(algorithm).withIssuer(jwtTokenConfiguration.getJWTIssuer()).build();

View File

@ -7,5 +7,5 @@ Provides metadata version information.
from incremental import Version
__version__ = Version("metadata", 0, 11, 0, dev=2)
__version__ = Version("metadata", 0, 11, 0, dev=3)
__all__ = ["__version__"]