Skip to content
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

Dev UI: OIDC Updates #34002

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import io.quarkus.devconsole.spi.DevConsoleTemplateInfoBuildItem;
import io.quarkus.devui.spi.JsonRPCProvidersBuildItem;
import io.quarkus.devui.spi.page.CardPageBuildItem;
import io.quarkus.devui.spi.page.Page;
import io.quarkus.oidc.deployment.OidcBuildTimeConfig;
import io.quarkus.oidc.deployment.devservices.AbstractDevConsoleProcessor;
import io.quarkus.oidc.deployment.devservices.OidcAuthorizationCodePostHandler;
Expand Down Expand Up @@ -81,7 +82,9 @@ void produceProviderComponent(Optional<KeycloakDevServicesConfigBuildItem> confi
@SuppressWarnings("unchecked")
Map<String, String> users = (Map<String, String>) configProps.get().getProperties().get("oidc.users");

var cardPage = createProviderWebComponent(
String keycloakAdminUrl = configProps.get().getConfig().get("keycloak.url");

CardPageBuildItem cardPageBuildItem = createProviderWebComponent(
recorder,
capabilities,
"Keycloak",
Expand All @@ -97,11 +100,17 @@ void produceProviderComponent(Optional<KeycloakDevServicesConfigBuildItem> confi
oidcConfig.devui.grantOptions,
nonApplicationRootPathBuildItem,
configurationBuildItem,
configProps.get().getConfig().get("keycloak.url"),
keycloakAdminUrl,
users,
configProps.get().getProperties().get("keycloak.realms"),
configProps.get().isContainerRestarted());
cardPageProducer.produce(cardPage);

// Also add Admin page
cardPageBuildItem.addPage(Page.externalPageBuilder("Keycloak Admin")
.icon("font-awesome-solid:key")
.doNotEmbed(true)
.url(keycloakAdminUrl));
cardPageProducer.produce(cardPageBuildItem);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ class OidcPropertiesState extends LitState {
hideImplicitLoggedIn: false,
hideImplLoggedOut: false,
swaggerUiPath: null,
graphQlUiPath: null,
graphqlUiPath: null,
oidcProviderName: null,
oidcApplicationType: null,
oidcGrantType: null,
Expand Down Expand Up @@ -83,7 +83,7 @@ class OidcPropertiesState extends LitState {
propertiesState.keycloakAdminUrl = response.result.keycloakAdminUrl;
propertiesState.keycloakRealms = response.result.keycloakRealms;
propertiesState.swaggerUiPath = response.result.swaggerUiPath;
propertiesState.graphQlUiPath = response.result.graphQlUiPath;
propertiesState.graphqlUiPath = response.result.graphqlUiPath;

return {
// logout === true will trigger query params removal
Expand Down Expand Up @@ -123,26 +123,13 @@ export class QwcOidcProvider extends QwcHotReloadElement {
.full-width {
width: 100%;
}
@media (min-width: 1200px) {
.container {
max-width: 1140px;
}
}

.container {
width: 93%;
margin: auto;
align-items: stretch;
border: 1px solid rgba(0,0,0,.125);
border-radius: var(--lumo-border-radius-l);
}
@media (min-width: 768px) {
.container-btn-mn-lf {
margin-left: 16.666667%;
}
}
.container-btn {
phillip-kruger marked this conversation as resolved.
Show resolved Hide resolved
--lumo-success-color: #28a745;
}

.frm-field {
width: 83.333333%;
margin-left: 20px;
Expand All @@ -161,6 +148,9 @@ export class QwcOidcProvider extends QwcHotReloadElement {
.hidden {
display: none;
}
.heading {
font-size: larger;
}
.error-color {
color: var(--lumo-error-text-color);
}
Expand All @@ -174,7 +164,7 @@ export class QwcOidcProvider extends QwcHotReloadElement {
color: var(--lumo-primary-text-color);
}
.black-5pct {
background-color: var(--lumo-shade-5pct);
background-color: var(--lumo-contrast-10pct);
}
.margin-l-m {
margin-left: var(--lumo-space-m);
Expand Down Expand Up @@ -210,11 +200,10 @@ export class QwcOidcProvider extends QwcHotReloadElement {
}
}
.decoded-token, .encoded-token {
background-color: var(--lumo-contrast-90pct);
color: var(--lumo-success-contrast-color);
padding: 0 var(--lumo-space-m);
word-break: break-word;
word-wrap: break-word;
background-color: var(--lumo-contrast-5pct);
}
.decoded-token pre {
white-space: break-spaces;
Expand Down Expand Up @@ -351,27 +340,6 @@ export class QwcOidcProvider extends QwcHotReloadElement {
}

_renderProvider() {
const content = this._content();
if (propertiesState.keycloakAdminUrl) {
return html `
<vaadin-horizontal-layout
theme="spacing padding"
style="align-items: center"
>
${content}
<vaadin-button class="keycloak-btn" theme="tertiary"
@click=${() => QwcOidcProvider._goToKeycloakUrl()}>
<vaadin-icon icon="font-awesome-solid:key" slot="prefix" class="btn-icon"></vaadin-icon>
Keycloak Admin
</vaadin-button>
</vaadin-horizontal-layout>
`;
}

return content;
}

_content() {
if (QwcOidcProvider._isServiceOrHybridApp()) {
switch (propertiesState.oidcGrantType) {
case 'password':
Expand All @@ -395,7 +363,7 @@ export class QwcOidcProvider extends QwcHotReloadElement {
return html`
<vaadin-vertical-layout theme="spacing padding" class="height-4xl container">
${servicePathForm}
<vaadin-button class="container-btn-mn-lf container-btn" theme="primary success"
<vaadin-button theme="primary success"
@click=${() => this._signInToService()}>
Log into your Web Application
</vaadin-button>
Expand Down Expand Up @@ -454,7 +422,7 @@ export class QwcOidcProvider extends QwcHotReloadElement {
${extraFields}
${servicePathForm}
<vaadin-horizontal-layout class="full-width">
<vaadin-horizontal-layout class="container-btn-mn-lf full-width">
<vaadin-horizontal-layout class="full-width">
<vaadin-button class="fill-space margin-right-auto" theme="primary" title="Test service"
@click=${testSvcFun}>
Test service
Expand Down Expand Up @@ -668,12 +636,18 @@ export class QwcOidcProvider extends QwcHotReloadElement {
<vaadin-vertical-layout theme="spacing padding" class="height-4xl container"
?hidden="${propertiesState.hideImplLoggedOut}">
${keycloakRealms}
<vaadin-button class="container-btn-mn-lf container-btn" theme="primary success"
title="Log into Single Page Application to Get Access and ID Tokens"
@click=${() => this._signInToOidcProviderAndGetTokens()}>
<vaadin-icon icon="font-awesome-solid:user" slot="prefix" class="btn-icon"></vaadin-icon>
Log into Single Page Application
</vaadin-button>
<vaadin-form-layout class="txt-field-form full-width">
<vaadin-form-item class="full-width">
<vaadin-button theme="primary success"
title="Log into Single Page Application to Get Access and ID Tokens"
@click=${() => this._signInToOidcProviderAndGetTokens()}>
<vaadin-icon icon="font-awesome-solid:user" slot="prefix" class="btn-icon"></vaadin-icon>
Log into Single Page Application
</vaadin-button>
</vaadin-form-item>
</vaadin-form-layout>


</vaadin-vertical-layout>
<vaadin-horizontal-layout theme="spacing padding" class="height-4xl container vertical-center"
?hidden="${propertiesState.hideLogInErr}">
Expand All @@ -688,7 +662,7 @@ export class QwcOidcProvider extends QwcHotReloadElement {
<vaadin-vertical-layout class="full-width" ?hidden="${propertiesState.hideImplicitLoggedIn}">
<vaadin-vertical-layout class="height-4xl container">
<vaadin-horizontal-layout class="black-5pct vertical-center" theme="padding">
<span class="margin-right-auto default-cursor">Your tokens</span>
<span class="margin-right-auto default-cursor heading">Your tokens</span>
<span class="margin-right-space-m ${classMap({'display-none': !propertiesState.userName})}">
Logged in as ${propertiesState.userName}</span>
<vaadin-button theme="tertiary small" title="Click to logout and start again"
Expand Down Expand Up @@ -756,7 +730,7 @@ export class QwcOidcProvider extends QwcHotReloadElement {
</vaadin-vertical-layout>
<vaadin-vertical-layout theme="spacing" class="height-4xl container margin-top-space-m">
<vaadin-horizontal-layout class="black-5pct vertical-center" theme="padding">
<span class="margin-right-auto default-cursor">Test your service</span>
<span class="margin-right-auto default-cursor heading">Test your service</span>
<vaadin-button theme="tertiary small" title="Test in Swagger UI"
@click=${() => QwcOidcProvider._navigateToSwaggerUi()}
?hidden="${!propertiesState.swaggerIsAvailable}">
Expand All @@ -775,7 +749,7 @@ export class QwcOidcProvider extends QwcHotReloadElement {
<vaadin-vertical-layout theme="padding">
${servicePathForm}
<vaadin-horizontal-layout class="full-width">
<vaadin-horizontal-layout class="container-btn-mn-lf full-width">
<vaadin-horizontal-layout class="full-width">
<vaadin-button class="fill-space" theme="primary" title="Test With Access Token"
@click=${() => this._testServiceWithAccessToken()}>
With Access Token
Expand Down Expand Up @@ -933,10 +907,6 @@ export class QwcOidcProvider extends QwcHotReloadElement {
return result;
}

static _goToKeycloakUrl() {
window.open(propertiesState.keycloakAdminUrl, '_blank').focus();
}

static _areTokensInUrl() {
return QwcOidcProvider._getHashQueryStringParam('id_token')
&& QwcOidcProvider._getHashQueryStringParam('access_token');
Expand Down Expand Up @@ -1133,7 +1103,7 @@ export class QwcOidcProvider extends QwcHotReloadElement {
}

static _navigateToGraphQLUiWithToken(token) {
let url = propertiesState.graphQlUiPath;
let url = propertiesState.graphqlUiPath;
const headerJson = '{"authorization": "Bearer ' + token + '"}';
url += '/?' + encodeURIComponent('headers') + '=' + encodeURIComponent(headerJson);
window.open(url, '_blank').focus();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,22 @@ export class RouterController {
return false;
}

addExternalLink(page){
let path = this.getPageUrlFor(page);
if (!this.isExistingPath(path)) {
RouterController.pageMap.set(path, page);
if(RouterController.namespaceMap.has(page.namespace)){
// Existing
RouterController.namespaceMap.get(page.namespace).push(page);
}else{
// New
let namespacePages = [];
namespacePages.push(page);
RouterController.namespaceMap.set(page.namespace, namespacePages);
}
}
}

addRouteForMenu(page, defaultSelection){
this.addRoute(page.id, page.componentName, page.title, page, defaultSelection);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -195,17 +195,16 @@ export class QwcExtensionLink extends QwcHotReloadElement {
<vaadin-icon class="icon" icon="${this.iconName}"></vaadin-icon>
${this.displayName}
</span>
${this._renderBadge()}
</a>
${this._renderBadge()}
`;
}else{
return html`<a class="extensionLink" ?router-ignore=true>
<span class="iconAndName">
<vaadin-icon class="icon" icon="font-awesome-solid:spinner"></vaadin-icon>
loading ...
</span>
${this._renderBadge()}
</a>`;
</a>${this._renderBadge()}`;
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ export class QwcExtensions extends observeState(LitElement) {
if(page.embed){ // we need to register with the router
import(page.componentRef);
this.routerController.addRouteForExtension(page);
}else if(page.includeInSubMenu){ // we need to add the link to the submenu
phillip-kruger marked this conversation as resolved.
Show resolved Hide resolved
this.routerController.addExternalLink(page);
}
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -237,9 +237,7 @@ export class QwcHeader extends observeState(LitElement) {
if(subMenu){
this._rightSideNav = html`<vaadin-tabs selected="${subMenu.index}">
${subMenu.links.map(link =>
html`<vaadin-tab>
${this._renderSubMenuLink(link)}
</vaadin-tab>`
html`${this._renderTab(subMenu.index, link)}`
)}
</vaadin-tabs>`;
}else{
Expand All @@ -251,7 +249,19 @@ export class QwcHeader extends observeState(LitElement) {
}
}

_renderSubMenuLink(link){
_renderTab(index, link){
if(!link.page.embed && link.page.includeInSubMenu){
return html`
${this._renderSubMenuLink(index, link)}
`;
}else{
return html`<vaadin-tab>
${this._renderSubMenuLink(index, link)}
</vaadin-tab>`;
}
}

_renderSubMenuLink(index, link){

let relativePath = link.page.id.replace(link.page.namespace + "/", "");

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,13 @@ public ExternalPageBuilder mimeType(String mimeType) {
}

public ExternalPageBuilder doNotEmbed() {
return doNotEmbed(false);
}

public ExternalPageBuilder doNotEmbed(boolean includeInSubMenu) {
super.embed = false;
super.includeInSubMenu = includeInSubMenu;
return this;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ public class Page {
private final Map<String, String> metadata; // Key value Metadata

private final boolean embed; // if the component is embedded in the page. true in all cases except maybe external pages
private final boolean includeInSubMenu; // if this link should be added to the submenu. true in all cases except maybe external pages
private final boolean internalComponent; // True if this component is provided by dev-ui (usually provided by the extension)

private String namespace = null; // The namespace can be the extension path or, if internal, qwc
Expand All @@ -36,6 +37,7 @@ protected Page(String icon,
String componentLink,
Map<String, String> metadata,
boolean embed,
boolean includeInSubMenu,
boolean internalComponent,
String namespace,
String namespaceLabel,
Expand All @@ -50,6 +52,7 @@ protected Page(String icon,
this.componentLink = componentLink;
this.metadata = metadata;
this.embed = embed;
this.includeInSubMenu = includeInSubMenu;
this.internalComponent = internalComponent;
this.namespace = namespace;
this.namespaceLabel = namespaceLabel;
Expand Down Expand Up @@ -125,6 +128,10 @@ public boolean isEmbed() {
return embed;
}

public boolean isIncludeInSubMenu() {
return includeInSubMenu;
}

public boolean isInternal() {
return this.internalComponent && this.extensionId == null;
}
Expand All @@ -149,7 +156,8 @@ public String toString() {
+ ", \n\tnamespaceLabel=" + namespaceLabel
+ ", \n\tcomponentName=" + componentName
+ ", \n\tcomponentLink=" + componentLink
+ ", \n\tembed=" + embed + "\n}";
+ ", \n\tembed=" + embed
+ ", \n\tincludeInSubMenu=" + includeInSubMenu + "\n}";
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ public abstract class PageBuilder<T> {
protected String componentLink;
protected Map<String, String> metadata = new HashMap<>();
protected boolean embed = true; // default
protected boolean includeInSubMenu = true; // default
protected boolean internalComponent = false; // default
protected String namespace = null;
protected String namespaceLabel = null;
Expand Down Expand Up @@ -128,6 +129,7 @@ public Page build() {
componentLink,
metadata,
embed,
includeInSubMenu,
internalComponent,
namespace,
namespaceLabel,
Expand Down