| 
									
										
										
										
											2021-11-22 16:33:14 -08:00
										 |  |  | package client;
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import com.fasterxml.jackson.databind.ObjectMapper;
 | 
					
						
							|  |  |  | import java.util.Objects;
 | 
					
						
							|  |  |  | import javax.annotation.Nonnull;
 | 
					
						
							|  |  |  | import lombok.extern.slf4j.Slf4j;
 | 
					
						
							|  |  |  | import org.apache.http.HttpEntity;
 | 
					
						
							|  |  |  | import org.apache.http.HttpStatus;
 | 
					
						
							|  |  |  | import org.apache.http.client.methods.CloseableHttpResponse;
 | 
					
						
							|  |  |  | import org.apache.http.client.methods.HttpPost;
 | 
					
						
							|  |  |  | import org.apache.http.entity.StringEntity;
 | 
					
						
							|  |  |  | import org.apache.http.impl.client.CloseableHttpClient;
 | 
					
						
							|  |  |  | import org.apache.http.impl.client.HttpClients;
 | 
					
						
							|  |  |  | import org.apache.http.util.EntityUtils;
 | 
					
						
							|  |  |  | import play.mvc.Http;
 | 
					
						
							|  |  |  | import com.datahub.authentication.Authentication;
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /**
 | 
					
						
							|  |  |  |  * This class is responsible for coordinating authentication with the backend Metadata Service.
 | 
					
						
							|  |  |  |  */
 | 
					
						
							|  |  |  | @Slf4j
 | 
					
						
							|  |  |  | public class AuthServiceClient {
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   private static final String GENERATE_SESSION_TOKEN_ENDPOINT = "auth/generateSessionTokenForUser";
 | 
					
						
							| 
									
										
										
										
											2022-06-08 21:13:22 -04:00
										 |  |  |   private static final String SIGN_UP_ENDPOINT = "auth/signUp";
 | 
					
						
							|  |  |  |   private static final String RESET_NATIVE_USER_CREDENTIALS_ENDPOINT = "auth/resetNativeUserCredentials";
 | 
					
						
							|  |  |  |   private static final String VERIFY_NATIVE_USER_CREDENTIALS_ENDPOINT = "auth/verifyNativeUserCredentials";
 | 
					
						
							| 
									
										
										
										
											2021-11-22 16:33:14 -08:00
										 |  |  |   private static final String ACCESS_TOKEN_FIELD = "accessToken";
 | 
					
						
							|  |  |  |   private static final String USER_ID_FIELD = "userId";
 | 
					
						
							| 
									
										
										
										
											2022-06-08 21:13:22 -04:00
										 |  |  |   private static final String USER_URN_FIELD = "userUrn";
 | 
					
						
							|  |  |  |   private static final String FULL_NAME_FIELD = "fullName";
 | 
					
						
							|  |  |  |   private static final String EMAIL_FIELD = "email";
 | 
					
						
							|  |  |  |   private static final String TITLE_FIELD = "title";
 | 
					
						
							|  |  |  |   private static final String PASSWORD_FIELD = "password";
 | 
					
						
							|  |  |  |   private static final String INVITE_TOKEN_FIELD = "inviteToken";
 | 
					
						
							|  |  |  |   private static final String RESET_TOKEN_FIELD = "resetToken";
 | 
					
						
							|  |  |  |   private static final String IS_NATIVE_USER_CREATED_FIELD = "isNativeUserCreated";
 | 
					
						
							|  |  |  |   private static final String ARE_NATIVE_USER_CREDENTIALS_RESET_FIELD = "areNativeUserCredentialsReset";
 | 
					
						
							|  |  |  |   private static final String DOES_PASSWORD_MATCH_FIELD = "doesPasswordMatch";
 | 
					
						
							| 
									
										
										
										
											2021-11-22 16:33:14 -08:00
										 |  |  | 
 | 
					
						
							|  |  |  |   private final String metadataServiceHost;
 | 
					
						
							|  |  |  |   private final Integer metadataServicePort;
 | 
					
						
							|  |  |  |   private final Boolean metadataServiceUseSsl;
 | 
					
						
							|  |  |  |   private final Authentication systemAuthentication;
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-06-08 21:13:22 -04:00
										 |  |  |   public AuthServiceClient(@Nonnull final String metadataServiceHost, @Nonnull final Integer metadataServicePort,
 | 
					
						
							|  |  |  |       @Nonnull final Boolean useSsl, @Nonnull final Authentication systemAuthentication) {
 | 
					
						
							| 
									
										
										
										
											2021-11-22 16:33:14 -08:00
										 |  |  |     this.metadataServiceHost = Objects.requireNonNull(metadataServiceHost);
 | 
					
						
							|  |  |  |     this.metadataServicePort = Objects.requireNonNull(metadataServicePort);
 | 
					
						
							|  |  |  |     this.metadataServiceUseSsl = Objects.requireNonNull(useSsl);
 | 
					
						
							|  |  |  |     this.systemAuthentication = Objects.requireNonNull(systemAuthentication);
 | 
					
						
							|  |  |  |   }
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   /**
 | 
					
						
							|  |  |  |    * Call the Auth Service to generate a session token for a particular user with a unique actor id, or throws an exception if generation fails.
 | 
					
						
							|  |  |  |    *
 | 
					
						
							|  |  |  |    * Notice that the "userId" parameter should NOT be of type "urn", but rather the unique id of an Actor of type
 | 
					
						
							|  |  |  |    * USER.
 | 
					
						
							|  |  |  |    */
 | 
					
						
							|  |  |  |   @Nonnull
 | 
					
						
							|  |  |  |   public String generateSessionTokenForUser(@Nonnull final String userId) {
 | 
					
						
							|  |  |  |     Objects.requireNonNull(userId, "userId must not be null");
 | 
					
						
							|  |  |  |     CloseableHttpClient httpClient = HttpClients.createDefault();
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     try {
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       final String protocol = this.metadataServiceUseSsl ? "https" : "http";
 | 
					
						
							| 
									
										
										
										
											2022-05-10 18:15:53 -05:00
										 |  |  |       final HttpPost request = new HttpPost(String.format("%s://%s:%s/%s", protocol, this.metadataServiceHost,
 | 
					
						
							|  |  |  |           this.metadataServicePort, GENERATE_SESSION_TOKEN_ENDPOINT));
 | 
					
						
							| 
									
										
										
										
											2021-11-22 16:33:14 -08:00
										 |  |  | 
 | 
					
						
							|  |  |  |       // Build JSON request to generate a token on behalf of a user.
 | 
					
						
							|  |  |  |       String json = String.format("{ \"%s\":\"%s\" }", USER_ID_FIELD, userId);
 | 
					
						
							|  |  |  |       request.setEntity(new StringEntity(json));
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       // Add authorization header with DataHub frontend system id and secret.
 | 
					
						
							|  |  |  |       request.addHeader(Http.HeaderNames.AUTHORIZATION, this.systemAuthentication.getCredentials());
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       CloseableHttpResponse response = httpClient.execute(request);
 | 
					
						
							|  |  |  |       final HttpEntity entity = response.getEntity();
 | 
					
						
							|  |  |  |       if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK && entity != null) {
 | 
					
						
							|  |  |  |         // Successfully generated a token for the User
 | 
					
						
							|  |  |  |         final String jsonStr = EntityUtils.toString(entity);
 | 
					
						
							|  |  |  |         return getAccessTokenFromJson(jsonStr);
 | 
					
						
							|  |  |  |       } else {
 | 
					
						
							|  |  |  |         throw new RuntimeException(
 | 
					
						
							|  |  |  |             String.format("Bad response from the Metadata Service: %s %s",
 | 
					
						
							|  |  |  |                 response.getStatusLine().toString(), response.getEntity().toString()));
 | 
					
						
							|  |  |  |       }
 | 
					
						
							|  |  |  |     } catch (Exception e) {
 | 
					
						
							|  |  |  |       throw new RuntimeException("Failed to generate session token for user", e);
 | 
					
						
							|  |  |  |     } finally {
 | 
					
						
							|  |  |  |       try {
 | 
					
						
							|  |  |  |         httpClient.close();
 | 
					
						
							|  |  |  |       } catch (Exception e) {
 | 
					
						
							|  |  |  |         log.warn("Failed to close http client", e);
 | 
					
						
							|  |  |  |       }
 | 
					
						
							|  |  |  |     }
 | 
					
						
							|  |  |  |   }
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-06-08 21:13:22 -04:00
										 |  |  |   /**
 | 
					
						
							|  |  |  |    * Call the Auth Service to create a native Datahub user.
 | 
					
						
							|  |  |  |    */
 | 
					
						
							|  |  |  |   @Nonnull
 | 
					
						
							|  |  |  |   public boolean signUp(@Nonnull final String userUrn, @Nonnull final String fullName, @Nonnull final String email,
 | 
					
						
							|  |  |  |       @Nonnull final String title, @Nonnull final String password, @Nonnull final String inviteToken) {
 | 
					
						
							|  |  |  |     Objects.requireNonNull(userUrn, "userUrn must not be null");
 | 
					
						
							|  |  |  |     Objects.requireNonNull(fullName, "fullName must not be null");
 | 
					
						
							|  |  |  |     Objects.requireNonNull(email, "email must not be null");
 | 
					
						
							|  |  |  |     Objects.requireNonNull(title, "title must not be null");
 | 
					
						
							|  |  |  |     Objects.requireNonNull(password, "password must not be null");
 | 
					
						
							|  |  |  |     Objects.requireNonNull(inviteToken, "inviteToken must not be null");
 | 
					
						
							|  |  |  |     CloseableHttpClient httpClient = HttpClients.createDefault();
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     try {
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       final String protocol = this.metadataServiceUseSsl ? "https" : "http";
 | 
					
						
							|  |  |  |       final HttpPost request =
 | 
					
						
							|  |  |  |           new HttpPost(String.format("%s://%s:%s/%s", protocol, this.metadataServiceHost, this.metadataServicePort,
 | 
					
						
							|  |  |  |               SIGN_UP_ENDPOINT));
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       // Build JSON request to verify credentials for a native user.
 | 
					
						
							|  |  |  |       String json =
 | 
					
						
							|  |  |  |           String.format("{ \"%s\":\"%s\", \"%s\":\"%s\", \"%s\":\"%s\", \"%s\":\"%s\", \"%s\":\"%s\", \"%s\":\"%s\" }",
 | 
					
						
							|  |  |  |               USER_URN_FIELD, userUrn, FULL_NAME_FIELD, fullName, EMAIL_FIELD, email, TITLE_FIELD, title,
 | 
					
						
							|  |  |  |               PASSWORD_FIELD, password, INVITE_TOKEN_FIELD, inviteToken);
 | 
					
						
							|  |  |  |       request.setEntity(new StringEntity(json));
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       // Add authorization header with DataHub frontend system id and secret.
 | 
					
						
							|  |  |  |       request.addHeader(Http.HeaderNames.AUTHORIZATION, this.systemAuthentication.getCredentials());
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       CloseableHttpResponse response = httpClient.execute(request);
 | 
					
						
							|  |  |  |       final HttpEntity entity = response.getEntity();
 | 
					
						
							|  |  |  |       if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK && entity != null) {
 | 
					
						
							|  |  |  |         // Successfully generated a token for the User
 | 
					
						
							|  |  |  |         final String jsonStr = EntityUtils.toString(entity);
 | 
					
						
							|  |  |  |         return getIsNativeUserCreatedFromJson(jsonStr);
 | 
					
						
							|  |  |  |       } else {
 | 
					
						
							|  |  |  |         throw new RuntimeException(
 | 
					
						
							|  |  |  |             String.format("Bad response from the Metadata Service: %s %s", response.getStatusLine().toString(),
 | 
					
						
							|  |  |  |                 response.getEntity().toString()));
 | 
					
						
							|  |  |  |       }
 | 
					
						
							|  |  |  |     } catch (Exception e) {
 | 
					
						
							|  |  |  |       throw new RuntimeException("Failed to create user", e);
 | 
					
						
							|  |  |  |     } finally {
 | 
					
						
							|  |  |  |       try {
 | 
					
						
							|  |  |  |         httpClient.close();
 | 
					
						
							|  |  |  |       } catch (Exception e) {
 | 
					
						
							|  |  |  |         log.warn("Failed to close http client", e);
 | 
					
						
							|  |  |  |       }
 | 
					
						
							|  |  |  |     }
 | 
					
						
							|  |  |  |   }
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   /**
 | 
					
						
							|  |  |  |    * Call the Auth Service to reset credentials for a native DataHub user.
 | 
					
						
							|  |  |  |    */
 | 
					
						
							|  |  |  |   @Nonnull
 | 
					
						
							|  |  |  |   public boolean resetNativeUserCredentials(@Nonnull final String userUrn, @Nonnull final String password,
 | 
					
						
							|  |  |  |       @Nonnull final String resetToken) {
 | 
					
						
							|  |  |  |     Objects.requireNonNull(userUrn, "userUrn must not be null");
 | 
					
						
							|  |  |  |     Objects.requireNonNull(password, "password must not be null");
 | 
					
						
							|  |  |  |     Objects.requireNonNull(resetToken, "reset token must not be null");
 | 
					
						
							|  |  |  |     CloseableHttpClient httpClient = HttpClients.createDefault();
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     try {
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       final String protocol = this.metadataServiceUseSsl ? "https" : "http";
 | 
					
						
							|  |  |  |       final HttpPost request = new HttpPost(
 | 
					
						
							|  |  |  |           String.format("%s://%s:%s/%s", protocol, this.metadataServiceHost, this.metadataServicePort,
 | 
					
						
							|  |  |  |               RESET_NATIVE_USER_CREDENTIALS_ENDPOINT));
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       // Build JSON request to verify credentials for a native user.
 | 
					
						
							|  |  |  |       String json =
 | 
					
						
							|  |  |  |           String.format("{ \"%s\":\"%s\", \"%s\":\"%s\", \"%s\":\"%s\" }", USER_URN_FIELD, userUrn,
 | 
					
						
							|  |  |  |               PASSWORD_FIELD, password, RESET_TOKEN_FIELD, resetToken);
 | 
					
						
							|  |  |  |       request.setEntity(new StringEntity(json));
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       // Add authorization header with DataHub frontend system id and secret.
 | 
					
						
							|  |  |  |       request.addHeader(Http.HeaderNames.AUTHORIZATION, this.systemAuthentication.getCredentials());
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       CloseableHttpResponse response = httpClient.execute(request);
 | 
					
						
							|  |  |  |       final HttpEntity entity = response.getEntity();
 | 
					
						
							|  |  |  |       if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK && entity != null) {
 | 
					
						
							|  |  |  |         // Successfully generated a token for the User
 | 
					
						
							|  |  |  |         final String jsonStr = EntityUtils.toString(entity);
 | 
					
						
							|  |  |  |         return getAreNativeUserCredentialsResetFromJson(jsonStr);
 | 
					
						
							|  |  |  |       } else {
 | 
					
						
							|  |  |  |         throw new RuntimeException(
 | 
					
						
							|  |  |  |             String.format("Bad response from the Metadata Service: %s %s", response.getStatusLine().toString(),
 | 
					
						
							|  |  |  |                 response.getEntity().toString()));
 | 
					
						
							|  |  |  |       }
 | 
					
						
							|  |  |  |     } catch (Exception e) {
 | 
					
						
							|  |  |  |       throw new RuntimeException("Failed to reset credentials for user", e);
 | 
					
						
							|  |  |  |     } finally {
 | 
					
						
							|  |  |  |       try {
 | 
					
						
							|  |  |  |         httpClient.close();
 | 
					
						
							|  |  |  |       } catch (Exception e) {
 | 
					
						
							|  |  |  |         log.warn("Failed to close http client", e);
 | 
					
						
							|  |  |  |       }
 | 
					
						
							|  |  |  |     }
 | 
					
						
							|  |  |  |   }
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   /**
 | 
					
						
							|  |  |  |    * Call the Auth Service to verify the credentials for a native Datahub user.
 | 
					
						
							|  |  |  |    */
 | 
					
						
							|  |  |  |   @Nonnull
 | 
					
						
							|  |  |  |   public boolean verifyNativeUserCredentials(@Nonnull final String userUrn, @Nonnull final String password) {
 | 
					
						
							|  |  |  |     Objects.requireNonNull(userUrn, "userUrn must not be null");
 | 
					
						
							|  |  |  |     Objects.requireNonNull(password, "password must not be null");
 | 
					
						
							|  |  |  |     CloseableHttpClient httpClient = HttpClients.createDefault();
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     try {
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       final String protocol = this.metadataServiceUseSsl ? "https" : "http";
 | 
					
						
							|  |  |  |       final HttpPost request = new HttpPost(
 | 
					
						
							|  |  |  |           String.format("%s://%s:%s/%s", protocol, this.metadataServiceHost, this.metadataServicePort,
 | 
					
						
							|  |  |  |               VERIFY_NATIVE_USER_CREDENTIALS_ENDPOINT));
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       // Build JSON request to verify credentials for a native user.
 | 
					
						
							|  |  |  |       String json =
 | 
					
						
							|  |  |  |           String.format("{ \"%s\":\"%s\", \"%s\":\"%s\" }", USER_URN_FIELD, userUrn, PASSWORD_FIELD, password);
 | 
					
						
							|  |  |  |       request.setEntity(new StringEntity(json));
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       // Add authorization header with DataHub frontend system id and secret.
 | 
					
						
							|  |  |  |       request.addHeader(Http.HeaderNames.AUTHORIZATION, this.systemAuthentication.getCredentials());
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       CloseableHttpResponse response = httpClient.execute(request);
 | 
					
						
							|  |  |  |       final HttpEntity entity = response.getEntity();
 | 
					
						
							|  |  |  |       if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK && entity != null) {
 | 
					
						
							|  |  |  |         // Successfully generated a token for the User
 | 
					
						
							|  |  |  |         final String jsonStr = EntityUtils.toString(entity);
 | 
					
						
							|  |  |  |         return getDoesPasswordMatchFromJson(jsonStr);
 | 
					
						
							|  |  |  |       } else {
 | 
					
						
							|  |  |  |         throw new RuntimeException(
 | 
					
						
							|  |  |  |             String.format("Bad response from the Metadata Service: %s %s", response.getStatusLine().toString(),
 | 
					
						
							|  |  |  |                 response.getEntity().toString()));
 | 
					
						
							|  |  |  |       }
 | 
					
						
							|  |  |  |     } catch (Exception e) {
 | 
					
						
							|  |  |  |       throw new RuntimeException("Failed to verify credentials for user", e);
 | 
					
						
							|  |  |  |     } finally {
 | 
					
						
							|  |  |  |       try {
 | 
					
						
							|  |  |  |         httpClient.close();
 | 
					
						
							|  |  |  |       } catch (Exception e) {
 | 
					
						
							|  |  |  |         log.warn("Failed to close http client", e);
 | 
					
						
							|  |  |  |       }
 | 
					
						
							|  |  |  |     }
 | 
					
						
							|  |  |  |   }
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-11-22 16:33:14 -08:00
										 |  |  |   private String getAccessTokenFromJson(final String jsonStr) {
 | 
					
						
							|  |  |  |     ObjectMapper mapper = new ObjectMapper();
 | 
					
						
							|  |  |  |     try {
 | 
					
						
							|  |  |  |       return mapper.readTree(jsonStr).get(ACCESS_TOKEN_FIELD).asText();
 | 
					
						
							|  |  |  |     } catch (Exception e) {
 | 
					
						
							|  |  |  |       // Do not log the raw json in case it contains access token.
 | 
					
						
							|  |  |  |       throw new IllegalArgumentException("Failed to parse JSON received from the MetadataService!");
 | 
					
						
							|  |  |  |     }
 | 
					
						
							|  |  |  |   }
 | 
					
						
							| 
									
										
										
										
											2022-06-08 21:13:22 -04:00
										 |  |  | 
 | 
					
						
							|  |  |  |   private boolean getIsNativeUserCreatedFromJson(final String jsonStr) {
 | 
					
						
							|  |  |  |     ObjectMapper mapper = new ObjectMapper();
 | 
					
						
							|  |  |  |     try {
 | 
					
						
							|  |  |  |       return mapper.readTree(jsonStr).get(IS_NATIVE_USER_CREATED_FIELD).asBoolean();
 | 
					
						
							|  |  |  |     } catch (Exception e) {
 | 
					
						
							|  |  |  |       throw new IllegalArgumentException("Failed to parse JSON received from the MetadataService!");
 | 
					
						
							|  |  |  |     }
 | 
					
						
							|  |  |  |   }
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   private boolean getAreNativeUserCredentialsResetFromJson(final String jsonStr) {
 | 
					
						
							|  |  |  |     ObjectMapper mapper = new ObjectMapper();
 | 
					
						
							|  |  |  |     try {
 | 
					
						
							|  |  |  |       return mapper.readTree(jsonStr).get(ARE_NATIVE_USER_CREDENTIALS_RESET_FIELD).asBoolean();
 | 
					
						
							|  |  |  |     } catch (Exception e) {
 | 
					
						
							|  |  |  |       throw new IllegalArgumentException("Failed to parse JSON received from the MetadataService!");
 | 
					
						
							|  |  |  |     }
 | 
					
						
							|  |  |  |   }
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   private boolean getDoesPasswordMatchFromJson(final String jsonStr) {
 | 
					
						
							|  |  |  |     ObjectMapper mapper = new ObjectMapper();
 | 
					
						
							|  |  |  |     try {
 | 
					
						
							|  |  |  |       return mapper.readTree(jsonStr).get(DOES_PASSWORD_MATCH_FIELD).asBoolean();
 | 
					
						
							|  |  |  |     } catch (Exception e) {
 | 
					
						
							|  |  |  |       throw new IllegalArgumentException("Failed to parse JSON received from the MetadataService!");
 | 
					
						
							|  |  |  |     }
 | 
					
						
							|  |  |  |   }
 | 
					
						
							| 
									
										
										
										
											2021-11-22 16:33:14 -08:00
										 |  |  | }
 |