Skip to content

Commit

Permalink
Logout button.
Browse files Browse the repository at this point in the history
  • Loading branch information
piotrczarnas committed Nov 13, 2024
1 parent 5c9a564 commit 275348a
Show file tree
Hide file tree
Showing 9 changed files with 133 additions and 0 deletions.
8 changes: 8 additions & 0 deletions dqops/src/main/frontend/src/components/UserProfile/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,14 @@ export default function UserProfile({ name, email }: UserProfile) {
</div>
</>
)}
{userProfile.can_logout === true && (
<a
href="/logout"
className="block text-teal-500 text-sm underline mb-3 ml-1"
>
Logout
</a>
)}
</div>
<ChangePrincipalPasswordDialog
open={open}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
public class DqoCloudConfigurationProperties implements Cloneable {
private String apiKey;
private String apiKeyRequestUrl;
private String logoutUrl;
private String uiBaseUrl;
private String restApiBaseUrl;
private int apiKeyPickupTimeoutSeconds = 10 * 60;
Expand Down Expand Up @@ -73,6 +74,22 @@ public void setApiKeyRequestUrl(String apiKeyRequestUrl) {
this.apiKeyRequestUrl = apiKeyRequestUrl;
}

/**
* Returns the logout url.
* @return Logout url.
*/
public String getLogoutUrl() {
return logoutUrl;
}

/**
* Sets the logout url.
* @param logoutUrl Logout url.
*/
public void setLogoutUrl(String logoutUrl) {
this.logoutUrl = logoutUrl;
}

/**
* Returns the api key pickup timeout that the console is waiting, given in seconds.
* @return Api key pickup timeout.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,14 @@ public interface InstanceCloudLoginService {
*/
String makeDqoLoginUrl(String returnUrl);

/**
* Builds a url to the DQOps Cloud's logout page with the ticket granting ticket and the return url.
*
* @param returnUrl Return url.
* @return Url to the DQOps Cloud's login page to redirect to.
*/
String makeDqoLogoutUrl(String returnUrl);

/**
* Creates a signed authentication token from a refresh token.
* @param refreshToken Refresh token.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,51 @@ public String makeDqoLoginUrl(String returnUrl) {
}
}

/**
* Builds a url to the DQOps Cloud's logout page with the ticket granting ticket and the return url.
*
* @param returnUrl Return url.
* @return Url to the DQOps Cloud's login page to redirect to.
*/
@Override
public String makeDqoLogoutUrl(String returnUrl) {
String returnBaseUrl = this.getReturnBaseUrl();
if (this.dqoInstanceConfigurationProperties.isValidateReturnBaseUrl() && !returnUrl.startsWith(returnBaseUrl)) {
throw new DqoRuntimeException("Invalid return url. The valid return url for this DQOps instance must begin with " + returnBaseUrl +
". You can change the configuration by setting the --dqo.instance.return-base-url or setting the environment variable " +
"DQO_INSTANCE_RETURN_BASE_URL to the base url of your DQOps instance, for example --dqo.instance.return-base-url=https://dqoinstance.yourcompany.com");
}

String ticketGrantingTicket = this.getTicketGrantingTicket();
DqoUserPrincipal userPrincipalForAdministrator = this.principalProvider.createLocalInstanceAdminPrincipal();
DqoCloudApiKey apiKey = this.dqoCloudApiKeyProvider.getApiKey(userPrincipalForAdministrator.getDataDomainIdentity());

try {
if (!returnUrl.startsWith(returnBaseUrl)) {
URI originalReturnUri = new URI(returnUrl);
URIBuilder returnUrlBuilder = new URIBuilder(returnBaseUrl);
returnUrlBuilder.setPath(originalReturnUri.getPath());
if (originalReturnUri.getRawQuery() != null) {
returnUrlBuilder.setCustomQuery(originalReturnUri.getRawQuery());
}
returnUrl = returnUrlBuilder.build().toString();
}

URIBuilder uriBuilder = new URIBuilder(this.dqoCloudConfigurationProperties.getLogoutUrl());
uriBuilder.addParameter("tgt", ticketGrantingTicket);
if (!Strings.isNullOrEmpty(apiKey.getApiKeyPayload().getAccountName())) {
uriBuilder.addParameter("account", apiKey.getApiKeyPayload().getAccountName());
}
uriBuilder.addParameter("returnUrl", returnUrl);

String dqoLoginUrl = uriBuilder.build().toString();
return dqoLoginUrl;
}
catch (Exception ex) {
throw new DqoRuntimeException("Invalid DQOps Cloud base url, error: " + ex.getMessage(), ex);
}
}

/**
* Creates a signed authentication token from a refresh token.
* @param refreshToken Refresh token.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,12 @@ public class DqoUserProfileModel {
@JsonPropertyDescription("The DQOps instance is a paid version with advanced AI anomaly prediction.")
private boolean canUseAiAnomalyDetection;

/**
* This instance uses federated authentication and the user can log out.
*/
@JsonPropertyDescription("This instance uses federated authentication and the user can log out.")
private boolean canLogout;

/**
* Creates a user profile model from the API key.
* @param dqoCloudApiKey DQOps Cloud api key.
Expand Down Expand Up @@ -278,6 +284,7 @@ public static DqoUserProfileModel fromApiKeyAndPrincipal(DqoCloudApiKey dqoCloud
model.setCanUseDataDomains(dqoCloudApiKey.getApiKeyPayload().getLicenseType() == DqoCloudLicenseType.ENTERPRISE);
model.setCanUseAiAnomalyDetection(dqoCloudApiKey.getApiKeyPayload().getLicenseType() != DqoCloudLicenseType.FREE &&
dqoCloudApiKey.getApiKeyPayload().getExpiresAt() == null);
model.setCanLogout(principal.getUserTokenPayload() != null);
} else {
model.setTenant("Standalone");
model.setLicenseType(DqoCloudLicenseType.FREE.name());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,11 @@ public class AuthenticateWithDqoCloudWebFilter implements WebFilter {
*/
public static final String ISSUE_TOKEN_REQUEST_PATH = "/tokenissuer";

/**
* A special URL that performs a logout, clears the authentication cookie and redirects the user to the login server.
*/
public static final String LOGOUT_PATH = "/logout";

/**
* Health check url.
*/
Expand Down Expand Up @@ -244,6 +249,40 @@ public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
.then();
}

if (Objects.equals(requestPath, LOGOUT_PATH)) {
String hostHeader = request.getHeaders().getHost().getHostString();

int portPrefixIndex = hostHeader.indexOf(':');
if (portPrefixIndex > 0) {
hostHeader = hostHeader.substring(0, portPrefixIndex);
}

ResponseCookie dqoAccessTokenCookie = ResponseCookie.from(AUTHENTICATION_TOKEN_COOKIE, "")
.maxAge(0L)
.path("/")
.domain(hostHeader)
.build();
exchange.getResponse().getCookies().add(AUTHENTICATION_TOKEN_COOKIE, dqoAccessTokenCookie);

try {
String returnUrl = exchange.getRequest().getURI().resolve("/").toString();
String dqoCloudLoginUrl = this.instanceCloudLoginService.makeDqoLogoutUrl(returnUrl);

if (log.isDebugEnabled()) {
log.debug("Redirecting the user to logout and authenticate with DQOps Cloud federated authentication at " + dqoCloudLoginUrl);
}

exchange.getResponse().setStatusCode(HttpStatusCode.valueOf(303));
exchange.getResponse().getHeaders().add("Location", dqoCloudLoginUrl);
return exchange.getResponse().writeAndFlushWith(Mono.empty());
}
catch (Exception ex) {
log.error("Cannot create a DQOps Cloud login url: " + ex.getMessage(), ex);
exchange.getResponse().setStatusCode(HttpStatusCode.valueOf(500));
return exchange.getResponse().writeAndFlushWith(Mono.empty());
}
}

if (Objects.equals(requestPath, ISSUE_TOKEN_REQUEST_PATH)) {
exchange.getResponse().setStatusCode(HttpStatusCode.valueOf(303));
String returnUrl = exchange.getRequest().getQueryParams().getFirst("returnUrl");
Expand Down
1 change: 1 addition & 0 deletions dqops/src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ dqo:
#ui-base-url: http://localhost:8080
#rest-api-base-url: http://localhost:8080
api-key-request-url: ${dqo.cloud.ui-base-url}/requestapikey/
logout-url: ${dqo.cloud.ui-base-url}/login?logout=true
api-key-pickup-timeout-seconds: 600
api-key-pickup-retry-delay-millis: 1000
parallel-file-uploads: 500
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39351,6 +39351,10 @@
"can_use_ai_anomaly_detection" : {
"type" : "boolean",
"description" : "The DQOps instance is a paid version with advanced AI anomaly prediction."
},
"can_logout" : {
"type" : "boolean",
"description" : "This instance uses federated authentication and the user can log out."
}
},
"description" : "The model that describes the current user and his access rights."
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38080,6 +38080,10 @@ definitions:
type: "boolean"
description: "The DQOps instance is a paid version with advanced AI anomaly\
\ prediction."
can_logout:
type: "boolean"
description: "This instance uses federated authentication and the user can\
\ log out."
description: "The model that describes the current user and his access rights."
DqoUserRolesModel:
type: "object"
Expand Down

0 comments on commit 275348a

Please sign in to comment.