Skip to content

Commit 275348a

Browse files
committed
Logout button.
1 parent 5c9a564 commit 275348a

File tree

9 files changed

+133
-0
lines changed

9 files changed

+133
-0
lines changed

dqops/src/main/frontend/src/components/UserProfile/index.tsx

+8
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,14 @@ export default function UserProfile({ name, email }: UserProfile) {
255255
</div>
256256
</>
257257
)}
258+
{userProfile.can_logout === true && (
259+
<a
260+
href="/logout"
261+
className="block text-teal-500 text-sm underline mb-3 ml-1"
262+
>
263+
Logout
264+
</a>
265+
)}
258266
</div>
259267
<ChangePrincipalPasswordDialog
260268
open={open}

dqops/src/main/java/com/dqops/core/configuration/DqoCloudConfigurationProperties.java

+17
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
public class DqoCloudConfigurationProperties implements Cloneable {
2929
private String apiKey;
3030
private String apiKeyRequestUrl;
31+
private String logoutUrl;
3132
private String uiBaseUrl;
3233
private String restApiBaseUrl;
3334
private int apiKeyPickupTimeoutSeconds = 10 * 60;
@@ -73,6 +74,22 @@ public void setApiKeyRequestUrl(String apiKeyRequestUrl) {
7374
this.apiKeyRequestUrl = apiKeyRequestUrl;
7475
}
7576

77+
/**
78+
* Returns the logout url.
79+
* @return Logout url.
80+
*/
81+
public String getLogoutUrl() {
82+
return logoutUrl;
83+
}
84+
85+
/**
86+
* Sets the logout url.
87+
* @param logoutUrl Logout url.
88+
*/
89+
public void setLogoutUrl(String logoutUrl) {
90+
this.logoutUrl = logoutUrl;
91+
}
92+
7693
/**
7794
* Returns the api key pickup timeout that the console is waiting, given in seconds.
7895
* @return Api key pickup timeout.

dqops/src/main/java/com/dqops/core/dqocloud/login/InstanceCloudLoginService.java

+8
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,14 @@ public interface InstanceCloudLoginService {
4646
*/
4747
String makeDqoLoginUrl(String returnUrl);
4848

49+
/**
50+
* Builds a url to the DQOps Cloud's logout page with the ticket granting ticket and the return url.
51+
*
52+
* @param returnUrl Return url.
53+
* @return Url to the DQOps Cloud's login page to redirect to.
54+
*/
55+
String makeDqoLogoutUrl(String returnUrl);
56+
4957
/**
5058
* Creates a signed authentication token from a refresh token.
5159
* @param refreshToken Refresh token.

dqops/src/main/java/com/dqops/core/dqocloud/login/InstanceCloudLoginServiceImpl.java

+45
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,51 @@ public String makeDqoLoginUrl(String returnUrl) {
209209
}
210210
}
211211

212+
/**
213+
* Builds a url to the DQOps Cloud's logout page with the ticket granting ticket and the return url.
214+
*
215+
* @param returnUrl Return url.
216+
* @return Url to the DQOps Cloud's login page to redirect to.
217+
*/
218+
@Override
219+
public String makeDqoLogoutUrl(String returnUrl) {
220+
String returnBaseUrl = this.getReturnBaseUrl();
221+
if (this.dqoInstanceConfigurationProperties.isValidateReturnBaseUrl() && !returnUrl.startsWith(returnBaseUrl)) {
222+
throw new DqoRuntimeException("Invalid return url. The valid return url for this DQOps instance must begin with " + returnBaseUrl +
223+
". You can change the configuration by setting the --dqo.instance.return-base-url or setting the environment variable " +
224+
"DQO_INSTANCE_RETURN_BASE_URL to the base url of your DQOps instance, for example --dqo.instance.return-base-url=https://dqoinstance.yourcompany.com");
225+
}
226+
227+
String ticketGrantingTicket = this.getTicketGrantingTicket();
228+
DqoUserPrincipal userPrincipalForAdministrator = this.principalProvider.createLocalInstanceAdminPrincipal();
229+
DqoCloudApiKey apiKey = this.dqoCloudApiKeyProvider.getApiKey(userPrincipalForAdministrator.getDataDomainIdentity());
230+
231+
try {
232+
if (!returnUrl.startsWith(returnBaseUrl)) {
233+
URI originalReturnUri = new URI(returnUrl);
234+
URIBuilder returnUrlBuilder = new URIBuilder(returnBaseUrl);
235+
returnUrlBuilder.setPath(originalReturnUri.getPath());
236+
if (originalReturnUri.getRawQuery() != null) {
237+
returnUrlBuilder.setCustomQuery(originalReturnUri.getRawQuery());
238+
}
239+
returnUrl = returnUrlBuilder.build().toString();
240+
}
241+
242+
URIBuilder uriBuilder = new URIBuilder(this.dqoCloudConfigurationProperties.getLogoutUrl());
243+
uriBuilder.addParameter("tgt", ticketGrantingTicket);
244+
if (!Strings.isNullOrEmpty(apiKey.getApiKeyPayload().getAccountName())) {
245+
uriBuilder.addParameter("account", apiKey.getApiKeyPayload().getAccountName());
246+
}
247+
uriBuilder.addParameter("returnUrl", returnUrl);
248+
249+
String dqoLoginUrl = uriBuilder.build().toString();
250+
return dqoLoginUrl;
251+
}
252+
catch (Exception ex) {
253+
throw new DqoRuntimeException("Invalid DQOps Cloud base url, error: " + ex.getMessage(), ex);
254+
}
255+
}
256+
212257
/**
213258
* Creates a signed authentication token from a refresh token.
214259
* @param refreshToken Refresh token.

dqops/src/main/java/com/dqops/rest/models/platform/DqoUserProfileModel.java

+7
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,12 @@ public class DqoUserProfileModel {
228228
@JsonPropertyDescription("The DQOps instance is a paid version with advanced AI anomaly prediction.")
229229
private boolean canUseAiAnomalyDetection;
230230

231+
/**
232+
* This instance uses federated authentication and the user can log out.
233+
*/
234+
@JsonPropertyDescription("This instance uses federated authentication and the user can log out.")
235+
private boolean canLogout;
236+
231237
/**
232238
* Creates a user profile model from the API key.
233239
* @param dqoCloudApiKey DQOps Cloud api key.
@@ -278,6 +284,7 @@ public static DqoUserProfileModel fromApiKeyAndPrincipal(DqoCloudApiKey dqoCloud
278284
model.setCanUseDataDomains(dqoCloudApiKey.getApiKeyPayload().getLicenseType() == DqoCloudLicenseType.ENTERPRISE);
279285
model.setCanUseAiAnomalyDetection(dqoCloudApiKey.getApiKeyPayload().getLicenseType() != DqoCloudLicenseType.FREE &&
280286
dqoCloudApiKey.getApiKeyPayload().getExpiresAt() == null);
287+
model.setCanLogout(principal.getUserTokenPayload() != null);
281288
} else {
282289
model.setTenant("Standalone");
283290
model.setLicenseType(DqoCloudLicenseType.FREE.name());

dqops/src/main/java/com/dqops/rest/server/authentication/AuthenticateWithDqoCloudWebFilter.java

+39
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,11 @@ public class AuthenticateWithDqoCloudWebFilter implements WebFilter {
7878
*/
7979
public static final String ISSUE_TOKEN_REQUEST_PATH = "/tokenissuer";
8080

81+
/**
82+
* A special URL that performs a logout, clears the authentication cookie and redirects the user to the login server.
83+
*/
84+
public static final String LOGOUT_PATH = "/logout";
85+
8186
/**
8287
* Health check url.
8388
*/
@@ -244,6 +249,40 @@ public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
244249
.then();
245250
}
246251

252+
if (Objects.equals(requestPath, LOGOUT_PATH)) {
253+
String hostHeader = request.getHeaders().getHost().getHostString();
254+
255+
int portPrefixIndex = hostHeader.indexOf(':');
256+
if (portPrefixIndex > 0) {
257+
hostHeader = hostHeader.substring(0, portPrefixIndex);
258+
}
259+
260+
ResponseCookie dqoAccessTokenCookie = ResponseCookie.from(AUTHENTICATION_TOKEN_COOKIE, "")
261+
.maxAge(0L)
262+
.path("/")
263+
.domain(hostHeader)
264+
.build();
265+
exchange.getResponse().getCookies().add(AUTHENTICATION_TOKEN_COOKIE, dqoAccessTokenCookie);
266+
267+
try {
268+
String returnUrl = exchange.getRequest().getURI().resolve("/").toString();
269+
String dqoCloudLoginUrl = this.instanceCloudLoginService.makeDqoLogoutUrl(returnUrl);
270+
271+
if (log.isDebugEnabled()) {
272+
log.debug("Redirecting the user to logout and authenticate with DQOps Cloud federated authentication at " + dqoCloudLoginUrl);
273+
}
274+
275+
exchange.getResponse().setStatusCode(HttpStatusCode.valueOf(303));
276+
exchange.getResponse().getHeaders().add("Location", dqoCloudLoginUrl);
277+
return exchange.getResponse().writeAndFlushWith(Mono.empty());
278+
}
279+
catch (Exception ex) {
280+
log.error("Cannot create a DQOps Cloud login url: " + ex.getMessage(), ex);
281+
exchange.getResponse().setStatusCode(HttpStatusCode.valueOf(500));
282+
return exchange.getResponse().writeAndFlushWith(Mono.empty());
283+
}
284+
}
285+
247286
if (Objects.equals(requestPath, ISSUE_TOKEN_REQUEST_PATH)) {
248287
exchange.getResponse().setStatusCode(HttpStatusCode.valueOf(303));
249288
String returnUrl = exchange.getRequest().getQueryParams().getFirst("returnUrl");

dqops/src/main/resources/application.yml

+1
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@ dqo:
115115
#ui-base-url: http://localhost:8080
116116
#rest-api-base-url: http://localhost:8080
117117
api-key-request-url: ${dqo.cloud.ui-base-url}/requestapikey/
118+
logout-url: ${dqo.cloud.ui-base-url}/login?logout=true
118119
api-key-pickup-timeout-seconds: 600
119120
api-key-pickup-retry-delay-millis: 1000
120121
parallel-file-uploads: 500

dqops/src/main/resources/static/swagger-api/dqops-api-swagger-2.json

+4
Original file line numberDiff line numberDiff line change
@@ -39351,6 +39351,10 @@
3935139351
"can_use_ai_anomaly_detection" : {
3935239352
"type" : "boolean",
3935339353
"description" : "The DQOps instance is a paid version with advanced AI anomaly prediction."
39354+
},
39355+
"can_logout" : {
39356+
"type" : "boolean",
39357+
"description" : "This instance uses federated authentication and the user can log out."
3935439358
}
3935539359
},
3935639360
"description" : "The model that describes the current user and his access rights."

dqops/src/main/resources/static/swagger-api/dqops-api-swagger-2.yaml

+4
Original file line numberDiff line numberDiff line change
@@ -38080,6 +38080,10 @@ definitions:
3808038080
type: "boolean"
3808138081
description: "The DQOps instance is a paid version with advanced AI anomaly\
3808238082
\ prediction."
38083+
can_logout:
38084+
type: "boolean"
38085+
description: "This instance uses federated authentication and the user can\
38086+
\ log out."
3808338087
description: "The model that describes the current user and his access rights."
3808438088
DqoUserRolesModel:
3808538089
type: "object"

0 commit comments

Comments
 (0)