diff --git a/services/src/main/java/org/keycloak/services/resources/account/AccountConsole.java b/services/src/main/java/org/keycloak/services/resources/account/AccountConsole.java index de29fd7245..85cf2a7947 100644 --- a/services/src/main/java/org/keycloak/services/resources/account/AccountConsole.java +++ b/services/src/main/java/org/keycloak/services/resources/account/AccountConsole.java @@ -40,6 +40,7 @@ import org.keycloak.services.managers.Auth; import org.keycloak.services.managers.AuthenticationManager; import org.keycloak.services.util.ResolveRelative; import org.keycloak.services.validation.Validation; +import org.keycloak.theme.beans.MessageFormatterMethod; /** * Created by st on 29/03/17. @@ -101,7 +102,8 @@ public class AccountConsole { if (auth != null) { Locale locale = session.getContext().resolveLocale(auth.getUser()); map.put("locale", locale.toLanguageTag()); - map.put("msg", messagesToJsonString(theme.getMessages(locale))); + Properties messages = theme.getMessages(locale); + map.put("msg", messagesToJsonString(messages)); } } catch (Exception e) { logger.warn("Failed to load messages", e); diff --git a/themes/src/main/resources/theme/keycloak-preview/account/index.ftl b/themes/src/main/resources/theme/keycloak-preview/account/index.ftl index 872e42e227..3cc9398542 100644 --- a/themes/src/main/resources/theme/keycloak-preview/account/index.ftl +++ b/themes/src/main/resources/theme/keycloak-preview/account/index.ftl @@ -8,7 +8,7 @@ var baseUrl = '${baseUrl}'; var realm = '${realm}'; var resourceUrl = '${resourceUrl}'; - + <#if referrer??> var referrer = '${referrer}'; var referrer_uri = '${referrer_uri}'; @@ -17,6 +17,9 @@ <#if msg??> var locale = '${locale}'; var l18n_msg = JSON.parse('${msg?no_esc}'); + <#else> + var locale = 'en'; + var l18n_msg = {}; @@ -29,16 +32,6 @@ - <#if properties.styles?has_content> - <#list properties.styles?split(' ') as style> - - - - - - - - - - - - - - + + + + + + + + + <#if properties.styles?has_content> + <#list properties.styles?split(' ') as style> + + + + <#if properties.scripts?has_content> <#list properties.scripts?split(' ') as script> - + + + - - + + + + + + + + +
+

Welcome to Keycloak Account Management

+
+
+
+
+
+
+ +
+

+ Personal Info +

+

+ Account +

+
+
+
+
+
+
+
+ +
+

+ Account Security +

+

+ More stuff goes here +

+
+
+
+
+
+
+
+ +
+

+ Applications +

+

+ More stuff goes here +

+
+
+
+
+
+
+
+ +
+

+ My Resources +

+

+ More stuff goes here +

+
+
+
- - - - -
- - +
+
+
+ + diff --git a/themes/src/main/resources/theme/keycloak-preview/account/resources/app/app-routing.module.ts b/themes/src/main/resources/theme/keycloak-preview/account/resources/app/app-routing.module.ts new file mode 100644 index 0000000000..f11cdaf77e --- /dev/null +++ b/themes/src/main/resources/theme/keycloak-preview/account/resources/app/app-routing.module.ts @@ -0,0 +1,44 @@ +/* + * Copyright 2018 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { NgModule } from '@angular/core'; +import { Routes, RouterModule } from '@angular/router'; + +import {KeycloakGuard} from './keycloak-service/keycloak.guard'; + +import { HomePageComponent } from './content/home-page/home-page.component'; + +declare const resourceUrl: string; + +export const routes: Routes = [ + {path: '', canActivateChild:[KeycloakGuard], children: [ + { path: 'account', loadChildren: resourceUrl + '/app/content/account-page/account.module.js#AccountModule' }, + { path: 'password', loadChildren: resourceUrl + '/app/content/password-page/password.module.js#PasswordModule' }, + { path: 'authenticator', loadChildren: resourceUrl + '/app/content/authenticator-page/authenticator.module.js#AuthenticatorModule' }, + { path: 'sessions', loadChildren: resourceUrl + '/app/content/sessions-page/sessions.module.js#SessionsModule' }, + { path: 'applications', loadChildren: resourceUrl + '/app/content/applications-page/applications.module.js#ApplicationsModule' }, + { path: ':**', loadChildren: resourceUrl + '/app/content/page-not-found/page-not-found.module.js#PageNotFoundModule' }, + ] + } + ]; + +@NgModule({ + imports: [RouterModule.forRoot(routes)], + exports: [RouterModule], + declarations: [HomePageComponent] +}) +export class AppRoutingModule {} + diff --git a/themes/src/main/resources/theme/keycloak-preview/account/resources/app/app.component.html b/themes/src/main/resources/theme/keycloak-preview/account/resources/app/app.component.html index 2a65d8b8d8..cd1e829b74 100644 --- a/themes/src/main/resources/theme/keycloak-preview/account/resources/app/app.component.html +++ b/themes/src/main/resources/theme/keycloak-preview/account/resources/app/app.component.html @@ -1,9 +1,11 @@ - - +
+ +
+ +
-
\ No newline at end of file diff --git a/themes/src/main/resources/theme/keycloak-preview/account/resources/app/app.component.ts b/themes/src/main/resources/theme/keycloak-preview/account/resources/app/app.component.ts index 04b2c2efe8..72a19af0e1 100644 --- a/themes/src/main/resources/theme/keycloak-preview/account/resources/app/app.component.ts +++ b/themes/src/main/resources/theme/keycloak-preview/account/resources/app/app.component.ts @@ -15,10 +15,12 @@ * limitations under the License. */ import {Component, HostListener} from '@angular/core'; - +import {Router, NavigationEnd} from '@angular/router'; +import 'rxjs/add/observable/of'; // needed for ngx-translate import {TranslateService} from '@ngx-translate/core'; import {ResponsivenessService, ContentWidthClass, MenuClickListener} from "./responsiveness-service/responsiveness.service"; +import {KeycloakService} from "./keycloak-service/keycloak.service"; declare const locale: string; @@ -30,8 +32,12 @@ declare const locale: string; export class AppComponent implements MenuClickListener { private contentWidthClass: ContentWidthClass = this.respSvc.calcSideContentWidthClass(); + private showSideNav: boolean = false; - constructor(translate: TranslateService, private respSvc: ResponsivenessService) { + constructor(translate: TranslateService, + private respSvc: ResponsivenessService, + private router: Router, + private kcService: KeycloakService) { // this language will be used as a fallback when a translation isn't found in the current language translate.setDefaultLang('en'); @@ -39,6 +45,19 @@ export class AppComponent implements MenuClickListener { translate.use(locale); this.respSvc.addMenuClickListener(this); + + // show side nav if we are past the welcome screen + this.router.events.subscribe(value => { + if (value instanceof NavigationEnd) { + const navEnd = value as NavigationEnd; + console.log(navEnd.url); + if (navEnd.url !== '/') { + this.showSideNav = true; + var welcomeScreen = document.getElementById('welcomeScreen') + if (welcomeScreen) welcomeScreen.remove(); + } + } + }); } public menuClicked() : void { diff --git a/themes/src/main/resources/theme/keycloak-preview/account/resources/app/app.module.ts b/themes/src/main/resources/theme/keycloak-preview/account/resources/app/app.module.ts index 6fdd6db951..3aec819eca 100644 --- a/themes/src/main/resources/theme/keycloak-preview/account/resources/app/app.module.ts +++ b/themes/src/main/resources/theme/keycloak-preview/account/resources/app/app.module.ts @@ -26,6 +26,7 @@ import { TranslateModule } from '@ngx-translate/core'; import { KeycloakService } from './keycloak-service/keycloak.service'; import { KEYCLOAK_HTTP_PROVIDER } from './keycloak-service/keycloak.http'; +import {KeycloakGuard} from './keycloak-service/keycloak.guard'; import {ResponsivenessService} from './responsiveness-service/responsiveness.service' @@ -38,55 +39,15 @@ import { TopNavComponent } from './top-nav/top-nav.component'; import { NotificationComponent } from './top-nav/notification.component'; import { ToastNotifier } from './top-nav/toast.notifier'; import { SideNavComponent } from './side-nav/side-nav.component'; -import { AccountPageComponent } from './content/account-page/account-page.component'; -import { PasswordPageComponent } from './content/password-page/password-page.component'; -import { PageNotFoundComponent } from './content/page-not-found/page-not-found.component'; -import { AuthenticatorPageComponent } from './content/authenticator-page/authenticator-page.component'; - -import { SessionsPageComponent } from './content/sessions-page/sessions-page.component'; -import { LargeSessionCardComponent } from './content/sessions-page/large-session-card.component'; -import { SmallSessionCardComponent } from './content/sessions-page/small-session-card.component'; - -import { ApplicationsPageComponent } from './content/applications-page/applications-page.component'; -import { LargeAppCardComponent } from './content/applications-page/large-app-card.component'; -import { SmallAppCardComponent } from './content/applications-page/small-app-card.component'; -import { RowAppCardComponent } from './content/applications-page/row-app-card.component'; - -import { ToolbarComponent } from './content/widgets/toolbar.component'; - -import {OrderbyPipe} from './pipes/orderby.pipe'; -import {FilterbyPipe} from './pipes/filterby.pipe'; - -const routes: Routes = [ - { path: 'account', component: AccountPageComponent }, - { path: 'password', component: PasswordPageComponent }, - { path: 'authenticator', component: AuthenticatorPageComponent }, - { path: 'sessions', component: SessionsPageComponent }, - { path: 'applications', component: ApplicationsPageComponent }, - { path: '', redirectTo: '/account', pathMatch: 'full' }, - { path: '**', component: PageNotFoundComponent} -]; +/* Routing Module */ +import { AppRoutingModule } from './app-routing.module'; const decs = [ AppComponent, TopNavComponent, NotificationComponent, SideNavComponent, - AccountPageComponent, - PasswordPageComponent, - PageNotFoundComponent, - AuthenticatorPageComponent, - SessionsPageComponent, - LargeSessionCardComponent, - SmallSessionCardComponent, - ApplicationsPageComponent, - LargeAppCardComponent, - SmallAppCardComponent, - RowAppCardComponent, - ToolbarComponent, - OrderbyPipe, - FilterbyPipe ]; export const ORIGINAL_INCOMING_URL: Location = window.location; @@ -100,10 +61,11 @@ export const ORIGINAL_INCOMING_URL: Location = window.location; TranslateModule.forRoot({ loader: {provide: TranslateLoader, useClass: DeclaredVarTranslateLoader} }), - RouterModule.forRoot(routes) + AppRoutingModule, ], providers: [ KeycloakService, + KeycloakGuard, KEYCLOAK_HTTP_PROVIDER, ResponsivenessService, AccountServiceClient, diff --git a/themes/src/main/resources/theme/keycloak-preview/account/resources/app/assets/img/keycloak-logo-min.png b/themes/src/main/resources/theme/keycloak-preview/account/resources/app/assets/img/keycloak-logo-min.png new file mode 100644 index 0000000000..0bbf1d51e4 Binary files /dev/null and b/themes/src/main/resources/theme/keycloak-preview/account/resources/app/assets/img/keycloak-logo-min.png differ diff --git a/themes/src/main/resources/theme/keycloak-preview/account/resources/app/content/account-page/account-routing.module.ts b/themes/src/main/resources/theme/keycloak-preview/account/resources/app/content/account-page/account-routing.module.ts new file mode 100644 index 0000000000..635f4b8522 --- /dev/null +++ b/themes/src/main/resources/theme/keycloak-preview/account/resources/app/content/account-page/account-routing.module.ts @@ -0,0 +1,31 @@ +/* + * Copyright 2018 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { NgModule } from '@angular/core'; +import { Routes, RouterModule } from '@angular/router'; + +import { AccountPageComponent } from './account-page.component'; + +const routes: Routes = [ + { path: '**', component: AccountPageComponent }, +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + exports: [RouterModule] +}) +export class AccountRoutingModule {} + diff --git a/themes/src/main/resources/theme/keycloak-preview/account/resources/app/content/account-page/account.module.ts b/themes/src/main/resources/theme/keycloak-preview/account/resources/app/content/account-page/account.module.ts new file mode 100644 index 0000000000..1e65b14a16 --- /dev/null +++ b/themes/src/main/resources/theme/keycloak-preview/account/resources/app/content/account-page/account.module.ts @@ -0,0 +1,34 @@ +/* + * Copyright 2018 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { FormsModule } from '@angular/forms'; + +import { TranslateModule } from '@ngx-translate/core'; + +import { AccountPageComponent } from './account-page.component'; +import { AccountRoutingModule } from './account-routing.module'; + +@NgModule({ + imports: [ CommonModule, FormsModule, TranslateModule, AccountRoutingModule ], + declarations: [ AccountPageComponent ], + providers: [ ] +}) +export class AccountModule {} + + + diff --git a/themes/src/main/resources/theme/keycloak-preview/account/resources/app/content/applications-page/applications-routing.module.ts b/themes/src/main/resources/theme/keycloak-preview/account/resources/app/content/applications-page/applications-routing.module.ts new file mode 100644 index 0000000000..f35db61c33 --- /dev/null +++ b/themes/src/main/resources/theme/keycloak-preview/account/resources/app/content/applications-page/applications-routing.module.ts @@ -0,0 +1,31 @@ +/* + * Copyright 2018 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { NgModule } from '@angular/core'; +import { Routes, RouterModule } from '@angular/router'; + +import { ApplicationsPageComponent } from './applications-page.component'; + +const routes: Routes = [ + { path: '**', component: ApplicationsPageComponent }, +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + exports: [RouterModule] +}) +export class ApplicationsRoutingModule {} + diff --git a/themes/src/main/resources/theme/keycloak-preview/account/resources/app/content/applications-page/applications.module.ts b/themes/src/main/resources/theme/keycloak-preview/account/resources/app/content/applications-page/applications.module.ts new file mode 100644 index 0000000000..5cc64fc440 --- /dev/null +++ b/themes/src/main/resources/theme/keycloak-preview/account/resources/app/content/applications-page/applications.module.ts @@ -0,0 +1,44 @@ +/* + * Copyright 2018 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; + +import { TranslateModule } from '@ngx-translate/core'; + +import {WidgetsModule} from '../widgets/widgets.module'; + +import { ApplicationsPageComponent } from './applications-page.component'; +import { ApplicationsRoutingModule } from './applications-routing.module'; +import { LargeAppCardComponent } from './large-app-card.component'; +import { SmallAppCardComponent } from './small-app-card.component'; +import { RowAppCardComponent } from './row-app-card.component'; + +@NgModule({ + imports: [ CommonModule, + TranslateModule, + ApplicationsRoutingModule, + WidgetsModule ], + declarations: [ ApplicationsPageComponent, + LargeAppCardComponent, + SmallAppCardComponent, + RowAppCardComponent ], + providers: [ ] +}) +export class ApplicationsModule {} + + + diff --git a/themes/src/main/resources/theme/keycloak-preview/account/resources/app/content/authenticator-page/authenticator-routing.module.ts b/themes/src/main/resources/theme/keycloak-preview/account/resources/app/content/authenticator-page/authenticator-routing.module.ts new file mode 100644 index 0000000000..47c4d73d09 --- /dev/null +++ b/themes/src/main/resources/theme/keycloak-preview/account/resources/app/content/authenticator-page/authenticator-routing.module.ts @@ -0,0 +1,31 @@ +/* + * Copyright 2018 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { NgModule } from '@angular/core'; +import { Routes, RouterModule } from '@angular/router'; + +import { AuthenticatorPageComponent } from './authenticator-page.component'; + +const routes: Routes = [ + { path: '**', component: AuthenticatorPageComponent }, +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + exports: [RouterModule] +}) +export class AuthenticatorRoutingModule {} + diff --git a/themes/src/main/resources/theme/keycloak-preview/account/resources/app/content/authenticator-page/authenticator.module.ts b/themes/src/main/resources/theme/keycloak-preview/account/resources/app/content/authenticator-page/authenticator.module.ts new file mode 100644 index 0000000000..780bd7c0ac --- /dev/null +++ b/themes/src/main/resources/theme/keycloak-preview/account/resources/app/content/authenticator-page/authenticator.module.ts @@ -0,0 +1,33 @@ +/* + * Copyright 2018 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; + +import { TranslateModule } from '@ngx-translate/core'; + +import { AuthenticatorPageComponent } from './authenticator-page.component'; +import { AuthenticatorRoutingModule } from './authenticator-routing.module'; + +@NgModule({ + imports: [ CommonModule, TranslateModule, AuthenticatorRoutingModule ], + declarations: [ AuthenticatorPageComponent ], + providers: [ ] +}) +export class AuthenticatorModule {} + + + diff --git a/themes/src/main/resources/theme/keycloak-preview/account/resources/app/content/home-page/home-page.component.css b/themes/src/main/resources/theme/keycloak-preview/account/resources/app/content/home-page/home-page.component.css new file mode 100644 index 0000000000..e69de29bb2 diff --git a/themes/src/main/resources/theme/keycloak-preview/account/resources/app/content/home-page/home-page.component.html b/themes/src/main/resources/theme/keycloak-preview/account/resources/app/content/home-page/home-page.component.html new file mode 100644 index 0000000000..27a4940964 --- /dev/null +++ b/themes/src/main/resources/theme/keycloak-preview/account/resources/app/content/home-page/home-page.component.html @@ -0,0 +1,68 @@ +
+

Welcome to Keycloak Account Management

+
+
+
+
+
+
+ +
+

+ Personal Info +

+

+ More stuff goes here +

+
+
+
+
+
+
+
+ +
+

+ Account Security +

+

+ More stuff goes here +

+
+
+
+
+
+
+
+ +
+

+ Applications +

+

+ More stuff goes here +

+
+
+
+
+
+
+
+ +
+

+ My Resources +

+

+ More stuff goes here +

+
+
+
+ +
+
+
\ No newline at end of file diff --git a/themes/src/main/resources/theme/keycloak-preview/account/resources/app/content/home-page/home-page.component.ts b/themes/src/main/resources/theme/keycloak-preview/account/resources/app/content/home-page/home-page.component.ts new file mode 100644 index 0000000000..1a0c781dc5 --- /dev/null +++ b/themes/src/main/resources/theme/keycloak-preview/account/resources/app/content/home-page/home-page.component.ts @@ -0,0 +1,28 @@ +/* + * Copyright 2017 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import {Component} from '@angular/core'; + +@Component({ + selector: 'home-page', + templateUrl: './home-page.component.html', + styleUrls: ['./home-page.component.css'] +}) +export class HomePageComponent { + + constructor() {} + +} \ No newline at end of file diff --git a/themes/src/main/resources/theme/keycloak-preview/account/resources/app/content/page-not-found/page-not-found-routing.module.ts b/themes/src/main/resources/theme/keycloak-preview/account/resources/app/content/page-not-found/page-not-found-routing.module.ts new file mode 100644 index 0000000000..a23ca003bb --- /dev/null +++ b/themes/src/main/resources/theme/keycloak-preview/account/resources/app/content/page-not-found/page-not-found-routing.module.ts @@ -0,0 +1,31 @@ +/* + * Copyright 2018 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { NgModule } from '@angular/core'; +import { Routes, RouterModule } from '@angular/router'; + +import { PageNotFoundComponent } from './page-not-found.component'; + +const routes: Routes = [ + { path: '**', component: PageNotFoundComponent} +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + exports: [RouterModule] +}) +export class PageNotFoundRoutingModule {} + diff --git a/themes/src/main/resources/theme/keycloak-preview/account/resources/app/content/page-not-found/page-not-found.module.ts b/themes/src/main/resources/theme/keycloak-preview/account/resources/app/content/page-not-found/page-not-found.module.ts new file mode 100644 index 0000000000..9274bb1e9e --- /dev/null +++ b/themes/src/main/resources/theme/keycloak-preview/account/resources/app/content/page-not-found/page-not-found.module.ts @@ -0,0 +1,34 @@ +/* + * Copyright 2018 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { FormsModule } from '@angular/forms'; + +import { TranslateModule } from '@ngx-translate/core'; + +import { PageNotFoundComponent } from './page-not-found.component'; +import { PageNotFoundRoutingModule } from './page-not-found-routing.module'; + +@NgModule({ + imports: [ CommonModule, FormsModule, TranslateModule, PageNotFoundRoutingModule ], + declarations: [ PageNotFoundComponent ], + providers: [ ] +}) +export class PageNotFoundModule {} + + + diff --git a/themes/src/main/resources/theme/keycloak-preview/account/resources/app/content/password-page/password-routing.module.ts b/themes/src/main/resources/theme/keycloak-preview/account/resources/app/content/password-page/password-routing.module.ts new file mode 100644 index 0000000000..3208b405f0 --- /dev/null +++ b/themes/src/main/resources/theme/keycloak-preview/account/resources/app/content/password-page/password-routing.module.ts @@ -0,0 +1,31 @@ +/* + * Copyright 2018 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { NgModule } from '@angular/core'; +import { Routes, RouterModule } from '@angular/router'; + +import { PasswordPageComponent } from './password-page.component'; + +const routes: Routes = [ + { path: '**', component: PasswordPageComponent }, +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + exports: [RouterModule] +}) +export class PasswordRoutingModule {} + diff --git a/themes/src/main/resources/theme/keycloak-preview/account/resources/app/content/password-page/password.module.ts b/themes/src/main/resources/theme/keycloak-preview/account/resources/app/content/password-page/password.module.ts new file mode 100644 index 0000000000..9bd5d9dac0 --- /dev/null +++ b/themes/src/main/resources/theme/keycloak-preview/account/resources/app/content/password-page/password.module.ts @@ -0,0 +1,34 @@ +/* + * Copyright 2018 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { FormsModule } from '@angular/forms'; + +import { TranslateModule } from '@ngx-translate/core'; + +import { PasswordPageComponent } from './password-page.component'; +import { PasswordRoutingModule } from './password-routing.module'; + +@NgModule({ + imports: [ CommonModule, FormsModule, TranslateModule, PasswordRoutingModule ], + declarations: [ PasswordPageComponent ], + providers: [ ] +}) +export class PasswordModule {} + + + diff --git a/themes/src/main/resources/theme/keycloak-preview/account/resources/app/content/sessions-page/sessions-page.component.ts b/themes/src/main/resources/theme/keycloak-preview/account/resources/app/content/sessions-page/sessions-page.component.ts index 95a519a781..2ffb98945c 100644 --- a/themes/src/main/resources/theme/keycloak-preview/account/resources/app/content/sessions-page/sessions-page.component.ts +++ b/themes/src/main/resources/theme/keycloak-preview/account/resources/app/content/sessions-page/sessions-page.component.ts @@ -18,7 +18,7 @@ import {Component, OnInit} from '@angular/core'; import {Response} from '@angular/http'; import {AccountServiceClient} from '../../account-service/account.service'; - import {TranslateUtil} from '../../ngx-translate/translate.util'; +import {TranslateUtil} from '../../ngx-translate/translate.util'; import {View} from '../widgets/toolbar.component'; import {PropertyLabel} from '../widgets/property.label'; diff --git a/themes/src/main/resources/theme/keycloak-preview/account/resources/app/content/sessions-page/sessions-routing.module.ts b/themes/src/main/resources/theme/keycloak-preview/account/resources/app/content/sessions-page/sessions-routing.module.ts new file mode 100644 index 0000000000..da6e3ab6cd --- /dev/null +++ b/themes/src/main/resources/theme/keycloak-preview/account/resources/app/content/sessions-page/sessions-routing.module.ts @@ -0,0 +1,31 @@ +/* + * Copyright 2018 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { NgModule } from '@angular/core'; +import { Routes, RouterModule } from '@angular/router'; + +import { SessionsPageComponent } from './sessions-page.component'; + +const routes: Routes = [ + { path: '**', component: SessionsPageComponent }, +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + exports: [RouterModule] +}) +export class SessionsRoutingModule {} + diff --git a/themes/src/main/resources/theme/keycloak-preview/account/resources/app/content/sessions-page/sessions.module.ts b/themes/src/main/resources/theme/keycloak-preview/account/resources/app/content/sessions-page/sessions.module.ts new file mode 100644 index 0000000000..cec77c81a3 --- /dev/null +++ b/themes/src/main/resources/theme/keycloak-preview/account/resources/app/content/sessions-page/sessions.module.ts @@ -0,0 +1,43 @@ +/* + * Copyright 2018 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; + +import { TranslateModule } from '@ngx-translate/core'; + +import {WidgetsModule} from '../widgets/widgets.module'; + +import { SessionsRoutingModule } from './sessions-routing.module'; + +import { SessionsPageComponent } from './sessions-page.component'; +import { LargeSessionCardComponent } from './large-session-card.component'; +import { SmallSessionCardComponent } from './small-session-card.component'; + +@NgModule({ + imports: [ CommonModule, + TranslateModule, + SessionsRoutingModule, + WidgetsModule ], + declarations: [ SessionsPageComponent, + LargeSessionCardComponent, + SmallSessionCardComponent ], + providers: [ ] +}) +export class SessionsModule {} + + + diff --git a/themes/src/main/resources/theme/keycloak-preview/account/resources/app/pipes/filterby.pipe.ts b/themes/src/main/resources/theme/keycloak-preview/account/resources/app/content/widgets/filterby.pipe.ts similarity index 100% rename from themes/src/main/resources/theme/keycloak-preview/account/resources/app/pipes/filterby.pipe.ts rename to themes/src/main/resources/theme/keycloak-preview/account/resources/app/content/widgets/filterby.pipe.ts diff --git a/themes/src/main/resources/theme/keycloak-preview/account/resources/app/pipes/orderby.pipe.ts b/themes/src/main/resources/theme/keycloak-preview/account/resources/app/content/widgets/orderby.pipe.ts similarity index 100% rename from themes/src/main/resources/theme/keycloak-preview/account/resources/app/pipes/orderby.pipe.ts rename to themes/src/main/resources/theme/keycloak-preview/account/resources/app/content/widgets/orderby.pipe.ts diff --git a/themes/src/main/resources/theme/keycloak-preview/account/resources/app/content/widgets/widgets.module.ts b/themes/src/main/resources/theme/keycloak-preview/account/resources/app/content/widgets/widgets.module.ts new file mode 100644 index 0000000000..422fd306c5 --- /dev/null +++ b/themes/src/main/resources/theme/keycloak-preview/account/resources/app/content/widgets/widgets.module.ts @@ -0,0 +1,36 @@ +/* + * Copyright 2018 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { FormsModule } from '@angular/forms'; + +import { ToolbarComponent } from './toolbar.component'; +import {OrderbyPipe} from './orderby.pipe'; +import {FilterbyPipe} from './filterby.pipe'; + +@NgModule({ + imports: [ CommonModule, FormsModule ], + declarations: [ ToolbarComponent, OrderbyPipe, FilterbyPipe ], + exports: [ ToolbarComponent, + OrderbyPipe, + FilterbyPipe ], + providers: [ ] +}) +export class WidgetsModule {} + + + diff --git a/themes/src/main/resources/theme/keycloak-preview/account/resources/app/keycloak-service/keycloak.guard.ts b/themes/src/main/resources/theme/keycloak-preview/account/resources/app/keycloak-service/keycloak.guard.ts new file mode 100644 index 0000000000..39239a18d7 --- /dev/null +++ b/themes/src/main/resources/theme/keycloak-preview/account/resources/app/keycloak-service/keycloak.guard.ts @@ -0,0 +1,36 @@ +/* + * Copyright 2018 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {Injectable} from '@angular/core'; +import {CanActivateChild, ActivatedRouteSnapshot, RouterStateSnapshot} from '@angular/router'; +import {KeycloakService} from './keycloak.service'; + +@Injectable() +export class KeycloakGuard implements CanActivateChild { + constructor(private keycloakService: KeycloakService) {} + + canActivateChild(route: ActivatedRouteSnapshot, + state: RouterStateSnapshot): boolean { + + if (this.keycloakService.authenticated()) { + return true; + } + + this.keycloakService.login(); + return false; + } +} diff --git a/themes/src/main/resources/theme/keycloak-preview/account/resources/app/keycloak-service/keycloak.js b/themes/src/main/resources/theme/keycloak-preview/account/resources/app/keycloak-service/keycloak.js index a784936c78..80b44770d4 100644 --- a/themes/src/main/resources/theme/keycloak-preview/account/resources/app/keycloak-service/keycloak.js +++ b/themes/src/main/resources/theme/keycloak-preview/account/resources/app/keycloak-service/keycloak.js @@ -40,6 +40,8 @@ } } + var useNonce = true; + kc.init = function (initOptions) { kc.authenticated = false; @@ -50,7 +52,7 @@ } else if (initOptions && initOptions.adapter === 'default') { adapter = loadAdapter(); } else { - if (window.Cordova) { + if (window.Cordova || window.cordova) { adapter = loadAdapter('cordova'); } else { adapter = loadAdapter(); @@ -58,6 +60,10 @@ } if (initOptions) { + if (typeof initOptions.useNonce !== 'undefined') { + useNonce = initOptions.useNonce; + } + if (typeof initOptions.checkLoginIframe !== 'undefined') { loginIframe.enable = initOptions.checkLoginIframe; } @@ -159,10 +165,15 @@ var callback = parseCallback(window.location.href); if (callback) { - setupCheckLoginIframe(); window.history.replaceState({}, null, callback.newUrl); - processCallback(callback, initPromise); - return; + } + + if (callback && callback.valid) { + return setupCheckLoginIframe().success(function() { + processCallback(callback, initPromise); + }).error(function (e) { + initPromise.setError(); + }); } else if (initOptions) { if (initOptions.token && initOptions.refreshToken) { setToken(initOptions.token, initOptions.refreshToken, initOptions.idToken); @@ -221,7 +232,7 @@ var callbackState = { state: state, nonce: nonce, - redirectUri: encodeURIComponent(redirectUri), + redirectUri: encodeURIComponent(redirectUri) } if (options && options.prompt) { @@ -230,22 +241,25 @@ callbackStorage.add(callbackState); - var action = 'auth'; + var baseUrl; if (options && options.action == 'register') { - action = 'registrations'; + baseUrl = kc.endpoints.register(); + } else { + baseUrl = kc.endpoints.authorize(); } var scope = (options && options.scope) ? "openid " + options.scope : "openid"; - var url = getRealmUrl() - + '/protocol/openid-connect/' + action + var url = baseUrl + '?client_id=' + encodeURIComponent(kc.clientId) + '&redirect_uri=' + encodeURIComponent(redirectUri) + '&state=' + encodeURIComponent(state) - + '&nonce=' + encodeURIComponent(nonce) + '&response_mode=' + encodeURIComponent(kc.responseMode) + '&response_type=' + encodeURIComponent(kc.responseType) + '&scope=' + encodeURIComponent(scope); + if (useNonce) { + url = url + '&nonce=' + encodeURIComponent(nonce); + } if (options && options.prompt) { url += '&prompt=' + encodeURIComponent(options.prompt); @@ -275,8 +289,7 @@ } kc.createLogoutUrl = function(options) { - var url = getRealmUrl() - + '/protocol/openid-connect/logout' + var url = kc.endpoints.logout() + '?redirect_uri=' + encodeURIComponent(adapter.redirectUri(options, false)); return url; @@ -295,11 +308,14 @@ } kc.createAccountUrl = function(options) { - var url = getRealmUrl() + var realm = getRealmUrl(); + var url = undefined; + if (typeof realm !== 'undefined') { + url = realm + '/account' + '?referrer=' + encodeURIComponent(kc.clientId) + '&referrer_uri=' + encodeURIComponent(adapter.redirectUri(options)); - + } return url; } @@ -347,7 +363,7 @@ } kc.loadUserInfo = function() { - var url = getRealmUrl() + '/protocol/openid-connect/userinfo'; + var url = kc.endpoints.userinfo(); var req = new XMLHttpRequest(); req.open('GET', url, true); req.setRequestHeader('Accept', 'application/json'); @@ -412,7 +428,7 @@ promise.setSuccess(false); } else { var params = 'grant_type=refresh_token&' + 'refresh_token=' + kc.refreshToken; - var url = getRealmUrl() + '/protocol/openid-connect/token'; + var url = kc.endpoints.token(); refreshQueue.push(promise); @@ -486,10 +502,14 @@ } function getRealmUrl() { - if (kc.authServerUrl.charAt(kc.authServerUrl.length - 1) == '/') { - return kc.authServerUrl + 'realms/' + encodeURIComponent(kc.realm); + if (typeof kc.authServerUrl !== 'undefined') { + if (kc.authServerUrl.charAt(kc.authServerUrl.length - 1) == '/') { + return kc.authServerUrl + 'realms/' + encodeURIComponent(kc.realm); + } else { + return kc.authServerUrl + '/realms/' + encodeURIComponent(kc.realm); + } } else { - return kc.authServerUrl + '/realms/' + encodeURIComponent(kc.realm); + return undefined; } } @@ -523,7 +543,7 @@ if ((kc.flow != 'implicit') && code) { var params = 'code=' + code + '&grant_type=authorization_code'; - var url = getRealmUrl() + '/protocol/openid-connect/token'; + var url = kc.endpoints.token(); var req = new XMLHttpRequest(); req.open('POST', url, true); @@ -560,9 +580,9 @@ setToken(accessToken, refreshToken, idToken, timeLocal); - if ((kc.tokenParsed && kc.tokenParsed.nonce != oauth.storedNonce) || + if (useNonce && ((kc.tokenParsed && kc.tokenParsed.nonce != oauth.storedNonce) || (kc.refreshTokenParsed && kc.refreshTokenParsed.nonce != oauth.storedNonce) || - (kc.idTokenParsed && kc.idTokenParsed.nonce != oauth.storedNonce)) { + (kc.idTokenParsed && kc.idTokenParsed.nonce != oauth.storedNonce))) { console.info('[KEYCLOAK] Invalid nonce, clearing token'); kc.clearToken(); @@ -587,6 +607,65 @@ configUrl = config; } + function setupOidcEndoints(oidcConfiguration) { + if (! oidcConfiguration) { + kc.endpoints = { + authorize: function() { + return getRealmUrl() + '/protocol/openid-connect/auth'; + }, + token: function() { + return getRealmUrl() + '/protocol/openid-connect/token'; + }, + logout: function() { + return getRealmUrl() + '/protocol/openid-connect/logout'; + }, + checkSessionIframe: function() { + var src = getRealmUrl() + '/protocol/openid-connect/login-status-iframe.html'; + if (kc.iframeVersion) { + src = src + '?version=' + kc.iframeVersion; + } + return src; + }, + register: function() { + return getRealmUrl() + '/protocol/openid-connect/registrations'; + }, + userinfo: function() { + return getRealmUrl() + '/protocol/openid-connect/userinfo'; + } + }; + } else { + kc.endpoints = { + authorize: function() { + return oidcConfiguration.authorization_endpoint; + }, + token: function() { + return oidcConfiguration.token_endpoint; + }, + logout: function() { + if (!oidcConfiguration.end_session_endpoint) { + throw "Not supported by the OIDC server"; + } + return oidcConfiguration.end_session_endpoint; + }, + checkSessionIframe: function() { + if (!oidcConfiguration.check_session_iframe) { + throw "Not supported by the OIDC server"; + } + return oidcConfiguration.check_session_iframe; + }, + register: function() { + throw 'Redirection to "Register user" page not supported in standard OIDC mode'; + }, + userinfo: function() { + if (!oidcConfiguration.userinfo_endpoint) { + throw "Not supported by the OIDC server"; + } + return oidcConfiguration.userinfo_endpoint; + } + } + } + } + if (configUrl) { var req = new XMLHttpRequest(); req.open('GET', configUrl, true); @@ -601,7 +680,7 @@ kc.realm = config['realm']; kc.clientId = config['resource']; kc.clientSecret = (config['credentials'] || {})['secret']; - + setupOidcEndoints(null); promise.setSuccess(); } else { promise.setError(); @@ -611,30 +690,62 @@ req.send(); } else { - if (!config['url']) { - var scripts = document.getElementsByTagName('script'); - for (var i = 0; i < scripts.length; i++) { - if (scripts[i].src.match(/.*keycloak\.js/)) { - config.url = scripts[i].src.substr(0, scripts[i].src.indexOf('/js/keycloak.js')); - break; - } - } - } - - if (!config.realm) { - throw 'realm missing'; - } - if (!config.clientId) { throw 'clientId missing'; } - kc.authServerUrl = config.url; - kc.realm = config.realm; kc.clientId = config.clientId; kc.clientSecret = (config.credentials || {}).secret; - promise.setSuccess(); + var oidcProvider = config['oidcProvider']; + if (!oidcProvider) { + if (!config['url']) { + var scripts = document.getElementsByTagName('script'); + for (var i = 0; i < scripts.length; i++) { + if (scripts[i].src.match(/.*keycloak\.js/)) { + config.url = scripts[i].src.substr(0, scripts[i].src.indexOf('/js/keycloak.js')); + break; + } + } + } + if (!config.realm) { + throw 'realm missing'; + } + + kc.authServerUrl = config.url; + kc.realm = config.realm; + setupOidcEndoints(null); + promise.setSuccess(); + } else { + if (typeof oidcProvider === 'string') { + var oidcProviderConfigUrl; + if (oidcProvider.charAt(oidcProvider.length - 1) == '/') { + oidcProviderConfigUrl = oidcProvider + '.well-known/openid-configuration'; + } else { + oidcProviderConfigUrl = oidcProvider + '/.well-known/openid-configuration'; + } + var req = new XMLHttpRequest(); + req.open('GET', oidcProviderConfigUrl, true); + req.setRequestHeader('Accept', 'application/json'); + + req.onreadystatechange = function () { + if (req.readyState == 4) { + if (req.status == 200 || fileLoaded(req)) { + var oidcProviderConfig = JSON.parse(req.responseText); + setupOidcEndoints(oidcProviderConfig); + promise.setSuccess(); + } else { + promise.setError(); + } + } + }; + + req.send(); + } else { + setupOidcEndoints(oidcProvider); + promise.setSuccess(); + } + } } return promise.promise; @@ -753,23 +864,137 @@ } function parseCallback(url) { - var oauth = new CallbackParser(url, kc.responseMode).parseUri(); + var oauth = parseCallbackUrl(url); + if (!oauth) { + return; + } + var oauthState = callbackStorage.get(oauth.state); - if (oauthState && (oauth.code || oauth.error || oauth.access_token || oauth.id_token)) { + if (oauthState) { + oauth.valid = true; oauth.redirectUri = oauthState.redirectUri; oauth.storedNonce = oauthState.nonce; oauth.prompt = oauthState.prompt; + } - if (oauth.fragment) { - oauth.newUrl += '#' + oauth.fragment; + return oauth; + } + + function parseCallbackUrl(url) { + var supportedParams; + switch (kc.flow) { + case 'standard': + supportedParams = ['code', 'state', 'session_state']; + break; + case 'implicit': + supportedParams = ['access_token', 'id_token', 'state', 'session_state']; + break; + case 'hybrid': + supportedParams = ['access_token', 'id_token', 'code', 'state', 'session_state']; + break; + } + + supportedParams.push('error'); + supportedParams.push('error_description'); + supportedParams.push('error_uri'); + + var queryIndex = url.indexOf('?'); + var fragmentIndex = url.indexOf('#'); + + var newUrl; + var parsed; + + if (kc.responseMode === 'query' && queryIndex !== -1) { + newUrl = url.substring(0, queryIndex); + parsed = parseCallbackParams(url.substring(queryIndex + 1, fragmentIndex !== -1 ? fragmentIndex : url.length), supportedParams); + if (parsed.paramsString !== '') { + newUrl += '?' + parsed.paramsString; } + if (fragmentIndex !== -1) { + newUrl += url.substring(fragmentIndex); + } + } else if (kc.responseMode === 'fragment' && fragmentIndex !== -1) { + newUrl = url.substring(0, fragmentIndex); + parsed = parseCallbackParams(url.substring(fragmentIndex + 1), supportedParams); + if (parsed.paramsString !== '') { + newUrl += '#' + parsed.paramsString; + } + } - return oauth; + if (parsed && parsed.oauthParams) { + if (kc.flow === 'standard' || kc.flow === 'hybrid') { + if ((parsed.oauthParams.code || parsed.oauthParams.error) && parsed.oauthParams.state) { + parsed.oauthParams.newUrl = newUrl; + return parsed.oauthParams; + } + } else if (kc.flow === 'implicit') { + if ((parsed.oauthParams.access_token || parsed.oauthParams.error) && parsed.oauthParams.state) { + parsed.oauthParams.newUrl = newUrl; + return parsed.oauthParams; + } + } } } + function parseCallbackParams(paramsString, supportedParams) { + var p = paramsString.split('&'); + var result = { + paramsString: '', + oauthParams: {} + } + for (var i = 0; i < p.length; i++) { + var t = p[i].split('='); + if (supportedParams.indexOf(t[0]) !== -1) { + result.oauthParams[t[0]] = t[1]; + } else { + if (result.paramsString !== '') { + result.paramsString += '&'; + } + result.paramsString += p[i]; + } + } + return result; + } + function createPromise() { + if (typeof Promise === "function") { + return createNativePromise(); + } else { + return createLegacyPromise(); + } + } + + function createNativePromise() { + // Need to create a native Promise which also preserves the + // interface of the custom promise type previously used by the API + var p = { + setSuccess: function(result) { + p.success = true; + p.resolve(result); + }, + + setError: function(result) { + p.success = false; + p.reject(result); + } + }; + p.promise = new Promise(function(resolve, reject) { + p.resolve = resolve; + p.reject = reject; + }); + p.promise.success = function(callback) { + p.promise.then(callback); + return p.promise; + } + p.promise.error = function(callback) { + p.promise.catch(callback); + return p.promise; + } + return p; + } + + function createLegacyPromise() { var p = { setSuccess: function(result) { p.success = true; @@ -826,23 +1051,20 @@ loginIframe.iframe = iframe; iframe.onload = function() { - var realmUrl = getRealmUrl(); - if (realmUrl.charAt(0) === '/') { + var authUrl = kc.endpoints.authorize(); + if (authUrl.charAt(0) === '/') { loginIframe.iframeOrigin = getOrigin(); } else { - loginIframe.iframeOrigin = realmUrl.substring(0, realmUrl.indexOf('/', 8)); + loginIframe.iframeOrigin = authUrl.substring(0, authUrl.indexOf('/', 8)); } promise.setSuccess(); setTimeout(check, loginIframe.interval * 1000); } - var src = getRealmUrl() + '/protocol/openid-connect/login-status-iframe.html'; - if (kc.iframeVersion) { - src = src + '?version=' + kc.iframeVersion; - } - + var src = kc.endpoints.checkSessionIframe(); iframe.setAttribute('src', src ); + iframe.setAttribute('title', 'keycloak-session-iframe' ); iframe.style.display = 'none'; document.body.appendChild(iframe); @@ -920,7 +1142,12 @@ }, accountManagement : function() { - window.location.href = kc.createAccountUrl(); + var accountUrl = kc.createAccountUrl(); + if (typeof accountUrl !== 'undefined') { + window.location.href = accountUrl; + } else { + throw "Not supported by the OIDC server"; + } return createPromise().promise; }, @@ -934,12 +1161,7 @@ } else if (kc.redirectUri) { return kc.redirectUri; } else { - var redirectUri = location.href; - if (location.hash && encodeHash) { - redirectUri = redirectUri.substring(0, location.href.indexOf('#')); - redirectUri += (redirectUri.indexOf('?') == -1 ? '?' : '&') + 'redirect_fragment=' + encodeURIComponent(location.hash.substring(1)); - } - return redirectUri; + return location.href; } } }; @@ -947,7 +1169,14 @@ if (type == 'cordova') { loginIframe.enable = false; - + var cordovaOpenWindowWrapper = function(loginUrl, target, options) { + if (window.cordova && window.cordova.InAppBrowser) { + // Use inappbrowser for IOS and Android if available + return window.cordova.InAppBrowser.open(loginUrl, target, options); + } else { + return window.open(loginUrl, target, options); + } + }; return { login: function(options) { var promise = createPromise(); @@ -958,8 +1187,7 @@ } var loginUrl = kc.createLoginUrl(options); - var ref = window.open(loginUrl, '_blank', o); - + var ref = cordovaOpenWindowWrapper(loginUrl, '_blank', o); var completed = false; ref.addEventListener('loadstart', function(event) { @@ -992,7 +1220,7 @@ var promise = createPromise(); var logoutUrl = kc.createLogoutUrl(options); - var ref = window.open(logoutUrl, '_blank', 'location=no,hidden=yes'); + var ref = cordovaOpenWindowWrapper(logoutUrl, '_blank', 'location=no,hidden=yes'); var error; @@ -1025,7 +1253,7 @@ register : function() { var registerUrl = kc.createRegisterUrl(); - var ref = window.open(registerUrl, '_blank', 'location=no'); + var ref = cordovaOpenWindowWrapper(registerUrl, '_blank', 'location=no'); ref.addEventListener('loadstart', function(event) { if (event.url.indexOf('http://localhost') == 0) { ref.close(); @@ -1035,12 +1263,16 @@ accountManagement : function() { var accountUrl = kc.createAccountUrl(); - var ref = window.open(accountUrl, '_blank', 'location=no'); - ref.addEventListener('loadstart', function(event) { - if (event.url.indexOf('http://localhost') == 0) { - ref.close(); - } - }); + if (typeof accountUrl !== 'undefined') { + var ref = cordovaOpenWindowWrapper(accountUrl, '_blank', 'location=no'); + ref.addEventListener('loadstart', function(event) { + if (event.url.indexOf('http://localhost') == 0) { + ref.close(); + } + }); + } else { + throw "Not supported by the OIDC server"; + } }, redirectUri: function(options) { @@ -1170,99 +1402,6 @@ return new CookieStorage(); } - - var CallbackParser = function(uriToParse, responseMode) { - if (!(this instanceof CallbackParser)) { - return new CallbackParser(uriToParse, responseMode); - } - var parser = this; - - var initialParse = function() { - var baseUri = null; - var queryString = null; - var fragmentString = null; - - var questionMarkIndex = uriToParse.indexOf("?"); - var fragmentIndex = uriToParse.indexOf("#", questionMarkIndex + 1); - if (questionMarkIndex == -1 && fragmentIndex == -1) { - baseUri = uriToParse; - } else if (questionMarkIndex != -1) { - baseUri = uriToParse.substring(0, questionMarkIndex); - queryString = uriToParse.substring(questionMarkIndex + 1); - if (fragmentIndex != -1) { - fragmentIndex = queryString.indexOf("#"); - fragmentString = queryString.substring(fragmentIndex + 1); - queryString = queryString.substring(0, fragmentIndex); - } - } else { - baseUri = uriToParse.substring(0, fragmentIndex); - fragmentString = uriToParse.substring(fragmentIndex + 1); - } - - return { baseUri: baseUri, queryString: queryString, fragmentString: fragmentString }; - } - - var parseParams = function(paramString) { - var result = {}; - var params = paramString.split('&'); - for (var i = 0; i < params.length; i++) { - var p = params[i].split('='); - var paramName = decodeURIComponent(p[0]); - var paramValue = decodeURIComponent(p[1]); - result[paramName] = paramValue; - } - return result; - } - - var handleQueryParam = function(paramName, paramValue, oauth) { - var supportedOAuthParams = [ 'code', 'state', 'error', 'error_description' ]; - - for (var i = 0 ; i< supportedOAuthParams.length ; i++) { - if (paramName === supportedOAuthParams[i]) { - oauth[paramName] = paramValue; - return true; - } - } - return false; - } - - - parser.parseUri = function() { - var parsedUri = initialParse(); - - var queryParams = {}; - if (parsedUri.queryString) { - queryParams = parseParams(parsedUri.queryString); - } - - var oauth = { newUrl: parsedUri.baseUri }; - for (var param in queryParams) { - switch (param) { - case 'redirect_fragment': - oauth.fragment = queryParams[param]; - break; - default: - if (responseMode != 'query' || !handleQueryParam(param, queryParams[param], oauth)) { - oauth.newUrl += (oauth.newUrl.indexOf('?') == -1 ? '?' : '&') + param + '=' + encodeURIComponent(queryParams[param]); - } - break; - } - } - - if (responseMode === 'fragment') { - var fragmentParams = {}; - if (parsedUri.fragmentString) { - fragmentParams = parseParams(parsedUri.fragmentString); - } - for (var param in fragmentParams) { - oauth[param] = fragmentParams[param]; - } - } - - return oauth; - } - } - } if ( typeof module === "object" && module && typeof module.exports === "object" ) { diff --git a/themes/src/main/resources/theme/keycloak-preview/account/resources/app/keycloak-service/keycloak.service.ts b/themes/src/main/resources/theme/keycloak-preview/account/resources/app/keycloak-service/keycloak.service.ts index 54f2eaa3ea..97a74078ac 100644 --- a/themes/src/main/resources/theme/keycloak-preview/account/resources/app/keycloak-service/keycloak.service.ts +++ b/themes/src/main/resources/theme/keycloak-preview/account/resources/app/keycloak-service/keycloak.service.ts @@ -15,6 +15,7 @@ * limitations under the License. */ import {Injectable} from '@angular/core'; +import {KeycloakLoginOptions} from './keycloak.d'; // If using a local keycloak.js, uncomment this import. With keycloak.js fetched // from the server, you get a compile-time warning on use of the Keycloak() @@ -23,7 +24,7 @@ import {Injectable} from '@angular/core'; // import * as Keycloak from './keycloak'; -type KeycloakClient = Keycloak.KeycloakInstance; +export type KeycloakClient = Keycloak.KeycloakInstance; type InitOptions = Keycloak.KeycloakInitOptions; @Injectable() @@ -52,17 +53,21 @@ export class KeycloakService { }); }); } + + static setKeycloakAuth(kc:KeycloakClient) { + this.keycloakAuth = kc; + } authenticated(): boolean { return KeycloakService.keycloakAuth.authenticated; } - login() { - KeycloakService.keycloakAuth.login(); + login(options?: KeycloakLoginOptions) { + KeycloakService.keycloakAuth.login(options); } - logout() { - KeycloakService.keycloakAuth.logout(); + logout(redirectUri?: string) { + KeycloakService.keycloakAuth.logout({redirectUri: redirectUri}); } account() { diff --git a/themes/src/main/resources/theme/keycloak-preview/account/resources/app/top-nav/top-nav.component.html b/themes/src/main/resources/theme/keycloak-preview/account/resources/app/top-nav/top-nav.component.html index 4b30962ce6..0669b26105 100644 --- a/themes/src/main/resources/theme/keycloak-preview/account/resources/app/top-nav/top-nav.component.html +++ b/themes/src/main/resources/theme/keycloak-preview/account/resources/app/top-nav/top-nav.component.html @@ -1,17 +1,17 @@ - +