diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties index ac184013fc..dc3affce3d 100644 --- a/.mvn/wrapper/maven-wrapper.properties +++ b/.mvn/wrapper/maven-wrapper.properties @@ -6,7 +6,7 @@ # "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 +# https://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 @@ -14,5 +14,5 @@ # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. -distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.4/apache-maven-3.9.4-bin.zip -wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.6/apache-maven-3.8.6-bin.zip +wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.jar diff --git a/build/checkstyle.xml b/build/checkstyle.xml index 98e1584278..c8c5af1056 100644 --- a/build/checkstyle.xml +++ b/build/checkstyle.xml @@ -251,6 +251,7 @@ --> + - -m diff --git a/client/angular/images/icons/wait_25.gif b/client/angular/images/icons/wait_25.gif deleted file mode 100644 index eeff903c28..0000000000 Binary files a/client/angular/images/icons/wait_25.gif and /dev/null differ diff --git a/client/angular/images/user.png b/client/angular/images/user.png deleted file mode 100755 index 9caadb6a77..0000000000 Binary files a/client/angular/images/user.png and /dev/null differ diff --git a/client/angular/package.json b/client/angular/package.json deleted file mode 100644 index f90ff12118..0000000000 --- a/client/angular/package.json +++ /dev/null @@ -1,91 +0,0 @@ -{ - "name": "ng1-webpack", - "version": "0.0.1", - "description": "", - "main": "index.js", - "engines": { - "node": ">=6.2", - "npm": ">=3.9" - }, - "scripts": { - "build": "webpack --mode=development", - "clean": "rimraf dist/", - "test": "karma start test/karma.conf.js", - "test-single-run": "karma start test/karma.conf.js --singleRun --no-auto-watch", - "start": "webpack-dev-server --mode=development --port 4000 --history-api-fallback --colors --progress" - }, - "author": "", - "license": "ISC", - "dependencies": { - "@microfocus/ias-icons": "1.1.3", - "@microfocus/ng-ias": "1.0.1", - "@microfocus/ux-ias": "1.1.3", - "@uirouter/angularjs": "1.0.30", - "angular": "1.8.3", - "angular-aria": "1.8.3", - "angular-sanitize": "1.8.3", - "angular-translate": "2.19.0", - "core-js": "3.27.2" - }, - "devDependencies": { - "@types/angular": "^1.8.3", - "@types/angular-mocks": "1.5.11", - "@types/angular-translate": "2.15.2", - "@types/angular-ui-router": "1.1.40", - "@types/jasmine": "2.8.9", - "@types/node": "9.4.7", - "angular-mocks": "1.6.9", - "autoprefixer": "8.1.0", - "copy-webpack-plugin": "^5.1.1", - "css-loader": "^3.3.2", - "file-loader": "1.1.11", - "html-loader": "0.5.5", - "html-webpack-plugin": "3.0.6", - "ignore-loader": "0.1.2", - "imports-loader": "0.8.0", - "jasmine": "3.2.0", - "jasmine-core": "3.2.1", - "jshint": "^2.13.4", - "jshint-loader": "0.8.4", - "json-loader": "0.5.7", - "karma": "^6.3.20", - "karma-chrome-launcher": "2.2.0", - "karma-jasmine": "1.1.2", - "karma-jasmine-html-reporter": "1.3.1", - "karma-sourcemap-loader": "0.3.7", - "karma-spec-reporter": "0.0.32", - "karma-webpack": "5.0.0", - "lodash": ">=4.17.21", - "moment": "^2.29.4", - "ngtemplate-loader": "2.0.1", - "node-sass": "^7.0.0", - "postcss-loader": "2.1.1", - "raw-loader": "0.5.1", - "rimraf": "2.6.2", - "sass-loader": "6.0.7", - "serialize-javascript": "4.0.0", - "string-replace-loader": "2.1.1", - "style-loader": "0.20.3", - "trim-newlines": ">=3.0.1", - "ts-loader": "4.0.1", - "ts-mockito": "2.3.1", - "tslint": "5.9.1", - "tslint-loader": "3.6.0", - "typescript": "4.9.5", - "uglifyjs-webpack-plugin": "2.2.0", - "url-loader": "4.1.1", - "webpack": "^4.46.0", - "webpack-cli": "^3.3.10", - "webpack-dev-server": "4.11.1", - "webpack-merge": "4.1.2", - "write-file-webpack-plugin": "4.2.0" - }, - "overrides": { - "jshint-loader": { - "lodash": ">=4.17.21" - }, - "rcloader": { - "lodash": ">=4.17.21" - } - } -} diff --git a/client/angular/src/component.ts b/client/angular/src/component.ts deleted file mode 100644 index 123a49eca1..0000000000 --- a/client/angular/src/component.ts +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Password Management Servlets (PWM) - * http://www.pwm-project.org - * - * Copyright (c) 2006-2009 Novell, Inc. - * Copyright (c) 2009-2021 The PWM Project - * - * 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 * as angular from 'angular'; -import { IAugmentedJQuery, IAttributes} from 'angular'; - -export interface IContentTemplateFunction { - ($element: IAugmentedJQuery, $attrs?: IAttributes): string; -} - -export function Component(options: { - bindings?: any, - bindToController?: boolean, - controllerAs?: string, - template?: (string | any[] | IContentTemplateFunction), - templateUrl?: string, - transclude?: boolean, - stylesheetUrl?: string -}) { - return (controller: Function) => angular.extend(options, { controller: controller }); -} diff --git a/client/angular/src/components/changepassword/autogen-change-password.component.html b/client/angular/src/components/changepassword/autogen-change-password.component.html deleted file mode 100644 index af4d7cd195..0000000000 --- a/client/angular/src/components/changepassword/autogen-change-password.component.html +++ /dev/null @@ -1,56 +0,0 @@ - - - -
-
-
-
-
-
-
-

- - - - - - -
-
-
-
-
-
- {{ 'Button_More' | translate }} - - {{ 'Button_Cancel' | translate }} -
- - - - -
-
-
diff --git a/client/angular/src/components/changepassword/autogen-change-password.component.scss b/client/angular/src/components/changepassword/autogen-change-password.component.scss deleted file mode 100644 index 4a462bc61d..0000000000 --- a/client/angular/src/components/changepassword/autogen-change-password.component.scss +++ /dev/null @@ -1,35 +0,0 @@ -/*! - * Password Management Servlets (PWM) - * http://www.pwm-project.org - * - * Copyright (c) 2006-2009 Novell, Inc. - * Copyright (c) 2009-2021 The PWM Project - * - * 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. - */ - -ias-dialog { - &.autogen-change-password-dialog { - table { - td { - min-width: 160px; - height: 15px; - - div { - cursor: pointer; - font-family: monospace; - } - } - } - } -} diff --git a/client/angular/src/components/changepassword/autogen-change-password.controller.ts b/client/angular/src/components/changepassword/autogen-change-password.controller.ts deleted file mode 100644 index f65b8aad87..0000000000 --- a/client/angular/src/components/changepassword/autogen-change-password.controller.ts +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Password Management Servlets (PWM) - * http://www.pwm-project.org - * - * Copyright (c) 2006-2009 Novell, Inc. - * Copyright (c) 2009-2021 The PWM Project - * - * 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 {IHelpDeskService, IRandomPasswordResponse, ISuccessResponse} from '../../services/helpdesk.service'; -import {IPromise, IQService} from 'angular'; -import {IChangePasswordSuccess} from './success-change-password.controller'; - -const RANDOM_MAPPING_SIZE = 20; - -require('./autogen-change-password.component.scss'); - -export default class AutogenChangePasswordController { - fetchingRandoms: boolean; - passwordSuggestions: string[]; - - static $inject = [ '$q', 'HelpDeskService', 'IasDialogService', 'personUserKey' ]; - constructor(private $q: IQService, - private HelpDeskService: IHelpDeskService, - private IasDialogService: any, - private personUserKey: string) { - this.passwordSuggestions = []; - for (let i = 0; i < 20; i++) { - this.passwordSuggestions.push(''); - } - this.populatePasswordSuggestions(); - } - - generateRandomMapping(): number[] { - let map: number[] = []; - for (let i = 0; i < RANDOM_MAPPING_SIZE; i++) { - map.push(i); - } - let randomComparatorFunction = () => 0.5 - Math.random(); - map.sort(randomComparatorFunction); - map.sort(randomComparatorFunction); - return map; - } - - onChoosePasswordSuggestion(index: number) { - let chosenPassword = this.passwordSuggestions[index]; - this.HelpDeskService.setPassword(this.personUserKey, false, chosenPassword) - .then((result: ISuccessResponse) => { - // Send the password and success message to the parent element via the close() method. - let data: IChangePasswordSuccess = { password: chosenPassword, successMessage: result.successMessage }; - this.IasDialogService.close(data); - }); - } - - passwordSuggestionFactory(index: number): any { - return () => { - return this.HelpDeskService.getRandomPassword(this.personUserKey).then( - (result: IRandomPasswordResponse) => { - this.passwordSuggestions[index] = result.password; - } - ); - }; - } - - populatePasswordSuggestions() { - this.fetchingRandoms = true; - let ordering = this.generateRandomMapping(); - let promiseChain: IPromise = this.$q.when(); - ordering.forEach((index: number) => { - promiseChain = promiseChain.then(this.passwordSuggestionFactory(index)); - }); - promiseChain.then(() => { - this.fetchingRandoms = false; - }); - } -} diff --git a/client/angular/src/components/changepassword/random-change-password.component.html b/client/angular/src/components/changepassword/random-change-password.component.html deleted file mode 100644 index d7aa0e1f7e..0000000000 --- a/client/angular/src/components/changepassword/random-change-password.component.html +++ /dev/null @@ -1,46 +0,0 @@ - - - -
-
-
-
-
-
- -
-

-
- -
- {{ 'Button_OK' | translate }} - {{ 'Button_Cancel' | translate }} -
- - - - -
-
-
diff --git a/client/angular/src/components/changepassword/random-change-password.controller.ts b/client/angular/src/components/changepassword/random-change-password.controller.ts deleted file mode 100644 index 4aeb2c1e8a..0000000000 --- a/client/angular/src/components/changepassword/random-change-password.controller.ts +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Password Management Servlets (PWM) - * http://www.pwm-project.org - * - * Copyright (c) 2006-2009 Novell, Inc. - * Copyright (c) 2009-2021 The PWM Project - * - * 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 {IHelpDeskService, ISuccessResponse} from '../../services/helpdesk.service'; -import {IChangePasswordSuccess} from './success-change-password.controller'; - -export default class RandomChangePasswordController { - - static $inject = [ - 'HelpDeskService', - 'IasDialogService', - 'personUserKey', - 'translateFilter' - ]; - constructor(private HelpDeskService: IHelpDeskService, - private IasDialogService: any, - private personUserKey: string, - private translateFilter: (id: string) => string) { - } - - confirmSetRandomPassword() { - this.HelpDeskService.setPassword(this.personUserKey, true) - .then((result: ISuccessResponse) => { - // Send the password and success message to the parent element via the close() method. - let chosenPassword = '[' + this.translateFilter('Display_Random') + ']'; - let data: IChangePasswordSuccess = { password: chosenPassword, successMessage: result.successMessage }; - this.IasDialogService.close(data); - }); - } -} diff --git a/client/angular/src/components/changepassword/success-change-password.component.html b/client/angular/src/components/changepassword/success-change-password.component.html deleted file mode 100644 index 8f7f290f85..0000000000 --- a/client/angular/src/components/changepassword/success-change-password.component.html +++ /dev/null @@ -1,55 +0,0 @@ - - - -
-
-
-
-
-
- -
-

-
- - - {{ 'Button_Show' | translate }} - - -
-
- -
- {{ 'Button_OK' | translate }} - {{ 'Button_ClearResponses' | translate }} - -
- - - - -
-
-
diff --git a/client/angular/src/components/changepassword/success-change-password.controller.ts b/client/angular/src/components/changepassword/success-change-password.controller.ts deleted file mode 100644 index 3fe33b22b4..0000000000 --- a/client/angular/src/components/changepassword/success-change-password.controller.ts +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Password Management Servlets (PWM) - * http://www.pwm-project.org - * - * Copyright (c) 2006-2009 Novell, Inc. - * Copyright (c) 2009-2021 The PWM Project - * - * 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 {IHelpDeskService } from '../../services/helpdesk.service'; -import {IQService} from 'angular'; -import {IHelpDeskConfigService, PASSWORD_UI_MODES} from '../../services/helpdesk-config.service'; - -export interface IChangePasswordSuccess { - password: string; - successMessage: string; -} - -export default class SuccessChangePasswordController { - clearResponsesSetting: string; - maskPasswords: boolean; - password: string; - passwordMasked: boolean; - successMessage: string; - displayNewPassword: boolean; - - static $inject = [ - '$q', - 'changePasswordSuccessData', - 'ConfigService', - 'HelpDeskService', - 'IasDialogService', - 'personUserKey', - 'translateFilter' - ]; - constructor(private $q: IQService, - changePasswordSuccessData: IChangePasswordSuccess, - private configService: IHelpDeskConfigService, - private HelpDeskService: IHelpDeskService, - private IasDialogService: any, - private personUserKey: string, - private translateFilter: (id: string) => string) { - this.password = changePasswordSuccessData.password; - this.successMessage = changePasswordSuccessData.successMessage; - - let promise = this.$q.all([ - this.configService.getClearResponsesSetting(), - this.configService.maskPasswordsEnabled(), - this.configService.getPasswordUiMode() - ]); - promise.then((result) => { - this.clearResponsesSetting = result[0]; - this.maskPasswords = result[1]; - this.passwordMasked = this.maskPasswords; - - // If it's random, don't display the new password - this.displayNewPassword = (result[2] !== PASSWORD_UI_MODES.RANDOM); - }); - } - - clearAnswers() { - this.IasDialogService.close(); - } - - togglePasswordMasked() { - this.passwordMasked = !this.passwordMasked; - } -} diff --git a/client/angular/src/components/changepassword/type-change-password.component.html b/client/angular/src/components/changepassword/type-change-password.component.html deleted file mode 100644 index 17558b1c52..0000000000 --- a/client/angular/src/components/changepassword/type-change-password.component.html +++ /dev/null @@ -1,92 +0,0 @@ - - - -
-
-
-
-
-
-
-

- - - - - - - - - - - - -
- - - -
- - - - - - -
-
- - -
- -
-
- - -
-
-
- {{ 'Button_ChangePassword' | translate }} - - {{ 'Title_RandomPasswords' | translate }} - -
- - - - -
-
-
diff --git a/client/angular/src/components/changepassword/type-change-password.component.scss b/client/angular/src/components/changepassword/type-change-password.component.scss deleted file mode 100644 index dcfb8b240d..0000000000 --- a/client/angular/src/components/changepassword/type-change-password.component.scss +++ /dev/null @@ -1,105 +0,0 @@ -/*! - * Password Management Servlets (PWM) - * http://www.pwm-project.org - * - * Copyright (c) 2006-2009 Novell, Inc. - * Copyright (c) 2009-2021 The PWM Project - * - * 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. - */ - - -.ias-dialog { - &.type-change-password-dialog { - .ias-dialog-content { - table { - border: none; - margin-left: 0; - margin-right: 0; - width: auto; - - input { - margin: 7px; - vertical-align: middle; - } - - td { - border: none; - } - - .ias-icon-message_error_thick { - color: #e50000; - } - - .ias-icon-status_ok_thin { - color: #37c26a; - } - - .strength-meter { - position: relative; - - .ias-icon { - font-size: 35px; - } - - .strength { - position: absolute; - left: 0; - top: 0; - - &.ias-icon-strength1 { - color: #e50000; - } - - &.ias-icon-strength2 { - color: #f17e12; - } - - &.ias-icon-strength3 { - color: #ffd92d; - } - - &.ias-icon-strength4 { - color: #01a9e7; - } - - &.ias-icon-strength5 { - color: #37c26a; - } - } - - .strength-base { - color: #dae1e1; - } - } - } - } - } -} - -[dir="rtl"] { - .ias-dialog { - &.type-change-password-dialog { - .ias-dialog-content { - table { - .strength-meter { - .strength { - left: auto; - right: 0; - } - } - } - } - } - } -} diff --git a/client/angular/src/components/changepassword/type-change-password.controller.ts b/client/angular/src/components/changepassword/type-change-password.controller.ts deleted file mode 100644 index a413260379..0000000000 --- a/client/angular/src/components/changepassword/type-change-password.controller.ts +++ /dev/null @@ -1,188 +0,0 @@ -/* - * Password Management Servlets (PWM) - * http://www.pwm-project.org - * - * Copyright (c) 2006-2009 Novell, Inc. - * Copyright (c) 2009-2021 The PWM Project - * - * 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 {IHelpDeskService, ISuccessResponse} from '../../services/helpdesk.service'; -import {ILogService, IQService, IScope, ITimeoutService, IWindowService} from 'angular'; -import {IHelpDeskConfigService} from '../../services/helpdesk-config.service'; -import {IChangePasswordSuccess} from './success-change-password.controller'; -import {IPasswordService, IValidatePasswordData} from '../../services/password.service'; -import IPwmService from '../../services/pwm.service'; - -require('./type-change-password.component.scss'); - -const EMPTY_MATCH_STATUS = 'EMPTY'; -const IN_PROGRESS_MESSAGE_WAIT_MS = 5; - -export default class TypeChangePasswordController { - inputDebounce: number; - maskPasswords: boolean; - matchStatus: string; - message: string; - password1: string; - password2: string; - passwordAcceptable: boolean; - passwordMasked: boolean; - passwordSuggestions: string[]; - passwordUiMode: string; - pendingValidation: boolean; - showStrengthMeter: boolean; - strength: number; - - static $inject = [ - '$log', - '$q', - '$scope', - '$timeout', - 'ConfigService', - 'HelpDeskService', - 'IasDialogService', - 'PasswordService', - 'personUserKey', - 'PwmService', - 'translateFilter' - ]; - constructor(private $log: ILogService, - private $q: IQService, - private $scope: IScope, - private $timeout: ITimeoutService, - private configService: IHelpDeskConfigService, - private HelpDeskService: IHelpDeskService, - private IasDialogService: any, - private passwordService: IPasswordService, - private personUserKey: string, - private pwmService: IPwmService, - private translateFilter: (id: string) => string) { - this.inputDebounce = this.pwmService.ajaxTypingWait; - this.matchStatus = EMPTY_MATCH_STATUS; - this.message = translateFilter('Display_PasswordPrompt'); - this.password1 = ''; - this.password2 = ''; - this.passwordAcceptable = false; - this.passwordSuggestions = []; - for (let i = 0; i < 20; i++) { - this.passwordSuggestions.push(''); - } - this.pendingValidation = false; - this.showStrengthMeter = HelpDeskService.showStrengthMeter; - this.strength = 0; - - let promise = this.$q.all([ - this.configService.getPasswordUiMode(), - this.configService.maskPasswordsEnabled() - ]); - promise.then((result) => { - this.passwordUiMode = result[0]; - this.maskPasswords = result[1]; - this.passwordMasked = this.maskPasswords; - }); - - // Update dialog whenever a password field changes - this.$scope.$watch('$ctrl.password1', (newValue, oldValue) => { - if (newValue !== oldValue) { - if (this.password2.length) { - this.password2 = ''; - } - - this.updateDialog(); - } - }); - - this.$scope.$watch('$ctrl.password2', (newValue, oldValue) => { - if (newValue !== oldValue) { - this.updateDialog(); - } - }); - } - - chooseTypedPassword() { - if (!this.passwordAcceptable) { - return; - } - - this.HelpDeskService.setPassword(this.personUserKey, false, this.password1) - .then((result: ISuccessResponse) => { - // Send the password and success message to the parent element via the close() method. - let data: IChangePasswordSuccess = { password: this.password1, successMessage: result.successMessage }; - this.IasDialogService.close(data); - }); - } - - // Use the autogenPasswords property to signify to the parent element that the operator clicked "Random Passwords" - onClickRandomPasswords() { - this.IasDialogService.close({ autogenPasswords: true }); - } - - togglePasswordMasked() { - this.passwordMasked = !this.passwordMasked; - } - - updateDialog() { - // Since user may continue typing, don't process request if another is already in progress - if (this.pendingValidation) { - return; - } - this.pendingValidation = true; - - this.passwordService.validatePassword(this.password1, this.password2, this.personUserKey) - .then( - (data: IValidatePasswordData) => { - this.pendingValidation = false; - if (data.version !== 2) { - throw new Error('[ unexpected version string from server ]'); - } - - this.passwordAcceptable = data.passed && data.match === 'MATCH'; - this.matchStatus = data.match; - this.message = data.message; - - if (!this.password1) { - this.strength = 0; - } - if (data.strength < 20) { - this.strength = 1; - } - else if (data.strength < 45) { - this.strength = 2; - } - else if (data.strength < 70) { - this.strength = 3; - } - else if (data.strength < 100) { - this.strength = 4; - } - else { - this.strength = 5; - } - }, - (result: any) => { - this.pendingValidation = false; - this.$log.error(result); - this.message = this.translateFilter('Display_CommunicationError'); - } - ); - - this.$timeout(() => { - if (this.pendingValidation) { - this.message = this.translateFilter('Display_CheckingPassword'); - } - }, IN_PROGRESS_MESSAGE_WAIT_MS); - } -} diff --git a/client/angular/src/i18n/translations_en.json b/client/angular/src/i18n/translations_en.json deleted file mode 100644 index be5ffe6ca5..0000000000 --- a/client/angular/src/i18n/translations_en.json +++ /dev/null @@ -1,94 +0,0 @@ -{ - "Button_AddSearchAttribute": "Add Search Attribute", - "Button_Attributes": "User Data", - "Button_Cancel": "Cancel", - "Button_ChangePassword": "Change Password", - "Button_ClearResponses": "Clear Answers", - "Button_Close": "Close", - "Button_CloseWindow": "Close Window", - "Button_Confirm": "Confirm", - "Button_Delete": "Delete", - "Button_SendEmail": "Send Email", - "Button_Export": "Export", - "Button_Email": "Email", - "Button_ExportOrgChart": "Export Organizational Chart", - "Button_EmailTeam": "Email Team Members", - "Button_GoBack": "Go Back", - "Button_HelpdeskClearOtpSecret": "Clear OTP Secret", - "Button_More": "More", - "Button_OK": "OK", - "Button_OTP": "OTP", - "Button_TokenVerification": "Token Verification", - "Button_SendToken": "Send Token", - "Button_Remove": "Remove", - "Button_Show": "Show", - "Button_SMS": "SMS", - "Button_Unlock": "Unlock", - "Button_Verifications": "Verifications", - "Button_Verify": "Verify", - "Confirm": "Are you sure you wish to proceed?", - "Confirm_DeleteUser": "Are you sure you wish to proceed? If you continue, the selected user will be deleted permanently. This can not be undone.", - "Display_CaptchaRefresh": "Refresh", - "Display_CheckingPassword": "Checking Password....", - "Display_HelpdeskOtpValidation": "Instruct the user to load their mobile authentication app and share the current pass code.", - "Display_InvalidVerification": "Viewing details only available after a user has been successfully verified", - "Display_MatchCondition": "Match Condition", - "Display_NoResponses": "User does not have responses", - "Display_PasswordGeneration": "The following passwords have been randomly generated for you.", - "Display_PasswordPrompt": "Please type your new password", - "Display_PleaseWait": "Loading...", - "Display_Random": "Random", - "Display_SearchResultsExceeded": "Search results exceeded maximum search size", - "Display_SearchResultsNone": "No results", - "Display_SearchAttrsUnique": "Search attributes must be unique", - "Display_SelectAttribute": "Select attribute...", - "Display_SetRandomPasswordPrompt": "Set a new random password for this user?", - "Display_StrengthMeter": "Password Strength", - "Display_TokenDestination": "Token Destination", - "Display_ViewDetails": "View Details", - "Display_EmailPrefix": "Email - ", - "Display_SmsPrefix": "SMS - ", - "Display_PeopleSearch": "Please type your search query below. You may search for a person based on name, email address or telephone number.", - "Field_DateTime": "Date/Time", - "Field_Display": "Display", - "Field_LdapProfile": "LDAP Profile", - "Field_Method": "Method", - "Field_NewPassword": "New Password", - "Field_Policy": "Policy", - "Field_Profile": "Profile", - "Field_Username": "User Name", - "Long_Title_VerificationSend": "Before this user can be selected, the user's identity must be verified. Please select a verification method.", - "Instructions_ExportOrgChart1": "Click the Export button to begin download of the organizational chart data. After download is complete, you can import the data into a program like Visio so it can be formatted into the desired output.", - "Instructions_ExportOrgChart2": "Choose the export level depth, and press the Export button below.", - "Instructions_EmailTeam1": "The email list is generated based off of organizational data starting at this point. When you click the Send Email button, your default email program should automatically open, with the list of email addresses pre-filled.", - "Instructions_EmailTeam2": "Choose the team level depth, and press the Email button below.", - "Placeholder_Search": "Search", - "Title_AdvancedSearch": "Advanced Search", - "Title_ChangePassword": "Change Password", - "Title_DirectReports": "Direct Report(s)", - "Title_Helpdesk": "Help Desk", - "Title_HelpDeskCard": "Help Desk Cards", - "Title_HelpDeskTable": "Help Desk Table", - "Title_Management": "Management", - "Title_Organization": "Organization", - "Title_OrgChart": "Organizational Chart", - "Title_Print": "Print", - "Title_PasswordPolicy": "Password Policy", - "Title_PeopleSearch": "People Search", - "Title_PeopleSearchCard": "People Search Cards", - "Title_PeopleSearchTable": "People Search Table", - "Title_RandomPasswords": "Random Passwords", - "Title_RecentVerifications": "Recent Verifications", - "Title_SecurityResponses": "Security Responses", - "Title_Status": "Status", - "Title_Success": "Success", - "Title_UserEventHistory": "Password History", - "Title_ValidateCode": "Validate Code", - "Title_VerificationSend": "Select verification method", - "Title_ExportOrgChart": "Export Organizational Chart", - "Title_EmailOrgChart": "Email Team Members", - "Label_ExportLevelDepth": "Export Level Depth", - "Label_EmailLevelDepth": "Team Level Depth", - "Label_EmailTeamMembersFound": "Email Addresses Found", - "Label_StartingFrom": "starting from {{personName}}" -} diff --git a/client/angular/src/index-dev.html b/client/angular/src/index-dev.html deleted file mode 100644 index 9d3e60c252..0000000000 --- a/client/angular/src/index-dev.html +++ /dev/null @@ -1,42 +0,0 @@ - - - - - - - - PWM Development - - - - - - -Loading... - - - \ No newline at end of file diff --git a/client/angular/src/models/person.model.ts b/client/angular/src/models/person.model.ts deleted file mode 100644 index 73425409cb..0000000000 --- a/client/angular/src/models/person.model.ts +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Password Management Servlets (PWM) - * http://www.pwm-project.org - * - * Copyright (c) 2006-2009 Novell, Inc. - * Copyright (c) 2009-2021 The PWM Project - * - * 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. - */ - - -export interface IPerson { - // Common properties - userKey?: string; - numDirectReports?: number; - - // Autocomplete properties (via Search) - _displayName?: string; - - // Details properties (not available in search) - detail?: any; - displayNames?: string[]; - photoURL?: string; - links?: any[]; - - // Search properties (not available in details) - givenName?: string; - mail?: string; - sn?: string; - telephoneNumber?: string; - title?: string; -} diff --git a/client/angular/src/modules/changepassword/changepassword.controller.ts b/client/angular/src/modules/changepassword/changepassword.controller.ts deleted file mode 100644 index f37e5ba0df..0000000000 --- a/client/angular/src/modules/changepassword/changepassword.controller.ts +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Password Management Servlets (PWM) - * http://www.pwm-project.org - * - * Copyright (c) 2006-2009 Novell, Inc. - * Copyright (c) 2009-2021 The PWM Project - * - * 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. - */ - -/* tslint:disable */ - -import {ICompileService, IScope, ITemplateCacheService, element} from 'angular'; - -declare const PWM_GLOBAL: any; -declare const PWM_MAIN: any; - -const PWM_CHANGEPW = window['PWM_CHANGEPW']; -const PW_SUGGESTIONS_TEMPLATE = require("./password-suggestions.html"); -require("./password-suggestions.scss"); - -export default class ChangePasswordController { - static $inject = ["$scope", "$compile", "$templateCache"]; - constructor( - private $scope: IScope | any, - private $compile: ICompileService, - private $templateCache: ITemplateCacheService - ) { - } - - getString(key: string) { - return PWM_MAIN.showString(key); - } - - doRandomGeneration() { - PWM_MAIN.showDialog({ - title: PWM_MAIN.showString('Title_RandomPasswords'), - dialogClass: 'narrow', - text: "", - showOk: false, - showClose: true, - loadFunction: () => { - this.populateDialog() - } - }); - } - - populateDialog() { - this.$scope.$ctrl = this; - const passwordSuggestionsElement: JQuery = this.$compile(this.$templateCache.get(PW_SUGGESTIONS_TEMPLATE) as string)(this.$scope); - - var myElement = element( document.querySelector( '#dialogPopup .dialogBody, #html5Dialog .dialogBody' ) ); - myElement.replaceWith(passwordSuggestionsElement); - - this.$scope.$applyAsync(); - - PWM_CHANGEPW.beginFetchRandoms({}); - } - - onChoosePassword(event) { - PWM_CHANGEPW.copyToPasswordFields(event.target.textContent); - } - - onMoreRandomsButtonClick() { - PWM_CHANGEPW.beginFetchRandoms({}); - } - - onCancelRandomsButtonClick() { - PWM_MAIN.closeWaitDialog('dialogPopup'); - } -} diff --git a/client/angular/src/modules/changepassword/changepassword.module.ts b/client/angular/src/modules/changepassword/changepassword.module.ts deleted file mode 100644 index 87211ce035..0000000000 --- a/client/angular/src/modules/changepassword/changepassword.module.ts +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Password Management Servlets (PWM) - * http://www.pwm-project.org - * - * Copyright (c) 2006-2009 Novell, Inc. - * Copyright (c) 2009-2021 The PWM Project - * - * 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. - */ - -/* tslint:disable */ - -import { module } from 'angular'; -import ChangePasswordController from './changepassword.controller'; - -module("changepassword.module", []) - .controller("ChangePasswordController", ChangePasswordController); diff --git a/client/angular/src/modules/changepassword/password-suggestions.html b/client/angular/src/modules/changepassword/password-suggestions.html deleted file mode 100644 index b0aa2bc8ba..0000000000 --- a/client/angular/src/modules/changepassword/password-suggestions.html +++ /dev/null @@ -1,56 +0,0 @@ - - - -
- {{$ctrl.getString('Display_PasswordGeneration')}} -

- - - - - - - -
- -
-

- - - - - - - -
- - - -
-
diff --git a/client/angular/src/modules/helpdesk/date.filters.ts b/client/angular/src/modules/helpdesk/date.filters.ts deleted file mode 100644 index d96def56b2..0000000000 --- a/client/angular/src/modules/helpdesk/date.filters.ts +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Password Management Servlets (PWM) - * http://www.pwm-project.org - * - * Copyright (c) 2006-2009 Novell, Inc. - * Copyright (c) 2009-2021 The PWM Project - * - * 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 {IWindowService} from 'angular'; - -// Date filter - converts ISO8601 date to human-readable format -export default ['$window', - function($window: IWindowService): (isoDate: string) => string { - return (isoDate: string): string => { - let date = new Date(isoDate); - if ($window['PWM_MAIN']) { - return $window['PWM_MAIN'].TimestampHandler.formatDate(date); - } - else { - return date.toString(); - } - }; - } -]; diff --git a/client/angular/src/modules/helpdesk/helpdesk-detail-dialog.template.html b/client/angular/src/modules/helpdesk/helpdesk-detail-dialog.template.html deleted file mode 100644 index 597c83f872..0000000000 --- a/client/angular/src/modules/helpdesk/helpdesk-detail-dialog.template.html +++ /dev/null @@ -1,69 +0,0 @@ - - - - -
-
-
-
-
- -
-
-
-
-
-

-

-
-
- {{ 'Button_OK' | translate }} - {{ 'Button_Cancel' | translate }} -
- - - - -
- -
-
-
-
-
-

-
-
- {{ 'Button_OK' | translate }} -
- - - - -
-
-
diff --git a/client/angular/src/modules/helpdesk/helpdesk-detail.component.html b/client/angular/src/modules/helpdesk/helpdesk-detail.component.html deleted file mode 100644 index 94cad9fe2d..0000000000 --- a/client/angular/src/modules/helpdesk/helpdesk-detail.component.html +++ /dev/null @@ -1,195 +0,0 @@ - - - -
-

Help Desk

- -
- - - - - - -
-
- -
-
- -
-
- -
-
- {{ 'Button_ChangePassword' | translate }} - - {{ 'Button_Unlock' | translate }} - - - - {{ 'Button_ClearResponses' | translate }} - - - {{ 'Button_ClearResponses' | translate }} - - - {{ 'Button_HelpdeskClearOtpSecret' | translate }} - - {{ 'Button_Verify' | translate }} - - {{ 'Button_Delete' | translate }} - - {{ button.name }} - -
- -
-
-
- {{'Field_Profile' | translate}} -
-
- {{'Title_Status' | translate}} -
-
- {{'Title_UserEventHistory' | translate}} -
-
- {{'Title_PasswordPolicy' | translate}} -
-
- {{'Title_SecurityResponses' | translate}} -
-
- -
- - - - - - - - - - -
-
-
-
- - - - - - - - - - -
-
-
-
- - - - - - - - -
-
- - - - - - - - - - - - - - - - - - - - -
-
    -
  • -
-
-
- - - - - - - - - - -
-
-
-
-
-
-
diff --git a/client/angular/src/modules/helpdesk/helpdesk-detail.component.scss b/client/angular/src/modules/helpdesk/helpdesk-detail.component.scss deleted file mode 100644 index ecdb2772a8..0000000000 --- a/client/angular/src/modules/helpdesk/helpdesk-detail.component.scss +++ /dev/null @@ -1,263 +0,0 @@ -/*! - * Password Management Servlets (PWM) - * http://www.pwm-project.org - * - * Copyright (c) 2006-2009 Novell, Inc. - * Copyright (c) 2009-2021 The PWM Project - * - * 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. - */ - -.ias-styles-root { - help-desk-detail { - display: block; - height: 100%; - overflow: auto; - width: 100%; - - #page-content-title { - margin-bottom: 0; - } - - > .ias-header { - > .help-desk-icons { - margin-left: 15px; - } - } - - > .help-desk-content { - display: flex; - flex-flow: row-reverse wrap; - justify-content: flex-end; - - > .help-desk-buttons, - > .tabset-container { - margin-top: 15px; - } - - > .help-desk-buttons { - > .ias-button { - display: block; - margin-bottom: 5px; - min-width: 170px; - width: 100%; - - &:last-child { - margin-bottom: 0; - } - } - } - - > .tabset-container { - width: 100%; - - > .tab-panes { - > .ias-tab-pane { - table { - width: 100%; - display: block; - overflow: auto; - } - } - } - } - } - - .details-table { - border: none; - border-collapse: collapse; - - tr { - height: 25px; - - td { - border: none; - font-size: 12px; - height: 19px; - text-align: left; - - &:first-child { - color: #949494; - text-align: right; - padding: 3px 0; - } - - &:last-child { - padding: 3px 15px; - } - - > ul { - list-style: none; - margin: 0; - padding: 0; - - > li { - margin: 0; - padding: 0; - } - } - } - } - } - } - - @media (min-width: 850px) { - help-desk-detail { - > .help-desk-content { - > .help-desk-buttons { - margin-left: 15px; - } - - > .tabset-container { - max-width: 100%; - width: auto; - - > .tab-panes { - > .ias-tab-pane { - table { - max-width: 800px; - } - } - } - } - } - } - } - - @media (min-width: 1050px) { - help-desk-detail { - display: flex; - flex-flow: column nowrap; - - > .ias-header, - > .secondary-header { - flex-shrink: 0; - } - - > .help-desk-content { - flex: 1 1px; - flex-wrap: nowrap; - height: 100%; - margin-top: 15px; - overflow: auto; - - > .help-desk-buttons, - > .tabset-container { - margin-top: 0; - } - - > .help-desk-buttons { - flex-shrink: 0; - height: 100%; - overflow: auto; - padding-right: 17px; - } - - > .tabset-container { - display: flex; - flex-flow: column nowrap; - height: 100%; - overflow: auto; - - > .ias-tabset { - flex-shrink: 0; - } - - .tab-panes { - flex: 1 1px; - overflow: auto; - - > .ias-tab-pane { - table { - height: 100%; - } - } - } - } - } - } - } - - [dir="rtl"] { - help-desk-detail { - > .ias-header { - > .help-desk-icons { - margin-left: 0; - margin-right: 15px; - } - } - - .details-table { - tr { - td { - text-align: right; - - &:first-child { - text-align: left; - } - } - } - } - } - - @media (min-width: 850px) { - help-desk-detail { - > .help-desk-content { - > .help-desk-buttons { - margin-left: 0; - margin-right: 15px; - } - } - } - } - - @media (min-width: 1050px) { - help-desk-detail { - > .help-desk-content { - > .help-desk-buttons { - padding-left: 17px; - padding-right: 0; - } - } - } - } - } - - // Unlike IE, Edge, and Firefox, Chrome and Safari do not need extra space for the button scrollbar when the buttons - // extend below the bottom of the page - @media (min-width: 1050px) { - [data-browsertype=webkit] { - help-desk-detail { - > .help-desk-content { - > .help-desk-buttons { - padding-right: 0; - } - } - } - - &[dir="rtl"] { - help-desk-detail { - > .help-desk-content { - > .help-desk-buttons { - padding-left: 0; - } - } - } - } - } - } - - .ias-tab-pane.ias-open { - display: block; // Can remove once https://github.com/MicroFocus/ux-ias/issues/18 (2nd comment) is resolved - } -} diff --git a/client/angular/src/modules/helpdesk/helpdesk-detail.component.ts b/client/angular/src/modules/helpdesk/helpdesk-detail.component.ts deleted file mode 100644 index 35d817518a..0000000000 --- a/client/angular/src/modules/helpdesk/helpdesk-detail.component.ts +++ /dev/null @@ -1,435 +0,0 @@ -/* - * Password Management Servlets (PWM) - * http://www.pwm-project.org - * - * Copyright (c) 2006-2009 Novell, Inc. - * Copyright (c) 2009-2021 The PWM Project - * - * 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 '../../component'; -import {IHelpDeskService, ISuccessResponse} from '../../services/helpdesk.service'; -import {IScope, ui} from 'angular'; -import {noop} from 'angular'; -import {IActionButton, IHelpDeskConfigService, PASSWORD_UI_MODES} from '../../services/helpdesk-config.service'; -import {IPerson} from '../../models/person.model'; -import {IChangePasswordSuccess} from '../../components/changepassword/success-change-password.controller'; -import LocalStorageService from '../../services/local-storage.service'; - -let autogenChangePasswordTemplateUrl = - require('../../components/changepassword/autogen-change-password.component.html'); -let helpdeskDetailDialogTemplateUrl = require('./helpdesk-detail-dialog.template.html'); -let randomChangePasswordTemplateUrl = require('../../components/changepassword/random-change-password.component.html'); -let successChangePasswordTemplateUrl = - require('../../components/changepassword/success-change-password.component.html'); -let typeChangePasswordTemplateUrl = require('../../components/changepassword/type-change-password.component.html'); -let verificationsDialogTemplateUrl = require('./verifications-dialog.template.html'); - -const STATUS_WAIT = 'wait'; -const STATUS_CONFIRM = 'confirm'; -const STATUS_SUCCESS = 'success'; -const PROFILE_TAB_NAME = 'profileTab'; - -@Component({ - stylesheetUrl: require('./helpdesk-detail.component.scss'), - templateUrl: require('./helpdesk-detail.component.html') -}) -export default class HelpDeskDetailComponent { - customButtons: {[key: string]: IActionButton}; - person: any; - personCard: IPerson; - photosEnabled: boolean; - searchViewLocalStorageKey: string; - - static $inject = [ - '$state', - '$stateParams', - 'ConfigService', - 'HelpDeskService', - 'IasDialogService', - 'IasToggleService', - 'LocalStorageService' - ]; - - constructor(private $state: ui.IStateService, - private $stateParams: ui.IStateParamsService, - private configService: IHelpDeskConfigService, - private helpDeskService: IHelpDeskService, - private IasDialogService: any, - private toggleService: { showComponent: (componentName: string) => null }, - private localStorageService: LocalStorageService) { - this.searchViewLocalStorageKey = this.localStorageService.keys.HELPDESK_SEARCH_VIEW; - } - - $onInit(): void { - this.configService.getCustomButtons().then((customButtons: {[key: string]: IActionButton}) => { - this.customButtons = customButtons; - }); - - this.configService.photosEnabled().then((photosEnabled: boolean) => { - this.photosEnabled = photosEnabled; - }); - - this.initialize(); - } - - buttonDisabled(buttonName: string): boolean { - if (!this.person || !this.person.enabledButtons) { - return false; - } - - return (this.person.enabledButtons.indexOf(buttonName) === -1); - } - - buttonVisible(buttonName: string): boolean { - if (!this.person || !this.person.visibleButtons) { - return false; - } - - return (this.person.visibleButtons.indexOf(buttonName) !== -1); - } - - changePassword(): void { - this.configService.getPasswordUiMode() - .then((passwordUiMode) => { - if (passwordUiMode) { - if (passwordUiMode === PASSWORD_UI_MODES.TYPE) { - this.changePasswordType(); - } - else if (passwordUiMode === PASSWORD_UI_MODES.AUTOGEN) { - this.changePasswordAutogen(); - } - else if (passwordUiMode === PASSWORD_UI_MODES.BOTH) { - this.changePasswordType(); - } - else if (passwordUiMode === PASSWORD_UI_MODES.RANDOM) { - this.changePasswordRandom(); - } - } - else { - throw new Error('Unable to retrieve a valid password UI mode.'); - } - }); - } - - changePasswordAutogen() { - this.IasDialogService - .open({ - controller: 'AutogenChangePasswordController as $ctrl', - templateUrl: autogenChangePasswordTemplateUrl, - locals: { - personUserKey: this.getUserKey() - } - }) - // If the password was changed, the promise resolves. IasDialogService passes the data intact. - .then(this.changePasswordSuccess.bind(this), noop); - } - - changePasswordClearResponses() { - let userKey = this.getUserKey(); - - this.IasDialogService - .open({ - controller: [ - '$scope', - 'HelpDeskService', - 'translateFilter', - ($scope: IScope | any, - helpDeskService: IHelpDeskService, - translateFilter: (id: string) => string) => { - $scope.status = STATUS_WAIT; - $scope.title = translateFilter('Button_ClearResponses'); - helpDeskService.clearResponses(userKey).then((data: ISuccessResponse) => { - // TODO - error dialog? - $scope.status = STATUS_SUCCESS; - $scope.text = data.successMessage; - this.refresh(); - }); - } - ], - templateUrl: helpdeskDetailDialogTemplateUrl - }); - } - - changePasswordRandom() { - this.IasDialogService - .open({ - controller: 'RandomChangePasswordController as $ctrl', - templateUrl: randomChangePasswordTemplateUrl, - locals: { - personUserKey: this.getUserKey() - } - }) - // If the password was changed, the promise resolves. IasDialogService passes the data intact. - .then(this.changePasswordSuccess.bind(this), noop); - } - - changePasswordSuccess(data: IChangePasswordSuccess) { - this.IasDialogService - .open({ - controller: 'SuccessChangePasswordController as $ctrl', - templateUrl: successChangePasswordTemplateUrl, - locals: { - changePasswordSuccessData: data, - personUserKey: this.getUserKey() - } - }) - .then(this.changePasswordClearResponses.bind(this), this.refresh.bind(this)); - } - - changePasswordType() { - this.IasDialogService - .open({ - controller: 'TypeChangePasswordController as $ctrl', - templateUrl: typeChangePasswordTemplateUrl, - locals: { - personUserKey: this.getUserKey() - } - }) - // If the operator clicked "Random Passwords" or the password was changed, the promise resolves. - .then((data: IChangePasswordSuccess & { autogenPasswords: boolean }) => { - // If the operator clicked "Random Passwords", data.autogenPasswords will be true - if (data.autogenPasswords) { - this.changePasswordAutogen(); - } - else { - this.changePasswordSuccess(data); // IasDialogService passes the data intact. - } - }, noop); - } - - clearOtpSecret(): void { - if (this.buttonDisabled('clearOtpSecret')) { - return; - } - - let userKey = this.getUserKey(); - - this.IasDialogService - .open({ - controller: [ - '$scope', - 'HelpDeskService', - 'translateFilter', - ($scope: IScope | any, - helpDeskService: IHelpDeskService, - translateFilter: (id: string) => string) => { - $scope.status = STATUS_CONFIRM; - $scope.title = translateFilter('Button_HelpdeskClearOtpSecret'); - $scope.text = translateFilter('Confirm'); - $scope.confirm = () => { - $scope.status = STATUS_WAIT; - helpDeskService.clearOtpSecret(userKey).then((data: ISuccessResponse) => { - // TODO - error dialog? - $scope.status = STATUS_SUCCESS; - $scope.text = data.successMessage; - this.refresh(); - }); - }; - } - ], - templateUrl: helpdeskDetailDialogTemplateUrl - }); - } - - clearResponses(): void { - if (this.buttonDisabled('clearResponses')) { - return; - } - - let userKey = this.getUserKey(); - - this.IasDialogService - .open({ - controller: [ - '$scope', - 'HelpDeskService', - 'translateFilter', - ($scope: IScope | any, - helpDeskService: IHelpDeskService, - translateFilter: (id: string) => string) => { - $scope.status = STATUS_CONFIRM; - $scope.title = translateFilter('Button_ClearResponses'); - $scope.text = translateFilter('Confirm'); - $scope.confirm = () => { - $scope.status = STATUS_WAIT; - helpDeskService.clearResponses(userKey).then((data: ISuccessResponse) => { - // TODO - error dialog? - $scope.status = STATUS_SUCCESS; - $scope.text = data.successMessage; - this.refresh(); - }); - }; - } - ], - templateUrl: helpdeskDetailDialogTemplateUrl - }); - } - - clickCustomButton(button: IActionButton): void { - // Custom buttons are never disabled - - let userKey = this.getUserKey(); - - this.IasDialogService - .open({ - controller: [ - '$scope', - 'HelpDeskService', - 'translateFilter', - ($scope: IScope | any, - helpDeskService: IHelpDeskService, - translateFilter: (id: string) => string) => { - $scope.status = STATUS_CONFIRM; - $scope.title = translateFilter('Button_Confirm') + ' ' + button.name; - $scope.text = button.description; - $scope.secondaryText = translateFilter('Confirm'); - $scope.confirm = () => { - $scope.status = STATUS_WAIT; - helpDeskService.customAction(button.name, userKey).then((data: ISuccessResponse) => { - // TODO - error dialog? (note that this error dialog is slightly different) - $scope.status = STATUS_SUCCESS; - $scope.title = translateFilter('Title_Success'); - $scope.secondaryText = null; - $scope.text = data.successMessage; - this.refresh(); - }); - }; - } - ], - templateUrl: helpdeskDetailDialogTemplateUrl - }); - } - - deleteUser(): void { - if (this.buttonDisabled('deleteUser')) { - return; - } - - let userKey = this.getUserKey(); - - this.IasDialogService - .open({ - controller: [ - '$scope', - 'HelpDeskService', - 'IasDialogService', - 'translateFilter', - ($scope: IScope | any, - helpDeskService: IHelpDeskService, - IasDialogService: any, - translateFilter: (id: string) => string) => { - $scope.status = STATUS_CONFIRM; - $scope.title = translateFilter('Button_Confirm'); - $scope.text = translateFilter('Confirm_DeleteUser'); - $scope.confirm = () => { - $scope.status = STATUS_WAIT; - helpDeskService.deleteUser(userKey).then((data: ISuccessResponse) => { - // TODO - error dialog? - $scope.status = STATUS_SUCCESS; - $scope.title = translateFilter('Title_Success'); - $scope.text = data.successMessage; - $scope.close = () => { - IasDialogService.close(); - this.gotoSearch(); - }; - }); - }; - } - ], - templateUrl: helpdeskDetailDialogTemplateUrl - }); - } - - getUserKey(): string { - return this.$stateParams['personId']; - } - - gotoSearch(): void { - let view = this.localStorageService.getItem(this.searchViewLocalStorageKey); - if (view) { - this.$state.go(view); - } - else { - this.$state.go('search.cards'); - } - } - - initialize(): void { - const personId = this.getUserKey(); - - this.helpDeskService.getPersonCard(personId).then((personCard: IPerson) => { - this.personCard = personCard; - }); - - this.toggleService.showComponent(PROFILE_TAB_NAME); - - this.helpDeskService - .getPerson(personId) - .then((person: any) => { - this.person = person; - }, this.gotoSearch.bind(this)); - } - - refresh(): void { - this.person = null; - this.initialize(); - } - - unlockUser(): void { - if (this.buttonDisabled('unlock')) { - return; - } - - let userKey = this.getUserKey(); - - this.IasDialogService - .open({ - controller: [ - '$scope', - 'HelpDeskService', - 'translateFilter', - ($scope: IScope | any, - helpDeskService: IHelpDeskService, - translateFilter: (id: string) => string) => { - $scope.status = STATUS_CONFIRM; - $scope.title = translateFilter('Button_Unlock'); - $scope.text = translateFilter('Confirm'); - $scope.confirm = () => { - $scope.status = STATUS_WAIT; - helpDeskService.unlockIntruder(userKey).then((data: ISuccessResponse) => { - // TODO - error dialog? - $scope.status = STATUS_SUCCESS; - $scope.text = data.successMessage; - this.refresh(); - }); - }; - } - ], - templateUrl: helpdeskDetailDialogTemplateUrl - }); - } - - verifyUser(): void { - this.IasDialogService - .open({ - controller: 'VerificationsDialogController as $ctrl', - templateUrl: verificationsDialogTemplateUrl, - locals: { - personUserKey: this.getUserKey(), - showRequiredOnly: false - } - }); - } -} diff --git a/client/angular/src/modules/helpdesk/helpdesk-search-base.component.ts b/client/angular/src/modules/helpdesk/helpdesk-search-base.component.ts deleted file mode 100644 index 26ed65367d..0000000000 --- a/client/angular/src/modules/helpdesk/helpdesk-search-base.component.ts +++ /dev/null @@ -1,371 +0,0 @@ -/* - * Password Management Servlets (PWM) - * http://www.pwm-project.org - * - * Copyright (c) 2006-2009 Novell, Inc. - * Copyright (c) 2009-2021 The PWM Project - * - * 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 SearchResult from '../../models/search-result.model'; -import {isArray, isString, IPromise, IQService, IScope, ITimeoutService} from 'angular'; -import {IPerson} from '../../models/person.model'; -import {IHelpDeskConfigService} from '../../services/helpdesk-config.service'; -import LocalStorageService from '../../services/local-storage.service'; -import PromiseService from '../../services/promise.service'; -import {IHelpDeskService} from '../../services/helpdesk.service'; -import IPwmService from '../../services/pwm.service'; -import {IAdvancedSearchConfig, IAdvancedSearchQuery, IAttributeMetadata} from '../../services/base-config.service'; -import CommonSearchService from '../../services/common-search.service'; - - -let verificationsDialogTemplateUrl = require('./verifications-dialog.template.html'); -let recentVerificationsDialogTemplateUrl = require('./recent-verifications-dialog.template.html'); - -export default abstract class HelpDeskSearchBaseComponent { - advancedSearch = false; - advancedSearchTags = {}; - advancedSearchEnabled: boolean; - advancedSearchMaxRows: number; - columnConfiguration: any; - errorMessage: string; - inputDebounce: number; - protected pendingRequests: IPromise[] = []; - photosEnabled: boolean; - query: string; - queries: IAdvancedSearchQuery[]; - searchMessage: string; - searchResult: SearchResult; - searchTextLocalStorageKey: string; - searchViewLocalStorageKey: string; - verificationsEnabled: boolean; - - constructor(protected $q: IQService, - protected $scope: IScope, - protected $state: angular.ui.IStateService, - protected $stateParams: angular.ui.IStateParamsService, - protected $timeout: ITimeoutService, - protected $translate: angular.translate.ITranslateService, - protected configService: IHelpDeskConfigService, - protected helpDeskService: IHelpDeskService, - protected IasDialogService: any, - protected localStorageService: LocalStorageService, - protected promiseService: PromiseService, - protected pwmService: IPwmService, - protected commonSearchService: CommonSearchService) { - this.searchTextLocalStorageKey = this.localStorageService.keys.HELPDESK_SEARCH_TEXT; - this.searchViewLocalStorageKey = this.localStorageService.keys.HELPDESK_SEARCH_VIEW; - - this.inputDebounce = this.pwmService.ajaxTypingWait; - } - - protected initialize(): IPromise { - return this.$q.all( - [ - this.configService.verificationsEnabled().then((verificationsEnabled: boolean) => { - this.verificationsEnabled = verificationsEnabled; - }), - this.configService.advancedSearchConfig().then((advancedSearchConfig: IAdvancedSearchConfig) => { - this.advancedSearchEnabled = advancedSearchConfig.enabled; - this.advancedSearchMaxRows = advancedSearchConfig.maxRows; - - for (let advancedSearchTag of advancedSearchConfig.attributes) { - this.advancedSearchTags[advancedSearchTag.attribute] = advancedSearchTag; - } - }) - ] - ).then(result => { - const searchQuery = this.getSearchQuery(); - if (searchQuery) { - // A search query has been passed in, disregard the current search state - this.query = searchQuery; - this.advancedSearch = false; - this.storeSearchText(); - this.commonSearchService.setHdAdvancedSearchActive(this.advancedSearch); - this.commonSearchService.setHdAdvSearchQueries([]); - } else { - this.query = this.getSearchText(); - this.advancedSearch = this.commonSearchService.isHdAdvancedSearchActive(); - this.queries = this.commonSearchService.getHdAdvSearchQueries(); - if (this.queries.length === 0) { - this.addSearchTag(); - } - } - - // Once from ng-ias allows the autofocus attribute, we can remove this code - this.$timeout(() => { - document.getElementsByTagName('input')[0].focus(); - }); - - this.$scope.$watch('$ctrl.query', (newValue: string, oldValue: string) => { - this.onSearchTextChange(newValue, oldValue); - }); - }); - } - - getMessage(): string { - return this.errorMessage || this.searchMessage; - } - - private getSearchQuery(): string { - let param: string = this.$stateParams['query']; - // If multiple query parameters are defined, use the first one - if (isArray(param)) { - param = param[0].trim(); - } - else if (isString(param)) { - param = param.trim(); - } - - return param; - } - - private getSearchText(): string { - return this.localStorageService.getItem(this.searchTextLocalStorageKey); - } - - abstract fetchData(): void; - - protected clearSearch(): void { - this.query = null; - this.queries = []; - this.searchResult = null; - this.clearErrorMessage(); - this.clearSearchMessage(); - this.abortPendingRequests(); - } - - protected fetchSearchData(): IPromise { - this.abortPendingRequests(); - this.searchResult = null; - let promise; - - if (this.advancedSearch) { - if (!this.queries || (this.queries.length === 1 && !this.queries[0].key)) { - this.clearSearch(); - return null; - } - - const keys = new Set(); - for (let searchQuery of this.queries) { - keys.add(searchQuery.key); - } - - const duplicateSearchAttrsFound = keys.size < this.queries.length; - if (duplicateSearchAttrsFound) { - this.$translate('Display_SearchAttrsUnique') - .then((translation: string) => { - this.searchMessage = translation; - }); - - return null; - } - - promise = this.helpDeskService.advancedSearch(this.queries); - } - else { - if (!this.query) { - this.clearSearch(); - return null; - } - - promise = this.helpDeskService.search(this.query); - } - - this.pendingRequests.push(promise); - - return promise - .then( - function(searchResult: SearchResult) { - this.clearErrorMessage(); - this.clearSearchMessage(); - - // Aborted request - if (!searchResult) { - return; - } - - // Too many results returned - if (searchResult.sizeExceeded) { - this.setSearchMessage('Display_SearchResultsExceeded'); - } - - // No results returned. Not an else if statement so that the more important message is presented - if (!searchResult.people.length) { - this.setSearchMessage('Display_SearchResultsNone'); - } - - return searchResult; - }.bind(this), - function(error) { - this.setErrorMessage(error); - this.clearSearchMessage(); - }.bind(this)) - .finally(function() { - this.removePendingRequest(promise); - }.bind(this)); - } - - private gotoState(state: string): void { - this.$state.go(state); - } - - private initiateSearch() { - this.clearSearchMessage(); - this.clearErrorMessage(); - this.fetchData(); - } - - private onSearchTextChange(newValue: string, oldValue: string): void { - if (newValue === oldValue) { - return; - } - - this.storeSearchText(); - this.initiateSearch(); - } - - protected abortPendingRequests() { - for (let index = 0; index < this.pendingRequests.length; index++) { - let pendingRequest = this.pendingRequests[index]; - this.promiseService.abort(pendingRequest); - } - - this.pendingRequests = []; - } - - protected setErrorMessage(message: string) { - this.errorMessage = message; - } - - protected clearErrorMessage() { - this.errorMessage = null; - } - - // If message is a string it will be translated. If it is a promise it will assign the string from the resolved - // promise - protected setSearchMessage(translationKey: string) { - if (!translationKey) { - this.clearSearchMessage(); - return; - } - - const self = this; - this.$translate(translationKey.toString()) - .then((translation: string) => { - self.searchMessage = translation; - }); - } - - protected clearSearchMessage(): void { - this.searchMessage = null; - } - - protected removePendingRequest(promise: IPromise) { - let index = this.pendingRequests.indexOf(promise); - - if (index > -1) { - this.pendingRequests.splice(index, 1); - } - } - - private onAdvancedSearchAttributeChanged(query: IAdvancedSearchQuery) { - // Make sure we set the default value if the type is select - const attributeMetadata: IAttributeMetadata = this.advancedSearchTags[query.key]; - if (attributeMetadata.type == 'select') { - query.value = this.commonSearchService.getDefaultValue(attributeMetadata); - } - - this.commonSearchService.setHdAdvSearchQueries(this.queries); - this.initiateSearch(); - } - - private onAdvancedSearchAttributeValueChanged() { - this.commonSearchService.setHdAdvSearchQueries(this.queries); - this.initiateSearch(); - } - - private onAdvancedSearchValueChanged() { - this.commonSearchService.setHdAdvSearchQueries(this.queries); - this.initiateSearch(); - } - - removeSearchTag(tagIndex: number): void { - this.queries.splice(tagIndex, 1); - this.commonSearchService.setHdAdvSearchQueries(this.queries); - - if (this.queries.length > 0) { - this.initiateSearch(); - } - else { - this.clearSearch(); - this.advancedSearch = false; - this.commonSearchService.setHdAdvancedSearchActive(this.advancedSearch); - } - } - - addSearchTag(): void { - const firstTagName = Object.keys(this.advancedSearchTags)[0]; - const attributeMetaData: IAttributeMetadata = this.advancedSearchTags[firstTagName]; - - const query: IAdvancedSearchQuery = { - key: attributeMetaData.attribute, - value: this.commonSearchService.getDefaultValue(attributeMetaData), - }; - - this.queries.push(query); - } - - protected selectPerson(person: IPerson): void { - this.IasDialogService - .open({ - controller: 'VerificationsDialogController as $ctrl', - templateUrl: verificationsDialogTemplateUrl, - locals: { - personUserKey: person.userKey, - showRequiredOnly: true - } - }); - } - - protected showVerifications(): void { - this.IasDialogService - .open({ - controller: 'RecentVerificationsDialogController as $ctrl', - templateUrl: recentVerificationsDialogTemplateUrl - }); - } - - protected storeSearchText(): void { - this.localStorageService.setItem(this.searchTextLocalStorageKey, this.query || ''); - } - - enableAdvancedSearch(): void { - this.clearSearch(); - this.addSearchTag(); - this.advancedSearch = true; - this.commonSearchService.setHdAdvancedSearchActive(this.advancedSearch); - } - - protected toggleView(state: string): void { - this.storeSearchView(state); - this.storeSearchText(); - this.gotoState(state); - } - - private storeSearchView(state: string) { - this.localStorageService.setItem(this.searchViewLocalStorageKey, state); - } -} diff --git a/client/angular/src/modules/helpdesk/helpdesk-search-cards.component.html b/client/angular/src/modules/helpdesk/helpdesk-search-cards.component.html deleted file mode 100644 index 8f6e9c7b0c..0000000000 --- a/client/angular/src/modules/helpdesk/helpdesk-search-cards.component.html +++ /dev/null @@ -1,96 +0,0 @@ - - - - -
-
-

Help Desk

- - - - - - -
-
-
- - - - - - - - - - - -
- - - -
- -
- - - - - - -
-
- -
-
-
-
- -
-
- - -
-
diff --git a/client/angular/src/modules/helpdesk/helpdesk-search-cards.component.ts b/client/angular/src/modules/helpdesk/helpdesk-search-cards.component.ts deleted file mode 100644 index 01ba2c9ccc..0000000000 --- a/client/angular/src/modules/helpdesk/helpdesk-search-cards.component.ts +++ /dev/null @@ -1,133 +0,0 @@ -/* - * Password Management Servlets (PWM) - * http://www.pwm-project.org - * - * Copyright (c) 2006-2009 Novell, Inc. - * Copyright (c) 2009-2021 The PWM Project - * - * 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 '../../component'; -import {IQService, IScope, ITimeoutService} from 'angular'; -import {IHelpDeskConfigService} from '../../services/helpdesk-config.service'; -import LocalStorageService from '../../services/local-storage.service'; -import HelpDeskSearchBaseComponent from './helpdesk-search-base.component'; -import SearchResult from '../../models/search-result.model'; -import {IPerson} from '../../models/person.model'; -import PromiseService from '../../services/promise.service'; -import {IHelpDeskService} from '../../services/helpdesk.service'; -import IPwmService from '../../services/pwm.service'; -import CommonSearchService from '../../services/common-search.service'; - -@Component({ - stylesheetUrl: require('./helpdesk-search.component.scss'), - templateUrl: require('./helpdesk-search-cards.component.html') -}) -export default class HelpDeskSearchCardsComponent extends HelpDeskSearchBaseComponent { - static $inject = [ - '$q', - '$scope', - '$state', - '$stateParams', - '$timeout', - '$translate', - 'ConfigService', - 'HelpDeskService', - 'IasDialogService', - 'LocalStorageService', - 'PromiseService', - 'PwmService', - 'CommonSearchService' - ]; - constructor($q: IQService, - $scope: IScope, - $state: angular.ui.IStateService, - $stateParams: angular.ui.IStateParamsService, - $timeout: ITimeoutService, - $translate: angular.translate.ITranslateService, - configService: IHelpDeskConfigService, - helpDeskService: IHelpDeskService, - IasDialogService: any, - localStorageService: LocalStorageService, - promiseService: PromiseService, - pwmService: IPwmService, - commonSearchService: CommonSearchService) { - super($q, $scope, $state, $stateParams, $timeout, $translate, configService, helpDeskService, IasDialogService, - localStorageService, promiseService, pwmService, commonSearchService); - } - - $onInit() { - this.initialize().then(() => { - this.fetchData(); - }); - - this.configService.photosEnabled().then((photosEnabled: boolean) => { - this.photosEnabled = photosEnabled; - }); - } - - fetchData() { - let searchResult = this.fetchSearchData(); - if (searchResult) { - searchResult.then(this.onSearchResult.bind(this)); - } - } - - gotoTableView(): void { - this.toggleView('search.table'); - } - - private onSearchResult(searchResult: SearchResult): void { - // Aborted request - if (!searchResult) { - return; - } - - this.searchResult = new SearchResult({ - sizeExceeded: searchResult.sizeExceeded, - searchResults: [] - }); - - this.pendingRequests = searchResult.people.map( - (person: IPerson) => { - // Store this promise because it is abortable - let promise = this.helpDeskService.getPersonCard(person.userKey); - - promise - .then(function(person: IPerson) { - // Aborted request - if (!person) { - return; - } - - // searchResult may be overwritten by ESC->[LETTER] typed in after a search - // has started but before all calls to helpdeskService.getPersonCard have resolved - if (this.searchResult) { - this.searchResult.people.push(person); - } - }.bind(this), - (error) => { - this.setErrorMessage(error); - }) - .finally(() => { - this.removePendingRequest(promise); - }); - - return promise; - }, - this - ); - } -} diff --git a/client/angular/src/modules/helpdesk/helpdesk-search-table.component.html b/client/angular/src/modules/helpdesk/helpdesk-search-table.component.html deleted file mode 100644 index b51e686302..0000000000 --- a/client/angular/src/modules/helpdesk/helpdesk-search-table.component.html +++ /dev/null @@ -1,118 +0,0 @@ - - - -
-
-

Help Desk

- - - - - - -
-
-
- - - - - - - - - - - -
- - - -
- -
- - - - - - -
- - - - -
-
- - {{value.label}} -
-
-
-
-
- -
-
-
-
- -
- - - - - - - - - - - - -
{{value.label}}
- -
-
diff --git a/client/angular/src/modules/helpdesk/helpdesk-search-table.component.ts b/client/angular/src/modules/helpdesk/helpdesk-search-table.component.ts deleted file mode 100644 index 5208ed04ba..0000000000 --- a/client/angular/src/modules/helpdesk/helpdesk-search-table.component.ts +++ /dev/null @@ -1,110 +0,0 @@ -/* - * Password Management Servlets (PWM) - * http://www.pwm-project.org - * - * Copyright (c) 2006-2009 Novell, Inc. - * Copyright (c) 2009-2021 The PWM Project - * - * 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 {IQService, IScope, ITimeoutService} from 'angular'; -import HelpDeskSearchBaseComponent from './helpdesk-search-base.component'; -import {Component} from '../../component'; -import SearchResult from '../../models/search-result.model'; -import {IHelpDeskConfigService} from '../../services/helpdesk-config.service'; -import LocalStorageService from '../../services/local-storage.service'; -import PromiseService from '../../services/promise.service'; -import {IHelpDeskService} from '../../services/helpdesk.service'; -import IPwmService from '../../services/pwm.service'; -import CommonSearchService from '../../services/common-search.service'; - -@Component({ - stylesheetUrl: require('./helpdesk-search.component.scss'), - templateUrl: require('./helpdesk-search-table.component.html') -}) -export default class HelpDeskSearchTableComponent extends HelpDeskSearchBaseComponent { - columnConfiguration: any; - - static $inject = [ - '$q', - '$scope', - '$state', - '$stateParams', - '$timeout', - '$translate', - 'ConfigService', - 'HelpDeskService', - 'IasDialogService', - 'LocalStorageService', - 'PromiseService', - 'PwmService', - 'CommonSearchService' - ]; - constructor($q: IQService, - $scope: IScope, - $state: angular.ui.IStateService, - $stateParams: angular.ui.IStateParamsService, - $timeout: ITimeoutService, - $translate: angular.translate.ITranslateService, - configService: IHelpDeskConfigService, - helpDeskService: IHelpDeskService, - IasDialogService: any, - localStorageService: LocalStorageService, - promiseService: PromiseService, - pwmService: IPwmService, - commonSearchService: CommonSearchService) { - super($q, $scope, $state, $stateParams, $timeout, $translate, configService, helpDeskService, IasDialogService, - localStorageService, promiseService, pwmService, commonSearchService); - } - - $onInit() { - this.initialize().then(() => { - this.fetchData(); - }); - - // The table columns are dynamic and configured via a service - this.configService.getColumnConfig().then( - (columnConfiguration: any) => { - this.columnConfiguration = Object.keys(columnConfiguration).reduce( - function(accumulator, columnId) { - accumulator[columnId] = { - label: columnConfiguration[columnId], - visible: true - }; - - return accumulator; - }, - {}); - }, - (error) => { - this.setErrorMessage(error); - }); - } - - fetchData() { - let searchResult = this.fetchSearchData(); - if (searchResult) { - searchResult.then(this.onSearchResult.bind(this)); - } - } - - gotoCardsView(): void { - this.toggleView('search.cards'); - } - - private onSearchResult(searchResult: SearchResult): void { - this.searchResult = searchResult; - } -} diff --git a/client/angular/src/modules/helpdesk/helpdesk-search.component.scss b/client/angular/src/modules/helpdesk/helpdesk-search.component.scss deleted file mode 100644 index 775ba0f526..0000000000 --- a/client/angular/src/modules/helpdesk/helpdesk-search.component.scss +++ /dev/null @@ -1,118 +0,0 @@ -/*! - * Password Management Servlets (PWM) - * http://www.pwm-project.org - * - * Copyright (c) 2006-2009 Novell, Inc. - * Copyright (c) 2009-2021 The PWM Project - * - * 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. - */ - -.ias-styles-root { - help-desk-search-cards, - help-desk-search-table { - display: flex; - flex-flow: column nowrap; - height: 100%; - - #page-content-title { - margin-bottom: 0; - } - - .verifications-button { - margin: 5px 5px 5px 0; - } - - > .people-search-component-content { - flex: 1 1; - overflow: auto; - } - - .helpdesk-search-header { - display: flex; - align-items: flex-start; - - .basic-search-container { - display: flex; - align-items: center; - margin-bottom: 15px; - - > * + * { - margin-left: 10px; - } - } - - .advanced-search-container { - display: flex; - flex-direction: column; - align-items: flex-start; - margin-bottom: 15px; - - > * + * { - margin-top: 5px; - } - - & + div { - margin-top: 15px; - } - } - } - } - - .aligned-input { - margin-top: 15px; - - > * { - vertical-align: middle; - } - - .ias-button { - margin-right: 5px; - } - } - - .loading-gif-25 { - background-image: url('../../../images/icons/wait_25.gif'); - display: inline-block; - height: 25px; - width: 25px; - } - - [dir="rtl"] { - help-desk-search-cards, - help-desk-search-table { - .ias-search { - margin-left: 10px; - margin-right: 0; - } - - .verifications-button { - margin: 5px 0 5px 5px; - } - } - - .aligned-input { - .ias-button { - margin-left: 5px; - margin-right: 0; - } - } - } - - .ias-input-container > .checkbox-button > .ias-button.toggle-column-btn { - &:focus, &:hover { - background-color: transparent; - box-shadow: none; - } - } -} diff --git a/client/angular/src/modules/helpdesk/helpdesk.module.ts b/client/angular/src/modules/helpdesk/helpdesk.module.ts deleted file mode 100644 index 8e176c357a..0000000000 --- a/client/angular/src/modules/helpdesk/helpdesk.module.ts +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Password Management Servlets (PWM) - * http://www.pwm-project.org - * - * Copyright (c) 2006-2009 Novell, Inc. - * Copyright (c) 2009-2021 The PWM Project - * - * 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. - */ - -// These need to be at the top so imported components can override the default styling -require('../../styles.scss'); -require('../peoplesearch/peoplesearch.scss'); - -import 'angular-aria'; - -import {IComponentOptions, module} from 'angular'; -import DateFilter from './date.filters'; -import HelpDeskDetailComponent from './helpdesk-detail.component'; -import HelpDeskSearchTableComponent from './helpdesk-search-table.component'; -import HelpDeskSearchCardsComponent from './helpdesk-search-cards.component'; -import LocalStorageService from '../../services/local-storage.service'; -import ObjectService from '../../services/object.service'; -import PersonCardDirective from '../peoplesearch/person-card.component'; -import PromiseService from '../../services/promise.service'; -import RecentVerificationsDialogController from './recent-verifications-dialog.controller'; -import uxModule from '../../ux/ux.module'; -import VerificationsDialogController from './verifications-dialog.controller'; -import AutogenChangePasswordController from '../../components/changepassword/autogen-change-password.controller'; -import RandomChangePasswordController from '../../components/changepassword/random-change-password.controller'; -import SuccessChangePasswordController from '../../components/changepassword/success-change-password.controller'; -import TypeChangePasswordController from '../../components/changepassword/type-change-password.controller'; -import CommonSearchService from '../../services/common-search.service'; - -const moduleName = 'help-desk'; - -module(moduleName, [ - 'ngAria', - 'ngSanitize', - uxModule -]) - - .component('helpDeskSearchCards', HelpDeskSearchCardsComponent as IComponentOptions) - .component('helpDeskSearchTable', HelpDeskSearchTableComponent as IComponentOptions) - .component('helpDeskDetail', HelpDeskDetailComponent as IComponentOptions) - .directive('personCard', PersonCardDirective) - .controller('AutogenChangePasswordController', AutogenChangePasswordController) - .controller('RandomChangePasswordController', RandomChangePasswordController) - .controller('RecentVerificationsDialogController', RecentVerificationsDialogController) - .controller('SuccessChangePasswordController', SuccessChangePasswordController) - .controller('TypeChangePasswordController', TypeChangePasswordController) - .controller('VerificationsDialogController', VerificationsDialogController) - .filter('dateFilter', DateFilter) - .service('ObjectService', ObjectService) - .service('PromiseService', PromiseService) - .service('LocalStorageService', LocalStorageService) - .service('CommonSearchService', CommonSearchService); - -export default moduleName; diff --git a/client/angular/src/modules/helpdesk/main.dev.ts b/client/angular/src/modules/helpdesk/main.dev.ts deleted file mode 100644 index ca5d352c1a..0000000000 --- a/client/angular/src/modules/helpdesk/main.dev.ts +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Password Management Servlets (PWM) - * http://www.pwm-project.org - * - * Copyright (c) 2006-2009 Novell, Inc. - * Copyright (c) 2009-2021 The PWM Project - * - * 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 'angular'; -import 'angular-translate'; -import '@microfocus/ng-ias/dist/ng-ias'; - -import { bootstrap, module } from 'angular'; -import helpDeskModule from './helpdesk.module'; -import routes from './routes'; -import uiRouter from '@uirouter/angularjs'; -import PeopleService from '../../services/people.service.dev'; -import HelpDeskConfigService from '../../services/helpdesk-config.service.dev'; -import HelpDeskService from '../../services/helpdesk.service.dev'; -import PasswordService from '../../services/password.service.dev'; -import PwmService from '../../services/pwm.service.dev'; - - -module('app', [ - uiRouter, - helpDeskModule, - 'pascalprecht.translate', - 'ng-ias' -]) - .config(['$translateProvider', ($translateProvider: angular.translate.ITranslateProvider) => { - $translateProvider.translations('en', require('../../i18n/translations_en.json')); - $translateProvider.useSanitizeValueStrategy('escapeParameters'); - $translateProvider.preferredLanguage('en'); - }]) - .config(routes) - .service('HelpDeskService', HelpDeskService) - .service('PasswordService', PasswordService) - .service('PeopleService', PeopleService) - .service('PwmService', PwmService) - .service('ConfigService', HelpDeskConfigService); - -// Attach to the page document -bootstrap(document, ['app']); diff --git a/client/angular/src/modules/helpdesk/main.ts b/client/angular/src/modules/helpdesk/main.ts deleted file mode 100644 index e831804028..0000000000 --- a/client/angular/src/modules/helpdesk/main.ts +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Password Management Servlets (PWM) - * http://www.pwm-project.org - * - * Copyright (c) 2006-2009 Novell, Inc. - * Copyright (c) 2009-2021 The PWM Project - * - * 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 'angular'; -import 'angular-translate'; -import 'angular-sanitize'; -import '@microfocus/ng-ias/dist/ng-ias'; - -// Add a polyfill for Set() for IE11, since it's used in peoplesearch-base.component.ts -import 'core-js/es/set'; - -import { bootstrap, module } from 'angular'; -import helpDeskModule from './helpdesk.module'; -import PeopleService from '../../services/people.service'; -import PwmService from '../../services/pwm.service'; -import routes from './routes'; -import TranslationsLoaderFactory from '../../services/translations-loader.factory'; -import uiRouter from '@uirouter/angularjs'; -import HelpDeskConfigService from '../../services/helpdesk-config.service'; -import HelpDeskService from '../../services/helpdesk.service'; -import PasswordService from '../../services/password.service'; - - -module('app', [ - uiRouter, - helpDeskModule, - 'pascalprecht.translate', - 'ng-ias' -]) - .config(routes) - .config([ - '$translateProvider', - ($translateProvider: angular.translate.ITranslateProvider) => { - $translateProvider - .translations('fallback', require('../../i18n/translations_en.json')) - .useLoader('translationsLoader') - .useSanitizeValueStrategy('escapeParameters') - .preferredLanguage('en') - .fallbackLanguage('fallback') - .forceAsyncReload(true); - }]) - .service('HelpDeskService', HelpDeskService) - .service('PasswordService', PasswordService) - .service('PeopleService', PeopleService) - .service('PwmService', PwmService) - .service('ConfigService', HelpDeskConfigService) - .factory('translationsLoader', TranslationsLoaderFactory); - -// Attach to the page document, wait for PWM to load first -window['PWM_GLOBAL'].startupFunctions.push(() => { - bootstrap(document, ['app'], { strictDi: true }); -}); diff --git a/client/angular/src/modules/helpdesk/recent-verifications-dialog.controller.ts b/client/angular/src/modules/helpdesk/recent-verifications-dialog.controller.ts deleted file mode 100644 index 494465ead4..0000000000 --- a/client/angular/src/modules/helpdesk/recent-verifications-dialog.controller.ts +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Password Management Servlets (PWM) - * http://www.pwm-project.org - * - * Copyright (c) 2006-2009 Novell, Inc. - * Copyright (c) 2009-2021 The PWM Project - * - * 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 {IHelpDeskService, IRecentVerifications} from '../../services/helpdesk.service'; - -export default class RecentVerificationsDialogController { - recentVerifications: IRecentVerifications; - - static $inject = [ 'HelpDeskService' ]; - constructor(helpDeskService: IHelpDeskService) { - helpDeskService.getRecentVerifications() - .then((recentVerifications) => { - this.recentVerifications = recentVerifications; - }); - } -} diff --git a/client/angular/src/modules/helpdesk/recent-verifications-dialog.template.html b/client/angular/src/modules/helpdesk/recent-verifications-dialog.template.html deleted file mode 100644 index 51ef48ec50..0000000000 --- a/client/angular/src/modules/helpdesk/recent-verifications-dialog.template.html +++ /dev/null @@ -1,69 +0,0 @@ - - - -
-
-
-
-
-
- -
-

- - - - - - - - - - - - - - - - - -
-
- - {{ method.label | translate }} - -
-
- -
- {{ 'Button_OK' | translate }} -
- - - - -
-
-
diff --git a/client/angular/src/modules/helpdesk/routes.ts b/client/angular/src/modules/helpdesk/routes.ts deleted file mode 100644 index b044306299..0000000000 --- a/client/angular/src/modules/helpdesk/routes.ts +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Password Management Servlets (PWM) - * http://www.pwm-project.org - * - * Copyright (c) 2006-2009 Novell, Inc. - * Copyright (c) 2009-2021 The PWM Project - * - * 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 LocalStorageService from '../../services/local-storage.service'; - -export default [ - '$stateProvider', - '$urlRouterProvider', - ( - $stateProvider: angular.ui.IStateProvider, - $urlRouterProvider: angular.ui.IUrlRouterProvider - ) => { - $urlRouterProvider.otherwise( - ($injector: angular.auto.IInjectorService, $location: angular.ILocationService) => { - let $state: angular.ui.IStateService = $injector.get('$state'); - let localStorageService: LocalStorageService = - $injector.get('LocalStorageService'); - - let storedView = localStorageService.getItem(localStorageService.keys.HELPDESK_SEARCH_VIEW); - - if (storedView) { - $state.go(storedView); - } - else { - $location.url('search/cards'); - } - }); - - $stateProvider.state('search', { - url: '/search?query', - abstract: true, - template: '
', - }); - $stateProvider.state('search.cards', { url: '/cards', component: 'helpDeskSearchCards' }); - $stateProvider.state('search.table', { url: '/table', component: 'helpDeskSearchTable' }); - $stateProvider.state('details', { url: '/details/{personId}', component: 'helpDeskDetail' }); - }]; diff --git a/client/angular/src/modules/helpdesk/verifications-dialog.controller.ts b/client/angular/src/modules/helpdesk/verifications-dialog.controller.ts deleted file mode 100644 index 91bf0d1a99..0000000000 --- a/client/angular/src/modules/helpdesk/verifications-dialog.controller.ts +++ /dev/null @@ -1,216 +0,0 @@ -/* - * Password Management Servlets (PWM) - * http://www.pwm-project.org - * - * Copyright (c) 2006-2009 Novell, Inc. - * Copyright (c) 2009-2021 The PWM Project - * - * 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. - */ - -require('./verifications-dialog.component.scss'); - -import {ui, ITimeoutService} from 'angular'; -import { - IHelpDeskConfigService, IVerificationMap, TOKEN_CHOICE, VERIFICATION_METHOD_LABELS, - VERIFICATION_METHOD_NAMES -} from '../../services/helpdesk-config.service'; -import { - IHelpDeskService, - IVerificationOptions, - IVerificationStatus, - IVerificationTokenResponse -} from '../../services/helpdesk.service'; -import ObjectService from '../../services/object.service'; - -const STATUS_FAILED = 'failed'; -const STATUS_NONE = 'none'; -const STATUS_PASSED = 'passed'; -const STATUS_SELECT = 'select'; -const STATUS_VERIFY = 'verify'; -const STATUS_WAIT = 'wait'; - -export default class VerificationsDialogController { - verificationOptions: IVerificationOptions; - tokenDestinationID: string; - sendingVerificationToken = false; - verificationTokenSent = false; - - availableVerificationMethods: IVerificationMap; - formData: any = {}; - inputs: { name: string, label: string }[]; - isDetailsView: boolean; - status: string; - tokenData: string; - viewDetailsEnabled: boolean; - verificationMethod: string; - verificationStatus: string; - - static $inject = [ - '$state', - '$timeout', - 'ConfigService', - 'HelpDeskService', - 'IasDialogService', - 'ObjectService', - 'personUserKey', - 'showRequiredOnly' - ]; - constructor(private $state: ui.IStateService, - private $timeout: ITimeoutService, - private configService: IHelpDeskConfigService, - private helpDeskService: IHelpDeskService, - private IasDialogService: any, - private objectService: ObjectService, - private personUserKey: string, - private showRequiredOnly: boolean) { - - this.isDetailsView = (this.$state.current.name === 'details'); - this.status = STATUS_WAIT; - this.verificationStatus = STATUS_NONE; - this.viewDetailsEnabled = false; - - this.helpDeskService - .checkVerification(this.personUserKey) - .then((response: IVerificationStatus) => { - this.verificationOptions = response.verificationOptions; - - if (!this.isDetailsView && response.passed) { - // If we're not on the details page already, and verifications have been passed, then just go right - // to the details page: - this.gotoDetailsPage(); - } - else { - this.status = STATUS_SELECT; - this.determineAvailableVerificationMethods(); - } - }) - .catch((reason: any) => { - alert(reason); - - this.status = STATUS_NONE; - this.verificationStatus = STATUS_NONE; - this.IasDialogService.close(); - }); - } - - determineAvailableVerificationMethods() { - this.availableVerificationMethods = []; - - const methodNames: string[] = this.showRequiredOnly ? - this.verificationOptions.verificationMethods.required : - this.verificationOptions.verificationMethods.optional; - - if (methodNames) { - for (let methodName of methodNames) { - this.availableVerificationMethods.push({ - name: methodName, - label: VERIFICATION_METHOD_LABELS[methodName] - }); - } - } - } - - clickOkButton() { - if (this.verificationStatus === STATUS_PASSED) { - this.IasDialogService.close(); - } - } - - private gotoDetailsPage() { - this.$timeout(() => { - this.IasDialogService.close(); - this.$state.go('details', {personId: this.personUserKey}); - }); - } - - selectVerificationMethod(method: string) { - this.verificationMethod = method; - - if (method === VERIFICATION_METHOD_NAMES.ATTRIBUTES) { - this.configService.getVerificationAttributes() - .then((response) => { - this.status = STATUS_VERIFY; - this.inputs = response; - - // Need to initialize the formData values to empty strings, otherwise null values will be sent to - // the server - for (let i = 0; i < this.inputs.length; i++) { - this.formData[this.inputs[i].name] = ''; - } - }); - } - else if (method === VERIFICATION_METHOD_NAMES.TOKEN) { - this.status = STATUS_VERIFY; - - try { - // Select the first destination in the list as default. - this.tokenDestinationID = this.verificationOptions.tokenDestinations[0].id; - } catch (error) {} - } - else if (method === VERIFICATION_METHOD_NAMES.OTP) { - this.status = STATUS_VERIFY; - } - } - - sendVerificationData() { - this.verificationStatus = STATUS_WAIT; - let data = {}; - this.objectService.assign(data, this.formData); - if (this.tokenData) { - this.objectService.assign(data, { - tokenData: this.tokenData - }); - } - this.helpDeskService.validateVerificationData(this.personUserKey, data, this.verificationMethod) - .then((response) => { - if (response.passed) { - this.verificationStatus = STATUS_PASSED; - } - else { - this.verificationStatus = STATUS_FAILED; - } - }) - .catch((reason) => { - this.verificationStatus = STATUS_FAILED; - }); - } - - onTokenDestinationChanged() { - this.verificationTokenSent = false; - } - - sendVerificationTokenToDestination() { - this.sendingVerificationToken = true; - this.verificationTokenSent = false; - - this.helpDeskService.sendVerificationToken(this.personUserKey, this.tokenDestinationID) - .then((response) => { - this.verificationTokenSent = true; - this.tokenData = (response as any).data.tokenData; - }) - .catch((reason) => { - this.verificationTokenSent = false; - alert(reason); - }) - .finally(() => { - this.sendingVerificationToken = false; - }); - } - - viewDetails() { - if (this.verificationStatus === STATUS_PASSED) { - this.gotoDetailsPage(); - } - } -} diff --git a/client/angular/src/modules/helpdesk/verifications-dialog.template.html b/client/angular/src/modules/helpdesk/verifications-dialog.template.html deleted file mode 100644 index e7b71e2fa3..0000000000 --- a/client/angular/src/modules/helpdesk/verifications-dialog.template.html +++ /dev/null @@ -1,133 +0,0 @@ - - - -
-
-
-
-
-
-
-

- - - {{ method.label | translate }} - - -
-
- {{ 'Button_Cancel' | translate }} -
- - - - -
- -
-
-
-
- -
-
-
-
- - -
-
-
- -
- - - {{ 'Button_SendToken' | translate }} - -
- -
-
- - -
- -
-
-

-
- - -
-
- -
- - {{ 'Button_Verify' | translate }} - -
- - -
-

-
-
- -
- - - {{ 'Button_OK' | translate }} - - {{ 'Button_Cancel' | translate }} -
- - - - -
-
-
diff --git a/client/angular/src/modules/peoplesearch/main.dev.ts b/client/angular/src/modules/peoplesearch/main.dev.ts deleted file mode 100644 index 8c3d86aef2..0000000000 --- a/client/angular/src/modules/peoplesearch/main.dev.ts +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Password Management Servlets (PWM) - * http://www.pwm-project.org - * - * Copyright (c) 2006-2009 Novell, Inc. - * Copyright (c) 2009-2021 The PWM Project - * - * 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 'angular'; -import 'angular-translate'; -import '@microfocus/ng-ias/dist/ng-ias'; - -import { bootstrap, module } from 'angular'; -import ConfigService from '../../services/peoplesearch-config.service.dev'; -import peopleSearchModule from './peoplesearch.module'; -import PeopleService from '../../services/people.service.dev'; -import PwmService from '../../services/pwm.service.dev'; -import routes from '../../routes'; -import routeErrorHandler from '../../route-error-handler'; -import uiRouter from '@uirouter/angularjs'; - - -module('app', [ - uiRouter, - peopleSearchModule, - 'pascalprecht.translate', - 'ng-ias' -]) - - .config(routes) - .config(['$translateProvider', ($translateProvider: angular.translate.ITranslateProvider) => { - $translateProvider.translations('en', require('../../i18n/translations_en.json')); - $translateProvider.useSanitizeValueStrategy('escapeParameters'); - $translateProvider.preferredLanguage('en'); - }]) - .run(routeErrorHandler) - .service('PeopleService', PeopleService) - .service('PwmService', PwmService) - .service('ConfigService', ConfigService); - -// Attach to the page document -bootstrap(document, ['app']); diff --git a/client/angular/src/modules/peoplesearch/main.ts b/client/angular/src/modules/peoplesearch/main.ts deleted file mode 100644 index c5a429be4f..0000000000 --- a/client/angular/src/modules/peoplesearch/main.ts +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Password Management Servlets (PWM) - * http://www.pwm-project.org - * - * Copyright (c) 2006-2009 Novell, Inc. - * Copyright (c) 2009-2021 The PWM Project - * - * 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 'angular'; -import 'angular-translate'; -import '@microfocus/ng-ias/dist/ng-ias'; - -// Add a polyfill for Set() for IE11, since it's used in peoplesearch-base.component.ts -import 'core-js/es/set'; - -import { bootstrap, module } from 'angular'; -import ConfigService from '../../services/peoplesearch-config.service'; -import peopleSearchModule from './peoplesearch.module'; -import PeopleService from '../../services/people.service'; -import PwmService from '../../services/pwm.service'; -import routes from '../../routes'; -import routeErrorHandler from '../../route-error-handler'; -import TranslationsLoaderFactory from '../../services/translations-loader.factory'; -import uiRouter from '@uirouter/angularjs'; - -module('app', [ - uiRouter, - peopleSearchModule, - 'pascalprecht.translate', - 'ng-ias' -]) - - .config(routes) - .config([ - '$translateProvider', - ($translateProvider: angular.translate.ITranslateProvider) => { - $translateProvider - .translations('fallback', require('../../i18n/translations_en.json')) - .useLoader('translationsLoader') - .useSanitizeValueStrategy('escapeParameters') - .preferredLanguage('en') - .fallbackLanguage('fallback') - .forceAsyncReload(true); - }]) - .config([ - '$locationProvider', ($locationProvider: angular.ILocationProvider) => { - $locationProvider.hashPrefix(''); - }]) - .run(routeErrorHandler) - .service('PeopleService', PeopleService) - .service('PwmService', PwmService) - .service('ConfigService', ConfigService) - .factory('translationsLoader', TranslationsLoaderFactory); - -// Attach to the page document, wait for PWM to load first -window['PWM_GLOBAL'].startupFunctions.push(() => { - bootstrap(document, ['app'], { strictDi: true }); -}); - diff --git a/client/angular/src/modules/peoplesearch/orgchart-email.component.html b/client/angular/src/modules/peoplesearch/orgchart-email.component.html deleted file mode 100644 index dae4d433d9..0000000000 --- a/client/angular/src/modules/peoplesearch/orgchart-email.component.html +++ /dev/null @@ -1,53 +0,0 @@ - - - -
-
-
-
-
-
-
-

-
- - - {{ 'Label_StartingFrom' | translate: { personName: $ctrl.personName } }} -
-
- - -
-
-
- {{ 'Button_SendEmail' | translate }} -
- - - -
-
-
diff --git a/client/angular/src/modules/peoplesearch/orgchart-email.component.scss b/client/angular/src/modules/peoplesearch/orgchart-email.component.scss deleted file mode 100644 index 7e00bb4ff2..0000000000 --- a/client/angular/src/modules/peoplesearch/orgchart-email.component.scss +++ /dev/null @@ -1,32 +0,0 @@ -/*! - * Password Management Servlets (PWM) - * http://www.pwm-project.org - * - * Copyright (c) 2006-2009 Novell, Inc. - * Copyright (c) 2009-2021 The PWM Project - * - * 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. - */ - -.ias-styles-root { - #teamMembersContainer { - display: flex; - flex-direction: column; - height: 150px; - - > textarea { - align-self: stretch; - flex-grow: 1; - } - } -} diff --git a/client/angular/src/modules/peoplesearch/orgchart-email.controller.ts b/client/angular/src/modules/peoplesearch/orgchart-email.controller.ts deleted file mode 100644 index d69cded035..0000000000 --- a/client/angular/src/modules/peoplesearch/orgchart-email.controller.ts +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Password Management Servlets (PWM) - * http://www.pwm-project.org - * - * Copyright (c) 2006-2009 Novell, Inc. - * Copyright (c) 2009-2021 The PWM Project - * - * 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 * as angular from 'angular'; -import {IPeopleService} from '../../services/people.service'; - -require('./orgchart-email.component.scss'); - -export default class OrgchartEmailController { - depth = '1'; - fetchingTeamMembers = false; - teamEmailList: string; - - static $inject = [ - '$window', - 'IasDialogService', - 'translateFilter', - 'peopleService', - 'maxDepth', - 'personName', - 'userKey' - ]; - constructor(private $window: angular.IWindowService, - private IasDialogService: any, - private translateFilter: (id: string) => string, - private peopleService: IPeopleService, - private maxDepth: number, - private personName: string, - private userKey: string) { - - this.fetchEmailList(); - } - - emailOrgChart() { - this.$window.location.href = `mailto:${this.teamEmailList}`; - this.IasDialogService.close(); - } - - depthChanged() { - this.fetchEmailList(); - } - - fetchEmailList() { - this.fetchingTeamMembers = true; - - this.peopleService.getTeamEmails(this.userKey, +this.depth) - .then((teamEmails: string[]) => { - this.teamEmailList = teamEmails.toString(); - }) - .finally(() => { - this.fetchingTeamMembers = false; - }); - } -} diff --git a/client/angular/src/modules/peoplesearch/orgchart-export.component.html b/client/angular/src/modules/peoplesearch/orgchart-export.component.html deleted file mode 100644 index d1164a6251..0000000000 --- a/client/angular/src/modules/peoplesearch/orgchart-export.component.html +++ /dev/null @@ -1,49 +0,0 @@ - - - -
-
-
-
-
-
-
-

- -
- - {{ 'Label_StartingFrom' | translate: { personName: $ctrl.personName } }} -
-
-
- {{ 'Button_Export' | translate }} -
- - - -
-
-
diff --git a/client/angular/src/modules/peoplesearch/orgchart-export.controller.ts b/client/angular/src/modules/peoplesearch/orgchart-export.controller.ts deleted file mode 100644 index 74a68ca349..0000000000 --- a/client/angular/src/modules/peoplesearch/orgchart-export.controller.ts +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Password Management Servlets (PWM) - * http://www.pwm-project.org - * - * Copyright (c) 2006-2009 Novell, Inc. - * Copyright (c) 2009-2021 The PWM Project - * - * 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 {IPwmService} from '../../services/pwm.service'; - -require('./orgchart-export.component.scss'); - -export default class OrgchartExportController { - depth = '1'; - - static $inject = [ - '$window', - 'IasDialogService', - 'translateFilter', - 'PwmService', - 'maxDepth', - 'personName', - 'userKey' - ]; - constructor(private $window: angular.IWindowService, - private IasDialogService: any, - private translateFilter: (id: string) => string, - private pwmService: IPwmService, - private maxDepth: number, - private personName: string, - private userKey: string) { - } - - exportOrgChart() { - this.$window.location.href = this.pwmService.getServerUrl('exportOrgChart', { - depth: this.depth, - userKey: this.userKey - }); - - this.IasDialogService.close(); - } -} diff --git a/client/angular/src/modules/peoplesearch/orgchart-search.component.html b/client/angular/src/modules/peoplesearch/orgchart-search.component.html deleted file mode 100644 index f1d0db9d36..0000000000 --- a/client/angular/src/modules/peoplesearch/orgchart-search.component.html +++ /dev/null @@ -1,64 +0,0 @@ - - - -
-

Organization

- - - - - - - - - - - - -
- - - - - - -
- - - - - - diff --git a/client/angular/src/modules/peoplesearch/orgchart-search.component.scss b/client/angular/src/modules/peoplesearch/orgchart-search.component.scss deleted file mode 100644 index 6c0104ba7e..0000000000 --- a/client/angular/src/modules/peoplesearch/orgchart-search.component.scss +++ /dev/null @@ -1,36 +0,0 @@ -/*! - * Password Management Servlets (PWM) - * http://www.pwm-project.org - * - * Copyright (c) 2006-2009 Novell, Inc. - * Copyright (c) 2009-2021 The PWM Project - * - * 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. - */ - -.ias-styles-root { - org-chart-search { - #page-content-title { - margin-bottom: 0; - } - - display: flex; - flex-flow: column nowrap; - height: 100%; - - > org-chart { - flex: 1 1; - overflow-x: auto; - } - } -} diff --git a/client/angular/src/modules/peoplesearch/orgchart-search.component.ts b/client/angular/src/modules/peoplesearch/orgchart-search.component.ts deleted file mode 100644 index ca5ebb69a6..0000000000 --- a/client/angular/src/modules/peoplesearch/orgchart-search.component.ts +++ /dev/null @@ -1,176 +0,0 @@ -/* - * Password Management Servlets (PWM) - * http://www.pwm-project.org - * - * Copyright (c) 2006-2009 Novell, Inc. - * Copyright (c) 2009-2021 The PWM Project - * - * 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 '../../component'; -import { IPeopleSearchConfigService } from '../../services/peoplesearch-config.service'; -import { IPeopleService } from '../../services/people.service'; -import IPwmService from '../../services/pwm.service'; -import {isArray, isString, IPromise, IQService, IScope, ITimeoutService, IWindowService} from 'angular'; -import LocalStorageService from '../../services/local-storage.service'; -import IOrgChartData from '../../models/orgchart-data.model'; -import { IPerson } from '../../models/person.model'; - -@Component({ - stylesheetUrl: require('./orgchart-search.component.scss'), - templateUrl: require('./orgchart-search.component.html') -}) -export default class OrgChartSearchComponent { - directReports: IPerson[]; - inputDebounce: number; - managementChain: IPerson[]; - assistant: IPerson; - person: IPerson; - photosEnabled: boolean; - managementChainLimit: number; - query: string; - searchTextLocalStorageKey: string; - printEnabled: boolean; - - static $inject = [ - '$state', - '$stateParams', - '$timeout', - '$window', - 'ConfigService', - 'LocalStorageService', - 'PeopleService', - 'PwmService' - ]; - constructor(private $state: angular.ui.IStateService, - private $stateParams: angular.ui.IStateParamsService, - private $timeout: ITimeoutService, - private $window: IWindowService, - private configService: IPeopleSearchConfigService, - private localStorageService: LocalStorageService, - private peopleService: IPeopleService, - private pwmService: IPwmService) { - this.searchTextLocalStorageKey = this.localStorageService.keys.SEARCH_TEXT; - this.inputDebounce = this.pwmService.ajaxTypingWait; - } - - $onInit(): void { - const self = this; - - this.configService.photosEnabled().then( - (photosEnabled: boolean) => { - this.photosEnabled = photosEnabled; - }); - - this.configService.getOrgChartMaxParents().then( - (orgChartMaxParents: number) => { - this.managementChainLimit = orgChartMaxParents; - }); - - this.configService.printingEnabled().then( - (printingEnabled: boolean) => { - this.printEnabled = printingEnabled; - }); - - this.query = this.getSearchText(); - - let personId: string = this.$stateParams['personId']; - - this.fetchOrgChartData(personId) - .then((orgChartData: IOrgChartData) => { - if (!orgChartData) { - return; - } - - // Override personId in case it was undefined - personId = orgChartData.self.userKey; - - if (orgChartData.assistant) { - self.assistant = orgChartData.assistant; - } - - self.peopleService.getPerson(personId) - .then((person: IPerson) => { - self.person = person; - }, - (error) => { - // TODO: handle error - }); - - self.peopleService.getManagementChain(personId, self.managementChainLimit) - .then((managementChain: IPerson[]) => { - self.managementChain = managementChain; - }, - (error) => { - // TODO: handle error - }); - - self.peopleService.getDirectReports(personId) - .then((directReports: IPerson[]) => { - self.directReports = directReports; - }, - (error) => { - // TODO: handle error - }); - }, - (error) => { - // TODO: handle error - }); - - // Once from ng-ias allows the autofocus attribute, we can remove this code - this.$timeout(() => { - document.getElementsByTagName('input')[0].focus(); - }); - } - - autoCompleteSearch(query: string): IPromise { - this.storeSearchText(query); - return this.peopleService.autoComplete(query); - } - - gotoSearchState(state: string) { - this.$state.go(state, { query: this.query }); - } - - onAutoCompleteItemSelected(person: IPerson): void { - this.storeSearchText(null); - this.$state.go('orgchart.search', { personId: person.userKey, query: null }); - } - - private fetchOrgChartData(personId): IPromise { - return this.peopleService.getOrgChartData(personId, true); - } - - private getSearchText(): string { - let param: string = this.$stateParams['query']; - // If multiple query parameters are defined, use the first one - if (isArray(param)) { - param = param[0].trim(); - } - else if (isString(param)) { - param = param.trim(); - } - - return param || this.localStorageService.getItem(this.searchTextLocalStorageKey); - } - - protected storeSearchText(query): void { - this.localStorageService.setItem(this.searchTextLocalStorageKey, query || ''); - } - - private printOrgChart(): void { - this.$window.print(); - } -} diff --git a/client/angular/src/modules/peoplesearch/orgchart.component.html b/client/angular/src/modules/peoplesearch/orgchart.component.html deleted file mode 100644 index 50bc6a7e9e..0000000000 --- a/client/angular/src/modules/peoplesearch/orgchart.component.html +++ /dev/null @@ -1,70 +0,0 @@ - - - -
-

Management

-
-
-
- - -
-
-
- -
- -
-
- -
-
- -
-

Direct Reports

-
- -
- - -
-
diff --git a/client/angular/src/modules/peoplesearch/orgchart.component.scss b/client/angular/src/modules/peoplesearch/orgchart.component.scss deleted file mode 100644 index 4f2d308865..0000000000 --- a/client/angular/src/modules/peoplesearch/orgchart.component.scss +++ /dev/null @@ -1,425 +0,0 @@ -/*! - * Password Management Servlets (PWM) - * http://www.pwm-project.org - * - * Copyright (c) 2006-2009 Novell, Inc. - * Copyright (c) 2009-2021 The PWM Project - * - * 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. - */ - -$org-chart-connector-color: #808080; -$org-chart-secondary-connector-color: #dae1e1; -$org-chart-text-color: #808080; - -$manager-connector-height: 16px; - -.ias-styles-root { - .reports { - background-color: #dae1e1; - border-radius: 2px; - color: #434c50; - font-size: 14px; - height: 25px; - line-height: 25px; - position: absolute; - right: 3px; - text-align: center; - top: 3px; - min-width: 35px; - } - - .assistant, - .manager { - .ias-tile { - border: none; - background-color: transparent; - display: block; - height: 96px; - padding: 0; - vertical-align: top; - width: 120px; - - > .ias-avatar { - border: 3px solid #808080; - border-radius: 100%; - display: block; - margin: 0 auto; - - &:hover { - //border-color: $person-card-border-color; - } - } - - > .reports { - right: 20px; - } - - > .ias-tile-content { - background-color: white; - display: block; - margin-top: 8px; - text-align: center; - width: 100%; - - :nth-child(n + 3) { - display: none; - } - } - } - - .reports { - right: 20px; - } - } - - .self { - &.ias-tile { - background-color: #ffffff; - border: 3px solid #808080; - border-radius: 3px; - height: auto; - min-height: 96px; - //width: 346px; - max-width: 100%; - - > .ias-avatar { - flex: 0 0 65px; - height: 65px; - width: 65px; - margin-bottom: 5px; - } - - > .ias-tile-content { - flex-flow: row nowrap; - } - } - } - - // (XS) Default display - org-chart { - display: block; - max-width: 100%; - - .assistant { - display: none; - } - - // (L) Wide enough to show main person offset to right and display managers horizontally - &.large { - > .org-chart-section { - text-align: left; - - > .ias-tile { - &[size="large"] { - margin: 0 0 0 128px; - } - } - - .org-chart-connector { - left: 172px; - margin: 0; - } - - &.managers { - margin-left: 0; - min-height: 128px; - overflow: hidden; - white-space: nowrap; - - > h3 { - left: 0; - position: absolute; - top: 6px; - } - - .org-chart-connector { - left: 42px; - } - - .manager { - display: inline-block; - text-align: left; - margin-left: 0; - margin-bottom: 32px; - - &:first-child { - margin-left: 115px; - - > .org-chart-connector { - bottom: initial; - top: 56px; - left: 57px; - height: 72px; - } - } - - &:not(:first-child) { - > .org-chart-connector { - background-color: $org-chart-secondary-connector-color; - bottom: initial; - height: 3px; - left: -37px; - top: 26px; - width: 69px; - } - - .ias-tile { - > .ias-avatar { - background-color: $org-chart-secondary-connector-color; - - &:not(:hover) { - border-color: $org-chart-secondary-connector-color; - } - } - > .ias-tile-content { - > .details { - > :first-child { - color: $org-chart-connector-color; - } - } - } - } - } - - &:not(:last-child) { - margin-right: 5px; - } - } - } - - &.self { - display: inline-flex; - - > .assistant { - display: inline-block; - margin-left: 33px; - position: relative; - - .ias-tile { - > .ias-avatar { - background-color: $org-chart-secondary-connector-color; - - &:not(:hover) { - border-color: $org-chart-secondary-connector-color; - } - } - } - - > .org-chart-connector { - background-color: transparent; - border-top: 3px dashed $org-chart-secondary-connector-color; - height: 0; - left: -37px; - top: 26px; - width: 69px; - } - } - } - } - } - - > .org-chart-section { - position: relative; - text-align: center; - width: 100%; - - &.direct-reports { - > .org-chart-connector { - height: 34px; - } - } - - &.managers { - min-height: 98px; - - &.overflow { - .manager { - &:last-child { - > .ias-tile { - > .reports { - display: none; - } - - > .ias-tile-content { - > .avatar { - //background-image: url('../../images/icons/m_circle-horz-menu_thin.svg'); - } - } - } - } - } - } - - .manager { - margin-bottom: $manager-connector-height; - position: relative; - text-align: center; - - &.empty-manager { - > .ias-tile { - cursor: initial; - - > .ias-tile-content { - > .avatar { - background: $org-chart-secondary-connector-color; - border-color: $org-chart-secondary-connector-color; - } - } - } - - > .org-chart-connector { - background-color: $org-chart-secondary-connector-color; - } - } - - > .ias-tile { - display: inline-block; - } - } - - .org-chart-connector { - bottom: -$manager-connector-height; - height: $manager-connector-height; - top: initial; - } - } - - > h3 { - color: $org-chart-text-color; - font-size: 12px; - font-weight: normal; - line-height: 14px; - margin: 0; - padding: 15px 0 5px 0; - text-align: left; - } - - > .ias-tile { - &[size="large"] { - margin: 0 auto; - } - } - - > .ias-grid { - border-top: 3px solid $org-chart-connector-color; - min-height: 90px; - padding-top: 5px; - } - - .org-chart-connector { - background-color: $org-chart-connector-color; - left: 0; - margin: 0 auto; - position: absolute; - right: 0; - top: 0; - width: 5px; - } - } - } - - [dir="rtl"] { - .assistant, - .manager { - .reports { - left: 20px; - right: auto; - } - } - - // (XS) Default display - org-chart { - > .org-chart-section { - > h3 { - text-align: right; - } - } - - // (L) Wide enough to show main person offset to right and display managers horizontally - &.large { - > .org-chart-section { - text-align: right; - - > .ias-tile { - &[size="large"] { - margin: 0 128px 0 0; - } - - .reports { - left: 3px; - right: initial; - } - } - - .org-chart-connector { - left: initial; - right: 172px; - } - - &.managers { - margin-left: auto; - - > h3 { - left: initial; - right: 0; - } - - .org-chart-connector { - left: initial; - right: 42px; - } - - .manager { - margin-left: 5px; - margin-right: 0; - text-align: right; - - &:first-child { - margin-right: 115px; - - > .org-chart-connector { - left: initial; - right: 57px; - } - } - - &:not(:first-child) { - > .org-chart-connector { - left: initial; - right: -37px; - } - } - - &:last-child { - margin-left: 0; - } - } - } - - &.self { - > .assistant { - margin-left: 0; - margin-right: 33px; - - > .org-chart-connector { - left: auto; - right: -37px; - } - } - } - } - } - } - } -} diff --git a/client/angular/src/modules/peoplesearch/orgchart.component.test.ts b/client/angular/src/modules/peoplesearch/orgchart.component.test.ts deleted file mode 100644 index fe8a921358..0000000000 --- a/client/angular/src/modules/peoplesearch/orgchart.component.test.ts +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Password Management Servlets (PWM) - * http://www.pwm-project.org - * - * Copyright (c) 2006-2009 Novell, Inc. - * Copyright (c) 2009-2021 The PWM Project - * - * 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. - */ - - -describe('testing OrgChartComponent', () => { - beforeEach(() => { - }); - - it('should pass', () => { - expect('foo').toEqual('foo'); - }); - - it('should fail', () => { - expect('foo').not.toEqual('bar'); - }); -}); diff --git a/client/angular/src/modules/peoplesearch/orgchart.component.ts b/client/angular/src/modules/peoplesearch/orgchart.component.ts deleted file mode 100644 index ed5570f1d5..0000000000 --- a/client/angular/src/modules/peoplesearch/orgchart.component.ts +++ /dev/null @@ -1,163 +0,0 @@ -/* - * Password Management Servlets (PWM) - * http://www.pwm-project.org - * - * Copyright (c) 2006-2009 Novell, Inc. - * Copyright (c) 2009-2021 The PWM Project - * - * 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 '../../component'; -import { element, IAugmentedJQuery, IFilterService, IScope, IWindowService } from 'angular'; -import ElementSizeService from '../../ux/element-size.service'; -import { IPerson } from '../../models/person.model'; -import {IPeopleSearchConfigService} from '../../services/peoplesearch-config.service'; - -export enum OrgChartSize { - ExtraSmall = 0, - Small = 365, - Large = 631 -} - -@Component({ - bindings: { - directReports: '<', - managementChain: '<', - assistant: '<', - person: '<', - showImages: '<' - }, - stylesheetUrl: require('./orgchart.component.scss'), - templateUrl: require('./orgchart.component.html') -}) -export default class OrgChartComponent { - directReports: IPerson[]; - elementWidth: number; - isLargeLayout: boolean; - managementChain: IPerson[]; - person: IPerson; - showDirectReports: boolean; - assistant: IPerson; - - private elementSize: OrgChartSize = OrgChartSize.ExtraSmall; - private maxVisibleManagers: number; - private visibleManagers: IPerson[]; - - static $inject = [ '$element', '$filter', '$scope', '$state', '$window', 'ConfigService', 'MfElementSizeService' ]; - constructor( - private $element: IAugmentedJQuery, - private $filter: IFilterService, - private $scope: IScope, - private $state: angular.ui.IStateService, - private $window: IWindowService, - private configService: IPeopleSearchConfigService, - private elementSizeService: ElementSizeService) { - } - - $onDestroy(): void { - // TODO: remove $window click listener - } - - $onInit(): void { - this.configService.orgChartShowChildCount().then( - (showChildCount: boolean) => { - this.showDirectReports = showChildCount; - }); - - // OrgChartComponent has different functionality at different widths. On element resize, we - // want to update the state of the component and trigger a $digest - this.elementSizeService - .watchWidth(this.$element, OrgChartSize) - .onResize(this.onResize.bind(this)); - - // In large displays managers are displayed in a row. Any time this property changes, we want - // to force our manager list to be recalculated in this.getManagementChain() so it returns the correct - // result at all element widths - this.$scope.$watch('$ctrl.maxVisibleManagers', () => { - this.resetManagerList(); - }); - } - - getManagerCardSize(): string { - return this.isLargeLayout ? 'small' : 'normal'; - } - - getManagementChain(): IPerson[] { - // Display managers in a row - if (this.isLargeLayout) { - // All managers can fit on screen - if (this.maxVisibleManagers >= this.managementChain.length) { - return this.managementChain; - } - - // Not all managers can fit on screen - if (!this.visibleManagers) { - // Show a blank manager as last manager in the chain in place of - // the last visible manager. Blank manager links to the new last visible manager. - this.visibleManagers = this.managementChain.slice(0, this.maxVisibleManagers - 1); - const lastManager = this.managementChain[this.maxVisibleManagers - 2]; - - this.visibleManagers.push(({ - userKey: lastManager.userKey, - photoURL: null, - displayNames: [] - })); - } - - return this.visibleManagers; - } - - // All managers can fit on screen in a column. Order is reversed for ease of rendering and style - return [].concat(this.managementChain).reverse(); - } - - hasDirectReports(): boolean { - return this.directReports && !!this.directReports.length; - } - - hasManagementChain(): boolean { - return this.managementChain && !!this.managementChain.length; - } - - onClickPerson(): void { - if (this.person) { - this.$state.go('orgchart.search.details', { personId: this.person.userKey }); - } - } - - selectPerson(userKey: string): void { - this.$state.go('orgchart.search', { personId: userKey }); - } - - showingOverflow(): boolean { - return this.visibleManagers && - this.managementChain && - this.visibleManagers.length < this.managementChain.length; - } - - private onResize(newValue: number): void { - this.isLargeLayout = (newValue >= OrgChartSize.Large); - if (!this.isLargeLayout) { - this.resetManagerList(); - } - this.maxVisibleManagers = Math.floor( - (newValue - 115 /* left margin */) / 125 /* card width + right margin */); - } - - // Remove all displayed managers so the list is updated on element resize - private resetManagerList(): void { - this.visibleManagers = null; - } -} diff --git a/client/angular/src/modules/peoplesearch/peoplesearch-base.component.ts b/client/angular/src/modules/peoplesearch/peoplesearch-base.component.ts deleted file mode 100644 index d2c1ddd006..0000000000 --- a/client/angular/src/modules/peoplesearch/peoplesearch-base.component.ts +++ /dev/null @@ -1,362 +0,0 @@ -/* - * Password Management Servlets (PWM) - * http://www.pwm-project.org - * - * Copyright (c) 2006-2009 Novell, Inc. - * Copyright (c) 2009-2021 The PWM Project - * - * 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 * as angular from 'angular'; -import {isArray, isString, IPromise, IQService, IScope, ITimeoutService} from 'angular'; -import { IPeopleSearchConfigService } from '../../services/peoplesearch-config.service'; -import { IPeopleService } from '../../services/people.service'; -import IPwmService from '../../services/pwm.service'; -import LocalStorageService from '../../services/local-storage.service'; -import { IPerson } from '../../models/person.model'; -import PromiseService from '../../services/promise.service'; -import SearchResult from '../../models/search-result.model'; -import {IAdvancedSearchConfig, IAdvancedSearchQuery, IAttributeMetadata} from '../../services/base-config.service'; -import CommonSearchService from '../../services/common-search.service'; - -abstract class PeopleSearchBaseComponent { - advancedSearch = false; - advancedSearchTags = {}; - advancedSearchEnabled: boolean; - advancedSearchMaxRows: number; - errorMessage: string; - inputDebounce: number; - orgChartEnabled: boolean; - protected pendingRequests: IPromise[] = []; - searchMessage: string; - searchResult: SearchResult; - query: string; - queries: IAdvancedSearchQuery[]; - searchTextLocalStorageKey: string; - searchViewLocalStorageKey: string; - - constructor(protected $q: IQService, - protected $scope: IScope, - protected $state: angular.ui.IStateService, - protected $stateParams: angular.ui.IStateParamsService, - protected $timeout: ITimeoutService, - protected $translate: angular.translate.ITranslateService, - protected configService: IPeopleSearchConfigService, - protected localStorageService: LocalStorageService, - protected peopleService: IPeopleService, - protected promiseService: PromiseService, - protected pwmService: IPwmService, - protected commonSearchService: CommonSearchService) { - this.searchTextLocalStorageKey = this.localStorageService.keys.SEARCH_TEXT; - this.searchViewLocalStorageKey = this.localStorageService.keys.SEARCH_VIEW; - - this.inputDebounce = this.pwmService.ajaxTypingWait; - } - - getMessage(): string { - return this.errorMessage || this.searchMessage; - } - - gotoOrgchart(): void { - this.gotoState('orgchart.index'); - } - - private gotoState(state: string): void { - this.$state.go(state); - } - - private initiateSearch() { - this.clearSearchMessage(); - this.clearErrorMessage(); - this.fetchData(); - } - - private onAdvancedSearchAttributeChanged(query: IAdvancedSearchQuery) { - // Make sure we set the default value if the type is select - const attributeMetadata: IAttributeMetadata = this.advancedSearchTags[query.key]; - if (attributeMetadata.type == 'select') { - query.value = this.commonSearchService.getDefaultValue(attributeMetadata); - } - - this.commonSearchService.setPsAdvSearchQueries(this.queries); - this.initiateSearch(); - } - - private onAdvancedSearchAttributeValueChanged() { - this.commonSearchService.setPsAdvSearchQueries(this.queries); - this.initiateSearch(); - } - - private onAdvancedSearchValueChanged() { - this.commonSearchService.setPsAdvSearchQueries(this.queries); - this.initiateSearch(); - } - - private onSearchTextChange(newValue: string, oldValue: string): void { - if (newValue === oldValue) { - return; - } - - this.storeSearchText(); - this.initiateSearch(); - } - - removeSearchTag(tagIndex: number): void { - this.queries.splice(tagIndex, 1); - this.commonSearchService.setPsAdvSearchQueries(this.queries); - - if (this.queries.length > 0) { - this.initiateSearch(); - } - else { - this.clearSearch(); - this.advancedSearch = false; - this.commonSearchService.setPsAdvancedSearchActive(this.advancedSearch); - } - } - - addSearchTag(): void { - const firstTagName = Object.keys(this.advancedSearchTags)[0]; - const attributeMetaData: IAttributeMetadata = this.advancedSearchTags[firstTagName]; - - const query: IAdvancedSearchQuery = { - key: attributeMetaData.attribute, - value: this.commonSearchService.getDefaultValue(attributeMetaData), - }; - - this.queries.push(query); - } - - selectPerson(person: IPerson): void { - this.$state.go('.details', { personId: person.userKey, query: this.query }); - } - - // We are still loading if there are pending requests but no search results have come back yet - get loading(): boolean { - return !!this.pendingRequests.length && !this.searchResult; - } - - protected abortPendingRequests() { - for (let index = 0; index < this.pendingRequests.length; index++) { - let pendingRequest = this.pendingRequests[index]; - this.promiseService.abort(pendingRequest); - } - - this.pendingRequests = []; - } - - protected removePendingRequest(promise: IPromise) { - let index = this.pendingRequests.indexOf(promise); - - if (index > -1) { - this.pendingRequests.splice(index, 1); - } - } - - protected setErrorMessage(message: string) { - this.errorMessage = message; - } - - protected clearErrorMessage() { - this.errorMessage = null; - } - - // If message is a string it will be translated. If it is a promise it will assign the string from the resolved - // promise - protected setSearchMessage(translationKey: string) { - if (!translationKey) { - this.clearSearchMessage(); - return; - } - - const self = this; - this.$translate(translationKey.toString()) - .then((translation: string) => { - self.searchMessage = translation; - }); - } - - protected clearSearch(): void { - this.query = null; - this.queries = []; - this.searchResult = null; - this.clearErrorMessage(); - this.clearSearchMessage(); - this.abortPendingRequests(); - } - - protected clearSearchMessage(): void { - this.searchMessage = null; - } - - abstract fetchData(): void; - - protected fetchSearchData(): IPromise { - this.abortPendingRequests(); - this.searchResult = null; - - const self = this; - let promise; - - if (this.advancedSearch) { - if (!this.queries || (this.queries.length === 1 && !this.queries[0].key)) { - this.clearSearch(); - return null; - } - - const keys = new Set(); - for (let searchQuery of this.queries) { - keys.add(searchQuery.key); - } - - const duplicateSearchAttrsFound = keys.size < this.queries.length; - if (duplicateSearchAttrsFound) { - this.$translate('Display_SearchAttrsUnique') - .then((translation: string) => { - this.searchMessage = translation; - }); - - return null; - } - - promise = this.peopleService.advancedSearch(this.queries); - } - else { - if (!this.query) { - this.clearSearch(); - return null; - } - - promise = this.peopleService.search(this.query); - } - - this.pendingRequests.push(promise); - - return promise - .then( - (searchResult: SearchResult) => { - self.clearErrorMessage(); - self.clearSearchMessage(); - - // Aborted request - if (!searchResult) { - return; - } - - // Too many results returned - if (searchResult.sizeExceeded) { - self.setSearchMessage('Display_SearchResultsExceeded'); - } - - // No results returned. Not an else if statement so that the more important message is presented - if (!searchResult.people.length) { - self.setSearchMessage('Display_SearchResultsNone'); - } - - return searchResult; - }, - (error) => { - self.setErrorMessage(error); - self.clearSearchMessage(); - }) - .finally(() => { - self.removePendingRequest(promise); - }); - } - - protected initialize(): IPromise { - return this.$q.all( - [ - // Determine whether org-chart should appear - this.configService.orgChartEnabled().then((orgChartEnabled: boolean) => { - this.orgChartEnabled = orgChartEnabled; - }), - this.configService.advancedSearchConfig().then((advancedSearchConfig: IAdvancedSearchConfig) => { - this.advancedSearchEnabled = advancedSearchConfig.enabled; - this.advancedSearchMaxRows = advancedSearchConfig.maxRows; - - for (let advancedSearchTag of advancedSearchConfig.attributes) { - this.advancedSearchTags[advancedSearchTag.attribute] = advancedSearchTag; - } - }) - ] - ).then(result => { - const searchQuery = this.getSearchQuery(); - if (searchQuery) { - // A search query has been passed in, disregard the current search state - this.query = searchQuery; - this.advancedSearch = false; - this.storeSearchText(); - this.commonSearchService.setPsAdvancedSearchActive(this.advancedSearch); - this.commonSearchService.setPsAdvSearchQueries([]); - } else { - this.query = this.getSearchText(); - this.advancedSearch = this.commonSearchService.isPsAdvancedSearchActive(); - this.queries = this.commonSearchService.getPsAdvSearchQueries(); - if (this.queries.length === 0) { - this.addSearchTag(); - } - } - - // Once from ng-ias allows the autofocus attribute, we can remove this code - this.$timeout(() => { - document.getElementsByTagName('input')[0].focus(); - }); - - this.$scope.$watch('$ctrl.query', (newValue: string, oldValue: string) => { - this.onSearchTextChange(newValue, oldValue); - }); - }); - } - - private getSearchQuery(): string { - let param: string = this.$stateParams['query']; - // If multiple query parameters are defined, use the first one - if (isArray(param)) { - param = param[0].trim(); - } - else if (isString(param)) { - param = param.trim(); - } - - return param; - } - - private getSearchText(): string { - return this.localStorageService.getItem(this.searchTextLocalStorageKey); - } - - protected storeSearchText(): void { - this.localStorageService.setItem(this.searchTextLocalStorageKey, this.query || ''); - } - - enableAdvancedSearch(): void { - this.clearSearch(); - this.addSearchTag(); - this.advancedSearch = true; - this.commonSearchService.setPsAdvancedSearchActive(this.advancedSearch); - } - - protected toggleView(state: string): void { - this.storeSearchView(state); - this.storeSearchText(); - this.gotoState(state); - } - - private storeSearchView(state: string) { - this.localStorageService.setItem(this.searchViewLocalStorageKey, state); - } -} - -export default PeopleSearchBaseComponent; diff --git a/client/angular/src/modules/peoplesearch/peoplesearch-cards.component.html b/client/angular/src/modules/peoplesearch/peoplesearch-cards.component.html deleted file mode 100644 index 1c5b1566fc..0000000000 --- a/client/angular/src/modules/peoplesearch/peoplesearch-cards.component.html +++ /dev/null @@ -1,100 +0,0 @@ - - - -
-
-

People Search

- - - - - - -
-
-
- - - - - - - - - - - -
- - - -
- -
- - - - - - -
- - - -
-
- -
-
-
-
- -
-
- - -
- - -
diff --git a/client/angular/src/modules/peoplesearch/peoplesearch-cards.component.scss b/client/angular/src/modules/peoplesearch/peoplesearch-cards.component.scss deleted file mode 100644 index efb22ee9c4..0000000000 --- a/client/angular/src/modules/peoplesearch/peoplesearch-cards.component.scss +++ /dev/null @@ -1,93 +0,0 @@ -/*! - * Password Management Servlets (PWM) - * http://www.pwm-project.org - * - * Copyright (c) 2006-2009 Novell, Inc. - * Copyright (c) 2009-2021 The PWM Project - * - * 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. - */ - -.ias-styles-root { - people-search-cards { - #page-content-title { - margin-bottom: 0; - } - - display: flex; - flex-flow: column nowrap; - height: 100%; - - // At medium size, cards are centered and no longer take up 100% width - &.medium { - > .people-search-component-content { - > .person-card-list { - > person-card { - margin: 0 auto; - display: block; - width: 272px; - } - } - } - } - - // At large size, cards fit next to each other - &.large { - > .people-search-component-content { - > .person-card-list { - text-align: left; - margin: 0; - - > person-card { - display: inline-block; - margin-right: 5px; - } - } - } - } - - > .people-search-component-content { - flex: 1 1; - overflow: auto; - text-align: center; - - > .person-card-list { - > person-card { - display: inline-block; - width: 100%; - - &:not(:last-child) { - margin-bottom: 5px; - } - } - } - } - } - - [dir="rtl"] { - people-search-cards { - &.large { - > .people-search-component-content { - .person-card-list { - text-align: right; - - > person-card { - margin-right: auto; - margin-left: 5px; - } - } - } - } - } - } -} diff --git a/client/angular/src/modules/peoplesearch/peoplesearch-cards.component.ts b/client/angular/src/modules/peoplesearch/peoplesearch-cards.component.ts deleted file mode 100644 index a67136082a..0000000000 --- a/client/angular/src/modules/peoplesearch/peoplesearch-cards.component.ts +++ /dev/null @@ -1,162 +0,0 @@ -/* - * Password Management Servlets (PWM) - * http://www.pwm-project.org - * - * Copyright (c) 2006-2009 Novell, Inc. - * Copyright (c) 2009-2021 The PWM Project - * - * 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 '../../component'; -import ElementSizeService from '../../ux/element-size.service'; -import IPeopleSearchConfigService from '../../services/peoplesearch-config.service'; -import IPeopleService from '../../services/people.service'; -import IPwmService from '../../services/pwm.service'; -import {isString, IAugmentedJQuery, IQService, IScope, ITimeoutService} from 'angular'; -import LocalStorageService from '../../services/local-storage.service'; -import PeopleSearchBaseComponent from './peoplesearch-base.component'; -import { IPerson } from '../../models/person.model'; -import PromiseService from '../../services/promise.service'; -import SearchResult from '../../models/search-result.model'; -import CommonSearchService from '../../services/common-search.service'; - -export enum PeopleSearchCardsSize { - Small = 0, - Medium = 365, - Large = 450 -} - -@Component({ - stylesheetUrl: require('./peoplesearch-cards.component.scss'), - templateUrl: require('./peoplesearch-cards.component.html') -}) -export default class PeopleSearchCardsComponent extends PeopleSearchBaseComponent { - photosEnabled: boolean; - - static $inject = [ - '$element', - '$q', - '$scope', - '$state', - '$stateParams', - '$timeout', - '$translate', - 'ConfigService', - 'LocalStorageService', - 'MfElementSizeService', - 'PeopleService', - 'PromiseService', - 'PwmService', - 'CommonSearchService' - ]; - constructor(private $element: IAugmentedJQuery, - $q: IQService, - $scope: IScope, - $state: angular.ui.IStateService, - $stateParams: angular.ui.IStateParamsService, - $timeout: ITimeoutService, - $translate: angular.translate.ITranslateService, - configService: IPeopleSearchConfigService, - localStorageService: LocalStorageService, - private elementSizeService: ElementSizeService, - peopleService: IPeopleService, - promiseService: PromiseService, - pwmService: IPwmService, - commonSearchService: CommonSearchService) { - super($q, - $scope, - $state, - $stateParams, - $timeout, - $translate, - configService, - localStorageService, - peopleService, - promiseService, - pwmService, - commonSearchService); - } - - $onDestroy(): void { - // TODO: remove $window click listener - } - - $onInit(): void { - this.initialize().then(() => { - this.fetchData(); - }); - - this.configService.photosEnabled().then((photosEnabled: boolean) => { - this.photosEnabled = photosEnabled; - }); - - this.elementSizeService.watchWidth(this.$element, PeopleSearchCardsSize); - } - - gotoTableView() { - this.toggleView('search.table'); - } - - fetchData() { - let searchResultPromise = this.fetchSearchData(); - if (searchResultPromise) { - - searchResultPromise.then(this.onSearchResult.bind(this)); - } - } - - private onSearchResult(searchResult: SearchResult): void { - // Aborted request - if (!searchResult) { - return; - } - - this.searchResult = new SearchResult({ - sizeExceeded: searchResult.sizeExceeded, - searchResults: [] - }); - - let self = this; - - this.pendingRequests = searchResult.people.map( - (person: IPerson) => { - // Store this promise because it is abortable - let promise = this.peopleService.getPerson(person.userKey); - - promise - .then((person: IPerson) => { - // Aborted request - if (!person) { - return; - } - - // searchResult may be overwritten by ESC->[LETTER] typed in after a search - // has started but before all calls to peopleService.getPerson have resolved - if (self.searchResult) { - self.searchResult.people.push(person); - } - }, - (error) => { - self.setErrorMessage(error); - }) - .finally(() => { - self.removePendingRequest(promise); - }); - - return promise; - } - ); - } -} diff --git a/client/angular/src/modules/peoplesearch/peoplesearch-table.component.html b/client/angular/src/modules/peoplesearch/peoplesearch-table.component.html deleted file mode 100644 index e23d4fd265..0000000000 --- a/client/angular/src/modules/peoplesearch/peoplesearch-table.component.html +++ /dev/null @@ -1,124 +0,0 @@ - - - -
-
-

People Search

- - - - - - -
-
-
- - - - - - - - - - - -
- - - -
- -
- - - - - - -
- - - - - - - -
-
- - {{value.label}} -
-
-
-
-
- -
-
-
-
- -
- - - - - - - - - - - - -
{{value.label}}
- -
- - -
diff --git a/client/angular/src/modules/peoplesearch/peoplesearch-table.component.scss b/client/angular/src/modules/peoplesearch/peoplesearch-table.component.scss deleted file mode 100644 index 1a005336f9..0000000000 --- a/client/angular/src/modules/peoplesearch/peoplesearch-table.component.scss +++ /dev/null @@ -1,51 +0,0 @@ -/*! - * Password Management Servlets (PWM) - * http://www.pwm-project.org - * - * Copyright (c) 2006-2009 Novell, Inc. - * Copyright (c) 2009-2021 The PWM Project - * - * 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. - */ - -.ias-styles-root { - people-search-table { - #page-content-title { - margin-bottom: 0; - } - - display: flex; - flex-flow: column nowrap; - height: 100%; - - > .people-search-component-content { - flex: 1 1; - overflow: auto; - position: relative; - text-align: center; - - .table-configuration-menu-toggle { - position: absolute; - top: 0; - right: 0; - } - } - } - - .ias-input-container > .checkbox-button > .ias-button.toggle-column-btn { - &:focus, &:hover { - background-color: transparent; - box-shadow: none; - } - } -} diff --git a/client/angular/src/modules/peoplesearch/peoplesearch-table.component.ts b/client/angular/src/modules/peoplesearch/peoplesearch-table.component.ts deleted file mode 100644 index fb345a89e4..0000000000 --- a/client/angular/src/modules/peoplesearch/peoplesearch-table.component.ts +++ /dev/null @@ -1,120 +0,0 @@ -/* - * Password Management Servlets (PWM) - * http://www.pwm-project.org - * - * Copyright (c) 2006-2009 Novell, Inc. - * Copyright (c) 2009-2021 The PWM Project - * - * 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 '../../component'; -import { IPeopleSearchConfigService } from '../../services/peoplesearch-config.service'; -import IPeopleService from '../../services/people.service'; -import IPwmService from '../../services/pwm.service'; -import {IQService, IScope, ITimeoutService} from 'angular'; -import LocalStorageService from '../../services/local-storage.service'; -import PeopleSearchBaseComponent from './peoplesearch-base.component'; -import PromiseService from '../../services/promise.service'; -import SearchResult from '../../models/search-result.model'; -import CommonSearchService from '../../services/common-search.service'; - -@Component({ - stylesheetUrl: require('./peoplesearch-table.component.scss'), - templateUrl: require('./peoplesearch-table.component.html') -}) -export default class PeopleSearchTableComponent extends PeopleSearchBaseComponent { - columnConfiguration: any; - - static $inject = [ - '$q', - '$scope', - '$state', - '$stateParams', - '$timeout', - '$translate', - 'ConfigService', - 'LocalStorageService', - 'PeopleService', - 'PromiseService', - 'PwmService', - 'CommonSearchService' - ]; - constructor($q: IQService, - $scope: IScope, - $state: angular.ui.IStateService, - $stateParams: angular.ui.IStateParamsService, - $timeout: ITimeoutService, - $translate: angular.translate.ITranslateService, - configService: IPeopleSearchConfigService, - localStorageService: LocalStorageService, - peopleService: IPeopleService, - promiseService: PromiseService, - pwmService: IPwmService, - commonSearchService: CommonSearchService) { - super($q, - $scope, - $state, - $stateParams, - $timeout, - $translate, - configService, - localStorageService, - peopleService, - promiseService, - pwmService, - commonSearchService); - } - - $onInit(): void { - this.initialize().then(() => { - this.fetchData(); - }); - - let self = this; - - // The table columns are dynamic and configured via a service - this.configService.getColumnConfig().then( - (columnConfiguration: any) => { - self.columnConfiguration = Object.keys(columnConfiguration).reduce( - function(accumulator, columnId) { - accumulator[columnId] = { - label: columnConfiguration[columnId], - visible: true - }; - - return accumulator; - }, - {}); - }, - (error) => { - self.setErrorMessage(error); - }); - } - - gotoCardsView() { - this.toggleView('search.cards'); - } - - fetchData() { - let searchResult = this.fetchSearchData(); - if (searchResult) { - searchResult.then(this.onSearchResult.bind(this)); - } - } - - private onSearchResult(searchResult: SearchResult): void { - this.searchResult = searchResult; - } -} diff --git a/client/angular/src/modules/peoplesearch/peoplesearch.module.ts b/client/angular/src/modules/peoplesearch/peoplesearch.module.ts deleted file mode 100644 index 98b04986e3..0000000000 --- a/client/angular/src/modules/peoplesearch/peoplesearch.module.ts +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Password Management Servlets (PWM) - * http://www.pwm-project.org - * - * Copyright (c) 2006-2009 Novell, Inc. - * Copyright (c) 2009-2021 The PWM Project - * - * 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. - */ - -// These need to be at the top so imported components can override the default styling -require('../../styles.scss'); -require('./peoplesearch.scss'); - -import 'angular-aria'; - -import {IComponentOptions, module} from 'angular'; -import { HighlightFilter } from './string.filters'; -import { FullNameFilter } from './person.filters'; -import OrgChartComponent from './orgchart.component'; -import OrgChartSearchComponent from './orgchart-search.component'; -import PeopleSearchTableComponent from './peoplesearch-table.component'; -import PeopleSearchCardsComponent from './peoplesearch-cards.component'; -import PersonCardDirective from './person-card.component'; -import PersonDetailsDialogComponent from './person-details-dialog.component'; -import LocalStorageService from '../../services/local-storage.service'; -import PromiseService from '../../services/promise.service'; -import uxModule from '../../ux/ux.module'; -import CommonSearchService from '../../services/common-search.service'; -import OrgchartExportController from './orgchart-export.controller'; -import OrgchartEmailController from './orgchart-email.controller'; - -const moduleName = 'people-search'; - -module(moduleName, [ - 'ngAria', - 'pascalprecht.translate', - uxModule -]) - .filter('fullName', FullNameFilter) - .filter('highlight', HighlightFilter) - .component('orgChart', OrgChartComponent as IComponentOptions) - .component('orgChartSearch', OrgChartSearchComponent as IComponentOptions) - .directive('personCard', PersonCardDirective) - .component('peopleSearchTable', PeopleSearchTableComponent as IComponentOptions) - .component('peopleSearchCards', PeopleSearchCardsComponent as IComponentOptions) - .component('personDetailsDialogComponent', PersonDetailsDialogComponent as IComponentOptions) - .controller('OrgchartExportController', OrgchartExportController) - .controller('OrgchartEmailController', OrgchartEmailController) - .service('PromiseService', PromiseService) - .service('LocalStorageService', LocalStorageService) - .service('CommonSearchService', CommonSearchService); - -export default moduleName; diff --git a/client/angular/src/modules/peoplesearch/peoplesearch.scss b/client/angular/src/modules/peoplesearch/peoplesearch.scss deleted file mode 100644 index da26529031..0000000000 --- a/client/angular/src/modules/peoplesearch/peoplesearch.scss +++ /dev/null @@ -1,142 +0,0 @@ -/*! - * Password Management Servlets (PWM) - * http://www.pwm-project.org - * - * Copyright (c) 2006-2009 Novell, Inc. - * Copyright (c) 2009-2021 The PWM Project - * - * 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. - */ - -.ias-styles-root { - .help-desk-search-component, - .people-search-component { - height: 100%; - - > ui-view { - height: 100%; - overflow: auto; - } - - .peoplesearch-header { - display: flex; - align-items: flex-start; - - .basic-search-container { - display: flex; - align-items: center; - margin-bottom: 15px; - - > * + * { - margin-left: 10px; - } - } - - .advanced-search-container { - display: flex; - flex-direction: column; - align-items: flex-start; - margin-bottom: 15px; - - > * + * { - margin-top: 5px; - } - - & + div { - margin-top: 15px; - } - - select.attribute-value { - min-width: 210px; - } - } - } - - .search-info-container { - text-align: left; - - .search-info { - background-color: #fff6ce; - border: 1px solid #dae1e1; - border-radius: 3px; - color: #808080; - display: inline-block; - font-size: 14px; - margin: 0 auto 10px; - padding: 5px; - - &.loading { - background-color: white; - } - } - } - } - - .highlight { - color: #01a9e7; - } - - .ias-avatar { - background: transparent url('../../../images/user.png') no-repeat center center; - background-size: contain; - } - - .ias-header { - max-width: 100%; - } - - .checkbox-button { - align-items: center; - display: flex; - flex-flow: row nowrap; - } - - .icon-divider { - &.vertical { - background-color: rgba(#808080, .5); - height: 25px; - margin: 0 5px; - width: 1px; - } - } - - .single-line { - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - } - - @media (min-width: 768px) { - .ias-dialog { - .ias-dialog-container { - max-width: 100%; - width: 375px; - } - } - } - - [dir="rtl"] { - .ias-search { - > .ias-icon-button { - right: auto; // Can remove once https://github.com/MicroFocus/ux-ias/issues/18 is fixed - } - } - - .help-desk-search-component, - .people-search-component { - .search-info-container { - text-align: right; - } - } - } -} diff --git a/client/angular/src/modules/peoplesearch/person-card.component.html b/client/angular/src/modules/peoplesearch/person-card.component.html deleted file mode 100644 index 072e873259..0000000000 --- a/client/angular/src/modules/peoplesearch/person-card.component.html +++ /dev/null @@ -1,52 +0,0 @@ - - - -
-
- -
- -
-

-
-
- -
-

-
-
- -
-

-
-
-
diff --git a/client/angular/src/modules/peoplesearch/person-card.component.ts b/client/angular/src/modules/peoplesearch/person-card.component.ts deleted file mode 100644 index 9eeea27d0d..0000000000 --- a/client/angular/src/modules/peoplesearch/person-card.component.ts +++ /dev/null @@ -1,132 +0,0 @@ -/* - * Password Management Servlets (PWM) - * http://www.pwm-project.org - * - * Copyright (c) 2006-2009 Novell, Inc. - * Copyright (c) 2009-2021 The PWM Project - * - * 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 {IAugmentedJQuery} from 'angular'; -import { IPerson } from '../../models/person.model'; -import { IPeopleService } from '../../services/people.service'; - -const templateUrl = require('./person-card.component.html'); - -class PersonCardController { - private details: any[]; // For large style cards - private disableFocus: boolean; - private person: IPerson; - private directReports: IPerson[]; - private size: string; - private showDirectReportCount: boolean; - private showImage: boolean; - - static $inject = ['$element', 'PeopleService']; - constructor(private $element: IAugmentedJQuery, private peopleService: IPeopleService) { - this.details = []; - this.size = 'medium'; - } - - $onInit(): void { - if (!this.disableFocus) { - this.$element[0].tabIndex = 0; - this.$element.on('keydown', this.onKeyDown.bind(this)); - } - } - - $onChanges(): void { - if (this.person) { - this.setDisplayData(); - - if (this.showDirectReportCount) { - this.peopleService.getNumberOfDirectReports(this.person.userKey) - .then( - (numDirectReports) => { - this.person.numDirectReports = numDirectReports; - }, - (error) => { - // TODO: handle error. NOOP is fine for now because it won't try to display the result - }); - } - } - } - - $onDestroy(): void { - this.$element.off('keydown', this.onKeyDown.bind(this)); - } - - getAvatarStyle(): any { - if (!this.showImage) { - return { 'background-image': 'url()' }; - } - - if (this.person && this.person.photoURL) { - return { 'background-image': 'url(' + this.person.photoURL + ')' }; - } - - return {}; - } - - isSmall(): boolean { - return this.size === 'small'; - } - - get numDirectReportsVisible(): boolean { - return this.showDirectReportCount && this.person && !!this.person.numDirectReports; - } - - private onKeyDown(event: KeyboardEvent): void { - if (event.keyCode === 13 || event.keyCode === 32) { // 13 = Enter, 32 = Space - this.$element.triggerHandler('click'); - - event.preventDefault(); - event.stopImmediatePropagation(); - } - } - - private setDisplayData(): void { - if (this.person.detail) { - this.details = Object - .keys(this.person.detail) - .map((key: string) => { - return this.person.detail[key]; - }); - } - - if (this.directReports) { - this.person.numDirectReports = this.directReports.length; - } - } -} - -export default function PersonCardDirectiveFactory() { - return { - bindToController: true, - controller: PersonCardController, - controllerAs: '$ctrl', - restrict: 'E', - replace: true, - scope: { - directReports: '<', - disableFocus: '<', - person: '<', - showImage: '<', - size: '@', - showDirectReportCount: '<' - }, - templateUrl: templateUrl - }; -} diff --git a/client/angular/src/modules/peoplesearch/person-details-dialog.component.html b/client/angular/src/modules/peoplesearch/person-details-dialog.component.html deleted file mode 100644 index f6f680c9c1..0000000000 --- a/client/angular/src/modules/peoplesearch/person-details-dialog.component.html +++ /dev/null @@ -1,110 +0,0 @@ - - -
-
-
-
-
-
- -
-

- - - - -
-
-
-
-
- - - Organizational Chart - - - - - - - -
-
-
- - - - - - - - - - - -
-
    -
  • - -
  • -
-
-
-
    -
  • - -
  • -
-
-
- -
-
-
-
-
-
-
diff --git a/client/angular/src/modules/peoplesearch/person-details-dialog.component.scss b/client/angular/src/modules/peoplesearch/person-details-dialog.component.scss deleted file mode 100644 index aa3295d51f..0000000000 --- a/client/angular/src/modules/peoplesearch/person-details-dialog.component.scss +++ /dev/null @@ -1,135 +0,0 @@ -/*! - * Password Management Servlets (PWM) - * http://www.pwm-project.org - * - * Copyright (c) 2006-2009 Novell, Inc. - * Copyright (c) 2009-2021 The PWM Project - * - * 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. - */ - -.ias-styles-root { - .person-dialog-actions { - clear: both; - margin-top: 15px; - text-align: center; - - > .ias-icon-text-button + .ias-icon-button { - margin-left: 20px; - } - - > .ias-icon-button + .ias-icon-button { - margin-left: 10px; - } - } - - .person-details-dialog { - display: flex; - align-items: center; - justify-content: center; - text-align: left; - overflow: hidden; - - .ias-dialog-container { - display: flex; - margin: 0; - padding: 0; - position: relative; - top: auto; - transform: none; - - > .ias-dialog-content { - display: grid; - grid-template-rows: max-content 1fr; - flex-grow: 1; - - > .person-details-content { - overflow: auto; - } - } - } - - .ias-avatar { - float: left; - height: 100px; - margin-bottom: 15px; - margin-right: 15px; - width: 100px; - } - } - - .person-details-dialog-header { - background-color: #eef2f2; - color: #434c50; - font-size: 12px; - line-height: 15px; - padding: 15px; - } - - [data-browserType=ie] .person-details-dialog-header > .ias-header > h2 { - width: 185px; - } - - .person-details-content { - padding: 15px; - } - - .details-table { - border: none; - border-collapse: collapse; - width: 100%; - - tr { - height: 25px; - - td { - border: none; - font-size: 12px; - height: 19px; - text-align: left; - - &:first-child { - color: #949494; - width: 100px; - text-align: right; - padding: 3px 0; - } - - &:last-child { - padding: 3px 15px; - } - - ul { - list-style: none; - margin: 0; - padding: 0; - - > li { - margin: 0; - padding: 0; - } - } - } - } - - .details-table-search-link { - display: inline-block; - font-size: 25px; - vertical-align: middle; - - .ias-icon { - display: block; - } - } - } -} diff --git a/client/angular/src/modules/peoplesearch/person-details-dialog.component.ts b/client/angular/src/modules/peoplesearch/person-details-dialog.component.ts deleted file mode 100644 index 0b101ab74f..0000000000 --- a/client/angular/src/modules/peoplesearch/person-details-dialog.component.ts +++ /dev/null @@ -1,153 +0,0 @@ -/* - * Password Management Servlets (PWM) - * http://www.pwm-project.org - * - * Copyright (c) 2006-2009 Novell, Inc. - * Copyright (c) 2009-2021 The PWM Project - * - * 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 '../../component'; -import {IPeopleSearchConfigService, IPersonDetailsConfig} from '../../services/peoplesearch-config.service'; -import { IPeopleService } from '../../services/people.service'; -import {IAugmentedJQuery, ITimeoutService, noop} from 'angular'; -import { IPerson } from '../../models/person.model'; -import {IChangePasswordSuccess} from '../../components/changepassword/success-change-password.controller'; - -let orgchartExportTemplateUrl = require('./orgchart-export.component.html'); -let orgchartEmailTemplateUrl = require('./orgchart-email.component.html'); - -@Component({ - stylesheetUrl: require('./person-details-dialog.component.scss'), - templateUrl: require('./person-details-dialog.component.html') -}) -export default class PersonDetailsDialogComponent { - person: IPerson; - photosEnabled: boolean; - orgChartEnabled: boolean; - exportEnabled: boolean; - emailTeamEnabled: boolean; - maxExportDepth: number; - maxEmailDepth: number; - - static $inject = [ - '$element', - '$state', - '$stateParams', - '$timeout', - 'ConfigService', - 'PeopleService', - 'IasDialogService' - ]; - - constructor(private $element: IAugmentedJQuery, - private $state: angular.ui.IStateService, - private $stateParams: angular.ui.IStateParamsService, - private $timeout: ITimeoutService, - private configService: IPeopleSearchConfigService, - private peopleService: IPeopleService, - private IasDialogService: any) { - } - - $onInit(): void { - const personId = this.$stateParams['personId']; - - this.configService.orgChartEnabled().then((orgChartEnabled: boolean) => { - this.orgChartEnabled = orgChartEnabled; - }); - - this.configService.photosEnabled().then((photosEnabled: boolean) => { - this.photosEnabled = photosEnabled; - }); - - this.configService.personDetailsConfig().then((personDetailsConfig: IPersonDetailsConfig) => { - this.photosEnabled = personDetailsConfig.photosEnabled; - this.orgChartEnabled = personDetailsConfig.orgChartEnabled; - this.exportEnabled = personDetailsConfig.exportEnabled; - this.emailTeamEnabled = personDetailsConfig.emailTeamEnabled; - this.maxExportDepth = personDetailsConfig.maxExportDepth; - this.maxEmailDepth = personDetailsConfig.maxEmailDepth; - }); - - this.peopleService - .getPerson(personId) - .then( - (person: IPerson) => { - this.person = person; - }, - (error) => { - // TODO: Handle error. NOOP for now will not assign person - }); - } - - $postLink() { - const self = this; - this.$timeout(() => { - self.$element.find('button')[0].focus(); - }, 100); - } - - closeDialog(): void { - this.$state.go('^', { query: this.$stateParams['query'] }); - } - - getAvatarStyle(): any { - if (!this.person || !this.person.photoURL || !this.photosEnabled) { - return null; - } - - return { 'background-image': 'url(' + this.person.photoURL + ')' }; - } - - gotoOrgChart(): void { - this.$state.go('orgchart.search', { personId: this.person.userKey }); - } - - getPersonDetailsUrl(personId: string): string { - return this.$state.href('.', { personId: personId }, { inherit: true, }); - } - - searchText(text: string): void { - this.$state.go('search.table', { query: text }); - } - - beginExport() { - this.IasDialogService - .open({ - controller: 'OrgchartExportController as $ctrl', - templateUrl: orgchartExportTemplateUrl, - locals: { - peopleService: this.peopleService, - maxDepth: this.maxExportDepth, - personName: this.person.displayNames[0], - userKey: this.person.userKey - } - }); - } - - beginEmail() { - this.IasDialogService - .open({ - controller: 'OrgchartEmailController as $ctrl', - templateUrl: orgchartEmailTemplateUrl, - locals: { - peopleService: this.peopleService, - maxDepth: this.maxEmailDepth, - personName: this.person.displayNames[0], - userKey: this.person.userKey - } - }); - } -} diff --git a/client/angular/src/modules/peoplesearch/string.filters.ts b/client/angular/src/modules/peoplesearch/string.filters.ts deleted file mode 100644 index 24e2bee834..0000000000 --- a/client/angular/src/modules/peoplesearch/string.filters.ts +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Password Management Servlets (PWM) - * http://www.pwm-project.org - * - * Copyright (c) 2006-2009 Novell, Inc. - * Copyright (c) 2009-2021 The PWM Project - * - * 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. - */ - - -export function HighlightFilter() { - return (input: string, searchText: string): string => { - if (!input || !searchText || !searchText.length) { - return input; - } - - const searchTextRegExp = new RegExp(searchText, 'gi'); - - return input.replace(searchTextRegExp, function (match) { - return `${match}`; - }); - }; -} diff --git a/client/angular/src/route-error-handler.ts b/client/angular/src/route-error-handler.ts deleted file mode 100644 index 475a9d585f..0000000000 --- a/client/angular/src/route-error-handler.ts +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Password Management Servlets (PWM) - * http://www.pwm-project.org - * - * Copyright (c) 2006-2009 Novell, Inc. - * Copyright (c) 2009-2021 The PWM Project - * - * 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 { IAngularEvent, IRootScopeService } from 'angular'; -import { IStateService } from 'angular-ui-router'; - -export default [ - '$transitions', - '$state', - ($transitions, $state: IStateService) => { - $transitions.onError({}, (transition) => { - if (transition._error === 'OrgChart disabled') { - $state.go('search.cards'); - } - }); - } -]; diff --git a/client/angular/src/routes.ts b/client/angular/src/routes.ts deleted file mode 100644 index 912f51cd61..0000000000 --- a/client/angular/src/routes.ts +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Password Management Servlets (PWM) - * http://www.pwm-project.org - * - * Copyright (c) 2006-2009 Novell, Inc. - * Copyright (c) 2009-2021 The PWM Project - * - * 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 { IPeopleSearchConfigService } from './services/peoplesearch-config.service'; -import { IQService } from 'angular'; -import LocalStorageService from './services/local-storage.service'; - -export default [ - '$stateProvider', - '$urlRouterProvider', - ( - $stateProvider: angular.ui.IStateProvider, - $urlRouterProvider: angular.ui.IUrlRouterProvider - ) => { - $urlRouterProvider.otherwise( - ($injector: angular.auto.IInjectorService, $location: angular.ILocationService) => { - let $state: angular.ui.IStateService = $injector.get('$state'); - let localStorageService: LocalStorageService = - $injector.get('LocalStorageService'); - - let storedView = localStorageService.getItem(localStorageService.keys.SEARCH_VIEW); - - if (storedView) { - $state.go(storedView); - } - else { - $location.url('search/cards'); - } - }); - - $stateProvider.state('search', { - url: '/search?query', - abstract: true, - template: '
', - }); - $stateProvider.state('search.table', { url: '/table', component: 'peopleSearchTable' }); - $stateProvider.state('search.cards', { url: '/cards', component: 'peopleSearchCards' }); - $stateProvider.state('search.table.details', { - url: '/details/{personId}', - component: 'personDetailsDialogComponent' - }); - $stateProvider.state('search.cards.details', { - url: '/details/{personId}', - component: 'personDetailsDialogComponent' - }); - $stateProvider.state('orgchart', { url: '/orgchart?query', - abstract: true, - template: '', - resolve: { - enabled: [ - '$q', - 'ConfigService', - ($q: IQService, configService: IPeopleSearchConfigService) => { - let deferred = $q.defer(); - - configService - .orgChartEnabled() - .then((orgChartEnabled: boolean) => { - if (!orgChartEnabled) { - deferred.reject('OrgChart disabled'); - } - else { - deferred.resolve(); - } - }); - - return deferred.promise; - }] - } - }); - $stateProvider.state('orgchart.index', { url: '', component: 'orgChartSearch' }); - $stateProvider.state('orgchart.search', { url: '/{personId}', component: 'orgChartSearch' }); - $stateProvider.state('orgchart.search.details', { url: '/details', component: 'personDetailsDialogComponent' }); - }]; diff --git a/client/angular/src/services/base-config.service.dev.ts b/client/angular/src/services/base-config.service.dev.ts deleted file mode 100644 index 1a54b2e068..0000000000 --- a/client/angular/src/services/base-config.service.dev.ts +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Password Management Servlets (PWM) - * http://www.pwm-project.org - * - * Copyright (c) 2006-2009 Novell, Inc. - * Copyright (c) 2009-2021 The PWM Project - * - * 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 {IConfigService} from './base-config.service'; -import {IPromise, IQService} from 'angular'; - -export abstract class ConfigBaseService implements IConfigService { - abstract getColumnConfig(): IPromise; - - constructor(protected $q: IQService) { - } - - getValue(key: string): IPromise { - return null; - } - - photosEnabled(): IPromise { - return this.$q.resolve(true); - } -} diff --git a/client/angular/src/services/base-config.service.ts b/client/angular/src/services/base-config.service.ts deleted file mode 100644 index 5a83272884..0000000000 --- a/client/angular/src/services/base-config.service.ts +++ /dev/null @@ -1,106 +0,0 @@ -/* - * Password Management Servlets (PWM) - * http://www.pwm-project.org - * - * Copyright (c) 2006-2009 Novell, Inc. - * Copyright (c) 2009-2021 The PWM Project - * - * 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 {IHttpService, ILogService, IPromise, IQService} from 'angular'; -import {IPwmService} from './pwm.service'; - -const COLUMN_CONFIG = 'searchColumns'; -export const PHOTO_ENABLED = 'enablePhoto'; -const PRINTING_ENABLED = 'enableOrgChartPrinting'; - -export const ADVANCED_SEARCH_ENABLED = 'enableAdvancedSearch'; -export const ADVANCED_SEARCH_MAX_ATTRIBUTES = 'maxAdvancedSearchAttributes'; -export const ADVANCED_SEARCH_ATTRIBUTES = 'advancedSearchAttributes'; - -export interface IConfigService { - getColumnConfig(): IPromise; - getValue(key: string): IPromise; - photosEnabled(): IPromise; - printingEnabled(): IPromise; -} - -export interface IAttributeMetadata { - attribute: string; - label: string; - type: string; - options: any; -} - -export interface IAdvancedSearchConfig { - enabled: boolean; - maxRows: number; - attributes: IAttributeMetadata[]; -} - -export interface IAdvancedSearchQuery { - key: string; - value: string; -} - -export abstract class ConfigBaseService implements IConfigService { - - constructor(protected $http: IHttpService, - protected $log: ILogService, - protected $q: IQService, - protected pwmService: IPwmService) { - } - - getColumnConfig(): IPromise { - return this.getValue(COLUMN_CONFIG); - } - - private getEndpointValue(endpoint: string, key: string): IPromise { - return this.$http - .get(endpoint, { cache: true }) - .then((response) => { - if (response.data['error']) { - return this.handlePwmError(response); - } - - return response.data['data'][key]; - }, this.handleHttpError); - } - - getValue(key: string): IPromise { - let endpoint: string = this.pwmService.getServerUrl('clientData'); - return this.getEndpointValue(endpoint, key); - } - - private handleHttpError(error): void { - this.$log.error(error); - } - - private handlePwmError(response): IPromise { - const errorMessage = `${response.data['errorCode']}: ${response.data['errorMessage']}`; - this.$log.error(errorMessage); - - return this.$q.reject(response.data['errorMessage']); - } - - photosEnabled(): IPromise { - return this.getValue(PHOTO_ENABLED) - .then(null, () => { return true; }); // On error use default - } - - printingEnabled(): IPromise { - return this.getValue(PRINTING_ENABLED) - .then(null, () => { return true; }); // On error use default - } -} diff --git a/client/angular/src/services/common-search.service.test.ts b/client/angular/src/services/common-search.service.test.ts deleted file mode 100644 index 5b3974acf0..0000000000 --- a/client/angular/src/services/common-search.service.test.ts +++ /dev/null @@ -1,126 +0,0 @@ -/* - * Password Management Servlets (PWM) - * http://www.pwm-project.org - * - * Copyright (c) 2006-2009 Novell, Inc. - * Copyright (c) 2009-2021 The PWM Project - * - * 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 {IHttpService, ILogService, IQService, IWindowService, module} from 'angular'; -import LocalStorageService from './local-storage.service'; -import CommonSearchService from './common-search.service'; -import {anyNumber, anyString, deepEqual, instance, mock, strictEqual, verify, when} from 'ts-mockito'; -import {IAdvancedSearchQuery} from './base-config.service'; - -describe('In common-search.service.test.ts', () => { - beforeEach(() => { - module('app', []); - }); - - // Define some angular objects we'll grab from angular-mocks - let $log: ILogService; - - beforeEach(inject((_$http_, _$log_, _$q_, _$window_) => { - $log = _$log_; - $log.info('This is an info message'); - })); - - it('Pulls search queries from local storage, or empty array if undefined or bad data', (done: DoneFn) => { - let mockLocalStorageService = mock(LocalStorageService); - when(mockLocalStorageService.getItem('undefinedScenario')).thenReturn(undefined); - when(mockLocalStorageService.getItem('bogusScenario')).thenReturn('bogus'); - when(mockLocalStorageService.getItem('notArrayScenario')).thenReturn('{"key":"foo","value":"bar"}'); - when(mockLocalStorageService.getItem('goodScenario')).thenReturn(JSON.stringify([ - { - key: 'foo', - value: 'bar' - } - ])); - - const commonSearchService = new CommonSearchService(instance(mockLocalStorageService)); - - expect(commonSearchService.getAdvSearchQueries('undefinedScenario')).toEqual([]); - expect(commonSearchService.getAdvSearchQueries('bogusScenario')).toEqual([]); - expect(commonSearchService.getAdvSearchQueries('notArrayScenario')).toEqual([]); - expect(commonSearchService.getAdvSearchQueries('goodScenario')).toContain({ - key: 'foo', - value: 'bar' - }); - - done(); - }); - - it('Stores search queries into local storage, or does nothing if bad data', (done: DoneFn) => { - let mockLocalStorageService = mock(LocalStorageService); - - const queries: IAdvancedSearchQuery[] = [ - {key: 'foo', value: 'one'}, - {key: 'bar', value: 'two'}, - {key: 'baz', value: 'three'} - ]; - - const commonSearchService = new CommonSearchService(instance(mockLocalStorageService)); - - commonSearchService.setAdvSearchQueries('nullData', null); - commonSearchService.setAdvSearchQueries('undefinedData', undefined); - commonSearchService.setAdvSearchQueries('emptyArray', []); - commonSearchService.setAdvSearchQueries('lotsOfData', queries); - - verify(mockLocalStorageService.removeItem('nullData')).called(); - verify(mockLocalStorageService.removeItem('undefinedData')).called(); - verify(mockLocalStorageService.setItem('emptyArray', '[]')).called(); - verify(mockLocalStorageService.setItem('emptyArray', anyString())).called(); - - done(); - }); - - it('Pulls advanced search active state from local storage, or false if undefined or bad data', (done: DoneFn) => { - let mockLocalStorageService = mock(LocalStorageService); - when(mockLocalStorageService.getItem('undefinedScenario')).thenReturn(undefined); - when(mockLocalStorageService.getItem('bogusScenario')).thenReturn('bogus'); - when(mockLocalStorageService.getItem('invalidScenario')).thenReturn('{}'); - when(mockLocalStorageService.getItem('falseScenario')).thenReturn(JSON.stringify(false)); - when(mockLocalStorageService.getItem('trueScenario')).thenReturn(JSON.stringify(true)); - - const commonSearchService = new CommonSearchService(instance(mockLocalStorageService)); - - expect(commonSearchService.isAdvSearchActive('undefinedScenario')).toEqual(false); - expect(commonSearchService.isAdvSearchActive('bogusScenario')).toEqual(false); - expect(commonSearchService.isAdvSearchActive('invalidScenario')).toEqual(false); - expect(commonSearchService.isAdvSearchActive('falseScenario')).toEqual(false); - expect(commonSearchService.isAdvSearchActive('trueScenario')).toEqual(true); - - done(); - }); - - it('Stores the advanced search active state to local storage', (done: DoneFn) => { - let mockLocalStorageService = mock(LocalStorageService); - - const commonSearchService = new CommonSearchService(instance(mockLocalStorageService)); - - commonSearchService.setAdvSearchActive('nullData', null); - commonSearchService.setAdvSearchActive('undefinedData', undefined); - commonSearchService.setAdvSearchActive('trueScenario', true); - commonSearchService.setAdvSearchActive('falseScenario', false); - - verify(mockLocalStorageService.removeItem('nullData')).called(); - verify(mockLocalStorageService.removeItem('undefinedData')).called(); - verify(mockLocalStorageService.setItem('trueScenario', 'true')).called(); - verify(mockLocalStorageService.setItem('falseScenario', 'false')).called(); - - done(); - }); -}); diff --git a/client/angular/src/services/common-search.service.ts b/client/angular/src/services/common-search.service.ts deleted file mode 100644 index 9a9ea4be17..0000000000 --- a/client/angular/src/services/common-search.service.ts +++ /dev/null @@ -1,130 +0,0 @@ -/* - * Password Management Servlets (PWM) - * http://www.pwm-project.org - * - * Copyright (c) 2006-2009 Novell, Inc. - * Copyright (c) 2009-2021 The PWM Project - * - * 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 LocalStorageService from './local-storage.service'; -import {IAdvancedSearchQuery, IAttributeMetadata} from './base-config.service'; - -const PS_ADV_SEARCH_ACTIVE = 'psAdvancedSearchActive'; -const PS_ADV_SEARCH_QUERIES = 'psAdvancedSearchQueries'; -const HD_ADV_SEARCH_ACTIVE = 'hdAdvancedSearchActive'; -const HD_ADV_SEARCH_QUERIES = 'hdAdvancedSearchQueries'; - -export default class CommonSearchService { - static $inject = ['LocalStorageService']; - constructor(private localStorageService: LocalStorageService) { - } - - getDefaultValue(attributeMetaData: IAttributeMetadata) { - if (attributeMetaData) { - if (attributeMetaData.type === 'select') { - const keys: string[] = Object.keys(attributeMetaData.options); - if (keys && keys.length > 0) { - return keys[0]; - } - } - } - - return ''; - } - - isPsAdvancedSearchActive(): boolean { - return this.isAdvSearchActive(PS_ADV_SEARCH_ACTIVE); - } - - setPsAdvancedSearchActive(active: boolean): void { - this.setAdvSearchActive(PS_ADV_SEARCH_ACTIVE, active); - } - - getPsAdvSearchQueries(): IAdvancedSearchQuery[] { - return this.getAdvSearchQueries(PS_ADV_SEARCH_QUERIES); - } - - setPsAdvSearchQueries(queries: IAdvancedSearchQuery[]) { - this.setAdvSearchQueries(PS_ADV_SEARCH_QUERIES, queries); - } - - isHdAdvancedSearchActive(): boolean { - return this.isAdvSearchActive(HD_ADV_SEARCH_ACTIVE); - } - - setHdAdvancedSearchActive(active: boolean): void { - this.setAdvSearchActive(HD_ADV_SEARCH_ACTIVE, active); - } - - getHdAdvSearchQueries(): IAdvancedSearchQuery[] { - return this.getAdvSearchQueries(HD_ADV_SEARCH_QUERIES); - } - - setHdAdvSearchQueries(queries: IAdvancedSearchQuery[]) { - this.setAdvSearchQueries(HD_ADV_SEARCH_QUERIES, queries); - } - - isAdvSearchActive(storageName: string): boolean { - if (storageName) { - const storageValue = this.localStorageService.getItem(storageName); - if (storageValue) { - return (storageValue === 'true'); - } - } - - return false; - } - - setAdvSearchActive(storageName: string, active: boolean): void { - if (storageName) { - // Make sure active is a boolean first - if (typeof(active) === typeof(true)) { - this.localStorageService.setItem(storageName, JSON.stringify(active)); - } else { - // If we were given undefine or null data, then just remove the named item from local storage - this.localStorageService.removeItem(storageName); - } - } - } - - getAdvSearchQueries(storageName: string): IAdvancedSearchQuery[] { - if (storageName) { - const storageValue = this.localStorageService.getItem(storageName); - if (storageValue) { - try { - const parsedValue = JSON.parse(storageValue); - if (Array.isArray(parsedValue)) { - return parsedValue; - } - } catch (error) { - // Unparseable, an empty array will be returned below - } - } - } - - return []; - } - - setAdvSearchQueries(storageName: string, queries: IAdvancedSearchQuery[]) { - if (storageName) { - if (queries) { - this.localStorageService.setItem(storageName, JSON.stringify(queries)); - } else { - // If we were given undefine or null data, then just remove the named item from local storage - this.localStorageService.removeItem(storageName); - } - } - } -} diff --git a/client/angular/src/services/helpdesk-config.service.dev.ts b/client/angular/src/services/helpdesk-config.service.dev.ts deleted file mode 100644 index d48230d2e1..0000000000 --- a/client/angular/src/services/helpdesk-config.service.dev.ts +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Password Management Servlets (PWM) - * http://www.pwm-project.org - * - * Copyright (c) 2006-2009 Novell, Inc. - * Copyright (c) 2009-2021 The PWM Project - * - * 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 { IPromise, IQService } from 'angular'; -import {ConfigBaseService} from './base-config.service.dev'; -import {IConfigService} from './base-config.service'; -import { - IActionButton, - IHelpDeskConfigService, IVerificationMap, PASSWORD_UI_MODES, TOKEN_CHOICE, VERIFICATION_METHOD_LABELS, - VERIFICATION_METHOD_NAMES -} from './helpdesk-config.service'; - -export default class HelpDeskConfigService extends ConfigBaseService implements IConfigService, IHelpDeskConfigService { - static $inject = [ '$q' ]; - constructor($q: IQService) { - super($q); - } - - getClearResponsesSetting(): IPromise { - return this.$q.resolve('ask'); - } - - getColumnConfig(): IPromise { - return this.$q.resolve({ - givenName: 'First Name', - sn: 'Last Name', - title: 'Title', - mail: 'Email', - telephoneNumber: 'Telephone', - workforceId: 'Workforce ID' - }); - } - - getCustomButtons(): IPromise<{[key: string]: IActionButton}> { - return this.$q.resolve({ - 'Clone User': { - name: 'Clone User', - description: 'Clones the current user' - }, - 'Merge User': { - name: 'Merge User', - description: 'Merges the current user with another user' - } - }); - } - - getPasswordUiMode(): IPromise { - return this.$q.resolve(PASSWORD_UI_MODES.BOTH); - } - - getTokenSendMethod(): IPromise { - return this.$q.resolve(TOKEN_CHOICE); - } - - getVerificationAttributes(): IPromise { - return this.$q.resolve([ - { name: 'workforceID', label: 'Workforce ID' }, - { name: 'mail', label: 'Email Address' } - ]); - } - - getVerificationMethods(): IPromise { - return this.$q.resolve([ - { name: VERIFICATION_METHOD_NAMES.ATTRIBUTES, label: VERIFICATION_METHOD_LABELS.ATTRIBUTES }, - { name: VERIFICATION_METHOD_NAMES.SMS, label: VERIFICATION_METHOD_LABELS.SMS } - ]); - } - - maskPasswordsEnabled(): IPromise { - return this.$q.resolve(true); - } - - verificationsEnabled(): IPromise { - return this.$q.resolve(true); - } -} diff --git a/client/angular/src/services/helpdesk-config.service.test-data.ts b/client/angular/src/services/helpdesk-config.service.test-data.ts deleted file mode 100644 index dda83d65ca..0000000000 --- a/client/angular/src/services/helpdesk-config.service.test-data.ts +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Password Management Servlets (PWM) - * http://www.pwm-project.org - * - * Copyright (c) 2006-2009 Novell, Inc. - * Copyright (c) 2009-2021 The PWM Project - * - * 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. - */ - -/* tslint:disable */ - -export const helpdeskProcessAction_clientData = { - "error": false, - "errorCode": 0, - "data": { - "searchColumns": { - "cn": "Username", - "givenName": "First Name", - "sn": "Last Name", - "mail": "Email", - "workforceID": "Workforce ID" - }, - "maskPasswords": false, - "clearResponses": "ask", - "pwUiMode": "autogen", - "tokenSendMethod": "EMAILONLY", - "actions": {}, - "verificationMethods": { - "optional": [ - "TOKEN", - "OTP" - ], - "required": [ - "ATTRIBUTES", - "OTP" - ] - }, - "verificationForm": [ - { - "name": "workforceID", - "label": "workforceID" - } - ] - } -}; diff --git a/client/angular/src/services/helpdesk-config.service.test.ts b/client/angular/src/services/helpdesk-config.service.test.ts deleted file mode 100644 index 242f1a86b4..0000000000 --- a/client/angular/src/services/helpdesk-config.service.test.ts +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Password Management Servlets (PWM) - * http://www.pwm-project.org - * - * Copyright (c) 2006-2009 Novell, Inc. - * Copyright (c) 2009-2021 The PWM Project - * - * 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 {IHttpBackendService, IHttpService, ILogService, IQService, IWindowService, module} from 'angular'; -import ObjectService from './object.service'; -import {default as PwmService, IPwmService} from './pwm.service'; -import LocalStorageService from './local-storage.service'; -import HelpDeskConfigService, {IVerificationMap} from './helpdesk-config.service'; - -import {helpdeskProcessAction_clientData} from './helpdesk-config.service.test-data'; - -describe('In helpdesk-config.service.test.ts', () => { - beforeEach(() => { - module('app', []); - }); - - let localStorageService: LocalStorageService; - let objectService: ObjectService; - let pwmService: IPwmService; - let helpDeskConfigService: HelpDeskConfigService; - let $httpBackend: IHttpBackendService; - - beforeEach(inject(( - $http: IHttpService, - $log: ILogService, - $q: IQService, - $window: IWindowService, - _$httpBackend_: IHttpBackendService - ) => { - localStorageService = new LocalStorageService($log, $window); - objectService = new ObjectService(); - pwmService = new PwmService($http, $log, $q, $window); - $httpBackend = _$httpBackend_; - helpDeskConfigService = new HelpDeskConfigService($http, $log, $q, pwmService as PwmService); - })); - - it('getVerificationMethods returns only the required verification methods', (done: DoneFn) => { - $httpBackend.whenGET( '/context.html?processAction=clientData').respond(helpdeskProcessAction_clientData); - - helpDeskConfigService.getVerificationMethods() - .then((verifications: IVerificationMap) => { - expect(verifications.length).toBe(2); - - expect(verifications).toContain({name: 'ATTRIBUTES', label: 'Button_Attributes'}); - expect(verifications).toContain({name: 'OTP', label: 'Button_OTP'}); - - done(); - }) - .catch((error: Error) => { - done.fail(error); - }); - - // This causes the $http service to finally resolve the response: - $httpBackend.flush(); - }); - - // helpDeskConfigService should return both required and optional, because we passed in: {includeOptional: true} - it('getVerificationMethods returns both required and optional verification methods', (done: DoneFn) => { - $httpBackend.whenGET( '/context.html?processAction=clientData').respond(helpdeskProcessAction_clientData); - - helpDeskConfigService.getVerificationMethods({includeOptional: true}) - .then((verifications: IVerificationMap) => { - expect(verifications.length).toBe(3); - - expect(verifications).toContain({name: 'ATTRIBUTES', label: 'Button_Attributes'}); - expect(verifications).toContain({name: 'EMAIL', label: 'Button_Email'}); - expect(verifications).toContain({name: 'OTP', label: 'Button_OTP'}); - - done(); - }) - .catch((error: Error) => { - done.fail(error); - }); - - // This causes the $http service to finally resolve the response: - $httpBackend.flush(); - }); -}); diff --git a/client/angular/src/services/helpdesk-config.service.ts b/client/angular/src/services/helpdesk-config.service.ts deleted file mode 100644 index 712f19bcc8..0000000000 --- a/client/angular/src/services/helpdesk-config.service.ts +++ /dev/null @@ -1,139 +0,0 @@ -/* - * Password Management Servlets (PWM) - * http://www.pwm-project.org - * - * Copyright (c) 2006-2009 Novell, Inc. - * Copyright (c) 2009-2021 The PWM Project - * - * 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 { IHttpService, ILogService, IPromise, IQService } from 'angular'; -import IPwmService from './pwm.service'; -import PwmService from './pwm.service'; -import {ConfigBaseService, - IAdvancedSearchConfig, - IConfigService, - ADVANCED_SEARCH_ENABLED, - ADVANCED_SEARCH_MAX_ATTRIBUTES, - ADVANCED_SEARCH_ATTRIBUTES -} from './base-config.service'; - -const CLEAR_RESPONSES_CONFIG = 'clearResponses'; -const CUSTOM_BUTTON_CONFIG = 'actions'; -const MASK_PASSWORDS_CONFIG = 'maskPasswords'; -const PASSWORD_UI_MODE_CONFIG = 'pwUiMode'; -const TOKEN_SEND_METHOD_CONFIG = 'tokenSendMethod'; -const TOKEN_VERIFICATION_METHOD = 'TOKEN'; -const TOKEN_SMS_ONLY = 'SMSONLY'; -const TOKEN_EMAIL_ONLY = 'EMAILONLY'; -const VERIFICATION_FORM_CONFIG = 'verificationForm'; -const VERIFICATION_METHODS_CONFIG = 'verificationMethods'; -export const TOKEN_CHOICE = 'CHOICE_SMS_EMAIL'; - -export const PASSWORD_UI_MODES = { - NONE: 'none', - AUTOGEN: 'autogen', - RANDOM: 'random', - TYPE: 'type', - BOTH: 'both' -}; - -export const VERIFICATION_METHOD_NAMES = { - ATTRIBUTES: 'ATTRIBUTES', - TOKEN: 'TOKEN', - OTP: 'OTP' -}; - -export const VERIFICATION_METHOD_LABELS = { - ATTRIBUTES: 'Button_Attributes', - TOKEN: 'Button_TokenVerification', - OTP: 'Button_OTP' -}; - -interface IVerificationResponse { - optional: string[]; - required: string[]; -} - -export type IVerificationMap = {name: string, label: string}[]; - -export interface IActionButton { - description: string; - name: string; -} - -export interface IHelpDeskConfigService extends IConfigService { - getClearResponsesSetting(): IPromise; - getCustomButtons(): IPromise<{[key: string]: IActionButton}>; - getPasswordUiMode(): IPromise; - getTokenSendMethod(): IPromise; - getVerificationAttributes(): IPromise; - maskPasswordsEnabled(): IPromise; - verificationsEnabled(): IPromise; - advancedSearchConfig(): IPromise; -} - -export default class HelpDeskConfigService extends ConfigBaseService implements IConfigService, IHelpDeskConfigService { - - static $inject = ['$http', '$log', '$q', 'PwmService' ]; - constructor($http: IHttpService, $log: ILogService, $q: IQService, pwmService: IPwmService) { - super($http, $log, $q, pwmService); - } - - getClearResponsesSetting(): IPromise { - return this.getValue(CLEAR_RESPONSES_CONFIG); - } - - getCustomButtons(): IPromise<{[key: string]: IActionButton}> { - return this.getValue(CUSTOM_BUTTON_CONFIG); - } - - getPasswordUiMode(): IPromise { - return this.getValue(PASSWORD_UI_MODE_CONFIG); - } - - getTokenSendMethod(): IPromise { - return this.getValue(TOKEN_SEND_METHOD_CONFIG); - } - - getVerificationAttributes(): IPromise { - return this.getValue(VERIFICATION_FORM_CONFIG); - } - - maskPasswordsEnabled(): IPromise { - return this.getValue(MASK_PASSWORDS_CONFIG); - } - - verificationsEnabled(): IPromise { - return this.getValue(VERIFICATION_METHODS_CONFIG) - .then((result: IVerificationResponse) => { - return !!result.required.length; - }); - } - - advancedSearchConfig(): IPromise { - return this.$q.all([ - this.getValue(ADVANCED_SEARCH_ENABLED), - this.getValue(ADVANCED_SEARCH_MAX_ATTRIBUTES), - this.getValue(ADVANCED_SEARCH_ATTRIBUTES) - ]).then((result) => { - return { - enabled: result[0], - maxRows: result[1], - attributes: result[2] - }; - }); - } -} diff --git a/client/angular/src/services/helpdesk.service.dev.ts b/client/angular/src/services/helpdesk.service.dev.ts deleted file mode 100644 index 0330dc3598..0000000000 --- a/client/angular/src/services/helpdesk.service.dev.ts +++ /dev/null @@ -1,547 +0,0 @@ -/* - * Password Management Servlets (PWM) - * http://www.pwm-project.org - * - * Copyright (c) 2006-2009 Novell, Inc. - * Copyright (c) 2009-2021 The PWM Project - * - * 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 { - IHelpDeskService, IRandomPasswordResponse, IRecentVerifications, ISuccessResponse, IVerificationStatus, - IVerificationTokenResponse -} from './helpdesk.service'; -import {IPromise, IQService, ITimeoutService, IWindowService} from 'angular'; -import {IPerson} from '../models/person.model'; -import SearchResult from '../models/search-result.model'; - -const peopleData = require('./people.data.json'); - -const MAX_RESULTS = 10; -const SIMULATED_RESPONSE_TIME = 300; - -export default class HelpDeskService implements IHelpDeskService { - private people: IPerson[]; - - static $inject = [ '$q', '$timeout', '$window' ]; - constructor(private $q: IQService, private $timeout: ITimeoutService, private $window: IWindowService) { - this.people = peopleData.map((person) => (person)); - } - - checkVerification(userKey: string): IPromise { - return this.simulateResponse({ passed: false }); - } - - clearOtpSecret(userKey: string): IPromise { - return this.simulateResponse({ successMessage: 'OTP Secret successfully cleared.' }); - } - - clearResponses(userKey: string): IPromise { - return this.simulateResponse({ successMessage: 'Security answers successfully cleared.' }); - } - - customAction(actionName: string, userKey: string): IPromise { - if (actionName === 'Clone User') { - return this.simulateResponse({ successMessage: 'User successfully cloned.' }); - } - else if (actionName === 'Merge User') { - return this.simulateResponse({ successMessage: 'User successfully merged.' }); - } - else { - this.$q.reject('Error! Action name doesn\'t exist.'); - } - } - - deleteUser(userKey: string): IPromise { - return this.simulateResponse({ successMessage: 'User successfully deleted.' }); - } - - getPerson(userKey: string): IPromise { - return this.simulateResponse({ - 'userDisplayName': 'Andrew Astin - aastin - aastin@ad.utopia.netiq.com', - 'userHistory': [ - { - 'timestamp': '2017-12-13T18:36:50Z', - 'label': 'Help Desk Set Password' - }, - { - 'timestamp': '2017-12-13T18:47:11Z', - 'label': 'Help Desk Set Password' - }, - { - 'timestamp': '2017-12-13T18:50:35Z', - 'label': 'Setup Password Responses' - }, - { - 'timestamp': '2017-12-13T19:19:33Z', - 'label': 'Help Desk Set Password' - }, - { - 'timestamp': '2017-12-13T19:23:50Z', - 'label': 'Change Password' - }, - { - 'timestamp': '2017-12-13T20:47:57Z', - 'label': 'Clear Responses' - }, - { - 'timestamp': '2017-12-13T20:48:38Z', - 'label': 'Setup Password Responses' - } - ], - 'passwordPolicyRules': { - 'Policy Enabled': 'True', - 'Minimum Length': '2', - 'Maximum Length': '64', - 'Minimum Upper Case': '0', - 'Maximum Upper Case': '0', - 'Minimum Lower Case': '0', - 'Maximum Lower Case': '0', - 'Allow Numeric': 'True', - 'Minimum Numeric': '0', - 'Maximum Numeric': '0', - 'Minimum Unique': '0', - 'Allow First Character Numeric': 'True', - 'Allow Last Character Numeric': 'True', - 'Allow Special': 'True', - 'Minimum Special': '0', - 'Maximum Special': '0', - 'Allow First Character Special': 'True', - 'Allow Last Character Special': 'True', - 'Maximum Repeat': '0', - 'Maximum Sequential Repeat': '0', - 'Change Message': '', - 'Expiration Interval': '0', - 'Minimum Lifetime': '0', - 'Case Sensitive': 'True', - 'Unique Required': 'False', - 'Disallowed Values': 'password\ntest', - 'Disallowed Attributes': 'givenName\ncn\nsn', - 'Disallow Current': 'True', - 'Maximum AD Complexity Violations': '2', - 'Regular Expression Match': '', - 'Regular Expression No Match': '', - 'Minimum Alpha': '0', - 'Maximum Alpha': '0', - 'Minimum Non-Alpha': '0', - 'Maximum Non-Alpha': '0', - 'Enable Word List': 'True', - 'Minimum Strength': '0', - 'Maximum Consecutive': '0', - 'Character Groups Minimum Required': '0', - 'Character Group Values': '.*[0-9]\n.*[^A-Za-z0-9]\n.*[A-Z]\n.*[a-z]', - 'Rule_AllowMacroInRegExSetting': 'True' - }, - 'passwordRequirements': [ - 'Password is case sensitive.', - 'Must be at least 2 characters long.', - 'Must not include any of the following values: password test', - 'Must not include part of your name or user name.', - 'Must not include a common word or commonly used sequence of characters.' - ], - 'passwordPolicyDN': 'cn=Policy,cn=Password Policies,cn=Security', - 'passwordPolicyID': 'n/a', - 'statusData': [ - { - 'key': 'Field_Username', - 'type': 'string', - 'label': 'User Name', - 'value': 'aastin' - }, - { - 'key': 'Field_UserDN', - 'type': 'string', - 'label': 'User DN', - 'value': 'cn=aastin,ou=users,o=novell' - }, - { - 'key': 'Field_UserEmail', - 'type': 'string', - 'label': 'Email', - 'value': 'aastin@ad.utopia.netiq.com' - }, - { - 'key': 'Field_UserSMS', - 'type': 'string', - 'label': 'SMS', - 'value': 'n/a' - }, - { - 'key': 'Field_AccountEnabled', - 'type': 'string', - 'label': 'Account Enabled', - 'value': 'True' - }, - { - 'key': 'Field_AccountExpired', - 'type': 'string', - 'label': 'Account Expired', - 'value': 'False' - }, - { - 'key': 'Field_UserGUID', - 'type': 'string', - 'label': 'User GUID', - 'value': 'ae95c9790234624d9848ae95c9790234' - }, - { - 'key': 'Field_AccountExpirationTime', - 'type': 'timestamp', - 'label': 'Account Expiration Time', - 'value': 'n/a' - }, - { - 'key': 'Field_LastLoginTime', - 'type': 'timestamp', - 'label': 'Last Login Time', - 'value': '2017-12-13T20:48:33Z' - }, - { - 'key': 'Field_LastLoginTimeDelta', - 'type': 'string', - 'label': 'Last Login Time Delta', - 'value': '4 days, 22 hours, 49 minutes, 3 seconds' - }, - { - 'key': 'Field_PasswordExpired', - 'type': 'string', - 'label': 'Password Expired', - 'value': 'False' - }, - { - 'key': 'Field_PasswordPreExpired', - 'type': 'string', - 'label': 'Password Pre-Expired', - 'value': 'False' - }, - { - 'key': 'Field_PasswordWithinWarningPeriod', - 'type': 'string', - 'label': 'Within Warning Period', - 'value': 'False' - }, - { - 'key': 'Field_PasswordSetTime', - 'type': 'timestamp', - 'label': 'Password Set Time', - 'value': '2017-12-13T19:23:49Z' - }, - { - 'key': 'Field_PasswordSetTimeDelta', - 'type': 'string', - 'label': 'Password Set Time Delta', - 'value': '5 days, 13 minutes, 47 seconds' - }, - { - 'key': 'Field_PasswordExpirationTime', - 'type': 'timestamp', - 'label': 'Password Expiration Time', - 'value': 'n/a' - }, - { - 'key': 'Field_PasswordLocked', - 'type': 'string', - 'label': 'Password Locked (Intruder Detect)', - 'value': 'False' - }, - { - 'key': 'Field_ResponsesStored', - 'type': 'string', - 'label': 'Responses Stored', - 'value': 'True' - }, - { - 'key': 'Field_ResponsesNeeded', - 'type': 'string', - 'label': 'Response Updates are Needed', - 'value': 'False' - }, - { - 'key': 'Field_ResponsesTimestamp', - 'type': 'timestamp', - 'label': 'Stored Responses Timestamp', - 'value': '2017-12-13T20:48:37Z' - }, - { - 'key': 'Field_ResponsesTimestamp', - 'type': 'timestamp', - 'label': 'Stored Responses Timestamp', - 'value': '2017-12-13T20:48:37Z' - } - ], - 'profileData': [ - { - 'key': 'cn', - 'type': 'string', - 'label': 'CN', - 'value': 'aastin' - }, - { - 'key': 'givenName', - 'type': 'string', - 'label': 'First Name', - 'value': 'Andrew' - }, - { - 'key': 'sn', - 'type': 'string', - 'label': 'Last Name', - 'value': 'Astin' - }, - { - 'key': 'mail', - 'type': 'string', - 'label': 'Email Address', - 'value': 'aastin@ad.utopia.netiq.com' - }, - { - 'key': 'telephoneNumber', - 'type': 'multiString', - 'label': 'Telephone Number', - 'values': [ - '801-802-0259' - ] - }, - { - 'key': 'title', - 'type': 'string', - 'label': 'Title', - 'value': 'Identity Administrator' - }, - { - 'key': 'ou', - 'type': 'string', - 'label': 'Department', - 'value': 'Information Technology' - }, - { - 'key': 'businessCategory', - 'type': 'string', - 'label': 'Business Category', - 'value': 'Identity' - }, - { - 'key': 'company', - 'type': 'string', - 'label': 'Company', - 'value': 'Utopia Corp' - }, - { - 'key': 'street', - 'type': 'string', - 'label': 'Street', - 'value': '50 Upper 5th' - }, - { - 'key': 'physicalDeliveryOfficeName', - 'type': 'string', - 'label': 'City', - 'value': 'New York City' - }, - { - 'key': 'st', - 'type': 'string', - 'label': 'State', - 'value': 'New York' - }, - { - 'key': 'l', - 'type': 'string', - 'label': 'Location', - 'value': 'Manhattan' - }, - { - 'key': 'employeeStatus', - 'type': 'string', - 'label': 'Employee Status', - 'value': 'A' - }, - { - 'key': 'workforceID', - 'type': 'string', - 'label': 'Workforce ID', - 'value': 'E000259' - } - ], - 'helpdeskResponses': [ - { - 'key': 'item_1', - 'type': 'string', - 'label': 'In what year were you born?', - 'value': '1987' - }, - { - 'key': 'item_2', - 'type': 'string', - 'label': 'What is your favorite type of weather?', - 'value': 'Clear sky with funny-shaped clouds' - }, - { - 'key': 'item_3', - 'type': 'string', - 'label': 'What is your favorite song?', - 'value': 'Rudolph the Red-Nosed Reindeer' - } - ], - 'visibleButtons': [ - 'refresh', - 'back', - 'changePassword', - 'unlock', - 'clearResponses', - 'clearOtpSecret', - 'verification', - 'deleteUser' - ], - 'enabledButtons': [ - 'refresh', - 'back', - 'changePassword', - 'unlock', - 'clearResponses', - 'clearOtpSecret', - 'verification', - 'deleteUser' - ], - }); - } - - getPersonCard(userKey: string): IPromise { - let self = this; - - let deferred = this.$q.defer(); - let deferredAbort = this.$q.defer(); - - let timeoutPromise = this.$timeout(() => { - const person = this.findPerson(userKey); - - if (person) { - deferred.resolve(person); - } - else { - deferred.reject(`Person with id: "${userKey}" not found.`); - } - }, SIMULATED_RESPONSE_TIME); - - // To simulate an abortable promise, edit SIMULATED_RESPONSE_TIME - deferred.promise['_httpTimeout'] = deferredAbort; - deferredAbort.promise.then(() => { - self.$timeout.cancel(timeoutPromise); - deferred.resolve(); - }); - - return deferred.promise; - } - - getRandomPassword(userKey: string): IPromise { - let randomNumber = Math.floor(Math.random() * Math.floor(100)); - let passwordSuggestion = 'suggestion' + randomNumber; - return this.simulateResponse({ password: passwordSuggestion }); - } - - getRecentVerifications(): IPromise { - return this.simulateResponse([ - { - timestamp: '2017-12-06T23:19:07Z', - profile: 'default', - username: 'aastin', - method: 'Personal Data' - }, - { - timestamp: '2017-12-03T22:11:07Z', - profile: 'default', - username: 'bjroach', - method: 'Personal Data' - }, - { - timestamp: '2017-12-02T13:09:07Z', - profile: 'default', - username: 'rrhoads', - method: 'Personal Data' - } - ]); - } - - search(query: string): IPromise { - let people = this.people.filter((person: IPerson) => { - if (!query) { - return false; - } - return person._displayName.toLowerCase().indexOf(query.toLowerCase()) >= 0; - }); - - const sizeExceeded = (people.length > MAX_RESULTS); - if (sizeExceeded) { - people = people.slice(MAX_RESULTS); - } - - let result = new SearchResult({sizeExceeded: sizeExceeded, searchResults: people}); - return this.simulateResponse(result); - } - - sendVerificationToken(userKey: string, choice: string): IPromise { - return this.simulateResponse({ destination: 'bcarrolj@paypal.com' }); - } - - setPassword(userKey: string, random: boolean, password?: string): IPromise { - if (random) { - return this.simulateResponse({ successMessage: 'Random password successfully set!' }); - } - else { - return this.simulateResponse({ successMessage: 'Password successfully set!' }); - } - } - - private simulateResponse(data: T): IPromise { - let self = this; - - let deferred = this.$q.defer(); - let deferredAbort = this.$q.defer(); - - let timeoutPromise = this.$timeout(() => { - deferred.resolve(data); - }, SIMULATED_RESPONSE_TIME); - - // To simulate an abortable promise, edit SIMULATED_RESPONSE_TIME - deferred.promise['_httpTimeout'] = deferredAbort; - deferredAbort.promise.then(() => { - self.$timeout.cancel(timeoutPromise); - deferred.resolve(); - }); - - return deferred.promise as IPromise; - } - - unlockIntruder(userKey: string): IPromise { - return this.simulateResponse({ successMessage: 'Unlock successful.' }); - } - - validateVerificationData(userKey: string, data: any, method: string): IPromise { - return this.simulateResponse({ passed: true }); - } - - private findPerson(id: string): IPerson { - const people = this.people.filter((person: IPerson) => person.userKey == id); - - if (people.length) { - return people[0]; - } - - return null; - } - - get showStrengthMeter(): boolean { - return true; - } -} diff --git a/client/angular/src/services/helpdesk.service.test-data.ts b/client/angular/src/services/helpdesk.service.test-data.ts deleted file mode 100644 index 8f6a6c3e8c..0000000000 --- a/client/angular/src/services/helpdesk.service.test-data.ts +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Password Management Servlets (PWM) - * http://www.pwm-project.org - * - * Copyright (c) 2006-2009 Novell, Inc. - * Copyright (c) 2009-2021 The PWM Project - * - * 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. - */ - -/* tslint:disable */ - -export const getRecentVerifications_response = { - "error": false, - "errorCode": 0, - "data": { - "records": [ - { - "timestamp": "2018-02-22T15:14:39Z", - "profile": "default", - "username": "bjenner", - "method": "Personal Data" - } - ] - } -}; diff --git a/client/angular/src/services/helpdesk.service.test.ts b/client/angular/src/services/helpdesk.service.test.ts deleted file mode 100644 index c012697151..0000000000 --- a/client/angular/src/services/helpdesk.service.test.ts +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Password Management Servlets (PWM) - * http://www.pwm-project.org - * - * Copyright (c) 2006-2009 Novell, Inc. - * Copyright (c) 2009-2021 The PWM Project - * - * 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 HelpDeskService, {IRecentVerifications} from './helpdesk.service'; -import {IHttpBackendService, IHttpService, ILogService, IQService, IWindowService, module} from 'angular'; -import ObjectService from './object.service'; -import {default as PwmService, IPwmService} from './pwm.service'; -import LocalStorageService from './local-storage.service'; -import {getRecentVerifications_response} from './helpdesk.service.test-data'; - -describe('In helpdesk.service.test.ts', () => { - beforeEach(() => { - module('app', []); - }); - - let localStorageService: LocalStorageService; - let objectService: ObjectService; - let pwmService: IPwmService; - let helpDeskService: HelpDeskService; - let $httpBackend: IHttpBackendService; - let $window: IWindowService; - - beforeEach(inject(( - $http: IHttpService, - $log: ILogService, - $q: IQService, - _$window_: IWindowService, - _$httpBackend_: IHttpBackendService - ) => { - $httpBackend = _$httpBackend_; - $window = _$window_; - - localStorageService = new LocalStorageService($log, $window); - objectService = new ObjectService(); - pwmService = new PwmService($http, $log, $q, $window); - helpDeskService = new HelpDeskService($log, $q, localStorageService, objectService, pwmService, $window); - })); - - it('getRecentVerifications returns the right record data', (done: DoneFn) => { - (pwmService as PwmService).PWM_GLOBAL = { pwmFormID: 'fake-pwm-form-id' }; - - $httpBackend.whenPOST( '/context.html?processAction=showVerifications&pwmFormID=fake-pwm-form-id') - .respond(getRecentVerifications_response); - - helpDeskService.getRecentVerifications() - .then((recentVerifications: IRecentVerifications) => { - expect(recentVerifications.length).toBe(1); - - expect(recentVerifications[0].username).toBe('bjenner'); - expect(recentVerifications[0].profile).toBe('default'); - expect(recentVerifications[0].timestamp).toBe('2018-02-22T15:14:39Z'); - expect(recentVerifications[0].method).toBe('Personal Data'); - - done(); - }) - .catch((error: Error) => { - done.fail(error); - }); - - // This causes the $http service to finally resolve the response: - $httpBackend.flush(); - }); -}); diff --git a/client/angular/src/services/helpdesk.service.ts b/client/angular/src/services/helpdesk.service.ts deleted file mode 100644 index 3707da4c67..0000000000 --- a/client/angular/src/services/helpdesk.service.ts +++ /dev/null @@ -1,363 +0,0 @@ -/* - * Password Management Servlets (PWM) - * http://www.pwm-project.org - * - * Copyright (c) 2006-2009 Novell, Inc. - * Copyright (c) 2009-2021 The PWM Project - * - * 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 {IPwmService} from './pwm.service'; -import {ILogService, IPromise, IQService, IWindowService} from 'angular'; -import LocalStorageService from './local-storage.service'; -import ObjectService from './object.service'; -import SearchResult from '../models/search-result.model'; -import {IQuery} from './people.service'; - -const VERIFICATION_PROCESS_ACTIONS = { - ATTRIBUTES: 'validateAttributes', - TOKEN: 'verifyVerificationToken', - OTP: 'validateOtpCode' -}; - -const DEFAULT_SHOW_STRENGTH_METER = false; - -export interface IHelpDeskService { - checkVerification(userKey: string): IPromise; - clearOtpSecret(userKey: string): IPromise; - clearResponses(userKey: string): IPromise; - customAction(actionName: string, userKey: string): IPromise; - deleteUser(userKey: string): IPromise; - getPerson(userKey: string): IPromise; - getPersonCard(userKey: string): IPromise; - getRandomPassword(userKey: string): IPromise; - getRecentVerifications(): IPromise; - search(query: string): IPromise; - advancedSearch(queries: IQuery[]): IPromise; - sendVerificationToken(userKey: string, choice: string): IPromise; - setPassword(userKey: string, random: boolean, password?: string): IPromise; - unlockIntruder(userKey: string): IPromise; - validateVerificationData(userKey: string, formData: any, method: any): IPromise; - showStrengthMeter: boolean; -} - -export type IRecentVerifications = IRecentVerification[]; - -type IRecentVerification = { - profile: string, - username: string, - timestamp: string, - method: string -}; - -export interface IRandomPasswordResponse { - password: string; -} - -export interface ISuccessResponse { - successMessage: string; -} - -interface IValidationStatus extends IVerificationStatus { - verificationState: string; -} - -export interface IVerificationOptions { - verificationMethods: { - optional: string[]; - required: string[]; - }; - verificationForm: [{ - name: string; - label: string; - }]; - tokenDestinations: [{ - id: string; - display: string; - type: string; - }]; -} - -export interface IVerificationStatus { - passed: boolean; - verificationOptions: IVerificationOptions; -} - -export interface IVerificationTokenResponse { - destination: string; -} - -export default class HelpDeskService implements IHelpDeskService { - PWM_GLOBAL: any; - - static $inject = [ '$log', '$q', 'LocalStorageService', 'ObjectService', 'PwmService', '$window' ]; - constructor(private $log: ILogService, - private $q: IQService, - private localStorageService: LocalStorageService, - private objectService: ObjectService, - private pwmService: IPwmService, - $window: IWindowService) { - if ($window['PWM_GLOBAL']) { - this.PWM_GLOBAL = $window['PWM_GLOBAL']; - } - else { - this.$log.warn('PWM_GLOBAL is not defined on window'); - } - } - - checkVerification(userKey: string): IPromise { - let url: string = this.pwmService.getServerUrl('checkVerification'); - let data = { - userKey: userKey, - verificationState: undefined - }; - - const verificationState = this.localStorageService.getItem(this.localStorageService.keys.VERIFICATION_STATE); - if (verificationState != null) { - data.verificationState = verificationState; - } - - return this.pwmService - .httpRequest(url, { data: data }) - .then((result: any) => { - return result.data; - }); - } - - clearOtpSecret(userKey: string): IPromise { - let url: string = this.pwmService.getServerUrl('clearOtpSecret'); - let data: any = { userKey: userKey }; - - return this.pwmService - .httpRequest(url, { data: data }) - .then((result: ISuccessResponse) => { - return result; - }); - } - - clearResponses(userKey: string): IPromise { - let url: string = this.pwmService.getServerUrl('clearResponses'); - url += `&userKey=${userKey}`; - - return this.pwmService - .httpRequest(url, {}) - .then((result: ISuccessResponse) => { - return result; - }); - } - - customAction(actionName: string, userKey: string): IPromise { - let url: string = this.pwmService.getServerUrl('executeAction'); - url += `&name=${actionName}`; - let data: any = { userKey: userKey }; - - return this.pwmService - .httpRequest(url, { data: data }) - .then((result: ISuccessResponse) => { - return result; - }); - } - - deleteUser(userKey: string): IPromise { - let url: string = this.pwmService.getServerUrl('deleteUser'); - url += `&userKey=${userKey}`; - - return this.pwmService - .httpRequest(url, {}) - .then((result: ISuccessResponse) => { - return result; - }); - } - - getPerson(userKey: string): IPromise { - let url: string = this.pwmService.getServerUrl('detail'); - url += `&userKey=${userKey}`; - - const verificationState = this.localStorageService.getItem(this.localStorageService.keys.VERIFICATION_STATE); - if (verificationState != null) { - url += `&verificationState=${verificationState}`; - } - - return this.pwmService - .httpRequest(url, {}) - .then((result: any) => { - return result.data; - }); - } - - getPersonCard(userKey: string): IPromise { - let url = this.pwmService.getServerUrl('card'); - url += `&userKey=${userKey}`; - - return this.pwmService - .httpRequest(url, {}) - .then((result: any) => { - return result.data; - }); - } - - getRandomPassword(userKey: string): IPromise { - let url: string = this.pwmService.getServerUrl('randomPassword'); - let data = { - username: userKey, - strength: 0 - }; - return this.pwmService - .httpRequest(url, { data: data }) - .then((result: { data: IRandomPasswordResponse }) => { - return result.data; - }); - } - - getRecentVerifications(): IPromise { - let url: string = this.pwmService.getServerUrl('showVerifications'); - const data = { - verificationState: undefined - }; - - const verificationState = this.localStorageService.getItem(this.localStorageService.keys.VERIFICATION_STATE); - if (verificationState != null) { - data.verificationState = verificationState; - } - - return this.pwmService - .httpRequest(url, { data: data }) - .then((result: any) => { - return result.data.records; - }); - } - - search(query: string): IPromise { - let formID: string = encodeURIComponent('&pwmFormID=' + this.PWM_GLOBAL['pwmFormID']); - let url: string = this.pwmService.getServerUrl('search') - + '&pwmFormID=' + this.PWM_GLOBAL['pwmFormID']; - - let data = { - mode: 'simple', - username: query, - pwmFormID: formID - }; - return this.pwmService - .httpRequest(url, { - data: data, - preventCache: true - }) - .then((result: any) => { - let receivedData: any = result.data; - let searchResult: SearchResult = new SearchResult(receivedData); - return searchResult; - }); - } - - advancedSearch(queries: IQuery[]): IPromise { - let formID: string = encodeURIComponent('&pwmFormID=' + this.PWM_GLOBAL['pwmFormID']); - let url: string = this.pwmService.getServerUrl('search') - + '&pwmFormID=' + this.PWM_GLOBAL['pwmFormID']; - - let data = { - mode: 'advanced', - pwmFormID: formID, - searchValues: queries - }; - return this.pwmService - .httpRequest(url, { - data: data, - preventCache: true - }) - .then((result: any) => { - let receivedData: any = result.data; - let searchResult: SearchResult = new SearchResult(receivedData); - return searchResult; - }); - } - - sendVerificationToken(userKey: string, destinationID: string): IPromise { - let url: string = this.pwmService.getServerUrl('sendVerificationToken'); - let data: any = { - userKey: userKey, - id: destinationID - }; - - return this.pwmService - .httpRequest(url, { data: data }) - .then((result: IVerificationTokenResponse) => { - return result; - }); - } - - setPassword(userKey: string, random: boolean, password?: string): IPromise { - let url: string = this.pwmService.getServerUrl('setPassword'); - let data: any = { username: userKey }; - if (random) { - data.random = true; - } - else { - data.password = password; - } - - return this.pwmService - .httpRequest(url, { data: data }) - .then((result: ISuccessResponse) => { - return result; - }); - } - - unlockIntruder(userKey: string): IPromise { - let url: string = this.pwmService.getServerUrl('unlockIntruder'); - url += `&userKey=${userKey}`; - - return this.pwmService - .httpRequest(url, {}) - .then((result: ISuccessResponse) => { - return result; - }); - } - - validateVerificationData(userKey: string, data: any, method: string): IPromise { - let processAction = VERIFICATION_PROCESS_ACTIONS[method]; - let url: string = this.pwmService.getServerUrl(processAction); - let content = { - userKey: userKey, - verificationState: undefined - }; - - const verificationState = this.localStorageService.getItem(this.localStorageService.keys.VERIFICATION_STATE); - if (verificationState != null) { - content.verificationState = verificationState; - } - - this.objectService.assign(data, content); - - return this.pwmService - .httpRequest(url, { data: data }) - .then((result: any) => { - const validationStatus: IValidationStatus = result.data; - - this.localStorageService.setItem( - this.localStorageService.keys.VERIFICATION_STATE, - validationStatus.verificationState - ); - return validationStatus; - }); - } - - get showStrengthMeter(): boolean { - if (this.PWM_GLOBAL) { - return this.PWM_GLOBAL['setting-showStrengthMeter'] || DEFAULT_SHOW_STRENGTH_METER; - } - - return DEFAULT_SHOW_STRENGTH_METER; - } -} diff --git a/client/angular/src/services/local-storage.service.ts b/client/angular/src/services/local-storage.service.ts deleted file mode 100644 index b92d8c2658..0000000000 --- a/client/angular/src/services/local-storage.service.ts +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Password Management Servlets (PWM) - * http://www.pwm-project.org - * - * Copyright (c) 2006-2009 Novell, Inc. - * Copyright (c) 2009-2021 The PWM Project - * - * 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 { ILogService, IWindowService } from 'angular'; - -const PWM_PREFIX = 'PWM_'; -const KEYS = { - SEARCH_TEXT: 'searchText', - HELPDESK_SEARCH_TEXT: 'helpdeskSearchText', - SEARCH_VIEW: 'searchView', - HELPDESK_SEARCH_VIEW: 'helpdeskSearchView', - VERIFICATION_STATE: 'verificationState' -}; - -export default class LocalStorageService { - keys: any = KEYS; - private localStorageEnabled = true; - - static $inject = [ '$log', '$window' ]; - constructor($log: ILogService, private $window: IWindowService) { - if (!$window.sessionStorage.getItem) { - this.localStorageEnabled = false; - $log.info('Local Storage API not enabled. Using NOOP implementation.'); - } - } - - getItem(key: string): any { - if (this.localStorageEnabled) { - return this.$window.sessionStorage[this.prepKey(key)]; - } - - return null; - } - - setItem(key: string, value: any): void { - if (this.localStorageEnabled && value) { - this.$window.sessionStorage[this.prepKey(key)] = value; - } - } - - removeItem(key: string): any { - if (this.localStorageEnabled) { - return this.$window.sessionStorage.removeItem(this.prepKey(key)); - } - } - - private prepKey(key: string) { - return PWM_PREFIX + key; - } -} diff --git a/client/angular/src/services/object.service.ts b/client/angular/src/services/object.service.ts deleted file mode 100644 index 563d22f203..0000000000 --- a/client/angular/src/services/object.service.ts +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Password Management Servlets (PWM) - * http://www.pwm-project.org - * - * Copyright (c) 2006-2009 Novell, Inc. - * Copyright (c) 2009-2021 The PWM Project - * - * 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. - */ - -export default class ObjectService { - // ES5 implementation of Object.assign - // Source from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign - assign(target, varArgs) { // .length of function is 2 - 'use strict'; - if (target == null) { // TypeError if undefined or null - throw new TypeError('Cannot convert undefined or null to object'); - } - - const to = Object(target); - - for (let index = 1; index < arguments.length; index++) { - const nextSource = arguments[index]; - - if (nextSource != null) { // Skip over if undefined or null - for (let nextKey in nextSource) { - // Avoid bugs when hasOwnProperty is shadowed - if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) { - to[nextKey] = nextSource[nextKey]; - } - } - } - } - return to; - } -} diff --git a/client/angular/src/services/password.service.dev.ts b/client/angular/src/services/password.service.dev.ts deleted file mode 100644 index 123edc8146..0000000000 --- a/client/angular/src/services/password.service.dev.ts +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Password Management Servlets (PWM) - * http://www.pwm-project.org - * - * Copyright (c) 2006-2009 Novell, Inc. - * Copyright (c) 2009-2021 The PWM Project - * - * 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. - */ - -const SIMULATED_RESPONSE_TIME = 100; -const STRENGTH_PER_PASSWORD_CHARACTER = 10; -const MAX_STRENGTH = 100; -const STRENGTH_REQUIRED = 40; - -import {IPasswordService, IValidatePasswordData} from './password.service'; -import {IPromise, IQService, ITimeoutService} from 'angular'; - -export default class PasswordService implements IPasswordService { - - static $inject = ['$q', '$timeout']; - constructor(private $q: IQService, private $timeout: ITimeoutService) { - } - - validatePassword(password1: string, password2: string, userKey: string): IPromise { - let strength = Math.min((password1.length * STRENGTH_PER_PASSWORD_CHARACTER), MAX_STRENGTH); - let match = (password1 === password2); - let message: string = null; - let passed = (strength >= STRENGTH_REQUIRED); - - if (!password1) { - message = 'Please type your new password'; - } - if (!passed) { - message = 'New password is too short'; - } - else if (!password2) { - message = 'Password meets requirements, please type confirmation password'; - } - else if (!match) { - message = 'Passwords do not match'; - } - else { - message = 'New password accepted, please click change password'; - } - - let matchStatus: string = null; - if (!password1) { - matchStatus = 'EMPTY'; - } - else { - matchStatus = match ? 'MATCH' : 'NO_MATCH'; - } - - let data = { - version: 2, - strength: strength, - match: matchStatus, - message: message, - passed: passed, - errorCode: 0 - }; - - let self = this; - - let deferred = this.$q.defer(); - let deferredAbort = this.$q.defer(); - - let timeoutPromise = this.$timeout(() => { - deferred.resolve(data); - }, SIMULATED_RESPONSE_TIME); - - // To simulate an abortable promise, edit SIMULATED_RESPONSE_TIME - deferred.promise['_httpTimeout'] = deferredAbort; - deferredAbort.promise.then(() => { - self.$timeout.cancel(timeoutPromise); - deferred.resolve(); - }); - - return deferred.promise as IPromise; - } -} diff --git a/client/angular/src/services/password.service.ts b/client/angular/src/services/password.service.ts deleted file mode 100644 index 2e788214ed..0000000000 --- a/client/angular/src/services/password.service.ts +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Password Management Servlets (PWM) - * http://www.pwm-project.org - * - * Copyright (c) 2006-2009 Novell, Inc. - * Copyright (c) 2009-2021 The PWM Project - * - * 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 {ILogService, IPromise, IQService, ITimeoutService, IWindowService} from 'angular'; -import {IPwmService} from './pwm.service'; - -export interface IPasswordService { - validatePassword(password1: string, password2: string, userKey: string): IPromise; -} - -export interface IValidatePasswordData { - version: number; - strength: number; - match: string; - message: string; - passed: boolean; - errorCode: number; -} - -export default class PasswordService implements IPasswordService { - PWM_MAIN: any; - - static $inject = ['$log', '$q', '$timeout', '$window', 'PwmService', 'translateFilter']; - - constructor(private $log: ILogService, - private $q: IQService, - private $timeout: ITimeoutService, - private $window: IWindowService, - private pwmService: IPwmService, - private translateFilter: (id: string) => string) { - if ($window['PWM_MAIN']) { - this.PWM_MAIN = $window['PWM_MAIN']; - } - else { - this.$log.warn('PWM_MAIN is not defined on window'); - } - } - - validatePassword(password1: string, password2: string, userKey: string): IPromise { - let data = { - password1: password1, - password2: password2, - username: userKey - }; - let url: string = this.pwmService.getServerUrl('checkPassword'); - - return this.pwmService - .httpRequest(url, {data: data}) - .then((result: { data: IValidatePasswordData }) => { - return result.data; - }); - } -} diff --git a/client/angular/src/services/people.data.json b/client/angular/src/services/people.data.json deleted file mode 100644 index abaebe1dc3..0000000000 --- a/client/angular/src/services/people.data.json +++ /dev/null @@ -1,1712 +0,0 @@ -[ - { - "userKey": 1, - "id": "f5728471-cf90-48d6-a435-6218c58dc7e3", - "givenName": "Jean", - "sn": "Ryan", - "mail": "jryan0@csmonitor.com", - "telephoneNumber": "(564) 683-6597", - "title": "Senior Sales Associate", - "workforceId": "E84-001", - "managerId": null, - "photoURL": "http://i.pravatar.cc/128?img=1", - "_displayName": "Jean Ryan - Senior Sales Associate", - "displayNames": [ - "Jean Ryan", - "jryan0@csmonitor.com", - "Senior Sales Associate", - "(564) 683-6597" - ], - "detail": { - "sn": { - "name": "sn", - "label": "Last Name", - "type": "text", - "searchable": true, - "values": [ - "Ryan" - ] - }, - "givenName": { - "name": "givenName", - "label": "First Name", - "type": "text", - "searchable": true, - "values": [ - "Jean" - ] - }, - "telephoneNumber": { - "name": "telephoneNumber", - "label": "Phone", - "type": "tel", - "values": [ - "(564) 683-6597" - ] - }, - "mail": { - "name": "mail", - "label": "Email Address", - "type": "email", - "values": [ - "jryan0@csmonitor.com" - ] - }, - "title": { - "name": "title", - "label": "Title", - "type": "text", - "searchable": true, - "values": [ - "Senior Sales Associate" - ] - }, - "manager": { - "name": "manager", - "label": "Manager", - "type": "userDN", - "userReferences": [ - { - "displayName": "#N/A", - "userKey": 0 - } - ] - } - }, - "links": [ - { - "link": "/orgchart/1", - "name": "Organization" - } - ] - }, - { - "userKey": 2, - "id": "75ac8b24-64a4-4df5-8709-b414bc65e63a", - "givenName": "Ruby", - "sn": "Bowman", - "mail": "rbowman1@pinterest.com", - "telephoneNumber": "(567) 798-6155", - "title": "Quality Engineer", - "workforceId": "E84-002", - "managerId": 1, - "photoURL": "http://i.pravatar.cc/128?img=2", - "_displayName": "Ruby Bowman - Quality Engineer", - "displayNames": [ - "Ruby Bowman", - "rbowman1@pinterest.com", - "Quality Engineer", - "(567) 798-6155" - ], - "detail": { - "sn": { - "name": "sn", - "label": "Last Name", - "type": "text", - "searchable": true, - "values": [ - "Bowman" - ] - }, - "givenName": { - "name": "givenName", - "label": "First Name", - "type": "text", - "searchable": true, - "values": [ - "Ruby" - ] - }, - "telephoneNumber": { - "name": "telephoneNumber", - "label": "Phone", - "type": "tel", - "values": [ - "(567) 798-6155" - ] - }, - "mail": { - "name": "mail", - "label": "Email Address", - "type": "email", - "values": [ - "rbowman1@pinterest.com" - ] - }, - "title": { - "name": "title", - "label": "Title", - "type": "text", - "searchable": true, - "values": [ - "Quality Engineer" - ] - }, - "manager": { - "name": "manager", - "label": "Manager", - "type": "userDN", - "userReferences": [ - { - "displayName": "Jean Ryan - Senior Sales Associate", - "userKey": 1 - } - ] - } - }, - "links": [ - { - "link": "/orgchart/2", - "name": "Organization" - } - ] - }, - { - "userKey": 3, - "id": "88a52657-45f5-4e8a-9081-fd8ece607e57", - "givenName": "William", - "sn": "Carter", - "mail": "wcarter2@delicious.com", - "telephoneNumber": "(523) 622-4293", - "title": "Community Outreach Specialist", - "workforceId": "E84-003", - "managerId": 1, - "photoURL": "http://i.pravatar.cc/128?img=3", - "_displayName": "William Carter - Community Outreach Specialist", - "displayNames": [ - "William Carter", - "wcarter2@delicious.com", - "Community Outreach Specialist", - "(523) 622-4293" - ], - "detail": { - "sn": { - "name": "sn", - "label": "Last Name", - "type": "text", - "searchable": true, - "values": [ - "Carter" - ] - }, - "givenName": { - "name": "givenName", - "label": "First Name", - "type": "text", - "searchable": true, - "values": [ - "William" - ] - }, - "telephoneNumber": { - "name": "telephoneNumber", - "label": "Phone", - "type": "tel", - "values": [ - "(523) 622-4293" - ] - }, - "mail": { - "name": "mail", - "label": "Email Address", - "type": "email", - "values": [ - "wcarter2@delicious.com" - ] - }, - "title": { - "name": "title", - "label": "Title", - "type": "text", - "searchable": true, - "values": [ - "Community Outreach Specialist" - ] - }, - "manager": { - "name": "manager", - "label": "Manager", - "type": "userDN", - "userReferences": [ - { - "displayName": "Jean Ryan - Senior Sales Associate", - "userKey": 1 - } - ] - } - }, - "links": [ - { - "link": "/orgchart/3", - "name": "Organization" - } - ] - }, - { - "userKey": 4, - "id": "5daa7e1b-6c40-45cb-a3bb-9c59f8d189a0", - "givenName": "Alan", - "sn": "Snyder", - "mail": "asnyder3@blinklist.com", - "telephoneNumber": "(345) 682-1430", - "title": "Research Nurse", - "workforceId": "E84-004", - "managerId": 1, - "photoURL": "http://i.pravatar.cc/128?img=4", - "_displayName": "Alan Snyder - Research Nurse", - "displayNames": [ - "Alan Snyder", - "asnyder3@blinklist.com", - "Research Nurse", - "(345) 682-1430" - ], - "detail": { - "sn": { - "name": "sn", - "label": "Last Name", - "type": "text", - "searchable": true, - "values": [ - "Snyder" - ] - }, - "givenName": { - "name": "givenName", - "label": "First Name", - "type": "text", - "searchable": true, - "values": [ - "Alan" - ] - }, - "telephoneNumber": { - "name": "telephoneNumber", - "label": "Phone", - "type": "tel", - "values": [ - "(345) 682-1430" - ] - }, - "mail": { - "name": "mail", - "label": "Email Address", - "type": "email", - "values": [ - "asnyder3@blinklist.com" - ] - }, - "title": { - "name": "title", - "label": "Title", - "type": "text", - "searchable": true, - "values": [ - "Research Nurse" - ] - }, - "manager": { - "name": "manager", - "label": "Manager", - "type": "userDN", - "userReferences": [ - { - "displayName": "Jean Ryan - Senior Sales Associate", - "userKey": 1 - } - ] - } - }, - "links": [ - { - "link": "/orgchart/4", - "name": "Organization" - } - ] - }, - { - "userKey": 5, - "id": "96637293-860a-4979-a9b9-6c7424f79249", - "givenName": "Aaron", - "sn": "Alvarez", - "mail": "aalvarez4@ezinearticles.com", - "telephoneNumber": "(457) 877-1797", - "title": "Financial Analyst", - "workforceId": "E84-005", - "managerId": 2, - "photoURL": "http://i.pravatar.cc/128?img=5", - "_displayName": "Aaron Alvarez - Financial Analyst", - "displayNames": [ - "Aaron Alvarez", - "aalvarez4@ezinearticles.com", - "Financial Analyst", - "(457) 877-1797" - ], - "detail": { - "sn": { - "name": "sn", - "label": "Last Name", - "type": "text", - "searchable": true, - "values": [ - "Alvarez" - ] - }, - "givenName": { - "name": "givenName", - "label": "First Name", - "type": "text", - "searchable": true, - "values": [ - "Aaron" - ] - }, - "telephoneNumber": { - "name": "telephoneNumber", - "label": "Phone", - "type": "tel", - "values": [ - "(457) 877-1797" - ] - }, - "mail": { - "name": "mail", - "label": "Email Address", - "type": "email", - "values": [ - "aalvarez4@ezinearticles.com" - ] - }, - "title": { - "name": "title", - "label": "Title", - "type": "text", - "searchable": true, - "values": [ - "Financial Analyst" - ] - }, - "manager": { - "name": "manager", - "label": "Manager", - "type": "userDN", - "userReferences": [ - { - "displayName": "Ruby Bowman - Quality Engineer", - "userKey": 2 - } - ] - } - }, - "links": [ - { - "link": "/orgchart/5", - "name": "Organization" - } - ] - }, - { - "userKey": 6, - "id": "3bc65639-ac59-44a2-8dc0-8b1d6c096c3a", - "givenName": "Deborah", - "sn": "Morrison", - "mail": "dmorrison5@nytimes.com", - "telephoneNumber": "(268) 336-2705", - "title": "Accountant II", - "workforceId": "E84-006", - "managerId": 2, - "photoURL": "", - "_displayName": "Deborah Morrison - Accountant II", - "displayNames": [ - "Deborah Morrison", - "dmorrison5@nytimes.com", - "Accountant II", - "(268) 336-2705" - ], - "detail": { - "sn": { - "name": "sn", - "label": "Last Name", - "type": "text", - "searchable": true, - "values": [ - "Morrison" - ] - }, - "givenName": { - "name": "givenName", - "label": "First Name", - "type": "text", - "searchable": true, - "values": [ - "Deborah" - ] - }, - "telephoneNumber": { - "name": "telephoneNumber", - "label": "Phone", - "type": "tel", - "values": [ - "(268) 336-2705" - ] - }, - "mail": { - "name": "mail", - "label": "Email Address", - "type": "email", - "values": [ - "dmorrison5@nytimes.com" - ] - }, - "title": { - "name": "title", - "label": "Title", - "type": "text", - "searchable": true, - "values": [ - "Accountant II" - ] - }, - "manager": { - "name": "manager", - "label": "Manager", - "type": "userDN", - "userReferences": [ - { - "displayName": "Ruby Bowman - Quality Engineer", - "userKey": 2 - } - ] - } - }, - "links": [ - { - "link": "/orgchart/6", - "name": "Organization" - } - ] - }, - { - "userKey": 7, - "id": "dc227135-7a34-413f-9a1a-6bbc65fccc52", - "givenName": "Mildred", - "sn": "Hayes", - "mail": "mhayes6@house.gov", - "telephoneNumber": "(638) 951-3305", - "title": "Design Engineer", - "workforceId": "E84-007", - "managerId": 2, - "photoURL": "http://i.pravatar.cc/128?img=7", - "_displayName": "Mildred Hayes - Design Engineer", - "displayNames": [ - "Mildred Hayes", - "mhayes6@house.gov", - "Design Engineer", - "(638) 951-3305" - ], - "detail": { - "sn": { - "name": "sn", - "label": "Last Name", - "type": "text", - "searchable": true, - "values": [ - "Hayes" - ] - }, - "givenName": { - "name": "givenName", - "label": "First Name", - "type": "text", - "searchable": true, - "values": [ - "Mildred" - ] - }, - "telephoneNumber": { - "name": "telephoneNumber", - "label": "Phone", - "type": "tel", - "values": [ - "(638) 951-3305" - ] - }, - "mail": { - "name": "mail", - "label": "Email Address", - "type": "email", - "values": [ - "mhayes6@house.gov" - ] - }, - "title": { - "name": "title", - "label": "Title", - "type": "text", - "searchable": true, - "values": [ - "Design Engineer" - ] - }, - "manager": { - "name": "manager", - "label": "Manager", - "type": "userDN", - "userReferences": [ - { - "displayName": "Ruby Bowman - Quality Engineer", - "userKey": 2 - } - ] - } - }, - "links": [ - { - "link": "/orgchart/7", - "name": "Organization" - } - ] - }, - { - "userKey": 8, - "id": "11ad9dce-2249-4526-8709-b50c5fc2f3e5", - "givenName": "Margaret", - "sn": "Holmes", - "mail": "mholmes7@nbcnews.com", - "telephoneNumber": "(564) 818-6794", - "title": "Structural Engineer", - "workforceId": "E84-008", - "managerId": 3, - "photoURL": "http://i.pravatar.cc/128?img=8", - "_displayName": "Margaret Holmes - Structural Engineer", - "displayNames": [ - "Margaret Holmes", - "mholmes7@nbcnews.com", - "Structural Engineer", - "(564) 818-6794" - ], - "detail": { - "sn": { - "name": "sn", - "label": "Last Name", - "type": "text", - "searchable": true, - "values": [ - "Holmes" - ] - }, - "givenName": { - "name": "givenName", - "label": "First Name", - "type": "text", - "searchable": true, - "values": [ - "Margaret" - ] - }, - "telephoneNumber": { - "name": "telephoneNumber", - "label": "Phone", - "type": "tel", - "values": [ - "(564) 818-6794" - ] - }, - "mail": { - "name": "mail", - "label": "Email Address", - "type": "email", - "values": [ - "mholmes7@nbcnews.com" - ] - }, - "title": { - "name": "title", - "label": "Title", - "type": "text", - "searchable": true, - "values": [ - "Structural Engineer" - ] - }, - "manager": { - "name": "manager", - "label": "Manager", - "type": "userDN", - "userReferences": [ - { - "displayName": "William Carter - Community Outreach Specialist", - "userKey": 3 - } - ] - } - }, - "links": [ - { - "link": "/orgchart/8", - "name": "Organization" - } - ] - }, - { - "userKey": 9, - "id": "e5ca9428-5319-4a4e-b21f-f4a30b44c4e7", - "givenName": "Jack", - "sn": "Jackson", - "mail": "jjackson8@thetimes.co.uk", - "telephoneNumber": "(206) 987-9763", - "title": "Junior Executive", - "workforceId": "E84-009", - "managerId": 3, - "photoURL": "http://i.pravatar.cc/128?img=9", - "_displayName": "Jack Jackson - Junior Executive", - "displayNames": [ - "Jack Jackson", - "jjackson8@thetimes.co.uk", - "Junior Executive", - "(206) 987-9763" - ], - "detail": { - "sn": { - "name": "sn", - "label": "Last Name", - "type": "text", - "searchable": true, - "values": [ - "Jackson" - ] - }, - "givenName": { - "name": "givenName", - "label": "First Name", - "type": "text", - "searchable": true, - "values": [ - "Jack" - ] - }, - "telephoneNumber": { - "name": "telephoneNumber", - "label": "Phone", - "type": "tel", - "values": [ - "(206) 987-9763" - ] - }, - "mail": { - "name": "mail", - "label": "Email Address", - "type": "email", - "values": [ - "jjackson8@thetimes.co.uk" - ] - }, - "title": { - "name": "title", - "label": "Title", - "type": "text", - "searchable": true, - "values": [ - "Junior Executive" - ] - }, - "manager": { - "name": "manager", - "label": "Manager", - "type": "userDN", - "userReferences": [ - { - "displayName": "William Carter - Community Outreach Specialist", - "userKey": 3 - } - ] - }, - "assistant": { - "name": "assistant", - "label": "Assistant", - "type": "userDN", - "userReferences": [ - { - "displayName": "Joe Stevens - Automation Specialist", - "userKey": 14 - } - ] - } - }, - "links": [ - { - "link": "/orgchart/9", - "name": "Organization" - } - ] - }, - { - "userKey": 10, - "id": "0f3d7561-5c2c-4e71-b716-cab2fbed991d", - "givenName": "Judy", - "sn": "Butler", - "mail": "jbutler9@reference.com", - "telephoneNumber": "(751) 250-5973", - "title": "Chief Design Engineer", - "workforceId": "E84-010", - "managerId": 3, - "photoURL": "http://i.pravatar.cc/128?img=10", - "_displayName": "Judy Butler - Chief Design Engineer", - "displayNames": [ - "Judy Butler", - "jbutler9@reference.com", - "Chief Design Engineer", - "(751) 250-5973" - ], - "detail": { - "sn": { - "name": "sn", - "label": "Last Name", - "type": "text", - "searchable": true, - "values": [ - "Butler" - ] - }, - "givenName": { - "name": "givenName", - "label": "First Name", - "type": "text", - "searchable": true, - "values": [ - "Judy" - ] - }, - "telephoneNumber": { - "name": "telephoneNumber", - "label": "Phone", - "type": "tel", - "values": [ - "(751) 250-5973" - ] - }, - "mail": { - "name": "mail", - "label": "Email Address", - "type": "email", - "values": [ - "jbutler9@reference.com" - ] - }, - "title": { - "name": "title", - "label": "Title", - "type": "text", - "searchable": true, - "values": [ - "Chief Design Engineer" - ] - }, - "manager": { - "name": "manager", - "label": "Manager", - "type": "userDN", - "userReferences": [ - { - "displayName": "William Carter - Community Outreach Specialist", - "userKey": 3 - } - ] - } - }, - "links": [ - { - "link": "/orgchart/10", - "name": "Organization" - } - ] - }, - { - "userKey": 11, - "id": "564e659a-b491-4dc6-820c-524785031d33", - "givenName": "Tina", - "sn": "Gutierrez", - "mail": "tgutierreza@godaddy.com", - "telephoneNumber": "(205) 653-6795", - "title": "Engineer IV", - "workforceId": "E84-011", - "managerId": 9, - "photoURL": "http://i.pravatar.cc/128?img=11", - "_displayName": "Tina Gutierrez - Engineer IV", - "displayNames": [ - "Tina Gutierrez", - "tgutierreza@godaddy.com", - "Engineer IV", - "(205) 653-6795" - ], - "detail": { - "sn": { - "name": "sn", - "label": "Last Name", - "type": "text", - "searchable": true, - "values": [ - "Gutierrez" - ] - }, - "givenName": { - "name": "givenName", - "label": "First Name", - "type": "text", - "searchable": true, - "values": [ - "Tina" - ] - }, - "telephoneNumber": { - "name": "telephoneNumber", - "label": "Phone", - "type": "tel", - "values": [ - "(205) 653-6795" - ] - }, - "mail": { - "name": "mail", - "label": "Email Address", - "type": "email", - "values": [ - "tgutierreza@godaddy.com" - ] - }, - "title": { - "name": "title", - "label": "Title", - "type": "text", - "searchable": true, - "values": [ - "Engineer IV" - ] - }, - "manager": { - "name": "manager", - "label": "Manager", - "type": "userDN", - "userReferences": [ - { - "displayName": "Jack Jackson - Junior Executive", - "userKey": 9 - } - ] - } - }, - "links": [ - { - "link": "/orgchart/11", - "name": "Organization" - } - ] - }, - { - "userKey": 12, - "id": "5203a7f8-a63b-47e9-bd05-1d00fb542eaa", - "givenName": "Jose", - "sn": "Cox", - "mail": "jcoxb@stumbleupon.com", - "telephoneNumber": "(816) 816-8474", - "title": "Human Resources Manager", - "workforceId": "E84-012", - "managerId": 9, - "photoURL": "http://i.pravatar.cc/128?img=12", - "_displayName": "Jose Cox - Human Resources Manager", - "displayNames": [ - "Jose Cox", - "jcoxb@stumbleupon.com", - "Human Resources Manager", - "(816) 816-8474" - ], - "detail": { - "sn": { - "name": "sn", - "label": "Last Name", - "type": "text", - "searchable": true, - "values": [ - "Cox" - ] - }, - "givenName": { - "name": "givenName", - "label": "First Name", - "type": "text", - "searchable": true, - "values": [ - "Jose" - ] - }, - "telephoneNumber": { - "name": "telephoneNumber", - "label": "Phone", - "type": "tel", - "values": [ - "(816) 816-8474" - ] - }, - "mail": { - "name": "mail", - "label": "Email Address", - "type": "email", - "values": [ - "jcoxb@stumbleupon.com" - ] - }, - "title": { - "name": "title", - "label": "Title", - "type": "text", - "searchable": true, - "values": [ - "Human Resources Manager" - ] - }, - "manager": { - "name": "manager", - "label": "Manager", - "type": "userDN", - "userReferences": [ - { - "displayName": "Jack Jackson - Junior Executive", - "userKey": 9 - } - ] - } - }, - "links": [ - { - "link": "/orgchart/12", - "name": "Organization" - } - ] - }, - { - "userKey": 13, - "id": "1fecf6c4-2375-4f49-a5da-8f2d90bc4a3f", - "givenName": "Paul", - "sn": "Barnes", - "mail": "pbarnesc@mediafire.com", - "telephoneNumber": "(207) 691-7625", - "title": "Internal Auditor", - "workforceId": "E84-013", - "managerId": 4, - "photoURL": "", - "_displayName": "Paul Barnes - Internal Auditor", - "displayNames": [ - "Paul Barnes", - "pbarnesc@mediafire.com", - "Internal Auditor", - "(207) 691-7625" - ], - "detail": { - "sn": { - "name": "sn", - "label": "Last Name", - "type": "text", - "searchable": true, - "values": [ - "Barnes" - ] - }, - "givenName": { - "name": "givenName", - "label": "First Name", - "type": "text", - "searchable": true, - "values": [ - "Paul" - ] - }, - "telephoneNumber": { - "name": "telephoneNumber", - "label": "Phone", - "type": "tel", - "values": [ - "(207) 691-7625" - ] - }, - "mail": { - "name": "mail", - "label": "Email Address", - "type": "email", - "values": [ - "pbarnesc@mediafire.com" - ] - }, - "title": { - "name": "title", - "label": "Title", - "type": "text", - "searchable": true, - "values": [ - "Internal Auditor" - ] - }, - "manager": { - "name": "manager", - "label": "Manager", - "type": "userDN", - "userReferences": [ - { - "displayName": "Alan Snyder - Research Nurse", - "userKey": 4 - } - ] - } - }, - "links": [ - { - "link": "/orgchart/13", - "name": "Organization" - } - ] - }, - { - "userKey": 14, - "id": "afd4cdf4-148b-4349-92b5-55a97bd5da4f", - "givenName": "Joe", - "sn": "Stevens", - "mail": "jstevensd@ocn.ne.jp", - "telephoneNumber": "(133) 596-4078", - "title": "Automation Specialist I", - "workforceId": "E84-014", - "managerId": 9, - "photoURL": "http://i.pravatar.cc/128?img=14", - "_displayName": "Joe Stevens - Automation Specialist I", - "displayNames": [ - "Joe Stevens", - "jstevensd@ocn.ne.jp", - "Automation Specialist I", - "(133) 596-4078" - ], - "detail": { - "sn": { - "name": "sn", - "label": "Last Name", - "type": "text", - "searchable": true, - "values": [ - "Stevens" - ] - }, - "givenName": { - "name": "givenName", - "label": "First Name", - "type": "text", - "searchable": true, - "values": [ - "Joe" - ] - }, - "telephoneNumber": { - "name": "telephoneNumber", - "label": "Phone", - "type": "tel", - "values": [ - "(133) 596-4078" - ] - }, - "mail": { - "name": "mail", - "label": "Email Address", - "type": "email", - "values": [ - "jstevensd@ocn.ne.jp" - ] - }, - "title": { - "name": "title", - "label": "Title", - "type": "text", - "searchable": true, - "values": [ - "Automation Specialist I" - ] - }, - "manager": { - "name": "manager", - "label": "Manager", - "type": "userDN", - "userReferences": [ - { - "displayName": "Jack Jackson - Junior Executive", - "userKey": 9 - } - ] - } - }, - "links": [ - { - "link": "/orgchart/14", - "name": "Organization" - } - ] - }, - { - "userKey": 15, - "id": "511652b6-cd3e-41ac-85ea-8154d1ba3df6", - "givenName": "Randy", - "sn": "Grant", - "mail": "rgrante@europa.eu", - "telephoneNumber": "(343) 776-3486", - "title": "Safety Technician I", - "workforceId": "E84-015", - "managerId": 20, - "photoURL": "http://i.pravatar.cc/128?img=15", - "_displayName": "Randy Grant - Safety Technician I", - "displayNames": [ - "Randy Grant", - "rgrante@europa.eu", - "Safety Technician I", - "(343) 776-3486" - ], - "detail": { - "sn": { - "name": "sn", - "label": "Last Name", - "type": "text", - "searchable": true, - "values": [ - "Grant" - ] - }, - "givenName": { - "name": "givenName", - "label": "First Name", - "type": "text", - "searchable": true, - "values": [ - "Randy" - ] - }, - "telephoneNumber": { - "name": "telephoneNumber", - "label": "Phone", - "type": "tel", - "values": [ - "(343) 776-3486" - ] - }, - "mail": { - "name": "mail", - "label": "Email Address", - "type": "email", - "values": [ - "rgrante@europa.eu" - ] - }, - "title": { - "name": "title", - "label": "Title", - "type": "text", - "searchable": true, - "values": [ - "Safety Technician I" - ] - }, - "manager": { - "name": "manager", - "label": "Manager", - "type": "userDN", - "userReferences": [ - { - "displayName": "Bruce Carroll - Desktop Support Technician", - "userKey": 20 - } - ] - } - }, - "links": [ - { - "link": "/orgchart/15", - "name": "Organization" - } - ] - }, - { - "userKey": 16, - "id": "cbf1bcd1-7ef1-4ed0-b1c7-ee42b8005c99", - "givenName": "Martin", - "sn": "Mason", - "mail": "mmasonf@who.int", - "telephoneNumber": "(285) 576-0850", - "title": "Compensation Analyst", - "workforceId": "E84-016", - "managerId": 9, - "photoURL": "http://i.pravatar.cc/128?img=16", - "_displayName": "Martin Mason - Compensation Analyst", - "displayNames": [ - "Martin Mason", - "mmasonf@who.int", - "Compensation Analyst", - "(285) 576-0850" - ], - "detail": { - "sn": { - "name": "sn", - "label": "Last Name", - "type": "text", - "searchable": true, - "values": [ - "Mason" - ] - }, - "givenName": { - "name": "givenName", - "label": "First Name", - "type": "text", - "searchable": true, - "values": [ - "Martin" - ] - }, - "telephoneNumber": { - "name": "telephoneNumber", - "label": "Phone", - "type": "tel", - "values": [ - "(285) 576-0850" - ] - }, - "mail": { - "name": "mail", - "label": "Email Address", - "type": "email", - "values": [ - "mmasonf@who.int" - ] - }, - "title": { - "name": "title", - "label": "Title", - "type": "text", - "searchable": true, - "values": [ - "Compensation Analyst" - ] - }, - "manager": { - "name": "manager", - "label": "Manager", - "type": "userDN", - "userReferences": [ - { - "displayName": "Jack Jackson - Junior Executive", - "userKey": 9 - } - ] - } - }, - "links": [ - { - "link": "/orgchart/16", - "name": "Organization" - } - ] - }, - { - "userKey": 17, - "id": "3f4d2955-d6e0-4d70-a3bb-cab342f887de", - "givenName": "Cynthia", - "sn": "Porter", - "mail": "cporterg@a8.net", - "telephoneNumber": "(594) 905-7773", - "title": "Data Coordiator", - "workforceId": "E84-017", - "managerId": 16, - "photoURL": "http://i.pravatar.cc/128?img=17", - "_displayName": "Cynthia Porter - Data Coordiator", - "displayNames": [ - "Cynthia Porter", - "cporterg@a8.net", - "Data Coordiator", - "(594) 905-7773" - ], - "detail": { - "sn": { - "name": "sn", - "label": "Last Name", - "type": "text", - "searchable": true, - "values": [ - "Porter" - ] - }, - "givenName": { - "name": "givenName", - "label": "First Name", - "type": "text", - "searchable": true, - "values": [ - "Cynthia" - ] - }, - "telephoneNumber": { - "name": "telephoneNumber", - "label": "Phone", - "type": "tel", - "values": [ - "(594) 905-7773" - ] - }, - "mail": { - "name": "mail", - "label": "Email Address", - "type": "email", - "values": [ - "cporterg@a8.net" - ] - }, - "title": { - "name": "title", - "label": "Title", - "type": "text", - "searchable": true, - "values": [ - "Data Coordiator" - ] - }, - "manager": { - "name": "manager", - "label": "Manager", - "type": "userDN", - "userReferences": [ - { - "displayName": "Martin Mason - Compensation Analyst", - "userKey": 16 - } - ] - } - }, - "links": [ - { - "link": "/orgchart/17", - "name": "Organization" - } - ] - }, - { - "userKey": 18, - "id": "fba89f94-29f9-4198-a0e5-f8b7c96620ee", - "givenName": "Nancy", - "sn": "Burns", - "mail": "nburnsh@wordpress.org", - "telephoneNumber": "(444) 231-5492", - "title": "Business Systems Development Analyst", - "workforceId": "E84-018", - "managerId": 17, - "photoURL": "http://i.pravatar.cc/128?img=18", - "_displayName": "Nancy Burns - Business Systems Development Analyst", - "displayNames": [ - "Nancy Burns", - "nburnsh@wordpress.org" - ], - "detail": { - "sn": { - "name": "sn", - "label": "Last Name", - "type": "text", - "searchable": true, - "values": [ - "Burns" - ] - }, - "givenName": { - "name": "givenName", - "label": "First Name", - "type": "text", - "searchable": true, - "values": [ - "Nancy" - ] - }, - "telephoneNumber": { - "name": "telephoneNumber", - "label": "Phone", - "type": "tel", - "values": [ - "(444) 231-5492" - ] - }, - "mail": { - "name": "mail", - "label": "Email Address", - "type": "email", - "values": [ - "nburnsh@wordpress.org" - ] - }, - "title": { - "name": "title", - "label": "Title", - "type": "text", - "searchable": true, - "values": [ - "Business Systems Development Analyst" - ] - }, - "manager": { - "name": "manager", - "label": "Manager", - "type": "userDN", - "userReferences": [ - { - "displayName": "Cynthia Porter - Data Coordiator", - "userKey": 17 - } - ] - } - }, - "links": [ - { - "link": "/orgchart/18", - "name": "Organization" - } - ] - }, - { - "userKey": 19, - "id": "95ae40b3-c8af-4588-94da-ee0f0d6bafdc", - "givenName": "Jimmy", - "sn": "Montgomery", - "mail": "jmontgomeryi@addtoany.com", - "telephoneNumber": "(675) 799-8793", - "title": "Structural Engineer", - "workforceId": "E84-019", - "managerId": 18, - "photoURL": "http://i.pravatar.cc/128?img=26", - "_displayName": "Jimmy Montgomery - Structural Engineer", - "displayNames": [ - "Jimmy Montgomery", - "jmontgomeryi@addtoany.com", - "Structural Engineer", - "(675) 799-8793" - ], - "detail": { - "sn": { - "name": "sn", - "label": "Last Name", - "type": "text", - "searchable": true, - "values": [ - "Montgomery" - ] - }, - "givenName": { - "name": "givenName", - "label": "First Name", - "type": "text", - "searchable": true, - "values": [ - "Jimmy" - ] - }, - "telephoneNumber": { - "name": "telephoneNumber", - "label": "Phone", - "type": "tel", - "values": [ - "(675) 799-8793" - ] - }, - "mail": { - "name": "mail", - "label": "Email Address", - "type": "email", - "values": [ - "jmontgomeryi@addtoany.com" - ] - }, - "title": { - "name": "title", - "label": "Title", - "type": "text", - "searchable": true, - "values": [ - "Structural Engineer" - ] - }, - "manager": { - "name": "manager", - "label": "Manager", - "type": "userDN", - "userReferences": [ - { - "displayName": "Nancy Burns - Business Systems Development Analyst", - "userKey": 18 - } - ] - } - }, - "links": [ - { - "link": "/orgchart/19", - "name": "Organization" - } - ] - }, - { - "userKey": 20, - "id": "2ae2d513-e12e-40a7-9cf5-2dd481706147", - "givenName": "Bruce", - "sn": "Carroll", - "mail": "bcarrollj@paypal.com", - "telephoneNumber": "(658) 289-4550", - "title": "Desktop Support Technician", - "workforceId": "E84-020", - "managerId": 19, - "photoURL": "http://i.pravatar.cc/128?img=20", - "_displayName": "Bruce Carroll - Desktop Support Technician", - "displayNames": [ - "Bruce Carroll", - "bcarrollj@paypal.com", - "Desktop Support Technician", - "(658) 289-4550" - ], - "detail": { - "sn": { - "name": "sn", - "label": "Last Name", - "type": "text", - "searchable": true, - "values": [ - "Carroll" - ] - }, - "givenName": { - "name": "givenName", - "label": "First Name", - "type": "text", - "searchable": true, - "values": [ - "Bruce" - ] - }, - "telephoneNumber": { - "name": "telephoneNumber", - "label": "Phone", - "type": "tel", - "values": [ - "(658) 289-4550" - ] - }, - "mail": { - "name": "mail", - "label": "Email Address", - "type": "email", - "values": [ - "bcarrollj@paypal.com" - ] - }, - "title": { - "name": "title", - "label": "Title", - "type": "text", - "searchable": true, - "values": [ - "Desktop Support Technician" - ] - }, - "manager": { - "name": "manager", - "label": "Manager", - "type": "userDN", - "userReferences": [ - { - "displayName": "Jimmy Montgomery - Structural Engineer", - "userKey": 19 - } - ] - } - }, - "links": [ - { - "link": "/orgchart/20", - "name": "Organization" - } - ] - }, - { - "userKey": 21, - "id": "6aeec76a-6a36-4825-965b-b9b5a009827a", - "givenName": "Orphan", - "sn": "User", - "mail": "orphan.user@gmail.com", - "telephoneNumber": "(454) 249-4440", - "title": "No Real Position", - "workforceId": "E84-021", - "managerId": null, - "photoURL": "http://i.pravatar.cc/128?img=21", - "_displayName": "Orphan User - No Real Position", - "displayNames": [ - "Orphan User", - "orphan.user@gmail.com", - "No Real Position", - "(454) 249-4440" - ], - "detail": { - "sn": { - "name": "sn", - "label": "Last Name", - "type": "text", - "searchable": true, - "values": [ - "User" - ] - }, - "givenName": { - "name": "givenName", - "label": "First Name", - "type": "text", - "searchable": true, - "values": [ - "Orphan" - ] - }, - "telephoneNumber": { - "name": "telephoneNumber", - "label": "Phone", - "type": "tel", - "values": [ - "(454) 249-4440" - ] - }, - "mail": { - "name": "mail", - "label": "Email Address", - "type": "email", - "values": [ - "orphan.user@gmail.com" - ] - }, - "title": { - "name": "title", - "label": "Title", - "type": "text", - "searchable": true, - "values": [ - "No Real Position" - ] - }, - "manager": { - "name": "manager", - "label": "Manager", - "type": "userDN", - "userReferences": [ - { - "displayName": "#N/A", - "userKey": 0 - } - ] - } - }, - "links": [ - { - "link": "/orgchart/21", - "name": "Organization" - } - ] - } -] \ No newline at end of file diff --git a/client/angular/src/services/people.service.dev.ts b/client/angular/src/services/people.service.dev.ts deleted file mode 100644 index 8bc2862404..0000000000 --- a/client/angular/src/services/people.service.dev.ts +++ /dev/null @@ -1,260 +0,0 @@ -/* - * Password Management Servlets (PWM) - * http://www.pwm-project.org - * - * Copyright (c) 2006-2009 Novell, Inc. - * Copyright (c) 2009-2021 The PWM Project - * - * 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 { IPromise, IQService, ITimeoutService } from 'angular'; -import { IPerson } from '../models/person.model'; -import {IPeopleService, IQuery} from './people.service'; -import IOrgChartData from '../models/orgchart-data.model'; -import SearchResult from '../models/search-result.model'; - -const peopleData = require('./people.data.json'); - -const MAX_RESULTS = 10; -const SIMULATED_RESPONSE_TIME = 0; - -export default class PeopleService implements IPeopleService { - private people: IPerson[]; - - static $inject = ['$q', '$timeout' ]; - constructor(private $q: IQService, private $timeout: ITimeoutService) { - this.people = peopleData.map((person) => (person)); - - // Create directReports detail (instead of managing this in people.data.json - this.people.forEach((person: IPerson) => { - const directReports = this.findDirectReports(person.userKey); - - if (!directReports.length) { - return; - } - - person.detail.directReports = { - name: 'directReports', - label: 'Direct Reports', - type: 'userDN', - userReferences: directReports - .map((directReport: IPerson) => { - return { - userKey: directReport.userKey, - displayName: directReport._displayName - }; - }) - }; - }, this); - } - - advancedSearch(queries: IQuery[]): IPromise { - let self = this; - - let deferred = this.$q.defer(); - let deferredAbort = this.$q.defer(); - - let timeoutPromise = this.$timeout(() => { - - let people = this.getAdvancedSearchResults(queries); - const sizeExceeded = (people.length > MAX_RESULTS); - if (sizeExceeded) { - people = people.slice(MAX_RESULTS); - } - - deferred.resolve(new SearchResult({sizeExceeded: sizeExceeded, searchResults: people})); - }, SIMULATED_RESPONSE_TIME * 6); - - // To simulate an abortable promise, edit SIMULATED_RESPONSE_TIME - deferred.promise['_httpTimeout'] = deferredAbort; - deferredAbort.promise.then(() => { - self.$timeout.cancel(timeoutPromise); - deferred.resolve(); - }); - - return deferred.promise as IPromise; - } - - autoComplete(query: string): IPromise { - return this.search(query) - .then((searchResult: SearchResult) => { - let people = searchResult.people; - // Alphabetize results by _displayName - people = people.sort((person1, person2) => person1._displayName.localeCompare(person2._displayName)); - - if (people && people.length > 10) { - return people.slice(0, 10); - } - - return people; - }); - } - - getDirectReports(id: string): angular.IPromise { - const people = this.findDirectReports(id); - - return this.$q.resolve(people); - } - - getManagementChain(id: string, managementChainLimit): IPromise { - let person = this.findPerson(id); - - if (person) { - const managementChain: IPerson[] = []; - - while (person = this.findManager(person)) { - managementChain.push(person); - } - - return this.$q.resolve(managementChain); - } - - return this.$q.reject(`Person with id: "${id}" not found.`); - } - - getOrgChartData(personId: string): angular.IPromise { - if (!personId) { - personId = '9'; - } - - const self = this.findPerson(personId); - - const orgChartData: IOrgChartData = { - manager: this.findManager(self), - children: this.findDirectReports(personId), - self: self, - assistant: this.findAssistant(self) - }; - - return this.$q.resolve(orgChartData); - } - - getNumberOfDirectReports(personId: string): IPromise { - return this.getDirectReports(personId) - .then((directReports: IPerson[]) => { - return directReports.length; - }); - } - - getPerson(id: string): IPromise { - let self = this; - - let deferred = this.$q.defer(); - let deferredAbort = this.$q.defer(); - - let timeoutPromise = this.$timeout(() => { - const person = this.findPerson(id); - - if (person) { - deferred.resolve(person); - } - else { - deferred.reject(`Person with id: "${id}" not found.`); - } - }, SIMULATED_RESPONSE_TIME); - - // To simulate an abortable promise, edit SIMULATED_RESPONSE_TIME - deferred.promise['_httpTimeout'] = deferredAbort; - deferredAbort.promise.then(() => { - self.$timeout.cancel(timeoutPromise); - deferred.resolve(); - }); - - return deferred.promise; - } - - search(query: string): angular.IPromise { - let self = this; - - let deferred = this.$q.defer(); - let deferredAbort = this.$q.defer(); - - let timeoutPromise = this.$timeout(() => { - let people = this.people.filter((person: IPerson) => { - if (!query) { - return false; - } - return person._displayName.toLowerCase().indexOf(query.toLowerCase()) >= 0; - }); - - const sizeExceeded = (people.length > MAX_RESULTS); - if (sizeExceeded) { - people = people.slice(MAX_RESULTS); - } - - deferred.resolve(new SearchResult({sizeExceeded: sizeExceeded, searchResults: people})); - }, SIMULATED_RESPONSE_TIME * 6); - - // To simulate an abortable promise, edit SIMULATED_RESPONSE_TIME - deferred.promise['_httpTimeout'] = deferredAbort; - deferredAbort.promise.then(() => { - self.$timeout.cancel(timeoutPromise); - deferred.resolve(); - }); - - return deferred.promise as IPromise; - } - - private findDirectReports(id: string): IPerson[] { - return this.people.filter((person: IPerson) => person.detail['manager']['userReferences'][0].userKey == id); - } - - private findAssistant(person: IPerson): IPerson { - if (!('assistant' in person.detail)) { - return null; - } - - return this.findPerson(person.detail['assistant']['userReferences'][0].userKey); - } - - private findManager(person: IPerson): IPerson { - return this.findPerson(person.detail['manager']['userReferences'][0].userKey); - } - - private findPerson(id: string): IPerson { - const people = this.people.filter((person: IPerson) => person.userKey == id); - - if (people.length) { - return people[0]; - } - - return null; - } - - private getAdvancedSearchResults(queries: IQuery[]): IPerson[] { - let people = queries.length ? this.people : []; - - queries.forEach((query: IQuery) => { - people = people.filter((person: IPerson) => { - if (!query.value) { - return false; - } - - let property = person[query.name]; - - if (!property) { - return false; - } - - if (typeof property === 'object' || typeof property === 'number') { - property = JSON.stringify(property); - } - - return property.toLowerCase().indexOf(query.value.toLowerCase()) >= 0; - }); - }); - - return people; - } -} diff --git a/client/angular/src/services/people.service.ts b/client/angular/src/services/people.service.ts deleted file mode 100644 index 381b2e5775..0000000000 --- a/client/angular/src/services/people.service.ts +++ /dev/null @@ -1,291 +0,0 @@ -/* - * Password Management Servlets (PWM) - * http://www.pwm-project.org - * - * Copyright (c) 2006-2009 Novell, Inc. - * Copyright (c) 2009-2021 The PWM Project - * - * 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 {IDeferred, IHttpService, ILogService, IPromise, IQService, IWindowService} from 'angular'; -import {IPerson} from '../models/person.model'; -import IPwmService from './pwm.service'; -import IOrgChartData from '../models/orgchart-data.model'; -import SearchResult from '../models/search-result.model'; -import {IPeopleSearchConfigService} from './peoplesearch-config.service'; - -export interface IQuery { - key: string; - value: string; -} - -export interface IPeopleService { - advancedSearch(queries: IQuery[]): IPromise; - - autoComplete(query: string): IPromise; - - getDirectReports(personId: string): IPromise; - - getNumberOfDirectReports(personId: string): IPromise; - - getManagementChain(id: string, managementChainLimit): IPromise; - - getOrgChartData(personId: string, skipChildren: boolean): IPromise; - - getPerson(id: string): IPromise; - - getTeamEmails(id: string, depth: number): IPromise; - - search(query: string): IPromise; -} - -export default class PeopleService implements IPeopleService { - PWM_GLOBAL: any; - - static $inject = ['$http', '$log', '$q', 'ConfigService', 'PwmService', '$window']; - - constructor(private $http: IHttpService, - private $log: ILogService, - private $q: IQService, - private configService: IPeopleSearchConfigService, - private pwmService: IPwmService, - $window: IWindowService) { - if ($window['PWM_GLOBAL']) { - this.PWM_GLOBAL = $window['PWM_GLOBAL']; - } - else { - this.$log.warn('PWM_GLOBAL is not defined on window'); - } - } - - advancedSearch(queries: IQuery[]): IPromise { - // Deferred object used for aborting requests. See promise.service.ts for more information - let httpTimeout = this.$q.defer(); - - let formID: string = encodeURIComponent('&pwmFormID=' + this.PWM_GLOBAL['pwmFormID']); - let url: string = this.pwmService.getServerUrl('search') - + '&pwmFormID=' + this.PWM_GLOBAL['pwmFormID']; - let request = this.$http - .post(url, { - mode: 'advanced', - pwmFormID: formID, - searchValues: queries - }, { - cache: true, - timeout: httpTimeout.promise, - headers: {'Content-Type': 'multipart/form-data'}, - }); - - let promise = request.then( - (response) => { - if (response.data['error']) { - return this.handlePwmError(response); - } - - let receivedData: any = response.data['data']; - let searchResult: SearchResult = new SearchResult(receivedData); - - return searchResult; - }, - this.handleHttpError.bind(this)); - - promise['_httpTimeout'] = httpTimeout; - - return promise; - } - - autoComplete(query: string): IPromise { - return this.search(query, {'includeDisplayName': true}) - .then((searchResult: SearchResult) => { - let people = searchResult.people; - - if (people && people.length > 10) { - return people.slice(0, 10); - } - - return people; - }); - } - - getDirectReports(id: string): IPromise { - return this.getOrgChartData(id, false).then((orgChartData: IOrgChartData) => { - let people: IPerson[] = []; - - for (let directReport of orgChartData.children) { - let person: IPerson = (directReport); - people.push(person); - } - - return people; - }); - } - - getNumberOfDirectReports(id: string): IPromise { - return this.getDirectReports(id).then((people: IPerson[]) => { - return people.length; - }); - } - - getManagementChain(id: string, managementChainLimit): IPromise { - let people: IPerson[] = []; - return this.getManagerRecursive(id, people, managementChainLimit); - } - - private getManagerRecursive(id: string, people: IPerson[], managementChainLimit): IPromise { - return this.getOrgChartData(id, true) - .then((orgChartData: IOrgChartData) => { - if (orgChartData.manager && people.length < managementChainLimit) { - people.push(orgChartData.manager); - - return this.getManagerRecursive(orgChartData.manager.userKey, people, managementChainLimit); - } - - return people; - }); - } - - getOrgChartData(personId: string, noChildren: boolean): angular.IPromise { - return this.$http - .get(this.pwmService.getServerUrl('orgChartData'), { - cache: true, - params: { - userKey: personId, - noChildren: noChildren - } - }) - .then( - (response) => { - if (response.data['error']) { - return this.handlePwmError(response); - } - - let responseData = response.data['data']; - - let manager: IPerson; - let assistant: IPerson; - - if ('parent' in responseData) { - manager = (responseData['parent']); - } - if ('assistant' in responseData) { - assistant = (responseData['assistant']); - } - - const children = responseData['children'].map((child: any) => (child)); - const self = (responseData['self']); - - return { - manager: manager, - children: children, - self: self, - assistant: assistant - }; - }, - this.handleHttpError.bind(this)); - } - - getPerson(id: string): IPromise { - // Deferred object used for aborting requests. See promise.service.ts for more information - let httpTimeout = this.$q.defer(); - - let request = this.$http - .get(this.pwmService.getServerUrl('detail'), { - cache: true, - params: {userKey: id}, - timeout: httpTimeout.promise - }); - - let promise = request.then( - (response) => { - if (response.data['error']) { - return this.handlePwmError(response); - } - - let person: IPerson = (response.data['data']); - return person; - }, - this.handleHttpError.bind(this)); - - promise['_httpTimeout'] = httpTimeout; - - return promise; - } - - getTeamEmails(id: string, depth: number): IPromise { - const deferredValue: IDeferred = this.$q.defer(); - - let request = this.$http - .get(this.pwmService.getServerUrl('mailtoLinks'), { - cache: true, - params: { - userKey: id, - depth: depth - } - }) - .then((response) => { - deferredValue.resolve(response.data['data']); - }); - - return deferredValue.promise; - } - - search(query: string, params?: any): IPromise { - // Deferred object used for aborting requests. See promise.service.ts for more information - let httpTimeout = this.$q.defer(); - - let formID: string = encodeURIComponent('&pwmFormID=' + this.PWM_GLOBAL['pwmFormID']); - let url: string = this.pwmService.getServerUrl('search', params) - + '&pwmFormID=' + this.PWM_GLOBAL['pwmFormID']; - let request = this.$http - .post(url, { - mode: 'simple', - username: query, - pwmFormID: formID, - includeDisplayName: (params && params.includeDisplayName) ? params.includeDisplayName : false - }, { - cache: true, - timeout: httpTimeout.promise, - headers: {'Content-Type': 'multipart/form-data'}, - }); - - let promise = request.then( - (response) => { - if (response.data['error']) { - return this.handlePwmError(response); - } - - let receivedData: any = response.data['data']; - let searchResult: SearchResult = new SearchResult(receivedData); - - return searchResult; - }, - this.handleHttpError.bind(this)); - - promise['_httpTimeout'] = httpTimeout; - - return promise; - } - - private handleHttpError(error): void { - this.$log.error(error); - } - - private handlePwmError(response): IPromise { - const errorMessage = `${response.data['errorCode']}: ${response.data['errorMessage']}`; - this.$log.error(errorMessage); - - return this.$q.reject(response.data['errorMessage']); - } -} diff --git a/client/angular/src/services/peoplesearch-config.service.dev.ts b/client/angular/src/services/peoplesearch-config.service.dev.ts deleted file mode 100644 index 83e9096d15..0000000000 --- a/client/angular/src/services/peoplesearch-config.service.dev.ts +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Password Management Servlets (PWM) - * http://www.pwm-project.org - * - * Copyright (c) 2006-2009 Novell, Inc. - * Copyright (c) 2009-2021 The PWM Project - * - * 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 { IPromise, IQService } from 'angular'; -import {ConfigBaseService} from './base-config.service.dev'; -import {IConfigService} from './base-config.service'; -import {AdvancedSearchConfig, IPeopleSearchConfigService} from './peoplesearch-config.service'; - - -export default class ConfigService - extends ConfigBaseService - implements IConfigService, IPeopleSearchConfigService { - static $inject = [ '$q' ]; - constructor($q: IQService) { - super($q); - } - - getColumnConfig(): IPromise { - return this.$q.resolve({ - givenName: 'First Name', - sn: 'Last Name', - title: 'Title', - mail: 'Email', - telephoneNumber: 'Telephone' - }); - } - - getOrgChartMaxParents(): IPromise { - return this.$q.resolve(50); - } - - orgChartEnabled(): IPromise { - return this.$q.resolve(true); - } - - orgChartShowChildCount(): IPromise { - return this.$q.resolve(true); - } - - advancedSearchConfig(): IPromise { - return this.$q.resolve({ - enabled: true, - maxRows: 3, - attributes: [ - { - id: 'title', - attribute: 'Title' - }, - { - id: 'givenName', - attribute: 'Given Name' - }, - { - id: 'sn', - attribute: 'First Name' - } - ] - }); - } -} diff --git a/client/angular/src/services/peoplesearch-config.service.ts b/client/angular/src/services/peoplesearch-config.service.ts deleted file mode 100644 index 424d7e2b70..0000000000 --- a/client/angular/src/services/peoplesearch-config.service.ts +++ /dev/null @@ -1,114 +0,0 @@ -/* - * Password Management Servlets (PWM) - * http://www.pwm-project.org - * - * Copyright (c) 2006-2009 Novell, Inc. - * Copyright (c) 2009-2021 The PWM Project - * - * 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 {IDeferred, IHttpService, ILogService, IPromise, IQService} from 'angular'; -import IPwmService from './pwm.service'; -import PwmService from './pwm.service'; -import { - ConfigBaseService, - IConfigService, - IAdvancedSearchConfig, - ADVANCED_SEARCH_ENABLED, - ADVANCED_SEARCH_MAX_ATTRIBUTES, - ADVANCED_SEARCH_ATTRIBUTES, PHOTO_ENABLED -} from './base-config.service'; - -const ORGCHART_ENABLED = 'orgChartEnabled'; -const ORGCHART_MAX_PARENTS = 'orgChartMaxParents'; -const ORGCHART_SHOW_CHILD_COUNT = 'orgChartShowChildCount'; -const EXPORT_ENABLED = 'enableExport'; -const EXPORT_MAX_DEPTH = 'exportMaxDepth'; -const MAILTO_ENABLED = 'enableMailtoLinks'; -const MAILTO_MAX_DEPTH = 'mailtoLinkMaxDepth'; - -export interface IPeopleSearchConfigService extends IConfigService { - getOrgChartMaxParents(): IPromise; - orgChartEnabled(): IPromise; - orgChartShowChildCount(): IPromise; - advancedSearchConfig(): IPromise; - personDetailsConfig(): IPromise; -} - -export interface IPersonDetailsConfig { - photosEnabled: boolean; - orgChartEnabled: boolean; - exportEnabled: boolean; - emailTeamEnabled: boolean; - maxExportDepth: number; - maxEmailDepth: number; -} - -export default class PeopleSearchConfigService - extends ConfigBaseService - implements IConfigService, IPeopleSearchConfigService { - - static $inject = ['$http', '$log', '$q', 'PwmService' ]; - constructor($http: IHttpService, $log: ILogService, $q: IQService, pwmService: IPwmService) { - super($http, $log, $q, pwmService); - } - - getOrgChartMaxParents(): IPromise { - return this.getValue(ORGCHART_MAX_PARENTS); - } - - orgChartEnabled(): IPromise { - return this.getValue(ORGCHART_ENABLED) - .then(null, () => { return true; }); // On error use default - } - - orgChartShowChildCount(): IPromise { - return this.getValue(ORGCHART_SHOW_CHILD_COUNT); - } - - personDetailsConfig(): IPromise { - return this.$q.all([ - this.getValue(PHOTO_ENABLED), - this.getValue(ORGCHART_ENABLED), - this.getValue(EXPORT_ENABLED), - this.getValue(EXPORT_MAX_DEPTH), - this.getValue(MAILTO_ENABLED), - this.getValue(MAILTO_MAX_DEPTH), - ]).then((results: any[]) => { - return { - photosEnabled: results[0], - orgChartEnabled: results[1], - exportEnabled: results[2], - maxExportDepth: results[3], - emailTeamEnabled: results[4], - maxEmailDepth: results[5] - }; - }); - } - - advancedSearchConfig(): IPromise { - return this.$q.all([ - this.getValue(ADVANCED_SEARCH_ENABLED), - this.getValue(ADVANCED_SEARCH_MAX_ATTRIBUTES), - this.getValue(ADVANCED_SEARCH_ATTRIBUTES) - ]).then((result) => { - return { - enabled: result[0], - maxRows: result[1], - attributes: result[2] - }; - }); - } -} diff --git a/client/angular/src/services/promise.service.ts b/client/angular/src/services/promise.service.ts deleted file mode 100644 index 4a39fca7d1..0000000000 --- a/client/angular/src/services/promise.service.ts +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Password Management Servlets (PWM) - * http://www.pwm-project.org - * - * Copyright (c) 2006-2009 Novell, Inc. - * Copyright (c) 2009-2021 The PWM Project - * - * 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 { IPromise, IQService } from 'angular'; - -// Pattern explained at https://www.bennadel.com/blog/2731-canceling-a-promise-in-angularjs.htm -export default class PromiseService { - static $inject = [ '$q' ]; - constructor(private $q: IQService) {} - - abort(promise: IPromise) { - if (promise && promise['_httpTimeout'] && promise['_httpTimeout'].resolve) { - promise['_httpTimeout'].resolve(); - } - } -} diff --git a/client/angular/src/services/pwm.service.dev.ts b/client/angular/src/services/pwm.service.dev.ts deleted file mode 100644 index 1a11b15dd0..0000000000 --- a/client/angular/src/services/pwm.service.dev.ts +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Password Management Servlets (PWM) - * http://www.pwm-project.org - * - * Copyright (c) 2006-2009 Novell, Inc. - * Copyright (c) 2009-2021 The PWM Project - * - * 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 {IHttpRequestOptions, IPwmService} from './pwm.service'; - -export default class PwmService implements IPwmService { - getServerUrl(processAction: string, additionalParameters?: any): string { - return null; - } - - httpRequest(url: string, options: IHttpRequestOptions): angular.IPromise { - return null; - } - - get ajaxTypingWait(): number { - return 300; - } - - get localeStrings(): any { - return {}; - } - - get startupFunctions(): any[] { - return []; - } -} diff --git a/client/angular/src/services/pwm.service.ts b/client/angular/src/services/pwm.service.ts deleted file mode 100644 index 1a1a887932..0000000000 --- a/client/angular/src/services/pwm.service.ts +++ /dev/null @@ -1,162 +0,0 @@ -/* - * Password Management Servlets (PWM) - * http://www.pwm-project.org - * - * Copyright (c) 2006-2009 Novell, Inc. - * Copyright (c) 2009-2021 The PWM Project - * - * 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 {IHttpService, ILogService, IPromise, IQService, IWindowService} from 'angular'; - -export interface IHttpRequestOptions { - data?: any; - preventCache?: boolean; -} - -export interface IPwmService { - getServerUrl(processAction: string, additionalParameters?: any): string; - httpRequest(url: string, options: IHttpRequestOptions): IPromise; - ajaxTypingWait: number; - localeStrings: any; - startupFunctions: any[]; -} - -const DEFAULT_AJAX_TYPING_WAIT = 700; - -export default class PwmService implements IPwmService { - PWM_GLOBAL: any; - PWM_MAIN: any; - - urlContext: string; - - static $inject = [ '$http', '$log', '$q', '$window' ]; - constructor(private $http: IHttpService, - private $log: ILogService, - private $q: IQService, - $window: IWindowService) { - this.urlContext = ''; - - // Search window references to PWM_GLOBAL and PWM_MAIN add by legacy PWM code - if ($window['PWM_GLOBAL']) { - this.PWM_GLOBAL = $window['PWM_GLOBAL']; - this.urlContext = this.PWM_GLOBAL['url-context']; - } - else { - this.$log.warn('PWM_GLOBAL is not defined on window'); - } - - if ($window['PWM_MAIN']) { - this.PWM_MAIN = $window['PWM_MAIN']; - } - else { - this.$log.warn('PWM_MAIN is not defined on window'); - } - } - - getServerUrl(processAction: string, additionalParameters?: any): string { - let url: string = window.location.pathname + '?processAction=' + processAction; - url = this.addParameters(url, additionalParameters); - - return url; - } - - private handlePwmError(response): IPromise { - // TODO: show error dialog (like PWM_MAIN.ajaxRequest) - const errorMessage = `${response.data['errorCode']}: ${response.data['errorMessage']}`; - this.$log.error(errorMessage); - - return this.$q.reject(response.data['errorMessage']); - } - - httpRequest(url: string, options: IHttpRequestOptions): IPromise { - // TODO: implement alternate http method, no Content-Type if no options.data - let formID: string = encodeURIComponent('&pwmFormID=' + this.PWM_GLOBAL['pwmFormID']); - url += '&pwmFormID=' + this.PWM_GLOBAL['pwmFormID']; - let promise = this.$http - .post(url, options.data, { - cache: !options.preventCache, - headers: { 'Accept': 'application/json', 'Content-Type': 'application/json' }, - }) - .then((response) => { - if (response.data['error']) { - return this.handlePwmError(response); - } - - // Note: sometimes response.data looks like this: - // { - // "error": false, - // "errorCode": 0, - // "data": { - // "foo": "1", - // "bar": "2" - // } - // } - - // Note: other times, response.data looks like this: - // { - // "error": false, - // "errorCode": 0, - // "successMessage": "The operation has been successfully completed." - // } - - // Since we can't make assumptions about the structure, we just need to return the whole response.data - // payload: - return response.data; - }); - - return promise; - } - - get ajaxTypingWait(): number { - if (this.PWM_GLOBAL) { - return this.PWM_GLOBAL['client.ajaxTypingWait'] || DEFAULT_AJAX_TYPING_WAIT; - } - - return DEFAULT_AJAX_TYPING_WAIT; - } - - get localeStrings(): any { - if (this.PWM_GLOBAL) { - return this.PWM_GLOBAL['localeStrings']; - } - - return {}; - } - - get startupFunctions(): any[] { - if (this.PWM_GLOBAL) { - return this.PWM_GLOBAL['startupFunctions']; - } - - return []; - } - - private addParameters(url: string, params: any): string { - if (!this.PWM_MAIN) { - return url; - } - - if (params) { - for (let name in params) { - if (params.hasOwnProperty(name)) { - url = this.PWM_MAIN.addParamToUrl(url, name, params[name]); - } - } - } - - return url; - } -} diff --git a/client/angular/src/services/translations-loader.factory.ts b/client/angular/src/services/translations-loader.factory.ts deleted file mode 100644 index f8d046977c..0000000000 --- a/client/angular/src/services/translations-loader.factory.ts +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Password Management Servlets (PWM) - * http://www.pwm-project.org - * - * Copyright (c) 2006-2009 Novell, Inc. - * Copyright (c) 2009-2021 The PWM Project - * - * 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 'angular-translate'; -import { IQService } from 'angular'; -import IPwmService from './pwm.service'; - -export default [ - '$q', - 'PwmService', - ($q: IQService, pwmService: IPwmService) => { - return function () { - return $q.resolve(pwmService.localeStrings['Display']); - }; - }]; diff --git a/client/angular/src/styles.scss b/client/angular/src/styles.scss deleted file mode 100644 index 29c2ca2c5c..0000000000 --- a/client/angular/src/styles.scss +++ /dev/null @@ -1,76 +0,0 @@ -/*! - * Password Management Servlets (PWM) - * http://www.pwm-project.org - * - * Copyright (c) 2006-2009 Novell, Inc. - * Copyright (c) 2009-2021 The PWM Project - * - * 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. - */ - -// Only apply the ux-ias styles when the elements are a child of .ias-styles-root. This ensures we are not mucking with the -// styles for the header and footer (things outside of this angular app). -.ias-styles-root { - @import "~@microfocus/ux-ias/src/ux-ias"; - - .ias-dialog { - // Need to fix this minor css problem in ux-ias until a version > 1.0.0-rc comes out. - position: fixed; - - > .ias-dialog-container { - &.grow-with-content { - @media (min-width: 768px) { - max-width: 100%; - min-width: 375px; - width: max-content; - } - } - - > .ias-dialog-label > .ias-title { - overflow: hidden; - text-overflow: ellipsis; - } - } - } - - // The ias-menu is added at the root of the document. This fixes when we have to add the ias-styles-root class - // to the ias-menu itself to get the IAS styles applied. - &.ias-menu { - background-color: transparent; - color: inherit; - font-family: inherit; - font-size: inherit; - font-weight: inherit; - line-height: inherit; - - bottom: 0; - display: none; - position: fixed; - left: 0; - right: 0; - top: 0; - - &.ias-open { - display: inline-block; - } - } - - // ux-ias normally applies the following to the body tag, but the way we are including ux-ias under .ias-styles-root - // means we have to define it here. - background-color: $body-bg-color; - color: $text-color; - font-family: $font-family; - font-size: $font-size; - font-weight: $font-weight; - line-height: normal; -} diff --git a/client/angular/src/ux/element-size.service.ts b/client/angular/src/ux/element-size.service.ts deleted file mode 100644 index 68b226e3ba..0000000000 --- a/client/angular/src/ux/element-size.service.ts +++ /dev/null @@ -1,123 +0,0 @@ -/* - * Password Management Servlets (PWM) - * http://www.pwm-project.org - * - * Copyright (c) 2006-2009 Novell, Inc. - * Copyright (c) 2009-2021 The PWM Project - * - * 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 { element, IAugmentedJQuery, IRootScopeService, IWindowService } from 'angular'; - -interface IResizeCallback { - (newValue: number, oldValue: number): void; -} - -function dasherize(input: string): string { - return input - .replace(/(?:^\w|[A-Z]|\b\w)/g, function (letter, index) { - return (index == 0 ? '' : '-') + letter.toLowerCase(); - }) - .replace(/\s+/g, ''); -} - -class ElementSizeWatcher { - callbacks: IResizeCallback[] = []; - sizes: any[] = []; - width: number; - - constructor(public element: IAugmentedJQuery, widths: T) { - // Build size information - for (let width in widths) { - if (widths.hasOwnProperty(width) && !/^\d+$/.test(width)) { - this.sizes.push({ - size: widths[width], - className: dasherize(width), - type: width - }); - } - } - - this.sizes.sort((size1: any, size2: any) => size1.size - size2.size); - - this.updateWidth(); - } - - get elementWidth(): number { - return this.element[0].clientWidth; - } - - onResize(callback: IResizeCallback) { - this.callbacks.push(callback); - - callback(this.elementWidth, this.width); - } - - updateWidth(): void { - if (this.width !== this.elementWidth) { - this.callbacks.forEach(callback => callback(this.elementWidth, this.width)); - this.width = this.elementWidth; - this.updateElementClass(); - } - } - - private updateElementClass(): void { - // Remove all size classes - this.sizes.forEach((size) => { - this.element.removeClass(size.className); - }); - - // Add applicable sizes - let applicableClasses = this.sizes - .filter((size: any) => this.width >= size.size) - .map((size: any) => size.className) - .join(' '); - this.element.addClass(applicableClasses); - } -} - -export default class ElementSizeService { - private elementSizeWatchers: ElementSizeWatcher[] = []; - - static $inject = ['$rootScope', '$window']; - constructor(private $rootScope: IRootScopeService, private $window: IWindowService) { - - } - - watchWidth(el: IAugmentedJQuery, widths: T): ElementSizeWatcher { - if (!this.elementSizeWatchers.length) { - element(this.$window).on('resize', this.onWindowResize.bind(this)); - } - - return this.addElementSizeWatcher(el, widths); - } - - private addElementSizeWatcher(element: IAugmentedJQuery, widths: T): ElementSizeWatcher { - let elementSizeWatcher = new ElementSizeWatcher(element, widths); - // TODO: check if element already exists - this.elementSizeWatchers.push(elementSizeWatcher); - return elementSizeWatcher; - } - - private onWindowResize() { - // TODO: optimizations - // TODO: height (later) - this.elementSizeWatchers.forEach((watcher: ElementSizeWatcher) => { - watcher.updateWidth(); - }); - - this.$rootScope.$apply(); - } -} diff --git a/client/angular/src/ux/ux.module.ts b/client/angular/src/ux/ux.module.ts deleted file mode 100644 index 661abeb6af..0000000000 --- a/client/angular/src/ux/ux.module.ts +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Password Management Servlets (PWM) - * http://www.pwm-project.org - * - * Copyright (c) 2006-2009 Novell, Inc. - * Copyright (c) 2009-2021 The PWM Project - * - * 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 { module } from 'angular'; -import ElementSizeService from './element-size.service'; - -const moduleName = 'peoplesearch.ux'; - -module(moduleName, [ ]) - .service('MfElementSizeService', ElementSizeService); - -export default moduleName; diff --git a/client/angular/test/karma-test-suite.ts b/client/angular/test/karma-test-suite.ts deleted file mode 100644 index 8b354d8e44..0000000000 --- a/client/angular/test/karma-test-suite.ts +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Password Management Servlets (PWM) - * http://www.pwm-project.org - * - * Copyright (c) 2006-2009 Novell, Inc. - * Copyright (c) 2009-2021 The PWM Project - * - * 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. - */ - -Error.stackTraceLimit = Infinity; - -import 'angular'; -import 'angular-mocks'; - -// This creates a single bundle with all test cases (*.test.ts), which improves performance -// (i.e. we don't create a webpack bundle for each test) - -// To run all tests, use this: -// var appContext = (require as any).context('../src', true, /\.test\.ts/); - -// To run a specific test, change the following regular expression and use this: -var appContext = (require as any).context('../src', true, /common-search.service.test.ts/); - -appContext.keys().forEach(appContext); diff --git a/client/angular/test/karma.conf.js b/client/angular/test/karma.conf.js deleted file mode 100644 index 474d534618..0000000000 --- a/client/angular/test/karma.conf.js +++ /dev/null @@ -1,110 +0,0 @@ -/* - * Password Management Servlets (PWM) - * http://www.pwm-project.org - * - * Copyright (c) 2006-2009 Novell, Inc. - * Copyright (c) 2009-2021 The PWM Project - * - * 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. - */ - -var webpack = require('webpack'); -var webpackConfig = require('../webpack.config.js')({}, {}); -var path = require("path"); -var os = require('os'); - -module.exports = function (config) { - config.set({ - // base path that will be used to resolve all patterns (eg. files, exclude) - basePath: '..', - - // frameworks to use - // available frameworks: https://npmjs.org/browse/keyword/karma-adapter - frameworks: [ 'jasmine' ], - - // list of files / patterns to load in the browser - files: [ - 'test/karma-test-suite.ts' - ], - - exclude: [], - - // preprocess matching files before serving them to the browser - // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor - preprocessors: { - "**/*.ts": ["webpack", "sourcemap"] - }, - - // fix typescript serving video/mp2t mime type - mime: { - 'text/x-typescript': ['ts', 'tsx'] - }, - - webpack: { - mode: 'development', - devtool: 'inline-source-map', - resolve: webpackConfig.resolve, - module: webpackConfig.module, - optimization: { - minimize: false - } - }, - - webpackMiddleware: { - // display no info to console (only warnings and errors) - noInfo: true, - stats: { - colors: true - } - }, - - // test results reporter to use - // possible values: 'dots', 'progress' - // available reporters: https://npmjs.org/browse/keyword/karma-reporter - reporters: ['kjhtml', 'spec'], - - // web server port - port: 9876, - - // enable / disable colors in the output (reporters and logs) - colors: true, - - // level of logging - // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG - logLevel: config.LOG_INFO, - - // enable / disable watching file and executing tests whenever any file changes - autoWatch: true, - - // start these browsers - // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher - browsers: ['Chrome_with_debug_plugins'], - - // Provides the ability to install plugins in Chrome (such as JetBrains debugger), and have them stick around - // between launches: - customLaunchers: { - Chrome_with_debug_plugins: { - base: 'Chrome', - chromeDataDir: path.resolve(os.homedir(), '.karma/customLaunchers/Chrome_with_debug_plugins') - } - }, - - // Continuous Integration mode - // if true, Karma captures browsers, runs the tests and exits - singleRun: false, - - // Concurrency level - // how many browser should be started simultaneous - concurrency: Infinity - }) -}; diff --git a/client/angular/tsconfig.json b/client/angular/tsconfig.json deleted file mode 100644 index b57b048c01..0000000000 --- a/client/angular/tsconfig.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "compilerOptions": { - "baseUrl": "./", - "experimentalDecorators": true, - "lib": [ - "es2015", - "es2015.iterable", - "dom" - ], - "module": "commonjs", - "removeComments": true, - "sourceMap": false, - "target": "es5" - }, - "files": [ - "src/modules/peoplesearch/main.ts" - ], - "exclude": [ - "dist", - "node_modules" - ] -} diff --git a/client/angular/tslint.json b/client/angular/tslint.json deleted file mode 100644 index 2723cb8a9b..0000000000 --- a/client/angular/tslint.json +++ /dev/null @@ -1,73 +0,0 @@ -{ - "rules": { - "class-name": true, - "comment-format": [ - true, - "check-space" - ], - "curly": true, - "eofline": true, - "file-header": [ - true, - "Copyright \\(c\\)" - ], - "indent": [ - true, - "spaces" - ], - "max-line-length": [ - true, - 120 - ], - "no-bitwise": true, - "no-console": [true, "log", "error"], - "no-duplicate-variable": true, - "no-eval": true, - "no-arg": true, - "no-inferrable-types": true, - "no-internal-module": true, - "no-trailing-whitespace": true, - "no-unused-expression": true, - "no-unused-variable": false, - "no-var-keyword": false, - "one-line": [ - true, - "check-catch", - "check-open-brace", - "check-whitespace" - ], - "quotemark": [ - true, - "single", - "avoid-escape" - ], - "semicolon": [ - true, - "always" - ], - "typedef-whitespace": [ - true, - { - "call-signature": "nospace", - "index-signature": "nospace", - "parameter": "nospace", - "property-declaration": "nospace", - "variable-declaration": "nospace" - } - ], - "variable-name": [ - true, - "ban-keywords", - "allow-leading-underscore", - "allow-pascal-case" - ], - "whitespace": [ - true, - "check-branch", - "check-decl", - "check-operator", - "check-separator", - "check-type" - ] - } -} \ No newline at end of file diff --git a/client/angular/vendor/angular-ui-router.js b/client/angular/vendor/angular-ui-router.js deleted file mode 100644 index 9d03dfdb3e..0000000000 --- a/client/angular/vendor/angular-ui-router.js +++ /dev/null @@ -1,8364 +0,0 @@ -/* - * Password Management Servlets (PWM) - * http://www.pwm-project.org - * - * Copyright (c) 2006-2009 Novell, Inc. - * Copyright (c) 2009-2021 The PWM Project - * - * 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. - */ - -/*! - * State-based routing for AngularJS - * @version v1.0.0-beta.3 - * @link https://ui-router.github.io - * @license MIT License, http://www.opensource.org/licenses/MIT - */ -(function webpackUniversalModuleDefinition(root, factory) { - if(typeof exports === 'object' && typeof module === 'object') - module.exports = factory(require("angular")); - else if(typeof define === 'function' && define.amd) - define("angular-ui-router", ["angular"], factory); - else if(typeof exports === 'object') - exports["angular-ui-router"] = factory(require("angular")); - else - root["angular-ui-router"] = factory(root["angular"]); -})(this, function(__WEBPACK_EXTERNAL_MODULE_57__) { -return /******/ (function(modules) { // webpackBootstrap -/******/ // The module cache -/******/ var installedModules = {}; -/******/ -/******/ // The require function -/******/ function __webpack_require__(moduleId) { -/******/ -/******/ // Check if module is in cache -/******/ if(installedModules[moduleId]) -/******/ return installedModules[moduleId].exports; -/******/ -/******/ // Create a new module (and put it into the cache) -/******/ var module = installedModules[moduleId] = { -/******/ exports: {}, -/******/ id: moduleId, -/******/ loaded: false -/******/ }; -/******/ -/******/ // Execute the module function -/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); -/******/ -/******/ // Flag the module as loaded -/******/ module.loaded = true; -/******/ -/******/ // Return the exports of the module -/******/ return module.exports; -/******/ } -/******/ -/******/ -/******/ // expose the modules object (__webpack_modules__) -/******/ __webpack_require__.m = modules; -/******/ -/******/ // expose the module cache -/******/ __webpack_require__.c = installedModules; -/******/ -/******/ // __webpack_public_path__ -/******/ __webpack_require__.p = ""; -/******/ -/******/ // Load entry module and return exports -/******/ return __webpack_require__(0); -/******/ }) -/************************************************************************/ -/******/ ([ -/* 0 */ -/***/ function(module, exports, __webpack_require__) { - - /** - * Main entry point for angular 1.x build - * @module ng1 - */ - /** for typedoc */ - "use strict"; - function __export(m) { - for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p]; - } - __export(__webpack_require__(1)); - __export(__webpack_require__(53)); - __export(__webpack_require__(55)); - __export(__webpack_require__(58)); - __webpack_require__(60); - __webpack_require__(61); - __webpack_require__(62); - __webpack_require__(63); - Object.defineProperty(exports, "__esModule", { value: true }); - exports.default = "ui.router"; - - -/***/ }, -/* 1 */ -/***/ function(module, exports, __webpack_require__) { - - /** @module common */ /** */ - "use strict"; - function __export(m) { - for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p]; - } - __export(__webpack_require__(2)); - __export(__webpack_require__(46)); - __export(__webpack_require__(47)); - __export(__webpack_require__(48)); - __export(__webpack_require__(49)); - __export(__webpack_require__(50)); - __export(__webpack_require__(51)); - __export(__webpack_require__(52)); - __export(__webpack_require__(44)); - var router_1 = __webpack_require__(25); - exports.UIRouter = router_1.UIRouter; - - -/***/ }, -/* 2 */ -/***/ function(module, exports, __webpack_require__) { - - "use strict"; - function __export(m) { - for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p]; - } - /** @module common */ /** for typedoc */ - __export(__webpack_require__(3)); - __export(__webpack_require__(6)); - __export(__webpack_require__(7)); - __export(__webpack_require__(5)); - __export(__webpack_require__(4)); - __export(__webpack_require__(8)); - __export(__webpack_require__(9)); - __export(__webpack_require__(12)); - - -/***/ }, -/* 3 */ -/***/ function(module, exports, __webpack_require__) { - - /** - * Random utility functions used in the UI-Router code - * - * @preferred @module common - */ /** for typedoc */ - "use strict"; - var predicates_1 = __webpack_require__(4); - var hof_1 = __webpack_require__(5); - var coreservices_1 = __webpack_require__(6); - var w = typeof window === 'undefined' ? {} : window; - var angular = w.angular || {}; - exports.fromJson = angular.fromJson || JSON.parse.bind(JSON); - exports.toJson = angular.toJson || JSON.stringify.bind(JSON); - exports.copy = angular.copy || _copy; - exports.forEach = angular.forEach || _forEach; - exports.extend = angular.extend || _extend; - exports.equals = angular.equals || _equals; - exports.identity = function (x) { return x; }; - exports.noop = function () { return undefined; }; - /** - * Binds and copies functions onto an object - * - * Takes functions from the 'from' object, binds those functions to the _this object, and puts the bound functions - * on the 'to' object. - * - * This example creates an new class instance whose functions are prebound to the new'd object. - * @example - * ``` - * - * class Foo { - * constructor(data) { - * // Binds all functions from Foo.prototype to 'this', - * // then copies them to 'this' - * bindFunctions(Foo.prototype, this, this); - * this.data = data; - * } - * - * log() { - * console.log(this.data); - * } - * } - * - * let myFoo = new Foo([1,2,3]); - * var logit = myFoo.log; - * logit(); // logs [1, 2, 3] from the myFoo 'this' instance - * ``` - * - * This example creates a bound version of a service function, and copies it to another object - * @example - * ``` - * - * var SomeService = { - * this.data = [3, 4, 5]; - * this.log = function() { - * console.log(this.data); - * } - * } - * - * // Constructor fn - * function OtherThing() { - * // Binds all functions from SomeService to SomeService, - * // then copies them to 'this' - * bindFunctions(SomeService, this, SomeService); - * } - * - * let myOtherThing = new OtherThing(); - * myOtherThing.log(); // logs [3, 4, 5] from SomeService's 'this' - * ``` - * - * @param from The object which contains the functions to be bound - * @param to The object which will receive the bound functions - * @param bindTo The object which the functions will be bound to - * @param fnNames The function names which will be bound (Defaults to all the functions found on the 'from' object) - */ - function bindFunctions(from, to, bindTo, fnNames) { - if (fnNames === void 0) { fnNames = Object.keys(from); } - return fnNames.filter(function (name) { return typeof from[name] === 'function'; }) - .forEach(function (name) { return to[name] = from[name].bind(bindTo); }); - } - exports.bindFunctions = bindFunctions; - /** - * prototypal inheritance helper. - * Creates a new object which has `parent` object as its prototype, and then copies the properties from `extra` onto it - */ - exports.inherit = function (parent, extra) { - return exports.extend(new (exports.extend(function () { }, { prototype: parent }))(), extra); - }; - /** - * Given an arguments object, converts the arguments at index idx and above to an array. - * This is similar to es6 rest parameters. - * - * Optionally, the argument at index idx may itself already be an array. - * - * For example, - * given either: - * arguments = [ obj, "foo", "bar" ] - * or: - * arguments = [ obj, ["foo", "bar"] ] - * then: - * restArgs(arguments, 1) == ["foo", "bar"] - * - * This allows functions like pick() to be implemented such that it allows either a bunch - * of string arguments (like es6 rest parameters), or a single array of strings: - * - * given: - * var obj = { foo: 1, bar: 2, baz: 3 }; - * then: - * pick(obj, "foo", "bar"); // returns { foo: 1, bar: 2 } - * pick(obj, ["foo", "bar"]); // returns { foo: 1, bar: 2 } - */ - var restArgs = function (args, idx) { - if (idx === void 0) { idx = 0; } - return Array.prototype.concat.apply(Array.prototype, Array.prototype.slice.call(args, idx)); - }; - /** Given an array, returns true if the object is found in the array, (using indexOf) */ - exports.inArray = function (array, obj) { - return array.indexOf(obj) !== -1; - }; - /** Given an array, and an item, if the item is found in the array, it removes it (in-place). The same array is returned */ - exports.removeFrom = hof_1.curry(function (array, obj) { - var idx = array.indexOf(obj); - if (idx >= 0) - array.splice(idx, 1); - return array; - }); - /** - * Applies a set of defaults to an options object. The options object is filtered - * to only those properties of the objects in the defaultsList. - * Earlier objects in the defaultsList take precedence when applying defaults. - */ - function defaults(opts) { - if (opts === void 0) { opts = {}; } - var defaultsList = []; - for (var _i = 1; _i < arguments.length; _i++) { - defaultsList[_i - 1] = arguments[_i]; - } - var defaults = merge.apply(null, [{}].concat(defaultsList)); - return exports.extend({}, defaults, pick(opts || {}, Object.keys(defaults))); - } - exports.defaults = defaults; - /** - * Merges properties from the list of objects to the destination object. - * If a property already exists in the destination object, then it is not overwritten. - */ - function merge(dst) { - var objs = []; - for (var _i = 1; _i < arguments.length; _i++) { - objs[_i - 1] = arguments[_i]; - } - exports.forEach(objs, function (obj) { - exports.forEach(obj, function (value, key) { - if (!dst.hasOwnProperty(key)) - dst[key] = value; - }); - }); - return dst; - } - exports.merge = merge; - /** Reduce function that merges each element of the list into a single object, using extend */ - exports.mergeR = function (memo, item) { return exports.extend(memo, item); }; - /** - * Finds the common ancestor path between two states. - * - * @param {Object} first The first state. - * @param {Object} second The second state. - * @return {Array} Returns an array of state names in descending order, not including the root. - */ - function ancestors(first, second) { - var path = []; - for (var n in first.path) { - if (first.path[n] !== second.path[n]) - break; - path.push(first.path[n]); - } - return path; - } - exports.ancestors = ancestors; - /** - * Performs a non-strict comparison of the subset of two objects, defined by a list of keys. - * - * @param {Object} a The first object. - * @param {Object} b The second object. - * @param {Array} keys The list of keys within each object to compare. If the list is empty or not specified, - * it defaults to the list of keys in `a`. - * @return {Boolean} Returns `true` if the keys match, otherwise `false`. - */ - function equalForKeys(a, b, keys) { - if (keys === void 0) { keys = Object.keys(a); } - for (var i = 0; i < keys.length; i++) { - var k = keys[i]; - if (a[k] != b[k]) - return false; // Not '===', values aren't necessarily normalized - } - return true; - } - exports.equalForKeys = equalForKeys; - function pickOmitImpl(predicate, obj) { - var keys = []; - for (var _i = 2; _i < arguments.length; _i++) { - keys[_i - 2] = arguments[_i]; - } - var objCopy = {}; - for (var key in obj) { - if (predicate(keys, key)) - objCopy[key] = obj[key]; - } - return objCopy; - } - /** Return a copy of the object only containing the whitelisted properties. */ - function pick(obj) { - return pickOmitImpl.apply(null, [exports.inArray].concat(restArgs(arguments))); - } - exports.pick = pick; - /** Return a copy of the object omitting the blacklisted properties. */ - function omit(obj) { - var notInArray = function (array, item) { return !exports.inArray(array, item); }; - return pickOmitImpl.apply(null, [notInArray].concat(restArgs(arguments))); - } - exports.omit = omit; - /** - * Maps an array, or object to a property (by name) - */ - function pluck(collection, propName) { - return map(collection, hof_1.prop(propName)); - } - exports.pluck = pluck; - /** Filters an Array or an Object's properties based on a predicate */ - function filter(collection, callback) { - var arr = predicates_1.isArray(collection), result = arr ? [] : {}; - var accept = arr ? function (x) { return result.push(x); } : function (x, key) { return result[key] = x; }; - exports.forEach(collection, function (item, i) { - if (callback(item, i)) - accept(item, i); - }); - return result; - } - exports.filter = filter; - /** Finds an object from an array, or a property of an object, that matches a predicate */ - function find(collection, callback) { - var result; - exports.forEach(collection, function (item, i) { - if (result) - return; - if (callback(item, i)) - result = item; - }); - return result; - } - exports.find = find; - /** Given an object, returns a new object, where each property is transformed by the callback function */ - exports.mapObj = map; - /** Maps an array or object properties using a callback function */ - function map(collection, callback) { - var result = predicates_1.isArray(collection) ? [] : {}; - exports.forEach(collection, function (item, i) { return result[i] = callback(item, i); }); - return result; - } - exports.map = map; - /** - * Given an object, return its enumerable property values - * - * @example - * ``` - * - * let foo = { a: 1, b: 2, c: 3 } - * let vals = values(foo); // [ 1, 2, 3 ] - * ``` - */ - exports.values = function (obj) { - return Object.keys(obj).map(function (key) { return obj[key]; }); - }; - /** - * Reduce function that returns true if all of the values are truthy. - * - * @example - * ``` - * - * let vals = [ 1, true, {}, "hello world"]; - * vals.reduce(allTrueR, true); // true - * - * vals.push(0); - * vals.reduce(allTrueR, true); // false - * ``` - */ - exports.allTrueR = function (memo, elem) { return memo && elem; }; - /** - * Reduce function that returns true if any of the values are truthy. - * - * * @example - * ``` - * - * let vals = [ 0, null, undefined ]; - * vals.reduce(anyTrueR, true); // false - * - * vals.push("hello world"); - * vals.reduce(anyTrueR, true); // true - * ``` - */ - exports.anyTrueR = function (memo, elem) { return memo || elem; }; - /** - * Reduce function which un-nests a single level of arrays - * @example - * ``` - * - * let input = [ [ "a", "b" ], [ "c", "d" ], [ [ "double", "nested" ] ] ]; - * input.reduce(unnestR, []) // [ "a", "b", "c", "d", [ "double, "nested" ] ] - * ``` - */ - exports.unnestR = function (memo, elem) { return memo.concat(elem); }; - /** - * Reduce function which recursively un-nests all arrays - * - * @example - * ``` - * - * let input = [ [ "a", "b" ], [ "c", "d" ], [ [ "double", "nested" ] ] ]; - * input.reduce(unnestR, []) // [ "a", "b", "c", "d", "double, "nested" ] - * ``` - */ - exports.flattenR = function (memo, elem) { - return predicates_1.isArray(elem) ? memo.concat(elem.reduce(exports.flattenR, [])) : pushR(memo, elem); - }; - /** - * Reduce function that pushes an object to an array, then returns the array. - * Mostly just for [[flattenR]] and [[uniqR]] - */ - function pushR(arr, obj) { - arr.push(obj); - return arr; - } - exports.pushR = pushR; - /** Reduce function that filters out duplicates */ - exports.uniqR = function (acc, token) { - return exports.inArray(acc, token) ? acc : pushR(acc, token); - }; - /** - * Return a new array with a single level of arrays unnested. - * - * @example - * ``` - * - * let input = [ [ "a", "b" ], [ "c", "d" ], [ [ "double", "nested" ] ] ]; - * unnest(input) // [ "a", "b", "c", "d", [ "double, "nested" ] ] - * ``` - */ - exports.unnest = function (arr) { return arr.reduce(exports.unnestR, []); }; - /** - * Return a completely flattened version of an array. - * - * @example - * ``` - * - * let input = [ [ "a", "b" ], [ "c", "d" ], [ [ "double", "nested" ] ] ]; - * flatten(input) // [ "a", "b", "c", "d", "double, "nested" ] - * ``` - */ - exports.flatten = function (arr) { return arr.reduce(exports.flattenR, []); }; - /** - * Given a .filter Predicate, builds a .filter Predicate which throws an error if any elements do not pass. - * @example - * ``` - * - * let isNumber = (obj) => typeof(obj) === 'number'; - * let allNumbers = [ 1, 2, 3, 4, 5 ]; - * allNumbers.filter(assertPredicate(isNumber)); //OK - * - * let oneString = [ 1, 2, 3, 4, "5" ]; - * oneString.filter(assertPredicate(isNumber, "Not all numbers")); // throws Error(""Not all numbers""); - * ``` - */ - function assertPredicate(predicate, errMsg) { - if (errMsg === void 0) { errMsg = "assert failure"; } - return function (obj) { - if (!predicate(obj)) { - throw new Error(predicates_1.isFunction(errMsg) ? errMsg(obj) : errMsg); - } - return true; - }; - } - exports.assertPredicate = assertPredicate; - /** - * Like _.pairs: Given an object, returns an array of key/value pairs - * - * @example - * ``` - * - * pairs({ foo: "FOO", bar: "BAR }) // [ [ "foo", "FOO" ], [ "bar": "BAR" ] ] - * ``` - */ - exports.pairs = function (obj) { - return Object.keys(obj).map(function (key) { return [key, obj[key]]; }); - }; - /** - * Given two or more parallel arrays, returns an array of tuples where - * each tuple is composed of [ a[i], b[i], ... z[i] ] - * - * @example - * ``` - * - * let foo = [ 0, 2, 4, 6 ]; - * let bar = [ 1, 3, 5, 7 ]; - * let baz = [ 10, 30, 50, 70 ]; - * arrayTuples(foo, bar); // [ [0, 1], [2, 3], [4, 5], [6, 7] ] - * arrayTuples(foo, bar, baz); // [ [0, 1, 10], [2, 3, 30], [4, 5, 50], [6, 7, 70] ] - * ``` - */ - function arrayTuples() { - var arrayArgs = []; - for (var _i = 0; _i < arguments.length; _i++) { - arrayArgs[_i - 0] = arguments[_i]; - } - if (arrayArgs.length === 0) - return []; - var length = arrayArgs.reduce(function (min, arr) { return Math.min(arr.length, min); }, 9007199254740991); // aka 2^53 − 1 aka Number.MAX_SAFE_INTEGER - return Array.apply(null, Array(length)).map(function (ignored, idx) { return arrayArgs.map(function (arr) { return arr[idx]; }); }); - } - exports.arrayTuples = arrayTuples; - /** - * Reduce function which builds an object from an array of [key, value] pairs. - * - * Each iteration sets the key/val pair on the memo object, then returns the memo for the next iteration. - * - * Each keyValueTuple should be an array with values [ key: string, value: any ] - * - * @example - * ``` - * - * var pairs = [ ["fookey", "fooval"], ["barkey", "barval"] ] - * - * var pairsToObj = pairs.reduce((memo, pair) => applyPairs(memo, pair), {}) - * // pairsToObj == { fookey: "fooval", barkey: "barval" } - * - * // Or, more simply: - * var pairsToObj = pairs.reduce(applyPairs, {}) - * // pairsToObj == { fookey: "fooval", barkey: "barval" } - * ``` - */ - function applyPairs(memo, keyValTuple) { - var key, value; - if (predicates_1.isArray(keyValTuple)) - key = keyValTuple[0], value = keyValTuple[1]; - if (!predicates_1.isString(key)) - throw new Error("invalid parameters to applyPairs"); - memo[key] = value; - return memo; - } - exports.applyPairs = applyPairs; - /** Get the last element of an array */ - function tail(arr) { - return arr.length && arr[arr.length - 1] || undefined; - } - exports.tail = tail; - /** - * shallow copy from src to dest - * - * note: This is a shallow copy, while angular.copy is a deep copy. - * ui-router uses `copy` only to make copies of state parameters. - */ - function _copy(src, dest) { - if (dest) - Object.keys(dest).forEach(function (key) { return delete dest[key]; }); - if (!dest) - dest = {}; - return exports.extend(dest, src); - } - /** Naive forEach implementation works with Objects or Arrays */ - function _forEach(obj, cb, _this) { - if (predicates_1.isArray(obj)) - return obj.forEach(cb, _this); - Object.keys(obj).forEach(function (key) { return cb(obj[key], key); }); - } - function _copyProps(to, from) { - Object.keys(from).forEach(function (key) { return to[key] = from[key]; }); - return to; - } - function _extend(toObj) { - return restArgs(arguments, 1).filter(exports.identity).reduce(_copyProps, toObj); - } - function _equals(o1, o2) { - if (o1 === o2) - return true; - if (o1 === null || o2 === null) - return false; - if (o1 !== o1 && o2 !== o2) - return true; // NaN === NaN - var t1 = typeof o1, t2 = typeof o2; - if (t1 !== t2 || t1 !== 'object') - return false; - var tup = [o1, o2]; - if (hof_1.all(predicates_1.isArray)(tup)) - return _arraysEq(o1, o2); - if (hof_1.all(predicates_1.isDate)(tup)) - return o1.getTime() === o2.getTime(); - if (hof_1.all(predicates_1.isRegExp)(tup)) - return o1.toString() === o2.toString(); - if (hof_1.all(predicates_1.isFunction)(tup)) - return true; // meh - var predicates = [predicates_1.isFunction, predicates_1.isArray, predicates_1.isDate, predicates_1.isRegExp]; - if (predicates.map(hof_1.any).reduce(function (b, fn) { return b || !!fn(tup); }, false)) - return false; - var key, keys = {}; - for (key in o1) { - if (!_equals(o1[key], o2[key])) - return false; - keys[key] = true; - } - for (key in o2) { - if (!keys[key]) - return false; - } - return true; - } - function _arraysEq(a1, a2) { - if (a1.length !== a2.length) - return false; - return arrayTuples(a1, a2).reduce(function (b, t) { return b && _equals(t[0], t[1]); }, true); - } - // issue #2676 - exports.silenceUncaughtInPromise = function (promise) { - return promise.catch(function (e) { return 0; }) && promise; - }; - exports.silentRejection = function (error) { - return exports.silenceUncaughtInPromise(coreservices_1.services.$q.reject(error)); - }; - - -/***/ }, -/* 4 */ -/***/ function(module, exports, __webpack_require__) { - - "use strict"; - /** Predicates @module common_predicates */ /** */ - var hof_1 = __webpack_require__(5); - var toStr = Object.prototype.toString; - var tis = function (t) { return function (x) { return typeof (x) === t; }; }; - exports.isUndefined = tis('undefined'); - exports.isDefined = hof_1.not(exports.isUndefined); - exports.isNull = function (o) { return o === null; }; - exports.isFunction = tis('function'); - exports.isNumber = tis('number'); - exports.isString = tis('string'); - exports.isObject = function (x) { return x !== null && typeof x === 'object'; }; - exports.isArray = Array.isArray; - exports.isDate = (function (x) { return toStr.call(x) === '[object Date]'; }); - exports.isRegExp = (function (x) { return toStr.call(x) === '[object RegExp]'; }); - /** - * Predicate which checks if a value is injectable - * - * A value is "injectable" if it is a function, or if it is an ng1 array-notation-style array - * where all the elements in the array are Strings, except the last one, which is a Function - */ - function isInjectable(val) { - if (exports.isArray(val) && val.length) { - var head = val.slice(0, -1), tail = val.slice(-1); - return !(head.filter(hof_1.not(exports.isString)).length || tail.filter(hof_1.not(exports.isFunction)).length); - } - return exports.isFunction(val); - } - exports.isInjectable = isInjectable; - /** - * Predicate which checks if a value looks like a Promise - * - * It is probably a Promise if it's an object, and it has a `then` property which is a Function - */ - exports.isPromise = hof_1.and(exports.isObject, hof_1.pipe(hof_1.prop('then'), exports.isFunction)); - - -/***/ }, -/* 5 */ -/***/ function(module, exports) { - - /** - * Higher order functions - * - * @module common_hof - */ /** */ - "use strict"; - /** - * Returns a new function for [Partial Application](https://en.wikipedia.org/wiki/Partial_application) of the original function. - * - * Given a function with N parameters, returns a new function that supports partial application. - * The new function accepts anywhere from 1 to N parameters. When that function is called with M parameters, - * where M is less than N, it returns a new function that accepts the remaining parameters. It continues to - * accept more parameters until all N parameters have been supplied. - * - * - * This contrived example uses a partially applied function as an predicate, which returns true - * if an object is found in both arrays. - * @example - * ``` - * // returns true if an object is in both of the two arrays - * function inBoth(array1, array2, object) { - * return array1.indexOf(object) !== -1 && - * array2.indexOf(object) !== 1; - * } - * let obj1, obj2, obj3, obj4, obj5, obj6, obj7 - * let foos = [obj1, obj3] - * let bars = [obj3, obj4, obj5] - * - * // A curried "copy" of inBoth - * let curriedInBoth = curry(inBoth); - * // Partially apply both the array1 and array2 - * let inFoosAndBars = curriedInBoth(foos, bars); - * - * // Supply the final argument; since all arguments are - * // supplied, the original inBoth function is then called. - * let obj1InBoth = inFoosAndBars(obj1); // false - * - * // Use the inFoosAndBars as a predicate. - * // Filter, on each iteration, supplies the final argument - * let allObjs = [ obj1, obj2, obj3, obj4, obj5, obj6, obj7 ]; - * let foundInBoth = allObjs.filter(inFoosAndBars); // [ obj3 ] - * - * ``` - * - * Stolen from: http://stackoverflow.com/questions/4394747/javascript-curry-function - * - * @param fn - * @returns {*|function(): (*|any)} - */ - function curry(fn) { - var initial_args = [].slice.apply(arguments, [1]); - var func_args_length = fn.length; - function curried(args) { - if (args.length >= func_args_length) - return fn.apply(null, args); - return function () { - return curried(args.concat([].slice.apply(arguments))); - }; - } - return curried(initial_args); - } - exports.curry = curry; - /** - * Given a varargs list of functions, returns a function that composes the argument functions, right-to-left - * given: f(x), g(x), h(x) - * let composed = compose(f,g,h) - * then, composed is: f(g(h(x))) - */ - function compose() { - var args = arguments; - var start = args.length - 1; - return function () { - var i = start, result = args[start].apply(this, arguments); - while (i--) - result = args[i].call(this, result); - return result; - }; - } - exports.compose = compose; - /** - * Given a varargs list of functions, returns a function that is composes the argument functions, left-to-right - * given: f(x), g(x), h(x) - * let piped = pipe(f,g,h); - * then, piped is: h(g(f(x))) - */ - function pipe() { - var funcs = []; - for (var _i = 0; _i < arguments.length; _i++) { - funcs[_i - 0] = arguments[_i]; - } - return compose.apply(null, [].slice.call(arguments).reverse()); - } - exports.pipe = pipe; - /** - * Given a property name, returns a function that returns that property from an object - * let obj = { foo: 1, name: "blarg" }; - * let getName = prop("name"); - * getName(obj) === "blarg" - */ - exports.prop = function (name) { - return function (obj) { return obj && obj[name]; }; - }; - /** - * Given a property name and a value, returns a function that returns a boolean based on whether - * the passed object has a property that matches the value - * let obj = { foo: 1, name: "blarg" }; - * let getName = propEq("name", "blarg"); - * getName(obj) === true - */ - exports.propEq = curry(function (name, val, obj) { return obj && obj[name] === val; }); - /** - * Given a dotted property name, returns a function that returns a nested property from an object, or undefined - * let obj = { id: 1, nestedObj: { foo: 1, name: "blarg" }, }; - * let getName = prop("nestedObj.name"); - * getName(obj) === "blarg" - * let propNotFound = prop("this.property.doesnt.exist"); - * propNotFound(obj) === undefined - */ - exports.parse = function (name) { - return pipe.apply(null, name.split(".").map(exports.prop)); - }; - /** - * Given a function that returns a truthy or falsey value, returns a - * function that returns the opposite (falsey or truthy) value given the same inputs - */ - exports.not = function (fn) { - return function () { - var args = []; - for (var _i = 0; _i < arguments.length; _i++) { - args[_i - 0] = arguments[_i]; - } - return !fn.apply(null, args); - }; - }; - /** - * Given two functions that return truthy or falsey values, returns a function that returns truthy - * if both functions return truthy for the given arguments - */ - function and(fn1, fn2) { - return function () { - var args = []; - for (var _i = 0; _i < arguments.length; _i++) { - args[_i - 0] = arguments[_i]; - } - return fn1.apply(null, args) && fn2.apply(null, args); - }; - } - exports.and = and; - /** - * Given two functions that return truthy or falsey values, returns a function that returns truthy - * if at least one of the functions returns truthy for the given arguments - */ - function or(fn1, fn2) { - return function () { - var args = []; - for (var _i = 0; _i < arguments.length; _i++) { - args[_i - 0] = arguments[_i]; - } - return fn1.apply(null, args) || fn2.apply(null, args); - }; - } - exports.or = or; - /** - * Check if all the elements of an array match a predicate function - * - * @param fn1 a predicate function `fn1` - * @returns a function which takes an array and returns true if `fn1` is true for all elements of the array - */ - exports.all = function (fn1) { - return function (arr) { return arr.reduce(function (b, x) { return b && !!fn1(x); }, true); }; - }; - exports.any = function (fn1) { - return function (arr) { return arr.reduce(function (b, x) { return b || !!fn1(x); }, false); }; - }; - /** Given a class, returns a Predicate function that returns true if the object is of that class */ - exports.is = function (ctor) { return function (obj) { - return (obj != null && obj.constructor === ctor || obj instanceof ctor); - }; }; - /** Given a value, returns a Predicate function that returns true if another value is === equal to the original value */ - exports.eq = function (val) { return function (other) { - return val === other; - }; }; - /** Given a value, returns a function which returns the value */ - exports.val = function (v) { return function () { return v; }; }; - function invoke(fnName, args) { - return function (obj) { - return obj[fnName].apply(obj, args); - }; - } - exports.invoke = invoke; - /** - * Sorta like Pattern Matching (a functional programming conditional construct) - * - * See http://c2.com/cgi/wiki?PatternMatching - * - * This is a conditional construct which allows a series of predicates and output functions - * to be checked and then applied. Each predicate receives the input. If the predicate - * returns truthy, then its matching output function (mapping function) is provided with - * the input and, then the result is returned. - * - * Each combination (2-tuple) of predicate + output function should be placed in an array - * of size 2: [ predicate, mapFn ] - * - * These 2-tuples should be put in an outer array. - * - * @example - * ``` - * - * // Here's a 2-tuple where the first element is the isString predicate - * // and the second element is a function that returns a description of the input - * let firstTuple = [ angular.isString, (input) => `Heres your string ${input}` ]; - * - * // Second tuple: predicate "isNumber", mapfn returns a description - * let secondTuple = [ angular.isNumber, (input) => `(${input}) That's a number!` ]; - * - * let third = [ (input) => input === null, (input) => `Oh, null...` ]; - * - * let fourth = [ (input) => input === undefined, (input) => `notdefined` ]; - * - * let descriptionOf = pattern([ firstTuple, secondTuple, third, fourth ]); - * - * console.log(descriptionOf(undefined)); // 'notdefined' - * console.log(descriptionOf(55)); // '(55) That's a number!' - * console.log(descriptionOf("foo")); // 'Here's your string foo' - * ``` - * - * @param struct A 2D array. Each element of the array should be an array, a 2-tuple, - * with a Predicate and a mapping/output function - * @returns {function(any): *} - */ - function pattern(struct) { - return function (x) { - for (var i = 0; i < struct.length; i++) { - if (struct[i][0](x)) - return struct[i][1](x); - } - }; - } - exports.pattern = pattern; - - -/***/ }, -/* 6 */ -/***/ function(module, exports) { - - "use strict"; - var notImplemented = function (fnname) { return function () { - throw new Error(fnname + "(): No coreservices implementation for UI-Router is loaded. You should include one of: ['angular1.js']"); - }; }; - var services = { - $q: undefined, - $injector: undefined, - location: {}, - locationConfig: {}, - template: {} - }; - exports.services = services; - ["setUrl", "path", "search", "hash", "onChange"] - .forEach(function (key) { return services.location[key] = notImplemented(key); }); - ["port", "protocol", "host", "baseHref", "html5Mode", "hashPrefix"] - .forEach(function (key) { return services.locationConfig[key] = notImplemented(key); }); - - -/***/ }, -/* 7 */ -/***/ function(module, exports) { - - "use strict"; - /** @module common */ - /** - * Matches state names using glob-like pattern strings. - * - * Globs can be used in specific APIs including: - * - * - [[StateService.is]] - * - [[StateService.includes]] - * - [[HookMatchCriteria.to]] - * - [[HookMatchCriteria.from]] - * - [[HookMatchCriteria.exiting]] - * - [[HookMatchCriteria.retained]] - * - [[HookMatchCriteria.entering]] - * - * A `Glob` string is a pattern which matches state names. - * Nested state names are split into segments (separated by a dot) when processing. - * The state named `foo.bar.baz` is split into three segments ['foo', 'bar', 'baz'] - * - * Globs work according to the following rules: - * - * ### Exact match: - * - * The glob `'A.B'` matches the state named exactly `'A.B'`. - * - * | Glob |Matches states named|Does not match state named| - * |:------------|:--------------------|:---------------------| - * | `'A'` | `'A'` | `'B'` , `'A.C'` | - * | `'A.B'` | `'A.B'` | `'A'` , `'A.B.C'` | - * | `'foo'` | `'foo'` | `'FOO'` , `'foo.bar'`| - * - * ### Single star (`*`) - * - * A single star (`*`) is a wildcard that matches exactly one segment. - * - * | Glob |Matches states named |Does not match state named | - * |:------------|:---------------------|:--------------------------| - * | `'*'` | `'A'` , `'Z'` | `'A.B'` , `'Z.Y.X'` | - * | `'A.*'` | `'A.B'` , `'A.C'` | `'A'` , `'A.B.C'` | - * | `'A.*.*'` | `'A.B.C'` , `'A.X.Y'`| `'A'`, `'A.B'` , `'Z.Y.X'`| - * - * ### Double star (`**`) - * - * A double star (`'**'`) is a wildcard that matches *zero or more segments* - * - * | Glob |Matches states named |Does not match state named | - * |:------------|:----------------------------------------------|:----------------------------------| - * | `'**'` | `'A'` , `'A.B'`, `'Z.Y.X'` | (matches all states) | - * | `'A.**'` | `'A'` , `'A.B'` , `'A.C.X'` | `'Z.Y.X'` | - * | `'**.X'` | `'X'` , `'A.X'` , `'Z.Y.X'` | `'A'` , `'A.login.Z'` | - * | `'A.**.X'` | `'A.X'` , `'A.B.X'` , `'A.B.C.X'` | `'A'` , `'A.B.C'` | - * - */ - var Glob = (function () { - function Glob(text) { - this.text = text; - this.glob = text.split('.'); - var regexpString = this.text.split('.') - .map(function (seg) { - if (seg === '**') - return '(?:|(?:\\.[^.]*)*)'; - if (seg === '*') - return '\\.[^.]*'; - return '\\.' + seg; - }).join(''); - this.regexp = new RegExp("^" + regexpString + "$"); - } - Glob.prototype.matches = function (name) { - return this.regexp.test('.' + name); - }; - /** @deprecated whats the point? */ - Glob.is = function (text) { - return text.indexOf('*') > -1; - }; - /** @deprecated whats the point? */ - Glob.fromString = function (text) { - if (!this.is(text)) - return null; - return new Glob(text); - }; - return Glob; - }()); - exports.Glob = Glob; - - -/***/ }, -/* 8 */ -/***/ function(module, exports) { - - /** @module common */ /** for typedoc */ - "use strict"; - var Queue = (function () { - function Queue(_items, _limit) { - if (_items === void 0) { _items = []; } - if (_limit === void 0) { _limit = null; } - this._items = _items; - this._limit = _limit; - } - Queue.prototype.enqueue = function (item) { - var items = this._items; - items.push(item); - if (this._limit && items.length > this._limit) - items.shift(); - return item; - }; - Queue.prototype.dequeue = function () { - if (this.size()) - return this._items.splice(0, 1)[0]; - }; - Queue.prototype.clear = function () { - var current = this._items; - this._items = []; - return current; - }; - Queue.prototype.size = function () { - return this._items.length; - }; - Queue.prototype.remove = function (item) { - var idx = this._items.indexOf(item); - return idx > -1 && this._items.splice(idx, 1)[0]; - }; - Queue.prototype.peekTail = function () { - return this._items[this._items.length - 1]; - }; - Queue.prototype.peekHead = function () { - if (this.size()) - return this._items[0]; - }; - return Queue; - }()); - exports.Queue = Queue; - - -/***/ }, -/* 9 */ -/***/ function(module, exports, __webpack_require__) { - - /** @module common_strings */ /** */ - "use strict"; - var predicates_1 = __webpack_require__(4); - var rejectFactory_1 = __webpack_require__(10); - var common_1 = __webpack_require__(3); - var hof_1 = __webpack_require__(5); - var transition_1 = __webpack_require__(11); - var resolvable_1 = __webpack_require__(19); - /** - * Returns a string shortened to a maximum length - * - * If the string is already less than the `max` length, return the string. - * Else return the string, shortened to `max - 3` and append three dots ("..."). - * - * @param max the maximum length of the string to return - * @param str the input string - */ - function maxLength(max, str) { - if (str.length <= max) - return str; - return str.substr(0, max - 3) + "..."; - } - exports.maxLength = maxLength; - /** - * Returns a string, with spaces added to the end, up to a desired str length - * - * If the string is already longer than the desired length, return the string. - * Else returns the string, with extra spaces on the end, such that it reaches `length` characters. - * - * @param length the desired length of the string to return - * @param str the input string - */ - function padString(length, str) { - while (str.length < length) - str += " "; - return str; - } - exports.padString = padString; - function kebobString(camelCase) { - return camelCase - .replace(/^([A-Z])/, function ($1) { return $1.toLowerCase(); }) // replace first char - .replace(/([A-Z])/g, function ($1) { return "-" + $1.toLowerCase(); }); // replace rest - } - exports.kebobString = kebobString; - function _toJson(obj) { - return JSON.stringify(obj); - } - function _fromJson(json) { - return predicates_1.isString(json) ? JSON.parse(json) : json; - } - function promiseToString(p) { - return "Promise(" + JSON.stringify(p) + ")"; - } - function functionToString(fn) { - var fnStr = fnToString(fn); - var namedFunctionMatch = fnStr.match(/^(function [^ ]+\([^)]*\))/); - var toStr = namedFunctionMatch ? namedFunctionMatch[1] : fnStr; - var fnName = fn['name'] || ""; - if (fnName && toStr.match(/function \(/)) { - return 'function ' + fnName + toStr.substr(9); - } - return toStr; - } - exports.functionToString = functionToString; - function fnToString(fn) { - var _fn = predicates_1.isArray(fn) ? fn.slice(-1)[0] : fn; - return _fn && _fn.toString() || "undefined"; - } - exports.fnToString = fnToString; - var stringifyPatternFn = null; - var stringifyPattern = function (value) { - var isTransitionRejectionPromise = rejectFactory_1.Rejection.isTransitionRejectionPromise; - stringifyPatternFn = stringifyPatternFn || hof_1.pattern([ - [hof_1.not(predicates_1.isDefined), hof_1.val("undefined")], - [predicates_1.isNull, hof_1.val("null")], - [predicates_1.isPromise, hof_1.val("[Promise]")], - [isTransitionRejectionPromise, function (x) { return x._transitionRejection.toString(); }], - [hof_1.is(rejectFactory_1.Rejection), hof_1.invoke("toString")], - [hof_1.is(transition_1.Transition), hof_1.invoke("toString")], - [hof_1.is(resolvable_1.Resolvable), hof_1.invoke("toString")], - [predicates_1.isInjectable, functionToString], - [hof_1.val(true), common_1.identity] - ]); - return stringifyPatternFn(value); - }; - function stringify(o) { - var seen = []; - function format(val) { - if (predicates_1.isObject(val)) { - if (seen.indexOf(val) !== -1) - return '[circular ref]'; - seen.push(val); - } - return stringifyPattern(val); - } - return JSON.stringify(o, function (key, val) { return format(val); }).replace(/\\"/g, '"'); - } - exports.stringify = stringify; - /** Returns a function that splits a string on a character or substring */ - exports.beforeAfterSubstr = function (char) { return function (str) { - if (!str) - return ["", ""]; - var idx = str.indexOf(char); - if (idx === -1) - return [str, ""]; - return [str.substr(0, idx), str.substr(idx + 1)]; - }; }; - - -/***/ }, -/* 10 */ -/***/ function(module, exports, __webpack_require__) { - - /** @module transition */ /** for typedoc */ - "use strict"; - var common_1 = __webpack_require__(3); - var strings_1 = __webpack_require__(9); - (function (RejectType) { - RejectType[RejectType["SUPERSEDED"] = 2] = "SUPERSEDED"; - RejectType[RejectType["ABORTED"] = 3] = "ABORTED"; - RejectType[RejectType["INVALID"] = 4] = "INVALID"; - RejectType[RejectType["IGNORED"] = 5] = "IGNORED"; - RejectType[RejectType["ERROR"] = 6] = "ERROR"; - })(exports.RejectType || (exports.RejectType = {})); - var RejectType = exports.RejectType; - var Rejection = (function () { - function Rejection(type, message, detail) { - this.type = type; - this.message = message; - this.detail = detail; - } - Rejection.prototype.toString = function () { - var detailString = function (d) { - return d && d.toString !== Object.prototype.toString ? d.toString() : strings_1.stringify(d); - }; - var type = this.type, message = this.message, detail = detailString(this.detail); - return "TransitionRejection(type: " + type + ", message: " + message + ", detail: " + detail + ")"; - }; - Rejection.prototype.toPromise = function () { - return common_1.extend(common_1.silentRejection(this), { _transitionRejection: this }); - }; - /** Returns true if the obj is a rejected promise created from the `asPromise` factory */ - Rejection.isTransitionRejectionPromise = function (obj) { - return obj && (typeof obj.then === 'function') && obj._transitionRejection instanceof Rejection; - }; - /** Returns a TransitionRejection due to transition superseded */ - Rejection.superseded = function (detail, options) { - var message = "The transition has been superseded by a different transition"; - var rejection = new Rejection(RejectType.SUPERSEDED, message, detail); - if (options && options.redirected) { - rejection.redirected = true; - } - return rejection; - }; - /** Returns a TransitionRejection due to redirected transition */ - Rejection.redirected = function (detail) { - return Rejection.superseded(detail, { redirected: true }); - }; - /** Returns a TransitionRejection due to invalid transition */ - Rejection.invalid = function (detail) { - var message = "This transition is invalid"; - return new Rejection(RejectType.INVALID, message, detail); - }; - /** Returns a TransitionRejection due to ignored transition */ - Rejection.ignored = function (detail) { - var message = "The transition was ignored"; - return new Rejection(RejectType.IGNORED, message, detail); - }; - /** Returns a TransitionRejection due to aborted transition */ - Rejection.aborted = function (detail) { - // TODO think about how to encapsulate an Error() object - var message = "The transition has been aborted"; - return new Rejection(RejectType.ABORTED, message, detail); - }; - /** Returns a TransitionRejection due to aborted transition */ - Rejection.errored = function (detail) { - // TODO think about how to encapsulate an Error() object - var message = "The transition errored"; - return new Rejection(RejectType.ERROR, message, detail); - }; - return Rejection; - }()); - exports.Rejection = Rejection; - - -/***/ }, -/* 11 */ -/***/ function(module, exports, __webpack_require__) { - - "use strict"; - /** @module transition */ /** for typedoc */ - var strings_1 = __webpack_require__(9); - var trace_1 = __webpack_require__(12); - var coreservices_1 = __webpack_require__(6); - var common_1 = __webpack_require__(3); - var predicates_1 = __webpack_require__(4); - var hof_1 = __webpack_require__(5); - var transitionHook_1 = __webpack_require__(13); - var hookRegistry_1 = __webpack_require__(15); - var hookBuilder_1 = __webpack_require__(16); - var node_1 = __webpack_require__(21); - var pathFactory_1 = __webpack_require__(20); - var targetState_1 = __webpack_require__(14); - var param_1 = __webpack_require__(22); - var resolvable_1 = __webpack_require__(19); - var rejectFactory_1 = __webpack_require__(10); - var resolveContext_1 = __webpack_require__(17); - var router_1 = __webpack_require__(25); - var transitionCount = 0; - var stateSelf = hof_1.prop("self"); - /** - * Represents a transition between two states. - * - * When navigating to a state, we are transitioning **from** the current state **to** the new state. - * - * This object contains all contextual information about the to/from states, parameters, resolves. - * It has information about all states being entered and exited as a result of the transition. - */ - var Transition = (function () { - /** - * Creates a new Transition object. - * - * If the target state is not valid, an error is thrown. - * - * @param fromPath The path of [[PathNode]]s from which the transition is leaving. The last node in the `fromPath` - * encapsulates the "from state". - * @param targetState The target state and parameters being transitioned to (also, the transition options) - * @param router The [[UIRouter]] instance - */ - function Transition(fromPath, targetState, router) { - var _this = this; - /** @hidden */ - this._deferred = coreservices_1.services.$q.defer(); - /** - * This promise is resolved or rejected based on the outcome of the Transition. - * - * When the transition is successful, the promise is resolved - * When the transition is unsuccessful, the promise is rejected with the [[TransitionRejection]] or javascript error - */ - this.promise = this._deferred.promise; - this.treeChanges = function () { return _this._treeChanges; }; - this.isActive = function () { return _this === _this._options.current(); }; - this.router = router; - this._targetState = targetState; - if (!targetState.valid()) { - throw new Error(targetState.error()); - } - // Makes the Transition instance a hook registry (onStart, etc) - hookRegistry_1.HookRegistry.mixin(new hookRegistry_1.HookRegistry(), this); - // current() is assumed to come from targetState.options, but provide a naive implementation otherwise. - this._options = common_1.extend({ current: hof_1.val(this) }, targetState.options()); - this.$id = transitionCount++; - var toPath = pathFactory_1.PathFactory.buildToPath(fromPath, targetState); - this._treeChanges = pathFactory_1.PathFactory.treeChanges(fromPath, toPath, this._options.reloadState); - var enteringStates = this._treeChanges.entering.map(function (node) { return node.state; }); - pathFactory_1.PathFactory.applyViewConfigs(router.transitionService.$view, this._treeChanges.to, enteringStates); - var rootResolvables = [ - new resolvable_1.Resolvable(router_1.UIRouter, function () { return router; }, [], undefined, router), - new resolvable_1.Resolvable(Transition, function () { return _this; }, [], undefined, this), - new resolvable_1.Resolvable('$transition$', function () { return _this; }, [], undefined, this), - new resolvable_1.Resolvable('$stateParams', function () { return _this.params(); }, [], undefined, this.params()) - ]; - var rootNode = this._treeChanges.to[0]; - var context = new resolveContext_1.ResolveContext(this._treeChanges.to); - context.addResolvables(rootResolvables, rootNode.state); - } - /** @inheritdoc */ - Transition.prototype.onBefore = function (matchCriteria, callback, options) { throw ""; }; - ; - /** @inheritdoc */ - Transition.prototype.onStart = function (matchCriteria, callback, options) { throw ""; }; - ; - /** @inheritdoc */ - Transition.prototype.onExit = function (matchCriteria, callback, options) { throw ""; }; - ; - /** @inheritdoc */ - Transition.prototype.onRetain = function (matchCriteria, callback, options) { throw ""; }; - ; - /** @inheritdoc */ - Transition.prototype.onEnter = function (matchCriteria, callback, options) { throw ""; }; - ; - /** @inheritdoc */ - Transition.prototype.onFinish = function (matchCriteria, callback, options) { throw ""; }; - ; - /** @inheritdoc */ - Transition.prototype.onSuccess = function (matchCriteria, callback, options) { throw ""; }; - ; - /** @inheritdoc */ - Transition.prototype.onError = function (matchCriteria, callback, options) { throw ""; }; - ; - Transition.prototype.$from = function () { - return common_1.tail(this._treeChanges.from).state; - }; - Transition.prototype.$to = function () { - return common_1.tail(this._treeChanges.to).state; - }; - /** - * Returns the "from state" - * - * @returns The state object for the Transition's "from state". - */ - Transition.prototype.from = function () { - return this.$from().self; - }; - /** - * Returns the "to state" - * - * @returns The state object for the Transition's target state ("to state"). - */ - Transition.prototype.to = function () { - return this.$to().self; - }; - /** - * Gets the Target State - * - * A transition's [[TargetState]] encapsulates the [[to]] state, the [[params]], and the [[options]]. - * - * @returns the [[TargetState]] of this Transition - */ - Transition.prototype.targetState = function () { - return this._targetState; - }; - /** - * Determines whether two transitions are equivalent. - */ - Transition.prototype.is = function (compare) { - if (compare instanceof Transition) { - // TODO: Also compare parameters - return this.is({ to: compare.$to().name, from: compare.$from().name }); - } - return !((compare.to && !hookRegistry_1.matchState(this.$to(), compare.to)) || - (compare.from && !hookRegistry_1.matchState(this.$from(), compare.from))); - }; - /** - * Gets transition parameter values - * - * @param pathname Pick which treeChanges path to get parameters for: - * (`'to'`, `'from'`, `'entering'`, `'exiting'`, `'retained'`) - * @returns transition parameter values for the desired path. - */ - Transition.prototype.params = function (pathname) { - if (pathname === void 0) { pathname = "to"; } - return this._treeChanges[pathname].map(hof_1.prop("paramValues")).reduce(common_1.mergeR, {}); - }; - /** - * Creates a [[UIInjector]] Dependency Injector - * - * Returns a Dependency Injector for the Transition's target state (to state). - * The injector provides resolve values which the target state has access to. - * - * The `UIInjector` can also provide values from the native root/global injector (ng1/ng2). - * - * If a `state` is provided, the injector that is returned will be limited to resolve values that the provided state has access to. - * - * @param state Limits the resolves provided to only the resolves the provided state has access to. - * @returns a [[UIInjector]] - */ - Transition.prototype.injector = function (state) { - var path = this.treeChanges().to; - if (state) - path = pathFactory_1.PathFactory.subPath(path, function (node) { return node.state === state || node.state.name === state; }); - return new resolveContext_1.ResolveContext(path).injector(); - }; - /** - * Gets all available resolve tokens (keys) - * - * This method can be used in conjunction with [[getResolve]] to inspect the resolve values - * available to the Transition. - * - * The returned tokens include those defined on [[StateDeclaration.resolve]] blocks, for the states - * in the Transition's [[TreeChanges.to]] path. - * - * @returns an array of resolve tokens (keys) - */ - Transition.prototype.getResolveTokens = function () { - return new resolveContext_1.ResolveContext(this._treeChanges.to).getTokens(); - }; - /** - * Gets resolved values - * - * This method can be used in conjunction with [[getResolveTokens]] to inspect what resolve values - * are available to the Transition. - * - * Given a token, returns the resolved data for that token. - * Given an array of tokens, returns an array of resolved data for those tokens. - * - * If a resolvable hasn't yet been fetched, returns `undefined` for that token - * If a resolvable doesn't exist for the token, throws an error. - * - * @param token the token (or array of tokens) - * - * @returns an array of resolve tokens (keys) - */ - Transition.prototype.getResolveValue = function (token) { - var resolveContext = new resolveContext_1.ResolveContext(this._treeChanges.to); - var getData = function (token) { - var resolvable = resolveContext.getResolvable(token); - if (resolvable === undefined) { - throw new Error("Dependency Injection token not found: " + strings_1.stringify(token)); - } - return resolvable.data; - }; - if (predicates_1.isArray(token)) { - return token.map(getData); - } - return getData(token); - }; - /** - * Gets a [[Resolvable]] primitive - * - * This is a lower level API that returns a [[Resolvable]] from the Transition for a given token. - * - * @param token the DI token - * - * @returns the [[Resolvable]] in the transition's to path, or undefined - */ - Transition.prototype.getResolvable = function (token) { - return new resolveContext_1.ResolveContext(this._treeChanges.to).getResolvable(token); - }; - /** - * Dynamically adds a new [[Resolvable]] (`resolve`) to this transition. - * - * @param resolvable an [[Resolvable]] object - * @param state the state in the "to path" which should receive the new resolve (otherwise, the root state) - */ - Transition.prototype.addResolvable = function (resolvable, state) { - if (state === void 0) { state = ""; } - var stateName = (typeof state === "string") ? state : state.name; - var topath = this._treeChanges.to; - var targetNode = common_1.find(topath, function (node) { return node.state.name === stateName; }); - var resolveContext = new resolveContext_1.ResolveContext(topath); - resolveContext.addResolvables([resolvable], targetNode.state); - }; - /** - * If the current transition is a redirect, returns the transition that was redirected. - * - * Gets the transition from which this transition was redirected. - * - * - * @example - * ```js - * - * let transitionA = $state.go('A').transitionA - * transitionA.onStart({}, () => $state.target('B')); - * $transitions.onSuccess({ to: 'B' }, (trans) => { - * trans.to().name === 'B'; // true - * trans.redirectedFrom() === transitionA; // true - * }); - * ``` - * - * @returns The previous Transition, or null if this Transition is not the result of a redirection - */ - Transition.prototype.redirectedFrom = function () { - return this._options.redirectedFrom || null; - }; - /** - * Get the transition options - * - * @returns the options for this Transition. - */ - Transition.prototype.options = function () { - return this._options; - }; - /** - * Gets the states being entered. - * - * @returns an array of states that will be entered during this transition. - */ - Transition.prototype.entering = function () { - return common_1.map(this._treeChanges.entering, hof_1.prop('state')).map(stateSelf); - }; - /** - * Gets the states being exited. - * - * @returns an array of states that will be exited during this transition. - */ - Transition.prototype.exiting = function () { - return common_1.map(this._treeChanges.exiting, hof_1.prop('state')).map(stateSelf).reverse(); - }; - /** - * Gets the states being retained. - * - * @returns an array of states that are already entered from a previous Transition, that will not be - * exited during this Transition - */ - Transition.prototype.retained = function () { - return common_1.map(this._treeChanges.retained, hof_1.prop('state')).map(stateSelf); - }; - /** - * Get the [[ViewConfig]]s associated with this Transition - * - * Each state can define one or more views (template/controller), which are encapsulated as `ViewConfig` objects. - * This method fetches the `ViewConfigs` for a given path in the Transition (e.g., "to" or "entering"). - * - * @param pathname the name of the path to fetch views for: - * (`'to'`, `'from'`, `'entering'`, `'exiting'`, `'retained'`) - * @param state If provided, only returns the `ViewConfig`s for a single state in the path - * - * @returns a list of ViewConfig objects for the given path. - */ - Transition.prototype.views = function (pathname, state) { - if (pathname === void 0) { pathname = "entering"; } - var path = this._treeChanges[pathname]; - path = !state ? path : path.filter(hof_1.propEq('state', state)); - return path.map(hof_1.prop("views")).filter(common_1.identity).reduce(common_1.unnestR, []); - }; - /** - * Creates a new transition that is a redirection of the current one. - * - * This transition can be returned from a [[TransitionService]] hook to - * redirect a transition to a new state and/or set of parameters. - * - * @returns Returns a new [[Transition]] instance. - */ - Transition.prototype.redirect = function (targetState) { - var newOptions = common_1.extend({}, this.options(), targetState.options(), { redirectedFrom: this, source: "redirect" }); - targetState = new targetState_1.TargetState(targetState.identifier(), targetState.$state(), targetState.params(), newOptions); - var newTransition = this.router.transitionService.create(this._treeChanges.from, targetState); - var originalEnteringNodes = this.treeChanges().entering; - var redirectEnteringNodes = newTransition.treeChanges().entering; - // --- Re-use resolve data from original transition --- - // When redirecting from a parent state to a child state where the parent parameter values haven't changed - // (because of the redirect), the resolves fetched by the original transition are still valid in the - // redirected transition. - // - // This allows you to define a redirect on a parent state which depends on an async resolve value. - // You can wait for the resolve, then redirect to a child state based on the result. - // The redirected transition does not have to re-fetch the resolve. - // --------------------------------------------------------- - var nodeIsReloading = function (reloadState) { return function (node) { - return reloadState && node.state.includes[reloadState.name]; - }; }; - // Find any "entering" nodes in the redirect path that match the original path and aren't being reloaded - var matchingEnteringNodes = node_1.PathNode.matching(redirectEnteringNodes, originalEnteringNodes) - .filter(hof_1.not(nodeIsReloading(targetState.options().reloadState))); - // Use the existing (possibly pre-resolved) resolvables for the matching entering nodes. - matchingEnteringNodes.forEach(function (node, idx) { - node.resolvables = originalEnteringNodes[idx].resolvables; - }); - return newTransition; - }; - /** @hidden If a transition doesn't exit/enter any states, returns any [[Param]] whose value changed */ - Transition.prototype._changedParams = function () { - var _a = this._treeChanges, to = _a.to, from = _a.from; - if (this._options.reload || common_1.tail(to).state !== common_1.tail(from).state) - return undefined; - var nodeSchemas = to.map(function (node) { return node.paramSchema; }); - var _b = [to, from].map(function (path) { return path.map(function (x) { return x.paramValues; }); }), toValues = _b[0], fromValues = _b[1]; - var tuples = common_1.arrayTuples(nodeSchemas, toValues, fromValues); - return tuples.map(function (_a) { - var schema = _a[0], toVals = _a[1], fromVals = _a[2]; - return param_1.Param.changed(schema, toVals, fromVals); - }).reduce(common_1.unnestR, []); - }; - /** - * Returns true if the transition is dynamic. - * - * A transition is dynamic if no states are entered nor exited, but at least one dynamic parameter has changed. - * - * @returns true if the Transition is dynamic - */ - Transition.prototype.dynamic = function () { - var changes = this._changedParams(); - return !changes ? false : changes.map(function (x) { return x.dynamic; }).reduce(common_1.anyTrueR, false); - }; - /** - * Returns true if the transition is ignored. - * - * A transition is ignored if no states are entered nor exited, and no parameter values have changed. - * - * @returns true if the Transition is ignored. - */ - Transition.prototype.ignored = function () { - var changes = this._changedParams(); - return !changes ? false : changes.length === 0; - }; - /** - * @hidden - */ - Transition.prototype.hookBuilder = function () { - return new hookBuilder_1.HookBuilder(this.router.transitionService, this, { - transition: this, - current: this._options.current - }); - }; - /** - * Runs the transition - * - * This method is generally called from the [[StateService.transitionTo]] - * - * @returns a promise for a successful transition. - */ - Transition.prototype.run = function () { - var _this = this; - var runSynchronousHooks = transitionHook_1.TransitionHook.runSynchronousHooks; - var hookBuilder = this.hookBuilder(); - var globals = this.router.globals; - globals.transitionHistory.enqueue(this); - var syncResult = runSynchronousHooks(hookBuilder.getOnBeforeHooks()); - if (rejectFactory_1.Rejection.isTransitionRejectionPromise(syncResult)) { - syncResult.catch(function () { return 0; }); // issue #2676 - var rejectReason = syncResult._transitionRejection; - this._deferred.reject(rejectReason); - return this.promise; - } - if (!this.valid()) { - var error = new Error(this.error()); - this._deferred.reject(error); - return this.promise; - } - if (this.ignored()) { - trace_1.trace.traceTransitionIgnored(this); - this._deferred.reject(rejectFactory_1.Rejection.ignored()); - return this.promise; - } - // When the chain is complete, then resolve or reject the deferred - var transitionSuccess = function () { - trace_1.trace.traceSuccess(_this.$to(), _this); - _this.success = true; - _this._deferred.resolve(_this.to()); - runSynchronousHooks(hookBuilder.getOnSuccessHooks(), true); - }; - var transitionError = function (reason) { - trace_1.trace.traceError(reason, _this); - _this.success = false; - _this._deferred.reject(reason); - _this._error = reason; - runSynchronousHooks(hookBuilder.getOnErrorHooks(), true); - }; - trace_1.trace.traceTransitionStart(this); - // Chain the next hook off the previous - var appendHookToChain = function (prev, nextHook) { - return prev.then(function () { return nextHook.invokeHook(); }); - }; - // Run the hooks, then resolve or reject the overall deferred in the .then() handler - hookBuilder.asyncHooks() - .reduce(appendHookToChain, syncResult) - .then(transitionSuccess, transitionError); - return this.promise; - }; - /** - * Checks if the Transition is valid - * - * @returns true if the Transition is valid - */ - Transition.prototype.valid = function () { - return !this.error() || this.success !== undefined; - }; - /** - * The Transition error reason. - * - * If the transition is invalid (and could not be run), returns the reason the transition is invalid. - * If the transition was valid and ran, but was not successful, returns the reason the transition failed. - * - * @returns an error message explaining why the transition is invalid, or the reason the transition failed. - */ - Transition.prototype.error = function () { - var state = this.$to(); - var redirects = 0, trans = this; - while ((trans = trans.redirectedFrom()) != null) { - if (++redirects > 20) - return "Too many Transition redirects (20+)"; - } - if (state.self.abstract) - return "Cannot transition to abstract state '" + state.name + "'"; - if (!param_1.Param.validates(state.parameters(), this.params())) - return "Param values not valid for state '" + state.name + "'"; - if (this.success === false) - return this._error; - }; - /** - * A string representation of the Transition - * - * @returns A string representation of the Transition - */ - Transition.prototype.toString = function () { - var fromStateOrName = this.from(); - var toStateOrName = this.to(); - var avoidEmptyHash = function (params) { - return (params["#"] !== null && params["#"] !== undefined) ? params : common_1.omit(params, "#"); - }; - // (X) means the to state is invalid. - var id = this.$id, from = predicates_1.isObject(fromStateOrName) ? fromStateOrName.name : fromStateOrName, fromParams = common_1.toJson(avoidEmptyHash(this._treeChanges.from.map(hof_1.prop('paramValues')).reduce(common_1.mergeR, {}))), toValid = this.valid() ? "" : "(X) ", to = predicates_1.isObject(toStateOrName) ? toStateOrName.name : toStateOrName, toParams = common_1.toJson(avoidEmptyHash(this.params())); - return "Transition#" + id + "( '" + from + "'" + fromParams + " -> " + toValid + "'" + to + "'" + toParams + " )"; - }; - Transition.diToken = Transition; - return Transition; - }()); - exports.Transition = Transition; - - -/***/ }, -/* 12 */ -/***/ function(module, exports, __webpack_require__) { - - "use strict"; - /** - * UI-Router Transition Tracing - * - * Enable transition tracing to print transition information to the console, in order to help debug your application. - * Tracing logs detailed information about each Transition to your console. - * - * To enable tracing, import the [[trace]] singleton and enable one or more categories. - * - * ES6 - * ``` - * - * import {trace} from "ui-router-ng2"; // or "angular-ui-router" - * trace.enable(1, 5); // TRANSITION and VIEWCONFIG - * ``` - * - * CJS - * ``` - * - * let trace = require("angular-ui-router").trace; // or "ui-router-ng2" - * trace.enable("TRANSITION", "VIEWCONFIG"); - * ``` - * - * Globals - * ``` - * - * let trace = window["angular-ui-router"].trace; // or "ui-router-ng2" - * trace.enable(); // Trace everything (very verbose) - * ``` - * - * @module trace - */ /** for typedoc */ - var hof_1 = __webpack_require__(5); - var predicates_1 = __webpack_require__(4); - var strings_1 = __webpack_require__(9); - /** @hidden */ - function uiViewString(viewData) { - if (!viewData) - return 'ui-view (defunct)'; - return ("[ui-view#" + viewData.id + " tag ") + - ("in template from '" + (viewData.creationContext && viewData.creationContext.name || '(root)') + "' state]: ") + - ("fqn: '" + viewData.fqn + "', ") + - ("name: '" + viewData.name + "@" + viewData.creationContext + "')"); - } - /** @hidden */ - var viewConfigString = function (viewConfig) { - return ("[ViewConfig#" + viewConfig.$id + " from '" + (viewConfig.viewDecl.$context.name || '(root)') + "' state]: target ui-view: '" + viewConfig.viewDecl.$uiViewName + "@" + viewConfig.viewDecl.$uiViewContextAnchor + "'"); - }; - /** @hidden */ - function normalizedCat(input) { - return predicates_1.isNumber(input) ? Category[input] : Category[Category[input]]; - } - /** - * Trace categories - * - * [[Trace.enable]] or [[Trace.disable]] a category - * - * `trace.enable(Category.TRANSITION)` - * - * These can also be provided using a matching string, or position ordinal - * - * `trace.enable("TRANSITION")` - * - * `trace.enable(1)` - */ - (function (Category) { - Category[Category["RESOLVE"] = 0] = "RESOLVE"; - Category[Category["TRANSITION"] = 1] = "TRANSITION"; - Category[Category["HOOK"] = 2] = "HOOK"; - Category[Category["UIVIEW"] = 3] = "UIVIEW"; - Category[Category["VIEWCONFIG"] = 4] = "VIEWCONFIG"; - })(exports.Category || (exports.Category = {})); - var Category = exports.Category; - /** - * Prints UI-Router Transition trace information to the console. - */ - var Trace = (function () { - function Trace() { - /** @hidden */ - this._enabled = {}; - this.approximateDigests = 0; - } - /** @hidden */ - Trace.prototype._set = function (enabled, categories) { - var _this = this; - if (!categories.length) { - categories = Object.keys(Category) - .map(function (k) { return parseInt(k, 10); }) - .filter(function (k) { return !isNaN(k); }) - .map(function (key) { return Category[key]; }); - } - categories.map(normalizedCat).forEach(function (category) { return _this._enabled[category] = enabled; }); - }; - /** - * Enables a trace [[Category]] - * - * ``` - * trace.enable("TRANSITION"); - * ``` - * - * @param categories categories to enable. If `categories` is omitted, all categories are enabled. - * Also takes strings (category name) or ordinal (category position) - */ - Trace.prototype.enable = function () { - var categories = []; - for (var _i = 0; _i < arguments.length; _i++) { - categories[_i - 0] = arguments[_i]; - } - this._set(true, categories); - }; - /** - * Disables a trace [[Category]] - * - * ``` - * trace.disable("VIEWCONFIG"); - * ``` - * - * @param categories categories to disable. If `categories` is omitted, all categories are disabled. - * Also takes strings (category name) or ordinal (category position) - */ - Trace.prototype.disable = function () { - var categories = []; - for (var _i = 0; _i < arguments.length; _i++) { - categories[_i - 0] = arguments[_i]; - } - this._set(false, categories); - }; - /** - * Retrieves the enabled stateus of a [[Category]] - * - * ``` - * trace.enabled("VIEWCONFIG"); // true or false - * ``` - * - * @returns boolean true if the category is enabled - */ - Trace.prototype.enabled = function (category) { - return !!this._enabled[normalizedCat(category)]; - }; - /** called by ui-router code */ - Trace.prototype.traceTransitionStart = function (transition) { - if (!this.enabled(Category.TRANSITION)) - return; - var tid = transition.$id, digest = this.approximateDigests, transitionStr = strings_1.stringify(transition); - console.log("Transition #" + tid + " Digest #" + digest + ": Started -> " + transitionStr); - }; - /** called by ui-router code */ - Trace.prototype.traceTransitionIgnored = function (trans) { - if (!this.enabled(Category.TRANSITION)) - return; - var tid = trans && trans.$id, digest = this.approximateDigests, transitionStr = strings_1.stringify(trans); - console.log("Transition #" + tid + " Digest #" + digest + ": Ignored <> " + transitionStr); - }; - /** called by ui-router code */ - Trace.prototype.traceHookInvocation = function (step, options) { - if (!this.enabled(Category.HOOK)) - return; - var tid = hof_1.parse("transition.$id")(options), digest = this.approximateDigests, event = hof_1.parse("traceData.hookType")(options) || "internal", context = hof_1.parse("traceData.context.state.name")(options) || hof_1.parse("traceData.context")(options) || "unknown", name = strings_1.functionToString(step.eventHook.callback); - console.log("Transition #" + tid + " Digest #" + digest + ": Hook -> " + event + " context: " + context + ", " + strings_1.maxLength(200, name)); - }; - /** called by ui-router code */ - Trace.prototype.traceHookResult = function (hookResult, transitionOptions) { - if (!this.enabled(Category.HOOK)) - return; - var tid = hof_1.parse("transition.$id")(transitionOptions), digest = this.approximateDigests, hookResultStr = strings_1.stringify(hookResult); - console.log("Transition #" + tid + " Digest #" + digest + ": <- Hook returned: " + strings_1.maxLength(200, hookResultStr)); - }; - /** called by ui-router code */ - Trace.prototype.traceResolvePath = function (path, when, trans) { - if (!this.enabled(Category.RESOLVE)) - return; - var tid = trans && trans.$id, digest = this.approximateDigests, pathStr = path && path.toString(); - console.log("Transition #" + tid + " Digest #" + digest + ": Resolving " + pathStr + " (" + when + ")"); - }; - /** called by ui-router code */ - Trace.prototype.traceResolvableResolved = function (resolvable, trans) { - if (!this.enabled(Category.RESOLVE)) - return; - var tid = trans && trans.$id, digest = this.approximateDigests, resolvableStr = resolvable && resolvable.toString(), result = strings_1.stringify(resolvable.data); - console.log("Transition #" + tid + " Digest #" + digest + ": <- Resolved " + resolvableStr + " to: " + strings_1.maxLength(200, result)); - }; - /** called by ui-router code */ - Trace.prototype.traceError = function (reason, trans) { - if (!this.enabled(Category.TRANSITION)) - return; - var tid = trans && trans.$id, digest = this.approximateDigests, transitionStr = strings_1.stringify(trans); - console.log("Transition #" + tid + " Digest #" + digest + ": <- Rejected " + transitionStr + ", reason: " + reason); - }; - /** called by ui-router code */ - Trace.prototype.traceSuccess = function (finalState, trans) { - if (!this.enabled(Category.TRANSITION)) - return; - var tid = trans && trans.$id, digest = this.approximateDigests, state = finalState.name, transitionStr = strings_1.stringify(trans); - console.log("Transition #" + tid + " Digest #" + digest + ": <- Success " + transitionStr + ", final state: " + state); - }; - /** called by ui-router code */ - Trace.prototype.traceUIViewEvent = function (event, viewData, extra) { - if (extra === void 0) { extra = ""; } - if (!this.enabled(Category.UIVIEW)) - return; - console.log("ui-view: " + strings_1.padString(30, event) + " " + uiViewString(viewData) + extra); - }; - /** called by ui-router code */ - Trace.prototype.traceUIViewConfigUpdated = function (viewData, context) { - if (!this.enabled(Category.UIVIEW)) - return; - this.traceUIViewEvent("Updating", viewData, " with ViewConfig from context='" + context + "'"); - }; - /** called by ui-router code */ - Trace.prototype.traceUIViewFill = function (viewData, html) { - if (!this.enabled(Category.UIVIEW)) - return; - this.traceUIViewEvent("Fill", viewData, " with: " + strings_1.maxLength(200, html)); - }; - /** called by ui-router code */ - Trace.prototype.traceViewServiceEvent = function (event, viewConfig) { - if (!this.enabled(Category.VIEWCONFIG)) - return; - console.log("VIEWCONFIG: " + event + " " + viewConfigString(viewConfig)); - }; - /** called by ui-router code */ - Trace.prototype.traceViewServiceUIViewEvent = function (event, viewData) { - if (!this.enabled(Category.VIEWCONFIG)) - return; - console.log("VIEWCONFIG: " + event + " " + uiViewString(viewData)); - }; - return Trace; - }()); - exports.Trace = Trace; - /** - * The [[Trace]] singleton - * - * @example - * ```js - * - * import {trace} from "angular-ui-router"; - * trace.enable(1, 5); - * ``` - */ - var trace = new Trace(); - exports.trace = trace; - - -/***/ }, -/* 13 */ -/***/ function(module, exports, __webpack_require__) { - - "use strict"; - var common_1 = __webpack_require__(3); - var strings_1 = __webpack_require__(9); - var predicates_1 = __webpack_require__(4); - var hof_1 = __webpack_require__(5); - var trace_1 = __webpack_require__(12); - var coreservices_1 = __webpack_require__(6); - var rejectFactory_1 = __webpack_require__(10); - var targetState_1 = __webpack_require__(14); - var defaultOptions = { - async: true, - rejectIfSuperseded: true, - current: common_1.noop, - transition: null, - traceData: {}, - bind: null - }; - /** @hidden */ - var TransitionHook = (function () { - function TransitionHook(transition, stateContext, eventHook, options) { - var _this = this; - this.transition = transition; - this.stateContext = stateContext; - this.eventHook = eventHook; - this.options = options; - this.isSuperseded = function () { - return _this.options.current() !== _this.options.transition; - }; - this.options = common_1.defaults(options, defaultOptions); - } - TransitionHook.prototype.invokeHook = function () { - var _a = this, options = _a.options, eventHook = _a.eventHook; - trace_1.trace.traceHookInvocation(this, options); - if (options.rejectIfSuperseded && this.isSuperseded()) { - return rejectFactory_1.Rejection.superseded(options.current()).toPromise(); - } - var synchronousHookResult = !eventHook._deregistered - ? eventHook.callback.call(options.bind, this.transition, this.stateContext) - : undefined; - return this.handleHookResult(synchronousHookResult); - }; - /** - * This method handles the return value of a Transition Hook. - * - * A hook can return false (cancel), a TargetState (redirect), - * or a promise (which may later resolve to false or a redirect) - * - * This also handles "transition superseded" -- when a new transition - * was started while the hook was still running - */ - TransitionHook.prototype.handleHookResult = function (result) { - // This transition is no longer current. - // Another transition started while this hook was still running. - if (this.isSuperseded()) { - // Abort this transition - return rejectFactory_1.Rejection.superseded(this.options.current()).toPromise(); - } - // Hook returned a promise - if (predicates_1.isPromise(result)) { - // Wait for the promise, then reprocess the resolved value - return result.then(this.handleHookResult.bind(this)); - } - trace_1.trace.traceHookResult(result, this.options); - // Hook returned false - if (result === false) { - // Abort this Transition - return rejectFactory_1.Rejection.aborted("Hook aborted transition").toPromise(); - } - var isTargetState = hof_1.is(targetState_1.TargetState); - // hook returned a TargetState - if (isTargetState(result)) { - // Halt the current Transition and start a redirected Transition (to the TargetState). - return rejectFactory_1.Rejection.redirected(result).toPromise(); - } - }; - TransitionHook.prototype.toString = function () { - var _a = this, options = _a.options, eventHook = _a.eventHook; - var event = hof_1.parse("traceData.hookType")(options) || "internal", context = hof_1.parse("traceData.context.state.name")(options) || hof_1.parse("traceData.context")(options) || "unknown", name = strings_1.fnToString(eventHook.callback); - return event + " context: " + context + ", " + strings_1.maxLength(200, name); - }; - /** - * Given an array of TransitionHooks, runs each one synchronously and sequentially. - * - * Returns a promise chain composed of any promises returned from each hook.invokeStep() call - */ - TransitionHook.runSynchronousHooks = function (hooks, swallowExceptions) { - if (swallowExceptions === void 0) { swallowExceptions = false; } - var results = []; - for (var i = 0; i < hooks.length; i++) { - var hook = hooks[i]; - try { - results.push(hook.invokeHook()); - } - catch (exception) { - if (!swallowExceptions) { - return rejectFactory_1.Rejection.errored(exception).toPromise(); - } - var errorHandler = hook.transition.router.stateService.defaultErrorHandler(); - errorHandler(exception); - } - } - var rejections = results.filter(rejectFactory_1.Rejection.isTransitionRejectionPromise); - if (rejections.length) - return rejections[0]; - return results - .filter(predicates_1.isPromise) - .reduce(function (chain, promise) { return chain.then(hof_1.val(promise)); }, coreservices_1.services.$q.when()); - }; - return TransitionHook; - }()); - exports.TransitionHook = TransitionHook; - - -/***/ }, -/* 14 */ -/***/ function(module, exports, __webpack_require__) { - - /** @module state */ /** for typedoc */ - "use strict"; - var common_1 = __webpack_require__(3); - /** - * @ngdoc object - * @name ui.router.state.type:TargetState - * - * @description - * Encapsulate the desired target of a transition. - * Wraps an identifier for a state, a set of parameters, and transition options with the definition of the state. - * - * @param {StateOrName} _identifier An identifier for a state. Either a fully-qualified path, or the object - * used to define the state. - * @param {IState} _definition The `State` object definition. - * @param {ParamsOrArray} _params Parameters for the target state - * @param {TransitionOptions} _options Transition options. - */ - var TargetState = (function () { - function TargetState(_identifier, _definition, _params, _options) { - if (_params === void 0) { _params = {}; } - if (_options === void 0) { _options = {}; } - this._identifier = _identifier; - this._definition = _definition; - this._options = _options; - this._params = _params || {}; - } - TargetState.prototype.name = function () { - return this._definition && this._definition.name || this._identifier; - }; - TargetState.prototype.identifier = function () { - return this._identifier; - }; - TargetState.prototype.params = function () { - return this._params; - }; - TargetState.prototype.$state = function () { - return this._definition; - }; - TargetState.prototype.state = function () { - return this._definition && this._definition.self; - }; - TargetState.prototype.options = function () { - return this._options; - }; - TargetState.prototype.exists = function () { - return !!(this._definition && this._definition.self); - }; - TargetState.prototype.valid = function () { - return !this.error(); - }; - TargetState.prototype.error = function () { - var base = this.options().relative; - if (!this._definition && !!base) { - var stateName = base.name ? base.name : base; - return "Could not resolve '" + this.name() + "' from state '" + stateName + "'"; - } - if (!this._definition) - return "No such state '" + this.name() + "'"; - if (!this._definition.self) - return "State '" + this.name() + "' has an invalid definition"; - }; - TargetState.prototype.toString = function () { - return "'" + this.name() + "'" + common_1.toJson(this.params()); - }; - return TargetState; - }()); - exports.TargetState = TargetState; - - -/***/ }, -/* 15 */ -/***/ function(module, exports, __webpack_require__) { - - "use strict"; - /** @module transition */ /** for typedoc */ - var common_1 = __webpack_require__(3); - var predicates_1 = __webpack_require__(4); - var glob_1 = __webpack_require__(7); - /** - * Determines if the given state matches the matchCriteria - * - * @hidden - * - * @param state a State Object to test against - * @param criterion - * - If a string, matchState uses the string as a glob-matcher against the state name - * - If an array (of strings), matchState uses each string in the array as a glob-matchers against the state name - * and returns a positive match if any of the globs match. - * - If a function, matchState calls the function with the state and returns true if the function's result is truthy. - * @returns {boolean} - */ - function matchState(state, criterion) { - var toMatch = predicates_1.isString(criterion) ? [criterion] : criterion; - function matchGlobs(_state) { - var globStrings = toMatch; - for (var i = 0; i < globStrings.length; i++) { - var glob = glob_1.Glob.fromString(globStrings[i]); - if ((glob && glob.matches(_state.name)) || (!glob && globStrings[i] === _state.name)) { - return true; - } - } - return false; - } - var matchFn = (predicates_1.isFunction(toMatch) ? toMatch : matchGlobs); - return !!matchFn(state); - } - exports.matchState = matchState; - /** @hidden */ - var EventHook = (function () { - function EventHook(matchCriteria, callback, options) { - if (options === void 0) { options = {}; } - this.callback = callback; - this.matchCriteria = common_1.extend({ to: true, from: true, exiting: true, retained: true, entering: true }, matchCriteria); - this.priority = options.priority || 0; - this.bind = options.bind || null; - this._deregistered = false; - } - EventHook._matchingNodes = function (nodes, criterion) { - if (criterion === true) - return nodes; - var matching = nodes.filter(function (node) { return matchState(node.state, criterion); }); - return matching.length ? matching : null; - }; - /** - * Determines if this hook's [[matchCriteria]] match the given [[TreeChanges]] - * - * @returns an IMatchingNodes object, or null. If an IMatchingNodes object is returned, its values - * are the matching [[PathNode]]s for each [[HookMatchCriterion]] (to, from, exiting, retained, entering) - */ - EventHook.prototype.matches = function (treeChanges) { - var mc = this.matchCriteria, _matchingNodes = EventHook._matchingNodes; - var matches = { - to: _matchingNodes([common_1.tail(treeChanges.to)], mc.to), - from: _matchingNodes([common_1.tail(treeChanges.from)], mc.from), - exiting: _matchingNodes(treeChanges.exiting, mc.exiting), - retained: _matchingNodes(treeChanges.retained, mc.retained), - entering: _matchingNodes(treeChanges.entering, mc.entering), - }; - // Check if all the criteria matched the TreeChanges object - var allMatched = ["to", "from", "exiting", "retained", "entering"] - .map(function (prop) { return matches[prop]; }) - .reduce(common_1.allTrueR, true); - return allMatched ? matches : null; - }; - return EventHook; - }()); - exports.EventHook = EventHook; - /** @hidden Return a registration function of the requested type. */ - function makeHookRegistrationFn(hooks, name) { - return function (matchObject, callback, options) { - if (options === void 0) { options = {}; } - var eventHook = new EventHook(matchObject, callback, options); - hooks[name].push(eventHook); - return function deregisterEventHook() { - eventHook._deregistered = true; - common_1.removeFrom(hooks[name])(eventHook); - }; - }; - } - /** - * Mixin class acts as a Transition Hook registry. - * - * Holds the registered [[HookFn]] objects. - * Exposes functions to register new hooks. - * - * This is a Mixin class which can be applied to other objects. - * - * The hook registration functions are [[onBefore]], [[onStart]], [[onEnter]], [[onRetain]], [[onExit]], [[onFinish]], [[onSuccess]], [[onError]]. - * - * This class is mixed into both the [[TransitionService]] and every [[Transition]] object. - * Global hooks are added to the [[TransitionService]]. - * Since each [[Transition]] is itself a `HookRegistry`, hooks can also be added to individual Transitions - * (note: the hook criteria still must match the Transition). - */ - var HookRegistry = (function () { - function HookRegistry() { - var _this = this; - this._transitionEvents = { - onBefore: [], onStart: [], onEnter: [], onRetain: [], onExit: [], onFinish: [], onSuccess: [], onError: [] - }; - this.getHooks = function (name) { return _this._transitionEvents[name]; }; - /** @inheritdoc */ - this.onBefore = makeHookRegistrationFn(this._transitionEvents, "onBefore"); - /** @inheritdoc */ - this.onStart = makeHookRegistrationFn(this._transitionEvents, "onStart"); - /** @inheritdoc */ - this.onEnter = makeHookRegistrationFn(this._transitionEvents, "onEnter"); - /** @inheritdoc */ - this.onRetain = makeHookRegistrationFn(this._transitionEvents, "onRetain"); - /** @inheritdoc */ - this.onExit = makeHookRegistrationFn(this._transitionEvents, "onExit"); - /** @inheritdoc */ - this.onFinish = makeHookRegistrationFn(this._transitionEvents, "onFinish"); - /** @inheritdoc */ - this.onSuccess = makeHookRegistrationFn(this._transitionEvents, "onSuccess"); - /** @inheritdoc */ - this.onError = makeHookRegistrationFn(this._transitionEvents, "onError"); - } - HookRegistry.mixin = function (source, target) { - Object.keys(source._transitionEvents).concat(["getHooks"]).forEach(function (key) { return target[key] = source[key]; }); - }; - return HookRegistry; - }()); - exports.HookRegistry = HookRegistry; - - -/***/ }, -/* 16 */ -/***/ function(module, exports, __webpack_require__) { - - /** @module transition */ /** for typedoc */ - "use strict"; - var common_1 = __webpack_require__(3); - var predicates_1 = __webpack_require__(4); - var transitionHook_1 = __webpack_require__(13); - var resolveContext_1 = __webpack_require__(17); - /** - * This class returns applicable TransitionHooks for a specific Transition instance. - * - * Hooks (IEventHook) may be registered globally, e.g., $transitions.onEnter(...), or locally, e.g. - * myTransition.onEnter(...). The HookBuilder finds matching IEventHooks (where the match criteria is - * determined by the type of hook) - * - * The HookBuilder also converts IEventHooks objects to TransitionHook objects, which are used to run a Transition. - * - * The HookBuilder constructor is given the $transitions service and a Transition instance. Thus, a HookBuilder - * instance may only be used for one specific Transition object. (side note: the _treeChanges accessor is private - * in the Transition class, so we must also provide the Transition's _treeChanges) - * - */ - var HookBuilder = (function () { - function HookBuilder($transitions, transition, baseHookOptions) { - var _this = this; - this.$transitions = $transitions; - this.transition = transition; - this.baseHookOptions = baseHookOptions; - this.getOnBeforeHooks = function () { return _this._buildNodeHooks("onBefore", "to", tupleSort(), { async: false }); }; - this.getOnStartHooks = function () { return _this._buildNodeHooks("onStart", "to", tupleSort()); }; - this.getOnExitHooks = function () { return _this._buildNodeHooks("onExit", "exiting", tupleSort(true), { stateHook: true }); }; - this.getOnRetainHooks = function () { return _this._buildNodeHooks("onRetain", "retained", tupleSort(false), { stateHook: true }); }; - this.getOnEnterHooks = function () { return _this._buildNodeHooks("onEnter", "entering", tupleSort(false), { stateHook: true }); }; - this.getOnFinishHooks = function () { return _this._buildNodeHooks("onFinish", "to", tupleSort()); }; - this.getOnSuccessHooks = function () { return _this._buildNodeHooks("onSuccess", "to", tupleSort(), { async: false, rejectIfSuperseded: false }); }; - this.getOnErrorHooks = function () { return _this._buildNodeHooks("onError", "to", tupleSort(), { async: false, rejectIfSuperseded: false }); }; - this.treeChanges = transition.treeChanges(); - this.toState = common_1.tail(this.treeChanges.to).state; - this.fromState = common_1.tail(this.treeChanges.from).state; - this.transitionOptions = transition.options(); - } - HookBuilder.prototype.asyncHooks = function () { - var onStartHooks = this.getOnStartHooks(); - var onExitHooks = this.getOnExitHooks(); - var onRetainHooks = this.getOnRetainHooks(); - var onEnterHooks = this.getOnEnterHooks(); - var onFinishHooks = this.getOnFinishHooks(); - var asyncHooks = [onStartHooks, onExitHooks, onRetainHooks, onEnterHooks, onFinishHooks]; - return asyncHooks.reduce(common_1.unnestR, []).filter(common_1.identity); - }; - /** - * Returns an array of newly built TransitionHook objects. - * - * - Finds all IEventHooks registered for the given `hookType` which matched the transition's [[TreeChanges]]. - * - Finds [[PathNode]] (or `PathNode[]`) to use as the TransitionHook context(s) - * - For each of the [[PathNode]]s, creates a TransitionHook - * - * @param hookType the name of the hook registration function, e.g., 'onEnter', 'onFinish'. - * @param matchingNodesProp selects which [[PathNode]]s from the [[IMatchingNodes]] object to create hooks for. - * @param getLocals a function which accepts a [[PathNode]] and returns additional locals to provide to the hook as injectables - * @param sortHooksFn a function which compares two HookTuple and returns <1, 0, or >1 - * @param options any specific Transition Hook Options - */ - HookBuilder.prototype._buildNodeHooks = function (hookType, matchingNodesProp, sortHooksFn, options) { - var _this = this; - // Find all the matching registered hooks for a given hook type - var matchingHooks = this._matchingHooks(hookType, this.treeChanges); - if (!matchingHooks) - return []; - var makeTransitionHooks = function (hook) { - // Fetch the Nodes that caused this hook to match. - var matches = hook.matches(_this.treeChanges); - // Select the PathNode[] that will be used as TransitionHook context objects - var matchingNodes = matches[matchingNodesProp]; - // When invoking 'exiting' hooks, give them the "from path" for resolve data. - // Everything else gets the "to path" - var resolvePath = matchingNodesProp === 'exiting' ? _this.treeChanges.from : _this.treeChanges.to; - var resolveContext = new resolveContext_1.ResolveContext(resolvePath); - // Return an array of HookTuples - return matchingNodes.map(function (node) { - var _options = common_1.extend({ bind: hook.bind, traceData: { hookType: hookType, context: node } }, _this.baseHookOptions, options); - var state = _options.stateHook ? node.state : null; - var transitionHook = new transitionHook_1.TransitionHook(_this.transition, state, hook, _options); - return { hook: hook, node: node, transitionHook: transitionHook }; - }); - }; - return matchingHooks.map(makeTransitionHooks) - .reduce(common_1.unnestR, []) - .sort(sortHooksFn) - .map(function (tuple) { return tuple.transitionHook; }); - }; - /** - * Finds all IEventHooks from: - * - The Transition object instance hook registry - * - The TransitionService ($transitions) global hook registry - * - * which matched: - * - the eventType - * - the matchCriteria (to, from, exiting, retained, entering) - * - * @returns an array of matched [[IEventHook]]s - */ - HookBuilder.prototype._matchingHooks = function (hookName, treeChanges) { - return [this.transition, this.$transitions] // Instance and Global hook registries - .map(function (reg) { return reg.getHooks(hookName); }) // Get named hooks from registries - .filter(common_1.assertPredicate(predicates_1.isArray, "broken event named: " + hookName)) // Sanity check - .reduce(common_1.unnestR, []) // Un-nest IEventHook[][] to IEventHook[] array - .filter(function (hook) { return hook.matches(treeChanges); }); // Only those satisfying matchCriteria - }; - return HookBuilder; - }()); - exports.HookBuilder = HookBuilder; - /** - * A factory for a sort function for HookTuples. - * - * The sort function first compares the PathNode depth (how deep in the state tree a node is), then compares - * the EventHook priority. - * - * @param reverseDepthSort a boolean, when true, reverses the sort order for the node depth - * @returns a tuple sort function - */ - function tupleSort(reverseDepthSort) { - if (reverseDepthSort === void 0) { reverseDepthSort = false; } - return function nodeDepthThenPriority(l, r) { - var factor = reverseDepthSort ? -1 : 1; - var depthDelta = (l.node.state.path.length - r.node.state.path.length) * factor; - return depthDelta !== 0 ? depthDelta : r.hook.priority - l.hook.priority; - }; - } - - -/***/ }, -/* 17 */ -/***/ function(module, exports, __webpack_require__) { - - "use strict"; - /** @module resolve */ /** for typedoc */ - var common_1 = __webpack_require__(3); - var hof_1 = __webpack_require__(5); - var trace_1 = __webpack_require__(12); - var coreservices_1 = __webpack_require__(6); - var interface_1 = __webpack_require__(18); - var resolvable_1 = __webpack_require__(19); - var pathFactory_1 = __webpack_require__(20); - var strings_1 = __webpack_require__(9); - var when = interface_1.resolvePolicies.when; - var ALL_WHENS = [when.EAGER, when.LAZY]; - var EAGER_WHENS = [when.EAGER]; - exports.NATIVE_INJECTOR_TOKEN = "Native Injector"; - /** - * Encapsulates Depenency Injection for a path of nodes - * - * UI-Router states are organized as a tree. - * A nested state has a path of ancestors to the root of the tree. - * When a state is being activated, each element in the path is wrapped as a [[PathNode]]. - * A `PathNode` is a stateful object that holds things like parameters and resolvables for the state being activated. - * - * The ResolveContext closes over the [[PathNode]]s, and provides DI for the last node in the path. - */ - var ResolveContext = (function () { - function ResolveContext(_path) { - this._path = _path; - } - /** Gets all the tokens found in the resolve context, de-duplicated */ - ResolveContext.prototype.getTokens = function () { - return this._path.reduce(function (acc, node) { return acc.concat(node.resolvables.map(function (r) { return r.token; })); }, []).reduce(common_1.uniqR, []); - }; - /** - * Gets the Resolvable that matches the token - * - * Gets the last Resolvable that matches the token in this context, or undefined. - * Throws an error if it doesn't exist in the ResolveContext - */ - ResolveContext.prototype.getResolvable = function (token) { - var matching = this._path.map(function (node) { return node.resolvables; }) - .reduce(common_1.unnestR, []) - .filter(function (r) { return r.token === token; }); - return common_1.tail(matching); - }; - /** - * Returns a ResolveContext that includes a portion of this one - * - * Given a state, this method creates a new ResolveContext from this one. - * The new context starts at the first node (root) and stops at the node for the `state` parameter. - * - * #### Why - * - * When a transition is created, the nodes in the "To Path" are injected from a ResolveContext. - * A ResolveContext closes over a path of [[PathNode]]s and processes the resolvables. - * The "To State" can inject values from its own resolvables, as well as those from all its ancestor state's (node's). - * This method is used to create a narrower context when injecting ancestor nodes. - * - * @example - * `let ABCD = new ResolveContext([A, B, C, D]);` - * - * Given a path `[A, B, C, D]`, where `A`, `B`, `C` and `D` are nodes for states `a`, `b`, `c`, `d`: - * When injecting `D`, `D` should have access to all resolvables from `A`, `B`, `C`, `D`. - * However, `B` should only be able to access resolvables from `A`, `B`. - * - * When resolving for the `B` node, first take the full "To Path" Context `[A,B,C,D]` and limit to the subpath `[A,B]`. - * `let AB = ABCD.subcontext(a)` - */ - ResolveContext.prototype.subContext = function (state) { - return new ResolveContext(pathFactory_1.PathFactory.subPath(this._path, function (node) { return node.state === state; })); - }; - /** - * Adds Resolvables to the node that matches the state - * - * This adds a [[Resolvable]] (generally one created on the fly; not declared on a [[StateDeclaration.resolve]] block). - * The resolvable is added to the node matching the `state` parameter. - * - * These new resolvables are not automatically fetched. - * The calling code should either fetch them, fetch something that depends on them, - * or rely on [[resolvePath]] being called when some state is being entered. - * - * Note: each resolvable's [[ResolvePolicy]] is merged with the state's policy, and the global default. - * - * @param newResolvables the new Resolvables - * @param state Used to find the node to put the resolvable on - */ - ResolveContext.prototype.addResolvables = function (newResolvables, state) { - var node = common_1.find(this._path, hof_1.propEq('state', state)); - var keys = newResolvables.map(function (r) { return r.token; }); - node.resolvables = node.resolvables.filter(function (r) { return keys.indexOf(r.token) === -1; }).concat(newResolvables); - }; - /** - * Returns a promise for an array of resolved path Element promises - * - * @param when - * @param trans - * @returns {Promise|any} - */ - ResolveContext.prototype.resolvePath = function (when, trans) { - var _this = this; - if (when === void 0) { when = "LAZY"; } - // This option determines which 'when' policy Resolvables we are about to fetch. - var whenOption = common_1.inArray(ALL_WHENS, when) ? when : "LAZY"; - // If the caller specified EAGER, only the EAGER Resolvables are fetched. - // if the caller specified LAZY, both EAGER and LAZY Resolvables are fetched.` - var matchedWhens = whenOption === interface_1.resolvePolicies.when.EAGER ? EAGER_WHENS : ALL_WHENS; - // get the subpath to the state argument, if provided - trace_1.trace.traceResolvePath(this._path, when, trans); - var promises = this._path.reduce(function (acc, node) { - var matchesRequestedPolicy = function (resolvable) { - return common_1.inArray(matchedWhens, resolvable.getPolicy(node.state).when); - }; - var nodeResolvables = node.resolvables.filter(matchesRequestedPolicy); - var subContext = _this.subContext(node.state); - // For the matching Resolvables, start their async fetch process. - var getResult = function (r) { return r.get(subContext, trans) - .then(function (value) { return ({ token: r.token, value: value }); }); }; - return acc.concat(nodeResolvables.map(getResult)); - }, []); - return coreservices_1.services.$q.all(promises); - }; - ResolveContext.prototype.injector = function () { - return this._injector || (this._injector = new UIInjectorImpl(this)); - }; - ResolveContext.prototype.findNode = function (resolvable) { - return common_1.find(this._path, function (node) { return common_1.inArray(node.resolvables, resolvable); }); - }; - /** - * Gets the async dependencies of a Resolvable - * - * Given a Resolvable, returns its dependencies as a Resolvable[] - */ - ResolveContext.prototype.getDependencies = function (resolvable) { - var _this = this; - var node = this.findNode(resolvable); - // Find which other resolvables are "visible" to the `resolvable` argument - // subpath stopping at resolvable's node, or the whole path (if the resolvable isn't in the path) - var subPath = pathFactory_1.PathFactory.subPath(this._path, function (x) { return x === node; }) || this._path; - var availableResolvables = subPath - .reduce(function (acc, node) { return acc.concat(node.resolvables); }, []) //all of subpath's resolvables - .filter(function (res) { return res !== resolvable; }); // filter out the `resolvable` argument - var getDependency = function (token) { - var matching = availableResolvables.filter(function (r) { return r.token === token; }); - if (matching.length) - return common_1.tail(matching); - var fromInjector = _this.injector().getNative(token); - if (!fromInjector) { - throw new Error("Could not find Dependency Injection token: " + strings_1.stringify(token)); - } - return new resolvable_1.Resolvable(token, function () { return fromInjector; }, [], fromInjector); - }; - return resolvable.deps.map(getDependency); - }; - return ResolveContext; - }()); - exports.ResolveContext = ResolveContext; - var UIInjectorImpl = (function () { - function UIInjectorImpl(context) { - this.context = context; - this.native = this.get(exports.NATIVE_INJECTOR_TOKEN) || coreservices_1.services.$injector; - } - UIInjectorImpl.prototype.get = function (token) { - var resolvable = this.context.getResolvable(token); - if (resolvable) { - if (!resolvable.resolved) { - throw new Error("Resolvable async .get() not complete:" + strings_1.stringify(resolvable.token)); - } - return resolvable.data; - } - return this.native && this.native.get(token); - }; - UIInjectorImpl.prototype.getAsync = function (token) { - var resolvable = this.context.getResolvable(token); - if (resolvable) - return resolvable.get(this.context); - return coreservices_1.services.$q.when(this.native.get(token)); - }; - UIInjectorImpl.prototype.getNative = function (token) { - return this.native.get(token); - }; - return UIInjectorImpl; - }()); - - -/***/ }, -/* 18 */ -/***/ function(module, exports) { - - "use strict"; - exports.resolvePolicies = { - when: { - LAZY: "LAZY", - EAGER: "EAGER" - }, - async: { - WAIT: "WAIT", - NOWAIT: "NOWAIT", - RXWAIT: "RXWAIT" - } - }; - - -/***/ }, -/* 19 */ -/***/ function(module, exports, __webpack_require__) { - - "use strict"; - /** @module resolve */ /** for typedoc */ - var common_1 = __webpack_require__(3); - var coreservices_1 = __webpack_require__(6); - var trace_1 = __webpack_require__(12); - var strings_1 = __webpack_require__(9); - var predicates_1 = __webpack_require__(4); - // TODO: explicitly make this user configurable - exports.defaultResolvePolicy = { - when: "LAZY", - async: "WAIT" - }; - /** - * The basic building block for the resolve system. - * - * Resolvables encapsulate a state's resolve's resolveFn, the resolveFn's declared dependencies, the wrapped (.promise), - * and the unwrapped-when-complete (.data) result of the resolveFn. - * - * Resolvable.get() either retrieves the Resolvable's existing promise, or else invokes resolve() (which invokes the - * resolveFn) and returns the resulting promise. - * - * Resolvable.get() and Resolvable.resolve() both execute within a context path, which is passed as the first - * parameter to those fns. - */ - var Resolvable = (function () { - function Resolvable(arg1, resolveFn, deps, policy, data) { - this.resolved = false; - this.promise = undefined; - if (arg1 instanceof Resolvable) { - common_1.extend(this, arg1); - } - else if (predicates_1.isFunction(resolveFn)) { - if (arg1 == null || arg1 == undefined) - throw new Error("new Resolvable(): token argument is required"); - if (!predicates_1.isFunction(resolveFn)) - throw new Error("new Resolvable(): resolveFn argument must be a function"); - this.token = arg1; - this.policy = policy; - this.resolveFn = resolveFn; - this.deps = deps || []; - this.data = data; - this.resolved = data !== undefined; - this.promise = this.resolved ? coreservices_1.services.$q.when(this.data) : undefined; - } - else if (predicates_1.isObject(arg1) && arg1.token && predicates_1.isFunction(arg1.resolveFn)) { - var literal = arg1; - return new Resolvable(literal.token, literal.resolveFn, literal.deps, literal.policy, literal.data); - } - } - Resolvable.prototype.getPolicy = function (state) { - var thisPolicy = this.policy || {}; - var statePolicy = state && state.resolvePolicy || {}; - return { - when: thisPolicy.when || statePolicy.when || exports.defaultResolvePolicy.when, - async: thisPolicy.async || statePolicy.async || exports.defaultResolvePolicy.async, - }; - }; - /** - * Asynchronously resolve this Resolvable's data - * - * Given a ResolveContext that this Resolvable is found in: - * Wait for this Resolvable's dependencies, then invoke this Resolvable's function - * and update the Resolvable's state - */ - Resolvable.prototype.resolve = function (resolveContext, trans) { - var _this = this; - var $q = coreservices_1.services.$q; - // Gets all dependencies from ResolveContext and wait for them to be resolved - var getResolvableDependencies = function () { - return $q.all(resolveContext.getDependencies(_this).map(function (r) { - return r.get(resolveContext, trans); - })); - }; - // Invokes the resolve function passing the resolved dependencies as arguments - var invokeResolveFn = function (resolvedDeps) { - return _this.resolveFn.apply(null, resolvedDeps); - }; - /** - * For RXWAIT policy: - * - * Given an observable returned from a resolve function: - * - enables .cache() mode (this allows multicast subscribers) - * - then calls toPromise() (this triggers subscribe() and thus fetches) - * - Waits for the promise, then return the cached observable (not the first emitted value). - */ - var waitForRx = function (observable$) { - var cached = observable$.cache(1); - return cached.take(1).toPromise().then(function () { return cached; }); - }; - // If the resolve policy is RXWAIT, wait for the observable to emit something. otherwise pass through. - var node = resolveContext.findNode(this); - var state = node && node.state; - var maybeWaitForRx = this.getPolicy(state).async === "RXWAIT" ? waitForRx : common_1.identity; - // After the final value has been resolved, update the state of the Resolvable - var applyResolvedValue = function (resolvedValue) { - _this.data = resolvedValue; - _this.resolved = true; - trace_1.trace.traceResolvableResolved(_this, trans); - return _this.data; - }; - // Sets the promise property first, then getsResolvableDependencies in the context of the promise chain. Always waits one tick. - return this.promise = $q.when() - .then(getResolvableDependencies) - .then(invokeResolveFn) - .then(maybeWaitForRx) - .then(applyResolvedValue); - }; - /** - * Gets a promise for this Resolvable's data. - * - * Fetches the data and returns a promise. - * Returns the existing promise if it has already been fetched once. - */ - Resolvable.prototype.get = function (resolveContext, trans) { - return this.promise || this.resolve(resolveContext, trans); - }; - Resolvable.prototype.toString = function () { - return "Resolvable(token: " + strings_1.stringify(this.token) + ", requires: [" + this.deps.map(strings_1.stringify) + "])"; - }; - Resolvable.prototype.clone = function () { - return new Resolvable(this); - }; - Resolvable.fromData = function (token, data) { - return new Resolvable(token, function () { return data; }, null, null, data); - }; - return Resolvable; - }()); - exports.Resolvable = Resolvable; - - -/***/ }, -/* 20 */ -/***/ function(module, exports, __webpack_require__) { - - /** @module path */ /** for typedoc */ - "use strict"; - var common_1 = __webpack_require__(3); - var hof_1 = __webpack_require__(5); - var targetState_1 = __webpack_require__(14); - var node_1 = __webpack_require__(21); - /** - * This class contains functions which convert TargetStates, Nodes and paths from one type to another. - */ - var PathFactory = (function () { - function PathFactory() { - } - /** Given a PathNode[], create an TargetState */ - PathFactory.makeTargetState = function (path) { - var state = common_1.tail(path).state; - return new targetState_1.TargetState(state, state, path.map(hof_1.prop("paramValues")).reduce(common_1.mergeR, {})); - }; - PathFactory.buildPath = function (targetState) { - var toParams = targetState.params(); - return targetState.$state().path.map(function (state) { return new node_1.PathNode(state).applyRawParams(toParams); }); - }; - /** Given a fromPath: PathNode[] and a TargetState, builds a toPath: PathNode[] */ - PathFactory.buildToPath = function (fromPath, targetState) { - var toPath = PathFactory.buildPath(targetState); - if (targetState.options().inherit) { - return PathFactory.inheritParams(fromPath, toPath, Object.keys(targetState.params())); - } - return toPath; - }; - /** - * Creates ViewConfig objects and adds to nodes. - * - * On each [[PathNode]], creates ViewConfig objects from the views: property of the node's state - */ - PathFactory.applyViewConfigs = function ($view, path, states) { - // Only apply the viewConfigs to the nodes for the given states - path.filter(function (node) { return common_1.inArray(states, node.state); }).forEach(function (node) { - var viewDecls = common_1.values(node.state.views || {}); - var subPath = PathFactory.subPath(path, function (n) { return n === node; }); - var viewConfigs = viewDecls.map(function (view) { return $view.createViewConfig(subPath, view); }); - node.views = viewConfigs.reduce(common_1.unnestR, []); - }); - }; - /** - * Given a fromPath and a toPath, returns a new to path which inherits parameters from the fromPath - * - * For a parameter in a node to be inherited from the from path: - * - The toPath's node must have a matching node in the fromPath (by state). - * - The parameter name must not be found in the toKeys parameter array. - * - * Note: the keys provided in toKeys are intended to be those param keys explicitly specified by some - * caller, for instance, $state.transitionTo(..., toParams). If a key was found in toParams, - * it is not inherited from the fromPath. - */ - PathFactory.inheritParams = function (fromPath, toPath, toKeys) { - if (toKeys === void 0) { toKeys = []; } - function nodeParamVals(path, state) { - var node = common_1.find(path, hof_1.propEq('state', state)); - return common_1.extend({}, node && node.paramValues); - } - /** - * Given an [[PathNode]] "toNode", return a new [[PathNode]] with param values inherited from the - * matching node in fromPath. Only inherit keys that aren't found in "toKeys" from the node in "fromPath"" - */ - function makeInheritedParamsNode(toNode) { - // All param values for the node (may include default key/vals, when key was not found in toParams) - var toParamVals = common_1.extend({}, toNode && toNode.paramValues); - // limited to only those keys found in toParams - var incomingParamVals = common_1.pick(toParamVals, toKeys); - toParamVals = common_1.omit(toParamVals, toKeys); - var fromParamVals = nodeParamVals(fromPath, toNode.state) || {}; - // extend toParamVals with any fromParamVals, then override any of those those with incomingParamVals - var ownParamVals = common_1.extend(toParamVals, fromParamVals, incomingParamVals); - return new node_1.PathNode(toNode.state).applyRawParams(ownParamVals); - } - // The param keys specified by the incoming toParams - return toPath.map(makeInheritedParamsNode); - }; - /** - * Computes the tree changes (entering, exiting) between a fromPath and toPath. - */ - PathFactory.treeChanges = function (fromPath, toPath, reloadState) { - var keep = 0, max = Math.min(fromPath.length, toPath.length); - var staticParams = function (state) { - return state.parameters({ inherit: false }).filter(hof_1.not(hof_1.prop('dynamic'))).map(hof_1.prop('id')); - }; - var nodesMatch = function (node1, node2) { - return node1.equals(node2, staticParams(node1.state)); - }; - while (keep < max && fromPath[keep].state !== reloadState && nodesMatch(fromPath[keep], toPath[keep])) { - keep++; - } - /** Given a retained node, return a new node which uses the to node's param values */ - function applyToParams(retainedNode, idx) { - var cloned = node_1.PathNode.clone(retainedNode); - cloned.paramValues = toPath[idx].paramValues; - return cloned; - } - var from, retained, exiting, entering, to; - from = fromPath; - retained = from.slice(0, keep); - exiting = from.slice(keep); - // Create a new retained path (with shallow copies of nodes) which have the params of the toPath mapped - var retainedWithToParams = retained.map(applyToParams); - entering = toPath.slice(keep); - to = (retainedWithToParams).concat(entering); - return { from: from, to: to, retained: retained, exiting: exiting, entering: entering }; - }; - /** - * Return a subpath of a path, which stops at the first matching node - * - * Given an array of nodes, returns a subset of the array starting from the first node, - * stopping when the first node matches the predicate. - * - * @param path a path of [[PathNode]]s - * @param predicate a [[Predicate]] fn that matches [[PathNode]]s - * @returns a subpath up to the matching node, or undefined if no match is found - */ - PathFactory.subPath = function (path, predicate) { - var node = common_1.find(path, predicate); - var elementIdx = path.indexOf(node); - return elementIdx === -1 ? undefined : path.slice(0, elementIdx + 1); - }; - /** Gets the raw parameter values from a path */ - PathFactory.paramValues = function (path) { return path.reduce(function (acc, node) { return common_1.extend(acc, node.paramValues); }, {}); }; - return PathFactory; - }()); - exports.PathFactory = PathFactory; - - -/***/ }, -/* 21 */ -/***/ function(module, exports, __webpack_require__) { - - "use strict"; - /** @module path */ /** for typedoc */ - var common_1 = __webpack_require__(3); - var hof_1 = __webpack_require__(5); - var param_1 = __webpack_require__(22); - /** - * A node in a [[TreeChanges]] path - * - * For a [[TreeChanges]] path, this class holds the stateful information for a single node in the path. - * Each PathNode corresponds to a state being entered, exited, or retained. - * The stateful information includes parameter values and resolve data. - */ - var PathNode = (function () { - function PathNode(stateOrPath) { - if (stateOrPath instanceof PathNode) { - var node = stateOrPath; - this.state = node.state; - this.paramSchema = node.paramSchema.slice(); - this.paramValues = common_1.extend({}, node.paramValues); - this.resolvables = node.resolvables.slice(); - this.views = node.views && node.views.slice(); - } - else { - var state = stateOrPath; - this.state = state; - this.paramSchema = state.parameters({ inherit: false }); - this.paramValues = {}; - this.resolvables = state.resolvables.map(function (res) { return res.clone(); }); - } - } - /** Sets [[paramValues]] for the node, from the values of an object hash */ - PathNode.prototype.applyRawParams = function (params) { - var getParamVal = function (paramDef) { return [paramDef.id, paramDef.value(params[paramDef.id])]; }; - this.paramValues = this.paramSchema.reduce(function (memo, pDef) { return common_1.applyPairs(memo, getParamVal(pDef)); }, {}); - return this; - }; - /** Gets a specific [[Param]] metadata that belongs to the node */ - PathNode.prototype.parameter = function (name) { - return common_1.find(this.paramSchema, hof_1.propEq("id", name)); - }; - /** - * @returns true if the state and parameter values for another PathNode are - * equal to the state and param values for this PathNode - */ - PathNode.prototype.equals = function (node, keys) { - var _this = this; - if (keys === void 0) { keys = this.paramSchema.map(function (p) { return p.id; }); } - var paramValsEq = function (key) { - return _this.parameter(key).type.equals(_this.paramValues[key], node.paramValues[key]); - }; - return this.state === node.state && keys.map(paramValsEq).reduce(common_1.allTrueR, true); - }; - /** Returns a clone of the PathNode */ - PathNode.clone = function (node) { - return new PathNode(node); - }; - /** - * Returns a new path which is a subpath of the first path which matched the second path. - * - * The new path starts from root and contains any nodes that match the nodes in the second path. - * Nodes are compared using their state property and parameter values. - * - * @param pathA the first path - * @param pathB the second path - * @param ignoreDynamicParams don't compare dynamic parameter values - */ - PathNode.matching = function (pathA, pathB, ignoreDynamicParams) { - if (ignoreDynamicParams === void 0) { ignoreDynamicParams = true; } - var matching = []; - for (var i = 0; i < pathA.length && i < pathB.length; i++) { - var a = pathA[i], b = pathB[i]; - if (a.state !== b.state) - break; - var changedParams = param_1.Param.changed(a.paramSchema, a.paramValues, b.paramValues) - .filter(function (param) { return !(ignoreDynamicParams && param.dynamic); }); - if (changedParams.length) - break; - matching.push(a); - } - return matching; - }; - return PathNode; - }()); - exports.PathNode = PathNode; - - -/***/ }, -/* 22 */ -/***/ function(module, exports, __webpack_require__) { - - "use strict"; - /** @module params */ /** for typedoc */ - var common_1 = __webpack_require__(3); - var hof_1 = __webpack_require__(5); - var predicates_1 = __webpack_require__(4); - var coreservices_1 = __webpack_require__(6); - var urlMatcherConfig_1 = __webpack_require__(23); - var type_1 = __webpack_require__(24); - var hasOwn = Object.prototype.hasOwnProperty; - var isShorthand = function (cfg) { - return ["value", "type", "squash", "array", "dynamic"].filter(hasOwn.bind(cfg || {})).length === 0; - }; - (function (DefType) { - DefType[DefType["PATH"] = 0] = "PATH"; - DefType[DefType["SEARCH"] = 1] = "SEARCH"; - DefType[DefType["CONFIG"] = 2] = "CONFIG"; - })(exports.DefType || (exports.DefType = {})); - var DefType = exports.DefType; - function unwrapShorthand(cfg) { - cfg = isShorthand(cfg) && { value: cfg } || cfg; - return common_1.extend(cfg, { - $$fn: predicates_1.isInjectable(cfg.value) ? cfg.value : function () { return cfg.value; } - }); - } - function getType(cfg, urlType, location, id, paramTypes) { - if (cfg.type && urlType && urlType.name !== 'string') - throw new Error("Param '" + id + "' has two type configurations."); - if (cfg.type && urlType && urlType.name === 'string' && paramTypes.type(cfg.type)) - return paramTypes.type(cfg.type); - if (urlType) - return urlType; - if (!cfg.type) - return (location === DefType.CONFIG ? paramTypes.type("any") : paramTypes.type("string")); - return cfg.type instanceof type_1.ParamType ? cfg.type : paramTypes.type(cfg.type); - } - /** - * returns false, true, or the squash value to indicate the "default parameter url squash policy". - */ - function getSquashPolicy(config, isOptional) { - var squash = config.squash; - if (!isOptional || squash === false) - return false; - if (!predicates_1.isDefined(squash) || squash == null) - return urlMatcherConfig_1.matcherConfig.defaultSquashPolicy(); - if (squash === true || predicates_1.isString(squash)) - return squash; - throw new Error("Invalid squash policy: '" + squash + "'. Valid policies: false, true, or arbitrary string"); - } - function getReplace(config, arrayMode, isOptional, squash) { - var replace, configuredKeys, defaultPolicy = [ - { from: "", to: (isOptional || arrayMode ? undefined : "") }, - { from: null, to: (isOptional || arrayMode ? undefined : "") } - ]; - replace = predicates_1.isArray(config.replace) ? config.replace : []; - if (predicates_1.isString(squash)) - replace.push({ from: squash, to: undefined }); - configuredKeys = common_1.map(replace, hof_1.prop("from")); - return common_1.filter(defaultPolicy, function (item) { return configuredKeys.indexOf(item.from) === -1; }).concat(replace); - } - var Param = (function () { - function Param(id, type, config, location, paramTypes) { - config = unwrapShorthand(config); - type = getType(config, type, location, id, paramTypes); - var arrayMode = getArrayMode(); - type = arrayMode ? type.$asArray(arrayMode, location === DefType.SEARCH) : type; - var isOptional = config.value !== undefined; - var dynamic = predicates_1.isDefined(config.dynamic) ? !!config.dynamic : !!type.dynamic; - var squash = getSquashPolicy(config, isOptional); - var replace = getReplace(config, arrayMode, isOptional, squash); - // array config: param name (param[]) overrides default settings. explicit config overrides param name. - function getArrayMode() { - var arrayDefaults = { array: (location === DefType.SEARCH ? "auto" : false) }; - var arrayParamNomenclature = id.match(/\[\]$/) ? { array: true } : {}; - return common_1.extend(arrayDefaults, arrayParamNomenclature, config).array; - } - common_1.extend(this, { id: id, type: type, location: location, squash: squash, replace: replace, isOptional: isOptional, dynamic: dynamic, config: config, array: arrayMode }); - } - Param.prototype.isDefaultValue = function (value) { - return this.isOptional && this.type.equals(this.value(), value); - }; - /** - * [Internal] Gets the decoded representation of a value if the value is defined, otherwise, returns the - * default value, which may be the result of an injectable function. - */ - Param.prototype.value = function (value) { - var _this = this; - /** - * [Internal] Get the default value of a parameter, which may be an injectable function. - */ - var $$getDefaultValue = function () { - if (!coreservices_1.services.$injector) - throw new Error("Injectable functions cannot be called at configuration time"); - var defaultValue = coreservices_1.services.$injector.invoke(_this.config.$$fn); - if (defaultValue !== null && defaultValue !== undefined && !_this.type.is(defaultValue)) - throw new Error("Default value (" + defaultValue + ") for parameter '" + _this.id + "' is not an instance of ParamType (" + _this.type.name + ")"); - return defaultValue; - }; - var $replace = function (val) { - var replacement = common_1.map(common_1.filter(_this.replace, hof_1.propEq('from', val)), hof_1.prop("to")); - return replacement.length ? replacement[0] : val; - }; - value = $replace(value); - return !predicates_1.isDefined(value) ? $$getDefaultValue() : this.type.$normalize(value); - }; - Param.prototype.isSearch = function () { - return this.location === DefType.SEARCH; - }; - Param.prototype.validates = function (value) { - // There was no parameter value, but the param is optional - if ((!predicates_1.isDefined(value) || value === null) && this.isOptional) - return true; - // The value was not of the correct ParamType, and could not be decoded to the correct ParamType - var normalized = this.type.$normalize(value); - if (!this.type.is(normalized)) - return false; - // The value was of the correct type, but when encoded, did not match the ParamType's regexp - var encoded = this.type.encode(normalized); - return !(predicates_1.isString(encoded) && !this.type.pattern.exec(encoded)); - }; - Param.prototype.toString = function () { - return "{Param:" + this.id + " " + this.type + " squash: '" + this.squash + "' optional: " + this.isOptional + "}"; - }; - /** Creates a new [[Param]] from a CONFIG block */ - Param.fromConfig = function (id, type, config, paramTypes) { - return new Param(id, type, config, DefType.CONFIG, paramTypes); - }; - /** Creates a new [[Param]] from a url PATH */ - Param.fromPath = function (id, type, config, paramTypes) { - return new Param(id, type, config, DefType.PATH, paramTypes); - }; - /** Creates a new [[Param]] from a url SEARCH */ - Param.fromSearch = function (id, type, config, paramTypes) { - return new Param(id, type, config, DefType.SEARCH, paramTypes); - }; - Param.values = function (params, values) { - if (values === void 0) { values = {}; } - return params.map(function (param) { return [param.id, param.value(values[param.id])]; }).reduce(common_1.applyPairs, {}); - }; - /** - * Finds [[Param]] objects which have different param values - * - * Filters a list of [[Param]] objects to only those whose parameter values differ in two param value objects - * - * @param params: The list of Param objects to filter - * @param values1: The first set of parameter values - * @param values2: the second set of parameter values - * - * @returns any Param objects whose values were different between values1 and values2 - */ - Param.changed = function (params, values1, values2) { - if (values1 === void 0) { values1 = {}; } - if (values2 === void 0) { values2 = {}; } - return params.filter(function (param) { return !param.type.equals(values1[param.id], values2[param.id]); }); - }; - /** - * Checks if two param value objects are equal (for a set of [[Param]] objects) - * - * @param params The list of [[Param]] objects to check - * @param values1 The first set of param values - * @param values2 The second set of param values - * - * @returns true if the param values in values1 and values2 are equal - */ - Param.equals = function (params, values1, values2) { - if (values1 === void 0) { values1 = {}; } - if (values2 === void 0) { values2 = {}; } - return Param.changed(params, values1, values2).length === 0; - }; - /** Returns true if a the parameter values are valid, according to the Param definitions */ - Param.validates = function (params, values) { - if (values === void 0) { values = {}; } - return params.map(function (param) { return param.validates(values[param.id]); }).reduce(common_1.allTrueR, true); - }; - return Param; - }()); - exports.Param = Param; - - -/***/ }, -/* 23 */ -/***/ function(module, exports, __webpack_require__) { - - "use strict"; - /** @module url */ /** for typedoc */ - var predicates_1 = __webpack_require__(4); - var MatcherConfig = (function () { - function MatcherConfig() { - this._isCaseInsensitive = false; - this._isStrictMode = true; - this._defaultSquashPolicy = false; - } - MatcherConfig.prototype.caseInsensitive = function (value) { - return this._isCaseInsensitive = predicates_1.isDefined(value) ? value : this._isCaseInsensitive; - }; - MatcherConfig.prototype.strictMode = function (value) { - return this._isStrictMode = predicates_1.isDefined(value) ? value : this._isStrictMode; - }; - MatcherConfig.prototype.defaultSquashPolicy = function (value) { - if (predicates_1.isDefined(value) && value !== true && value !== false && !predicates_1.isString(value)) - throw new Error("Invalid squash policy: " + value + ". Valid policies: false, true, arbitrary-string"); - return this._defaultSquashPolicy = predicates_1.isDefined(value) ? value : this._defaultSquashPolicy; - }; - return MatcherConfig; - }()); - exports.MatcherConfig = MatcherConfig; - // TODO: Do not export global instance; create one in UIRouter() constructor - exports.matcherConfig = new MatcherConfig(); - - -/***/ }, -/* 24 */ -/***/ function(module, exports, __webpack_require__) { - - "use strict"; - /** @module params */ /** for typedoc */ - var common_1 = __webpack_require__(3); - var predicates_1 = __webpack_require__(4); - /** - * Wraps up a `ParamType` object to handle array values. - */ - function ArrayType(type, mode) { - var _this = this; - // Wrap non-array value as array - function arrayWrap(val) { - return predicates_1.isArray(val) ? val : (predicates_1.isDefined(val) ? [val] : []); - } - // Unwrap array value for "auto" mode. Return undefined for empty array. - function arrayUnwrap(val) { - switch (val.length) { - case 0: return undefined; - case 1: return mode === "auto" ? val[0] : val; - default: return val; - } - } - // Wraps type (.is/.encode/.decode) functions to operate on each value of an array - function arrayHandler(callback, allTruthyMode) { - return function handleArray(val) { - if (predicates_1.isArray(val) && val.length === 0) - return val; - var arr = arrayWrap(val); - var result = common_1.map(arr, callback); - return (allTruthyMode === true) ? common_1.filter(result, function (x) { return !x; }).length === 0 : arrayUnwrap(result); - }; - } - // Wraps type (.equals) functions to operate on each value of an array - function arrayEqualsHandler(callback) { - return function handleArray(val1, val2) { - var left = arrayWrap(val1), right = arrayWrap(val2); - if (left.length !== right.length) - return false; - for (var i = 0; i < left.length; i++) { - if (!callback(left[i], right[i])) - return false; - } - return true; - }; - } - ['encode', 'decode', 'equals', '$normalize'].forEach(function (name) { - var paramTypeFn = type[name].bind(type); - var wrapperFn = name === 'equals' ? arrayEqualsHandler : arrayHandler; - _this[name] = wrapperFn(paramTypeFn); - }); - common_1.extend(this, { - dynamic: type.dynamic, - name: type.name, - pattern: type.pattern, - is: arrayHandler(type.is.bind(type), true), - $arrayMode: mode - }); - } - /** - * A class that implements Custom Parameter Type functionality. - * - * This class has naive implementations for all the [[ParamTypeDefinition]] methods. - * - * An instance of this class is created when a custom [[ParamTypeDefinition]] object is registered with the [[UrlMatcherFactory.type]]. - * - * Used by [[UrlMatcher]] when matching or formatting URLs, or comparing and validating parameter values. - * - * @example - * ``` - * - * { - * decode: function(val) { return parseInt(val, 10); }, - * encode: function(val) { return val && val.toString(); }, - * equals: function(a, b) { return this.is(a) && a === b; }, - * is: function(val) { return angular.isNumber(val) && isFinite(val) && val % 1 === 0; }, - * pattern: /\d+/ - * } - * ``` - */ - var ParamType = (function () { - /** - * @param def A configuration object which contains the custom type definition. The object's - * properties will override the default methods and/or pattern in `ParamType`'s public interface. - * @returns a new ParamType object - */ - function ParamType(def) { - this.pattern = /.*/; - common_1.extend(this, def); - } - // consider these four methods to be "abstract methods" that should be overridden - /** @inheritdoc */ - ParamType.prototype.is = function (val, key) { return true; }; - /** @inheritdoc */ - ParamType.prototype.encode = function (val, key) { return val; }; - /** @inheritdoc */ - ParamType.prototype.decode = function (val, key) { return val; }; - /** @inheritdoc */ - ParamType.prototype.equals = function (a, b) { return a == b; }; - ParamType.prototype.$subPattern = function () { - var sub = this.pattern.toString(); - return sub.substr(1, sub.length - 2); - }; - ParamType.prototype.toString = function () { - return "{ParamType:" + this.name + "}"; - }; - /** Given an encoded string, or a decoded object, returns a decoded object */ - ParamType.prototype.$normalize = function (val) { - return this.is(val) ? val : this.decode(val); - }; - /** - * Wraps an existing custom ParamType as an array of ParamType, depending on 'mode'. - * e.g.: - * - urlmatcher pattern "/path?{queryParam[]:int}" - * - url: "/path?queryParam=1&queryParam=2 - * - $stateParams.queryParam will be [1, 2] - * if `mode` is "auto", then - * - url: "/path?queryParam=1 will create $stateParams.queryParam: 1 - * - url: "/path?queryParam=1&queryParam=2 will create $stateParams.queryParam: [1, 2] - */ - ParamType.prototype.$asArray = function (mode, isSearch) { - if (!mode) - return this; - if (mode === "auto" && !isSearch) - throw new Error("'auto' array mode is for query parameters only"); - return new ArrayType(this, mode); - }; - return ParamType; - }()); - exports.ParamType = ParamType; - - -/***/ }, -/* 25 */ -/***/ function(module, exports, __webpack_require__) { - - "use strict"; - /** @module core */ /** */ - var urlMatcherFactory_1 = __webpack_require__(26); - var urlRouter_1 = __webpack_require__(29); - var urlRouter_2 = __webpack_require__(29); - var transitionService_1 = __webpack_require__(30); - var view_1 = __webpack_require__(37); - var stateRegistry_1 = __webpack_require__(38); - var stateService_1 = __webpack_require__(43); - var globals_1 = __webpack_require__(44); - /** - * The master class used to instantiate an instance of UI-Router. - * - * This class instantiates and wires the global UI-Router services. - * - * After instantiating a new instance of the Router class, configure it for your app. For instance, register - * your app states with the [[stateRegistry]] (and set url options using ...). Then, tell UI-Router to monitor - * the URL by calling `urlRouter.listen()` ([[URLRouter.listen]]) - */ - var UIRouter = (function () { - function UIRouter() { - this.viewService = new view_1.ViewService(); - this.transitionService = new transitionService_1.TransitionService(this); - this.globals = new globals_1.Globals(this.transitionService); - this.urlMatcherFactory = new urlMatcherFactory_1.UrlMatcherFactory(); - this.urlRouterProvider = new urlRouter_1.UrlRouterProvider(this.urlMatcherFactory, this.globals.params); - this.urlRouter = new urlRouter_2.UrlRouter(this.urlRouterProvider); - this.stateRegistry = new stateRegistry_1.StateRegistry(this.urlMatcherFactory, this.urlRouterProvider); - this.stateService = new stateService_1.StateService(this); - this.viewService.rootContext(this.stateRegistry.root()); - this.globals.$current = this.stateRegistry.root(); - this.globals.current = this.globals.$current.self; - } - return UIRouter; - }()); - exports.UIRouter = UIRouter; - - -/***/ }, -/* 26 */ -/***/ function(module, exports, __webpack_require__) { - - "use strict"; - /** @module url */ /** for typedoc */ - var common_1 = __webpack_require__(3); - var predicates_1 = __webpack_require__(4); - var urlMatcher_1 = __webpack_require__(27); - var urlMatcherConfig_1 = __webpack_require__(23); - var param_1 = __webpack_require__(22); - var paramTypes_1 = __webpack_require__(28); - /** @hidden */ - function getDefaultConfig() { - return { - strict: urlMatcherConfig_1.matcherConfig.strictMode(), - caseInsensitive: urlMatcherConfig_1.matcherConfig.caseInsensitive() - }; - } - /** - * Factory for [[UrlMatcher]] instances. - * - * The factory is available to ng1 services as - * `$urlMatcherFactor` or ng1 providers as `$urlMatcherFactoryProvider`. - */ - var UrlMatcherFactory = (function () { - function UrlMatcherFactory() { - this.paramTypes = new paramTypes_1.ParamTypes(); - common_1.extend(this, { UrlMatcher: urlMatcher_1.UrlMatcher, Param: param_1.Param }); - } - /** - * Defines whether URL matching should be case sensitive (the default behavior), or not. - * - * @param value `false` to match URL in a case sensitive manner; otherwise `true`; - * @returns the current value of caseInsensitive - */ - UrlMatcherFactory.prototype.caseInsensitive = function (value) { - return urlMatcherConfig_1.matcherConfig.caseInsensitive(value); - }; - /** - * Defines whether URLs should match trailing slashes, or not (the default behavior). - * - * @param value `false` to match trailing slashes in URLs, otherwise `true`. - * @returns the current value of strictMode - */ - UrlMatcherFactory.prototype.strictMode = function (value) { - return urlMatcherConfig_1.matcherConfig.strictMode(value); - }; - /** - * Sets the default behavior when generating or matching URLs with default parameter values. - * - * @param value A string that defines the default parameter URL squashing behavior. - * - `nosquash`: When generating an href with a default parameter value, do not squash the parameter value from the URL - * - `slash`: When generating an href with a default parameter value, squash (remove) the parameter value, and, if the - * parameter is surrounded by slashes, squash (remove) one slash from the URL - * - any other string, e.g. "~": When generating an href with a default parameter value, squash (remove) - * the parameter value from the URL and replace it with this string. - * @returns the current value of defaultSquashPolicy - */ - UrlMatcherFactory.prototype.defaultSquashPolicy = function (value) { - return urlMatcherConfig_1.matcherConfig.defaultSquashPolicy(value); - }; - /** - * Creates a [[UrlMatcher]] for the specified pattern. - * - * @param pattern The URL pattern. - * @param config The config object hash. - * @returns The UrlMatcher. - */ - UrlMatcherFactory.prototype.compile = function (pattern, config) { - return new urlMatcher_1.UrlMatcher(pattern, this.paramTypes, common_1.extend(getDefaultConfig(), config)); - }; - /** - * Returns true if the specified object is a [[UrlMatcher]], or false otherwise. - * - * @param object The object to perform the type check against. - * @returns `true` if the object matches the `UrlMatcher` interface, by - * implementing all the same methods. - */ - UrlMatcherFactory.prototype.isMatcher = function (object) { - // TODO: typeof? - if (!predicates_1.isObject(object)) - return false; - var result = true; - common_1.forEach(urlMatcher_1.UrlMatcher.prototype, function (val, name) { - if (predicates_1.isFunction(val)) - result = result && (predicates_1.isDefined(object[name]) && predicates_1.isFunction(object[name])); - }); - return result; - }; - ; - /** - * Creates and registers a custom [[ParamType]] object - * - * A [[ParamType]] can be used to generate URLs with typed parameters. - * - * @param name The type name. - * @param definition The type definition. See [[ParamTypeDefinition]] for information on the values accepted. - * @param definitionFn A function that is injected before the app runtime starts. - * The result of this function should be a [[ParamTypeDefinition]]. - * The result is merged into the existing `definition`. - * See [[ParamType]] for information on the values accepted. - * - * @returns - if a type was registered: the [[UrlMatcherFactory]] - * - if only the `name` parameter was specified: the currently registered [[ParamType]] object, or undefined - * - * Note: Register custom types *before using them* in a state definition. - * - * See [[ParamTypeDefinition]] for examples - */ - UrlMatcherFactory.prototype.type = function (name, definition, definitionFn) { - var type = this.paramTypes.type(name, definition, definitionFn); - return !predicates_1.isDefined(definition) ? type : this; - }; - ; - /** @hidden */ - UrlMatcherFactory.prototype.$get = function () { - this.paramTypes.enqueue = false; - this.paramTypes._flushTypeQueue(); - return this; - }; - ; - return UrlMatcherFactory; - }()); - exports.UrlMatcherFactory = UrlMatcherFactory; - - -/***/ }, -/* 27 */ -/***/ function(module, exports, __webpack_require__) { - - "use strict"; - /** @module url */ /** for typedoc */ - var common_1 = __webpack_require__(3); - var hof_1 = __webpack_require__(5); - var predicates_1 = __webpack_require__(4); - var param_1 = __webpack_require__(22); - var predicates_2 = __webpack_require__(4); - var param_2 = __webpack_require__(22); - var common_2 = __webpack_require__(3); - var common_3 = __webpack_require__(3); - /** @hidden */ - function quoteRegExp(string, param) { - var surroundPattern = ['', ''], result = string.replace(/[\\\[\]\^$*+?.()|{}]/g, "\\$&"); - if (!param) - return result; - switch (param.squash) { - case false: - surroundPattern = ['(', ')' + (param.isOptional ? '?' : '')]; - break; - case true: - result = result.replace(/\/$/, ''); - surroundPattern = ['(?:\/(', ')|\/)?']; - break; - default: - surroundPattern = [("(" + param.squash + "|"), ')?']; - break; - } - return result + surroundPattern[0] + param.type.pattern.source + surroundPattern[1]; - } - /** @hidden */ - var memoizeTo = function (obj, prop, fn) { - return obj[prop] = obj[prop] || fn(); - }; - /** - * Matches URLs against patterns. - * - * Matches URLs against patterns and extracts named parameters from the path or the search - * part of the URL. - * - * A URL pattern consists of a path pattern, optionally followed by '?' and a list of search (query) - * parameters. Multiple search parameter names are separated by '&'. Search parameters - * do not influence whether or not a URL is matched, but their values are passed through into - * the matched parameters returned by [[UrlMatcher.exec]]. - * - * - *Path parameters* are defined using curly brace placeholders (`/somepath/{param}`) - * or colon placeholders (`/somePath/:param`). - * - * - *A parameter RegExp* may be defined for a param after a colon - * (`/somePath/{param:[a-zA-Z0-9]+}`) in a curly brace placeholder. - * The regexp must match for the url to be matched. - * Should the regexp itself contain curly braces, they must be in matched pairs or escaped with a backslash. - * - * - *Custom parameter types* may also be specified after a colon (`/somePath/{param:int}`) - * in curly brace parameters. See [[UrlMatcherFactory.type]] for more information. - * - * - *Catch-all parameters* are defined using an asterisk placeholder (`/somepath/*catchallparam`). A catch-all - * parameter value will contain the remainder of the URL. - * - * --- - * - * Parameter names may contain only word characters (latin letters, digits, and underscore) and - * must be unique within the pattern (across both path and search parameters). - * A path parameter matches any number of characters other than '/'. For catch-all - * placeholders the path parameter matches any number of characters. - * - * Examples: - * - * * `'/hello/'` - Matches only if the path is exactly '/hello/'. There is no special treatment for - * trailing slashes, and patterns have to match the entire path, not just a prefix. - * * `'/user/:id'` - Matches '/user/bob' or '/user/1234!!!' or even '/user/' but not '/user' or - * '/user/bob/details'. The second path segment will be captured as the parameter 'id'. - * * `'/user/{id}'` - Same as the previous example, but using curly brace syntax. - * * `'/user/{id:[^/]*}'` - Same as the previous example. - * * `'/user/{id:[0-9a-fA-F]{1,8}}'` - Similar to the previous example, but only matches if the id - * parameter consists of 1 to 8 hex digits. - * * `'/files/{path:.*}'` - Matches any URL starting with '/files/' and captures the rest of the - * path into the parameter 'path'. - * * `'/files/*path'` - ditto. - * * `'/calendar/{start:date}'` - Matches "/calendar/2014-11-12" (because the pattern defined - * in the built-in `date` ParamType matches `2014-11-12`) and provides a Date object in $stateParams.start - * - */ - var UrlMatcher = (function () { - /** - * @param pattern The pattern to compile into a matcher. - * @param paramTypes The [[ParamTypes]] registry - * @param config A configuration object - * - `caseInsensitive` - `true` if URL matching should be case insensitive, otherwise `false`, the default value (for backward compatibility) is `false`. - * - `strict` - `false` if matching against a URL with a trailing slash should be treated as equivalent to a URL without a trailing slash, the default value is `true`. - */ - function UrlMatcher(pattern, paramTypes, config) { - var _this = this; - this.config = config; - /** @hidden */ - this._cache = { path: [], pattern: null }; - /** @hidden */ - this._children = []; - /** @hidden */ - this._params = []; - /** @hidden */ - this._segments = []; - /** @hidden */ - this._compiled = []; - this.pattern = pattern; - this.config = common_1.defaults(this.config, { - params: {}, - strict: true, - caseInsensitive: false, - paramMap: common_1.identity - }); - // Find all placeholders and create a compiled pattern, using either classic or curly syntax: - // '*' name - // ':' name - // '{' name '}' - // '{' name ':' regexp '}' - // The regular expression is somewhat complicated due to the need to allow curly braces - // inside the regular expression. The placeholder regexp breaks down as follows: - // ([:*])([\w\[\]]+) - classic placeholder ($1 / $2) (search version has - for snake-case) - // \{([\w\[\]]+)(?:\:\s*( ... ))?\} - curly brace placeholder ($3) with optional regexp/type ... ($4) (search version has - for snake-case - // (?: ... | ... | ... )+ - the regexp consists of any number of atoms, an atom being either - // [^{}\\]+ - anything other than curly braces or backslash - // \\. - a backslash escape - // \{(?:[^{}\\]+|\\.)*\} - a matched set of curly braces containing other atoms - var placeholder = /([:*])([\w\[\]]+)|\{([\w\[\]]+)(?:\:\s*((?:[^{}\\]+|\\.|\{(?:[^{}\\]+|\\.)*\})+))?\}/g, searchPlaceholder = /([:]?)([\w\[\].-]+)|\{([\w\[\].-]+)(?:\:\s*((?:[^{}\\]+|\\.|\{(?:[^{}\\]+|\\.)*\})+))?\}/g, last = 0, m, patterns = []; - var checkParamErrors = function (id) { - if (!UrlMatcher.nameValidator.test(id)) - throw new Error("Invalid parameter name '" + id + "' in pattern '" + pattern + "'"); - if (common_1.find(_this._params, hof_1.propEq('id', id))) - throw new Error("Duplicate parameter name '" + id + "' in pattern '" + pattern + "'"); - }; - // Split into static segments separated by path parameter placeholders. - // The number of segments is always 1 more than the number of parameters. - var matchDetails = function (m, isSearch) { - // IE[78] returns '' for unmatched groups instead of null - var id = m[2] || m[3], regexp = isSearch ? m[4] : m[4] || (m[1] === '*' ? '.*' : null); - return { - id: id, - regexp: regexp, - cfg: _this.config.params[id], - segment: pattern.substring(last, m.index), - type: !regexp ? null : paramTypes.type(regexp || "string") || common_1.inherit(paramTypes.type("string"), { - pattern: new RegExp(regexp, _this.config.caseInsensitive ? 'i' : undefined) - }) - }; - }; - var p, segment; - while ((m = placeholder.exec(pattern))) { - p = matchDetails(m, false); - if (p.segment.indexOf('?') >= 0) - break; // we're into the search part - checkParamErrors(p.id); - this._params.push(param_1.Param.fromPath(p.id, p.type, this.config.paramMap(p.cfg, false), paramTypes)); - this._segments.push(p.segment); - patterns.push([p.segment, common_1.tail(this._params)]); - last = placeholder.lastIndex; - } - segment = pattern.substring(last); - // Find any search parameter names and remove them from the last segment - var i = segment.indexOf('?'); - if (i >= 0) { - var search = segment.substring(i); - segment = segment.substring(0, i); - if (search.length > 0) { - last = 0; - while ((m = searchPlaceholder.exec(search))) { - p = matchDetails(m, true); - checkParamErrors(p.id); - this._params.push(param_1.Param.fromSearch(p.id, p.type, this.config.paramMap(p.cfg, true), paramTypes)); - last = placeholder.lastIndex; - } - } - } - this._segments.push(segment); - common_1.extend(this, { - _compiled: patterns.map(function (pattern) { return quoteRegExp.apply(null, pattern); }).concat(quoteRegExp(segment)), - prefix: this._segments[0] - }); - Object.freeze(this); - } - /** - * Creates a new concatenated UrlMatcher - * - * Builds a new UrlMatcher by appending another UrlMatcher to this one. - * - * @param url A `UrlMatcher` instance to append as a child of the current `UrlMatcher`. - */ - UrlMatcher.prototype.append = function (url) { - this._children.push(url); - common_1.forEach(url._cache, function (val, key) { return url._cache[key] = predicates_1.isArray(val) ? [] : null; }); - url._cache.path = this._cache.path.concat(this); - return url; - }; - /** @hidden */ - UrlMatcher.prototype.isRoot = function () { - return this._cache.path.length === 0; - }; - /** Returns the input pattern string */ - UrlMatcher.prototype.toString = function () { - return this.pattern; - }; - /** - * Tests the specified url/path against this matcher. - * - * Tests if the given url matches this matcher's pattern, and returns an object containing the captured - * parameter values. Returns null if the path does not match. - * - * The returned object contains the values - * of any search parameters that are mentioned in the pattern, but their value may be null if - * they are not present in `search`. This means that search parameters are always treated - * as optional. - * - * @example - * ```js - * - * new UrlMatcher('/user/{id}?q&r').exec('/user/bob', { - * x: '1', q: 'hello' - * }); - * // returns { id: 'bob', q: 'hello', r: null } - * ``` - * - * @param path The URL path to match, e.g. `$location.path()`. - * @param search URL search parameters, e.g. `$location.search()`. - * @param hash URL hash e.g. `$location.hash()`. - * @param options - * - * @returns The captured parameter values. - */ - UrlMatcher.prototype.exec = function (path, search, hash, options) { - var _this = this; - if (search === void 0) { search = {}; } - if (options === void 0) { options = {}; } - var match = memoizeTo(this._cache, 'pattern', function () { - return new RegExp([ - '^', - common_1.unnest(_this._cache.path.concat(_this).map(hof_1.prop('_compiled'))).join(''), - _this.config.strict === false ? '\/?' : '', - '$' - ].join(''), _this.config.caseInsensitive ? 'i' : undefined); - }).exec(path); - if (!match) - return null; - //options = defaults(options, { isolate: false }); - var allParams = this.parameters(), pathParams = allParams.filter(function (param) { return !param.isSearch(); }), searchParams = allParams.filter(function (param) { return param.isSearch(); }), nPathSegments = this._cache.path.concat(this).map(function (urlm) { return urlm._segments.length - 1; }).reduce(function (a, x) { return a + x; }), values = {}; - if (nPathSegments !== match.length - 1) - throw new Error("Unbalanced capture group in route '" + this.pattern + "'"); - function decodePathArray(string) { - var reverseString = function (str) { return str.split("").reverse().join(""); }; - var unquoteDashes = function (str) { return str.replace(/\\-/g, "-"); }; - var split = reverseString(string).split(/-(?!\\)/); - var allReversed = common_1.map(split, reverseString); - return common_1.map(allReversed, unquoteDashes).reverse(); - } - for (var i = 0; i < nPathSegments; i++) { - var param = pathParams[i]; - var value = match[i + 1]; - // if the param value matches a pre-replace pair, replace the value before decoding. - for (var j = 0; j < param.replace.length; j++) { - if (param.replace[j].from === value) - value = param.replace[j].to; - } - if (value && param.array === true) - value = decodePathArray(value); - if (predicates_2.isDefined(value)) - value = param.type.decode(value); - values[param.id] = param.value(value); - } - searchParams.forEach(function (param) { - var value = search[param.id]; - for (var j = 0; j < param.replace.length; j++) { - if (param.replace[j].from === value) - value = param.replace[j].to; - } - if (predicates_2.isDefined(value)) - value = param.type.decode(value); - values[param.id] = param.value(value); - }); - if (hash) - values["#"] = hash; - return values; - }; - /** - * @hidden - * Returns all the [[Param]] objects of all path and search parameters of this pattern in order of appearance. - * - * @returns {Array.} An array of [[Param]] objects. Must be treated as read-only. If the - * pattern has no parameters, an empty array is returned. - */ - UrlMatcher.prototype.parameters = function (opts) { - if (opts === void 0) { opts = {}; } - if (opts.inherit === false) - return this._params; - return common_1.unnest(this._cache.path.concat(this).map(hof_1.prop('_params'))); - }; - /** - * @hidden - * Returns a single parameter from this UrlMatcher by id - * - * @param id - * @param opts - * @returns {T|Param|any|boolean|UrlMatcher|null} - */ - UrlMatcher.prototype.parameter = function (id, opts) { - if (opts === void 0) { opts = {}; } - var parent = common_1.tail(this._cache.path); - return (common_1.find(this._params, hof_1.propEq('id', id)) || - (opts.inherit !== false && parent && parent.parameter(id)) || - null); - }; - /** - * Validates the input parameter values against this UrlMatcher - * - * Checks an object hash of parameters to validate their correctness according to the parameter - * types of this `UrlMatcher`. - * - * @param params The object hash of parameters to validate. - * @returns Returns `true` if `params` validates, otherwise `false`. - */ - UrlMatcher.prototype.validates = function (params) { - var _this = this; - var validParamVal = function (param, val) { - return !param || param.validates(val); - }; - return common_1.pairs(params || {}).map(function (_a) { - var key = _a[0], val = _a[1]; - return validParamVal(_this.parameter(key), val); - }).reduce(common_1.allTrueR, true); - }; - /** - * Given a set of parameter values, creates a URL from this UrlMatcher. - * - * Creates a URL that matches this pattern by substituting the specified values - * for the path and search parameters. - * - * @example - * ```js - * - * new UrlMatcher('/user/{id}?q').format({ id:'bob', q:'yes' }); - * // returns '/user/bob?q=yes' - * ``` - * - * @param values the values to substitute for the parameters in this pattern. - * @returns the formatted URL (path and optionally search part). - */ - UrlMatcher.prototype.format = function (values) { - if (values === void 0) { values = {}; } - if (!this.validates(values)) - return null; - // Build the full path of UrlMatchers (including all parent UrlMatchers) - var urlMatchers = this._cache.path.slice().concat(this); - // Extract all the static segments and Params into an ordered array - var pathSegmentsAndParams = urlMatchers.map(UrlMatcher.pathSegmentsAndParams).reduce(common_2.unnestR, []); - // Extract the query params into a separate array - var queryParams = urlMatchers.map(UrlMatcher.queryParams).reduce(common_2.unnestR, []); - /** - * Given a Param, - * Applies the parameter value, then returns details about it - */ - function getDetails(param) { - // Normalize to typed value - var value = param.value(values[param.id]); - var isDefaultValue = param.isDefaultValue(value); - // Check if we're in squash mode for the parameter - var squash = isDefaultValue ? param.squash : false; - // Allow the Parameter's Type to encode the value - var encoded = param.type.encode(value); - return { param: param, value: value, isDefaultValue: isDefaultValue, squash: squash, encoded: encoded }; - } - // Build up the path-portion from the list of static segments and parameters - var pathString = pathSegmentsAndParams.reduce(function (acc, x) { - // The element is a static segment (a raw string); just append it - if (predicates_1.isString(x)) - return acc + x; - // Otherwise, it's a Param. Fetch details about the parameter value - var _a = getDetails(x), squash = _a.squash, encoded = _a.encoded, param = _a.param; - // If squash is === true, try to remove a slash from the path - if (squash === true) - return (acc.match(/\/$/)) ? acc.slice(0, -1) : acc; - // If squash is a string, use the string for the param value - if (predicates_1.isString(squash)) - return acc + squash; - if (squash !== false) - return acc; // ? - if (encoded == null) - return acc; - // If this parameter value is an array, encode the value using encodeDashes - if (predicates_1.isArray(encoded)) - return acc + common_1.map(encoded, UrlMatcher.encodeDashes).join("-"); - // If the parameter type is "raw", then do not encodeURIComponent - if (param.type.raw) - return acc + encoded; - // Encode the value - return acc + encodeURIComponent(encoded); - }, ""); - // Build the query string by applying parameter values (array or regular) - // then mapping to key=value, then flattening and joining using "&" - var queryString = queryParams.map(function (param) { - var _a = getDetails(param), squash = _a.squash, encoded = _a.encoded, isDefaultValue = _a.isDefaultValue; - if (encoded == null || (isDefaultValue && squash !== false)) - return; - if (!predicates_1.isArray(encoded)) - encoded = [encoded]; - if (encoded.length === 0) - return; - if (!param.type.raw) - encoded = common_1.map(encoded, encodeURIComponent); - return encoded.map(function (val) { return (param.id + "=" + val); }); - }).filter(common_1.identity).reduce(common_2.unnestR, []).join("&"); - // Concat the pathstring with the queryString (if exists) and the hashString (if exists) - return pathString + (queryString ? "?" + queryString : "") + (values["#"] ? "#" + values["#"] : ""); - }; - /** @hidden */ - UrlMatcher.encodeDashes = function (str) { - return encodeURIComponent(str).replace(/-/g, function (c) { return ("%5C%" + c.charCodeAt(0).toString(16).toUpperCase()); }); - }; - /** @hidden Given a matcher, return an array with the matcher's path segments and path params, in order */ - UrlMatcher.pathSegmentsAndParams = function (matcher) { - var staticSegments = matcher._segments; - var pathParams = matcher._params.filter(function (p) { return p.location === param_2.DefType.PATH; }); - return common_3.arrayTuples(staticSegments, pathParams.concat(undefined)).reduce(common_2.unnestR, []).filter(function (x) { return x !== "" && predicates_2.isDefined(x); }); - }; - /** @hidden Given a matcher, return an array with the matcher's query params */ - UrlMatcher.queryParams = function (matcher) { - return matcher._params.filter(function (p) { return p.location === param_2.DefType.SEARCH; }); - }; - /** @hidden */ - UrlMatcher.nameValidator = /^\w+([-.]+\w+)*(?:\[\])?$/; - return UrlMatcher; - }()); - exports.UrlMatcher = UrlMatcher; - - -/***/ }, -/* 28 */ -/***/ function(module, exports, __webpack_require__) { - - "use strict"; - /** @module params */ /** for typedoc */ - var common_1 = __webpack_require__(3); - var predicates_1 = __webpack_require__(4); - var hof_1 = __webpack_require__(5); - var coreservices_1 = __webpack_require__(6); - var type_1 = __webpack_require__(24); - // Use tildes to pre-encode slashes. - // If the slashes are simply URLEncoded, the browser can choose to pre-decode them, - // and bidirectional encoding/decoding fails. - // Tilde was chosen because it's not a RFC 3986 section 2.2 Reserved Character - function valToString(val) { return val != null ? val.toString().replace(/(~|\/)/g, function (m) { return ({ '~': '~~', '/': '~2F' }[m]); }) : val; } - function valFromString(val) { return val != null ? val.toString().replace(/(~~|~2F)/g, function (m) { return ({ '~~': '~', '~2F': '/' }[m]); }) : val; } - var ParamTypes = (function () { - function ParamTypes() { - this.enqueue = true; - this.typeQueue = []; - this.defaultTypes = { - "hash": { - encode: valToString, - decode: valFromString, - is: hof_1.is(String), - pattern: /.*/, - equals: function (a, b) { return a == b; } // allow coersion for null/undefined/"" - }, - "string": { - encode: valToString, - decode: valFromString, - is: hof_1.is(String), - pattern: /[^/]*/ - }, - "int": { - encode: valToString, - decode: function (val) { return parseInt(val, 10); }, - is: function (val) { return predicates_1.isDefined(val) && this.decode(val.toString()) === val; }, - pattern: /-?\d+/ - }, - "bool": { - encode: function (val) { return val && 1 || 0; }, - decode: function (val) { return parseInt(val, 10) !== 0; }, - is: hof_1.is(Boolean), - pattern: /0|1/ - }, - "date": { - encode: function (val) { - return !this.is(val) ? undefined : [ - val.getFullYear(), - ('0' + (val.getMonth() + 1)).slice(-2), - ('0' + val.getDate()).slice(-2) - ].join("-"); - }, - decode: function (val) { - if (this.is(val)) - return val; - var match = this.capture.exec(val); - return match ? new Date(match[1], match[2] - 1, match[3]) : undefined; - }, - is: function (val) { return val instanceof Date && !isNaN(val.valueOf()); }, - equals: function (l, r) { - return ['getFullYear', 'getMonth', 'getDate'] - .reduce(function (acc, fn) { return acc && l[fn]() === r[fn](); }, true); - }, - pattern: /[0-9]{4}-(?:0[1-9]|1[0-2])-(?:0[1-9]|[1-2][0-9]|3[0-1])/, - capture: /([0-9]{4})-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1])/ - }, - "json": { - encode: common_1.toJson, - decode: common_1.fromJson, - is: hof_1.is(Object), - equals: common_1.equals, - pattern: /[^/]*/ - }, - "any": { - encode: common_1.identity, - decode: common_1.identity, - equals: common_1.equals, - pattern: /.*/ - } - }; - // Register default types. Store them in the prototype of this.types. - var makeType = function (definition, name) { return new type_1.ParamType(common_1.extend({ name: name }, definition)); }; - this.types = common_1.inherit(common_1.map(this.defaultTypes, makeType), {}); - } - ParamTypes.prototype.type = function (name, definition, definitionFn) { - if (!predicates_1.isDefined(definition)) - return this.types[name]; - if (this.types.hasOwnProperty(name)) - throw new Error("A type named '" + name + "' has already been defined."); - this.types[name] = new type_1.ParamType(common_1.extend({ name: name }, definition)); - if (definitionFn) { - this.typeQueue.push({ name: name, def: definitionFn }); - if (!this.enqueue) - this._flushTypeQueue(); - } - return this; - }; - ParamTypes.prototype._flushTypeQueue = function () { - while (this.typeQueue.length) { - var type = this.typeQueue.shift(); - if (type.pattern) - throw new Error("You cannot override a type's .pattern at runtime."); - common_1.extend(this.types[type.name], coreservices_1.services.$injector.invoke(type.def)); - } - }; - return ParamTypes; - }()); - exports.ParamTypes = ParamTypes; - - -/***/ }, -/* 29 */ -/***/ function(module, exports, __webpack_require__) { - - "use strict"; - /** @module url */ /** for typedoc */ - var common_1 = __webpack_require__(3); - var predicates_1 = __webpack_require__(4); - var coreservices_1 = __webpack_require__(6); - /** @hidden */ - var $location = coreservices_1.services.location; - /** @hidden Returns a string that is a prefix of all strings matching the RegExp */ - function regExpPrefix(re) { - var prefix = /^\^((?:\\[^a-zA-Z0-9]|[^\\\[\]\^$*+?.()|{}]+)*)/.exec(re.source); - return (prefix != null) ? prefix[1].replace(/\\(.)/g, "$1") : ''; - } - /** @hidden Interpolates matched values into a String.replace()-style pattern */ - function interpolate(pattern, match) { - return pattern.replace(/\$(\$|\d{1,2})/, function (m, what) { - return match[what === '$' ? 0 : Number(what)]; - }); - } - /** @hidden */ - function handleIfMatch($injector, $stateParams, handler, match) { - if (!match) - return false; - var result = $injector.invoke(handler, handler, { $match: match, $stateParams: $stateParams }); - return predicates_1.isDefined(result) ? result : true; - } - /** @hidden */ - function appendBasePath(url, isHtml5, absolute) { - var baseHref = coreservices_1.services.locationConfig.baseHref(); - if (baseHref === '/') - return url; - if (isHtml5) - return baseHref.slice(0, -1) + url; - if (absolute) - return baseHref.slice(1) + url; - return url; - } - // TODO: Optimize groups of rules with non-empty prefix into some sort of decision tree - /** @hidden */ - function update(rules, otherwiseFn, evt) { - if (evt && evt.defaultPrevented) - return; - function check(rule) { - var handled = rule(coreservices_1.services.$injector, $location); - if (!handled) - return false; - if (predicates_1.isString(handled)) { - $location.setUrl(handled, true); - } - return true; - } - var n = rules.length; - for (var i = 0; i < n; i++) { - if (check(rules[i])) - return; - } - // always check otherwise last to allow dynamic updates to the set of rules - if (otherwiseFn) - check(otherwiseFn); - } - /** - * Manages rules for client-side URL - * - * This class manages the router rules for what to do when the URL changes. - */ - var UrlRouterProvider = (function () { - function UrlRouterProvider($urlMatcherFactory, $stateParams) { - /** @hidden */ - this.rules = []; - /** @hidden */ - this.interceptDeferred = false; - this.$urlMatcherFactory = $urlMatcherFactory; - this.$stateParams = $stateParams; - } - /** - * Registers a url handler function. - * - * Registers a low level url handler (a `rule`). A rule detects specific URL patterns and returns - * a redirect, or performs some action. - * - * If a rule returns a string, the URL is replaced with the string, and all rules are fired again. - * - * @example - * ```js - * - * var app = angular.module('app', ['ui.router.router']); - * - * app.config(function ($urlRouterProvider) { - * // Here's an example of how you might allow case insensitive urls - * $urlRouterProvider.rule(function ($injector, $location) { - * var path = $location.path(), - * normalized = path.toLowerCase(); - * - * if (path !== normalized) { - * return normalized; - * } - * }); - * }); - * ``` - * - * @param rule - * Handler function that takes `$injector` and `$location` services as arguments. - * You can use them to detect a url and return a different url as a string. - * - * @return [[$urlRouterProvider]] (`this`) - */ - UrlRouterProvider.prototype.rule = function (rule) { - if (!predicates_1.isFunction(rule)) - throw new Error("'rule' must be a function"); - this.rules.push(rule); - return this; - }; - ; - /** - * Remove a rule previously registered - * - * @param rule the matcher rule that was previously registered using [[rule]] - * @return true if the rule was found (and removed) - */ - UrlRouterProvider.prototype.removeRule = function (rule) { - return this.rules.length !== common_1.removeFrom(this.rules, rule).length; - }; - /** - * Defines the path or behavior to use when no url can be matched. - * - * @example - * ```js - * - * var app = angular.module('app', ['ui.router.router']); - * - * app.config(function ($urlRouterProvider) { - * // if the path doesn't match any of the urls you configured - * // otherwise will take care of routing the user to the - * // specified url - * $urlRouterProvider.otherwise('/index'); - * - * // Example of using function rule as param - * $urlRouterProvider.otherwise(function ($injector, $location) { - * return '/a/valid/url'; - * }); - * }); - * ``` - * - * @param rule - * The url path you want to redirect to or a function rule that returns the url path or performs a `$state.go()`. - * The function version is passed two params: `$injector` and `$location` services, and should return a url string. - * - * @return {object} `$urlRouterProvider` - `$urlRouterProvider` instance - */ - UrlRouterProvider.prototype.otherwise = function (rule) { - if (!predicates_1.isFunction(rule) && !predicates_1.isString(rule)) - throw new Error("'rule' must be a string or function"); - this.otherwiseFn = predicates_1.isString(rule) ? function () { return rule; } : rule; - return this; - }; - ; - /** - * Registers a handler for a given url matching. - * - * If the handler is a string, it is - * treated as a redirect, and is interpolated according to the syntax of match - * (i.e. like `String.replace()` for `RegExp`, or like a `UrlMatcher` pattern otherwise). - * - * If the handler is a function, it is injectable. - * It gets invoked if `$location` matches. - * You have the option of inject the match object as `$match`. - * - * The handler can return - * - * - **falsy** to indicate that the rule didn't match after all, then `$urlRouter` - * will continue trying to find another one that matches. - * - **string** which is treated as a redirect and passed to `$location.url()` - * - **void** or any **truthy** value tells `$urlRouter` that the url was handled. - * - * @example - * ```js - * - * var app = angular.module('app', ['ui.router.router']); - * - * app.config(function ($urlRouterProvider) { - * $urlRouterProvider.when($state.url, function ($match, $stateParams) { - * if ($state.$current.navigable !== state || - * !equalForKeys($match, $stateParams) { - * $state.transitionTo(state, $match, false); - * } - * }); - * }); - * ``` - * - * @param what A pattern string to match, compiled as a [[UrlMatcher]]. - * @param handler The path (or function that returns a path) that you want to redirect your user to. - * @param ruleCallback [optional] A callback that receives the `rule` registered with [[UrlMatcher.rule]] - * - * Note: the handler may also invoke arbitrary code, such as `$state.go()` - */ - UrlRouterProvider.prototype.when = function (what, handler, ruleCallback) { - if (ruleCallback === void 0) { ruleCallback = function (rule) { }; } - var _a = this, $urlMatcherFactory = _a.$urlMatcherFactory, $stateParams = _a.$stateParams; - var redirect, handlerIsString = predicates_1.isString(handler); - // @todo Queue this - if (predicates_1.isString(what)) - what = $urlMatcherFactory.compile(what); - if (!handlerIsString && !predicates_1.isFunction(handler) && !predicates_1.isArray(handler)) - throw new Error("invalid 'handler' in when()"); - var strategies = { - matcher: function (_what, _handler) { - if (handlerIsString) { - redirect = $urlMatcherFactory.compile(_handler); - _handler = ['$match', redirect.format.bind(redirect)]; - } - return common_1.extend(function () { - return handleIfMatch(coreservices_1.services.$injector, $stateParams, _handler, _what.exec($location.path(), $location.search(), $location.hash())); - }, { - prefix: predicates_1.isString(_what.prefix) ? _what.prefix : '' - }); - }, - regex: function (_what, _handler) { - if (_what.global || _what.sticky) - throw new Error("when() RegExp must not be global or sticky"); - if (handlerIsString) { - redirect = _handler; - _handler = ['$match', function ($match) { return interpolate(redirect, $match); }]; - } - return common_1.extend(function () { - return handleIfMatch(coreservices_1.services.$injector, $stateParams, _handler, _what.exec($location.path())); - }, { - prefix: regExpPrefix(_what) - }); - } - }; - var check = { - matcher: $urlMatcherFactory.isMatcher(what), - regex: what instanceof RegExp - }; - for (var n in check) { - if (check[n]) { - var rule = strategies[n](what, handler); - ruleCallback(rule); - return this.rule(rule); - } - } - throw new Error("invalid 'what' in when()"); - }; - ; - /** - * Disables monitoring of the URL. - * - * Call this method before UI-Router has bootstrapped. - * It will stop UI-Router from performing the initial url sync. - * - * This can be useful to perform some asynchronous initialization before the router starts. - * Once the initialization is complete, call [[listen]] to tell UI-Router to start watching and synchronizing the URL. - * - * @example - * ```js - * - * var app = angular.module('app', ['ui.router']); - * - * app.config(function ($urlRouterProvider) { - * // Prevent $urlRouter from automatically intercepting URL changes; - * $urlRouterProvider.deferIntercept(); - * }) - * - * app.run(function (MyService, $urlRouter, $http) { - * $http.get("/stuff").then(function(resp) { - * MyService.doStuff(resp.data); - * $urlRouter.listen(); - * $urlRouter.sync(); - * }); - * }); - * ``` - * - * @param defer Indicates whether to defer location change interception. Passing - * no parameter is equivalent to `true`. - */ - UrlRouterProvider.prototype.deferIntercept = function (defer) { - if (defer === undefined) - defer = true; - this.interceptDeferred = defer; - }; - ; - return UrlRouterProvider; - }()); - exports.UrlRouterProvider = UrlRouterProvider; - var UrlRouter = (function () { - /** @hidden */ - function UrlRouter(urlRouterProvider) { - this.urlRouterProvider = urlRouterProvider; - common_1.bindFunctions(UrlRouter.prototype, this, this); - } - /** - * Checks the current URL for a matching rule - * - * Triggers an update; the same update that happens when the address bar url changes, aka `$locationChangeSuccess`. - * This method is useful when you need to use `preventDefault()` on the `$locationChangeSuccess` event, - * perform some custom logic (route protection, auth, config, redirection, etc) and then finally proceed - * with the transition by calling `$urlRouter.sync()`. - * - * @example - * ```js - * - * angular.module('app', ['ui.router']) - * .run(function($rootScope, $urlRouter) { - * $rootScope.$on('$locationChangeSuccess', function(evt) { - * // Halt state change from even starting - * evt.preventDefault(); - * // Perform custom logic - * var meetsRequirement = ... - * // Continue with the update and state transition if logic allows - * if (meetsRequirement) $urlRouter.sync(); - * }); - * }); - * ``` - */ - UrlRouter.prototype.sync = function () { - update(this.urlRouterProvider.rules, this.urlRouterProvider.otherwiseFn); - }; - /** - * Starts listening for URL changes - * - * Call this sometime after calling [[deferIntercept]] to start monitoring the url. - * This causes [[UrlRouter]] to start listening for changes to the URL, if it wasn't already listening. - */ - UrlRouter.prototype.listen = function () { - var _this = this; - return this.listener = this.listener || $location.onChange(function (evt) { return update(_this.urlRouterProvider.rules, _this.urlRouterProvider.otherwiseFn, evt); }); - }; - /** - * Internal API. - */ - UrlRouter.prototype.update = function (read) { - if (read) { - this.location = $location.path(); - return; - } - if ($location.path() === this.location) - return; - $location.setUrl(this.location, true); - }; - /** - * Internal API. - * - * Pushes a new location to the browser history. - * - * @param urlMatcher - * @param params - * @param options - */ - UrlRouter.prototype.push = function (urlMatcher, params, options) { - var replace = options && !!options.replace; - $location.setUrl(urlMatcher.format(params || {}), replace); - }; - /** - * Builds and returns a URL with interpolated parameters - * - * @example - * ```js - * - * $bob = $urlRouter.href(new UrlMatcher("/about/:person"), { - * person: "bob" - * }); - * // $bob == "/about/bob"; - * ``` - * - * @param urlMatcher The [[UrlMatcher]] object which is used as the template of the URL to generate. - * @param params An object of parameter values to fill the matcher's required parameters. - * @param options Options object. The options are: - * - * - **`absolute`** - {boolean=false}, If true will generate an absolute url, e.g. "http://www.example.com/fullurl". - * - * @returns Returns the fully compiled URL, or `null` if `params` fail validation against `urlMatcher` - */ - UrlRouter.prototype.href = function (urlMatcher, params, options) { - if (!urlMatcher.validates(params)) - return null; - var url = urlMatcher.format(params); - options = options || { absolute: false }; - var cfg = coreservices_1.services.locationConfig; - var isHtml5 = cfg.html5Mode(); - if (!isHtml5 && url !== null) { - url = "#" + cfg.hashPrefix() + url; - } - url = appendBasePath(url, isHtml5, options.absolute); - if (!options.absolute || !url) { - return url; - } - var slash = (!isHtml5 && url ? '/' : ''), port = cfg.port(); - port = (port === 80 || port === 443 ? '' : ':' + port); - return [cfg.protocol(), '://', cfg.host(), port, slash, url].join(''); - }; - return UrlRouter; - }()); - exports.UrlRouter = UrlRouter; - - -/***/ }, -/* 30 */ -/***/ function(module, exports, __webpack_require__) { - - "use strict"; - var transition_1 = __webpack_require__(11); - var hookRegistry_1 = __webpack_require__(15); - var resolve_1 = __webpack_require__(31); - var views_1 = __webpack_require__(32); - var url_1 = __webpack_require__(33); - var redirectTo_1 = __webpack_require__(34); - var onEnterExitRetain_1 = __webpack_require__(35); - var lazyLoadStates_1 = __webpack_require__(36); - /** - * The default [[Transition]] options. - * - * Include this object when applying custom defaults: - * let reloadOpts = { reload: true, notify: true } - * let options = defaults(theirOpts, customDefaults, defaultOptions); - */ - exports.defaultTransOpts = { - location: true, - relative: null, - inherit: false, - notify: true, - reload: false, - custom: {}, - current: function () { return null; }, - source: "unknown" - }; - /** - * This class provides services related to Transitions. - * - * - Most importantly, it allows global Transition Hooks to be registered. - * - It allows the default transition error handler to be set. - * - It also has a factory function for creating new [[Transition]] objects, (used internally by the [[StateService]]). - * - * At bootstrap, [[UIRouter]] creates a single instance (singleton) of this class. - */ - var TransitionService = (function () { - function TransitionService(_router) { - this._router = _router; - this.$view = _router.viewService; - hookRegistry_1.HookRegistry.mixin(new hookRegistry_1.HookRegistry(), this); - this._deregisterHookFns = {}; - this.registerTransitionHooks(); - } - /** @hidden */ - TransitionService.prototype.registerTransitionHooks = function () { - var fns = this._deregisterHookFns; - // Wire up redirectTo hook - fns.redirectTo = redirectTo_1.registerRedirectToHook(this); - // Wire up onExit/Retain/Enter state hooks - fns.onExit = onEnterExitRetain_1.registerOnExitHook(this); - fns.onRetain = onEnterExitRetain_1.registerOnRetainHook(this); - fns.onEnter = onEnterExitRetain_1.registerOnEnterHook(this); - // Wire up Resolve hooks - fns.eagerResolve = resolve_1.registerEagerResolvePath(this); - fns.lazyResolve = resolve_1.registerLazyResolveState(this); - // Wire up the View management hooks - fns.loadViews = views_1.registerLoadEnteringViews(this); - fns.activateViews = views_1.registerActivateViews(this); - // After globals.current is updated at priority: 10000 - fns.updateUrl = url_1.registerUpdateUrl(this); - // Lazy load state trees - fns.lazyLoad = lazyLoadStates_1.registerLazyLoadHook(this); - }; - /** @inheritdoc */ - TransitionService.prototype.onBefore = function (matchCriteria, callback, options) { throw ""; }; - ; - /** @inheritdoc */ - TransitionService.prototype.onStart = function (matchCriteria, callback, options) { throw ""; }; - ; - /** @inheritdoc */ - TransitionService.prototype.onExit = function (matchCriteria, callback, options) { throw ""; }; - ; - /** @inheritdoc */ - TransitionService.prototype.onRetain = function (matchCriteria, callback, options) { throw ""; }; - ; - /** @inheritdoc */ - TransitionService.prototype.onEnter = function (matchCriteria, callback, options) { throw ""; }; - ; - /** @inheritdoc */ - TransitionService.prototype.onFinish = function (matchCriteria, callback, options) { throw ""; }; - ; - /** @inheritdoc */ - TransitionService.prototype.onSuccess = function (matchCriteria, callback, options) { throw ""; }; - ; - /** @inheritdoc */ - TransitionService.prototype.onError = function (matchCriteria, callback, options) { throw ""; }; - ; - /** - * Creates a new [[Transition]] object - * - * This is a factory function for creating new Transition objects. - * It is used internally by the [[StateService]] and should generally not be called by application code. - * - * @param fromPath the path to the current state (the from state) - * @param targetState the target state (destination) - * @returns a Transition - */ - TransitionService.prototype.create = function (fromPath, targetState) { - return new transition_1.Transition(fromPath, targetState, this._router); - }; - return TransitionService; - }()); - exports.TransitionService = TransitionService; - - -/***/ }, -/* 31 */ -/***/ function(module, exports, __webpack_require__) { - - "use strict"; - /** @module hooks */ /** for typedoc */ - var common_1 = __webpack_require__(3); - var resolveContext_1 = __webpack_require__(17); - var hof_1 = __webpack_require__(5); - /** - * A [[TransitionHookFn]] which resolves all EAGER Resolvables in the To Path - * - * Registered using `transitionService.onStart({}, eagerResolvePath);` - * - * When a Transition starts, this hook resolves all the EAGER Resolvables, which the transition then waits for. - * - * See [[StateDeclaration.resolve]] - */ - var eagerResolvePath = function (trans) { - return new resolveContext_1.ResolveContext(trans.treeChanges().to) - .resolvePath("EAGER", trans) - .then(common_1.noop); - }; - exports.registerEagerResolvePath = function (transitionService) { - return transitionService.onStart({}, eagerResolvePath, { priority: 1000 }); - }; - /** - * A [[TransitionHookFn]] which resolves all LAZY Resolvables for the state (and all its ancestors) in the To Path - * - * Registered using `transitionService.onEnter({ entering: () => true }, lazyResolveState);` - * - * When a State is being entered, this hook resolves all the Resolvables for this state, which the transition then waits for. - * - * See [[StateDeclaration.resolve]] - */ - var lazyResolveState = function (trans, state) { - return new resolveContext_1.ResolveContext(trans.treeChanges().to) - .subContext(state) - .resolvePath("LAZY", trans) - .then(common_1.noop); - }; - exports.registerLazyResolveState = function (transitionService) { - return transitionService.onEnter({ entering: hof_1.val(true) }, lazyResolveState, { priority: 1000 }); - }; - - -/***/ }, -/* 32 */ -/***/ function(module, exports, __webpack_require__) { - - "use strict"; - /** @module hooks */ /** for typedoc */ - var common_1 = __webpack_require__(3); - var coreservices_1 = __webpack_require__(6); - /** - * A [[TransitionHookFn]] which waits for the views to load - * - * Registered using `transitionService.onStart({}, loadEnteringViews);` - * - * Allows the views to do async work in [[ViewConfig.load]] before the transition continues. - * In angular 1, this includes loading the templates. - */ - var loadEnteringViews = function (transition) { - var enteringViews = transition.views("entering"); - if (!enteringViews.length) - return; - return coreservices_1.services.$q.all(enteringViews.map(function (view) { return view.load(); })).then(common_1.noop); - }; - exports.registerLoadEnteringViews = function (transitionService) { - return transitionService.onStart({}, loadEnteringViews); - }; - /** - * A [[TransitionHookFn]] which activates the new views when a transition is successful. - * - * Registered using `transitionService.onSuccess({}, activateViews);` - * - * After a transition is complete, this hook deactivates the old views from the previous state, - * and activates the new views from the destination state. - * - * See [[ViewService]] - */ - var activateViews = function (transition) { - var enteringViews = transition.views("entering"); - var exitingViews = transition.views("exiting"); - if (!enteringViews.length && !exitingViews.length) - return; - var $view = transition.router.viewService; - exitingViews.forEach(function (vc) { return $view.deactivateViewConfig(vc); }); - enteringViews.forEach(function (vc) { return $view.activateViewConfig(vc); }); - $view.sync(); - }; - exports.registerActivateViews = function (transitionService) { - return transitionService.onSuccess({}, activateViews); - }; - - -/***/ }, -/* 33 */ -/***/ function(module, exports) { - - "use strict"; - /** - * A [[TransitionHookFn]] which updates the URL after a successful transition - * - * Registered using `transitionService.onSuccess({}, updateUrl);` - */ - var updateUrl = function (transition) { - var options = transition.options(); - var $state = transition.router.stateService; - var $urlRouter = transition.router.urlRouter; - // Dont update the url in these situations: - // The transition was triggered by a URL sync (options.source === 'url') - // The user doesn't want the url to update (options.location === false) - // The destination state, and all parents have no navigable url - if (options.source !== 'url' && options.location && $state.$current.navigable) { - var urlOptions = { replace: options.location === 'replace' }; - $urlRouter.push($state.$current.navigable.url, $state.params, urlOptions); - } - $urlRouter.update(true); - }; - exports.registerUpdateUrl = function (transitionService) { - return transitionService.onSuccess({}, updateUrl, { priority: 9999 }); - }; - - -/***/ }, -/* 34 */ -/***/ function(module, exports, __webpack_require__) { - - "use strict"; - /** @module hooks */ /** */ - var predicates_1 = __webpack_require__(4); - var coreservices_1 = __webpack_require__(6); - var targetState_1 = __webpack_require__(14); - /** - * A [[TransitionHookFn]] that redirects to a different state or params - * - * Registered using `transitionService.onStart({ to: (state) => !!state.redirectTo }, redirectHook);` - * - * See [[StateDeclaration.redirectTo]] - */ - var redirectToHook = function (trans) { - var redirect = trans.to().redirectTo; - if (!redirect) - return; - function handleResult(result) { - var $state = trans.router.stateService; - if (result instanceof targetState_1.TargetState) - return result; - if (predicates_1.isString(result)) - return $state.target(result, trans.params(), trans.options()); - if (result['state'] || result['params']) - return $state.target(result['state'] || trans.to(), result['params'] || trans.params(), trans.options()); - } - if (predicates_1.isFunction(redirect)) { - return coreservices_1.services.$q.when(redirect(trans)).then(handleResult); - } - return handleResult(redirect); - }; - exports.registerRedirectToHook = function (transitionService) { - return transitionService.onStart({ to: function (state) { return !!state.redirectTo; } }, redirectToHook); - }; - - -/***/ }, -/* 35 */ -/***/ function(module, exports) { - - "use strict"; - /** - * A factory which creates an onEnter, onExit or onRetain transition hook function - * - * The returned function invokes the (for instance) state.onEnter hook when the - * state is being entered. - * - * @hidden - */ - function makeEnterExitRetainHook(hookName) { - return function (transition, state) { - var hookFn = state[hookName]; - return hookFn(transition, state); - }; - } - /** - * The [[TransitionStateHookFn]] for onExit - * - * When the state is being exited, the state's .onExit function is invoked. - * - * Registered using `transitionService.onExit({ exiting: (state) => !!state.onExit }, onExitHook);` - * - * See: [[IHookRegistry.onExit]] - */ - var onExitHook = makeEnterExitRetainHook('onExit'); - exports.registerOnExitHook = function (transitionService) { - return transitionService.onExit({ exiting: function (state) { return !!state.onExit; } }, onExitHook); - }; - /** - * The [[TransitionStateHookFn]] for onRetain - * - * When the state was already entered, and is not being exited or re-entered, the state's .onRetain function is invoked. - * - * Registered using `transitionService.onRetain({ retained: (state) => !!state.onRetain }, onRetainHook);` - * - * See: [[IHookRegistry.onRetain]] - */ - var onRetainHook = makeEnterExitRetainHook('onRetain'); - exports.registerOnRetainHook = function (transitionService) { - return transitionService.onRetain({ retained: function (state) { return !!state.onRetain; } }, onRetainHook); - }; - /** - * The [[TransitionStateHookFn]] for onEnter - * - * When the state is being entered, the state's .onEnter function is invoked. - * - * Registered using `transitionService.onEnter({ entering: (state) => !!state.onEnter }, onEnterHook);` - * - * See: [[IHookRegistry.onEnter]] - */ - var onEnterHook = makeEnterExitRetainHook('onEnter'); - exports.registerOnEnterHook = function (transitionService) { - return transitionService.onEnter({ entering: function (state) { return !!state.onEnter; } }, onEnterHook); - }; - - -/***/ }, -/* 36 */ -/***/ function(module, exports, __webpack_require__) { - - "use strict"; - var coreservices_1 = __webpack_require__(6); - /** - * A [[TransitionHookFn]] that lazy loads a state tree. - * - * When transitioning to a state "abc" which has a `lazyLoad` function defined: - * - Invoke the `lazyLoad` function - * - The function should return a promise for an array of lazy loaded [[StateDeclaration]]s - * - Wait for the promise to resolve - * - Deregister the original state "abc" - * - The original state definition is a placeholder for the lazy loaded states - * - Register the new states - * - Retry the transition - * - * See [[StateDeclaration.lazyLoad]] - */ - var lazyLoadHook = function (transition) { - var toState = transition.to(); - var registry = transition.router.stateRegistry; - function retryOriginalTransition() { - if (transition.options().source === 'url') { - var loc = coreservices_1.services.location, path_1 = loc.path(), search_1 = loc.search(), hash_1 = loc.hash(); - var matchState = function (state) { return [state, state.url && state.url.exec(path_1, search_1, hash_1)]; }; - var matches = registry.get().map(function (s) { return s.$$state(); }).map(matchState).filter(function (_a) { - var state = _a[0], params = _a[1]; - return !!params; - }); - if (matches.length) { - var _a = matches[0], state = _a[0], params = _a[1]; - return transition.router.stateService.target(state, params, transition.options()); - } - transition.router.urlRouter.sync(); - } - // The original transition was not triggered via url sync - // The lazy state should be loaded now, so re-try the original transition - var orig = transition.targetState(); - return transition.router.stateService.target(orig.identifier(), orig.params(), orig.options()); - } - /** - * Replace the placeholder state with the newly loaded states from the NgModule. - */ - function updateStateRegistry(result) { - // deregister placeholder state - registry.deregister(transition.$to()); - if (result && Array.isArray(result.states)) { - result.states.forEach(function (state) { return registry.register(state); }); - } - } - var hook = toState.lazyLoad; - // Store/get the lazy load promise on/from the hookfn so it doesn't get re-invoked - var promise = hook['_promise']; - if (!promise) { - promise = hook['_promise'] = hook(transition).then(updateStateRegistry); - var cleanup = function () { return delete hook['_promise']; }; - promise.then(cleanup, cleanup); - } - return promise.then(retryOriginalTransition); - }; - exports.registerLazyLoadHook = function (transitionService) { - return transitionService.onBefore({ to: function (state) { return !!state.lazyLoad; } }, lazyLoadHook); - }; - - -/***/ }, -/* 37 */ -/***/ function(module, exports, __webpack_require__) { - - "use strict"; - /** @module view */ /** for typedoc */ - var common_1 = __webpack_require__(3); - var hof_1 = __webpack_require__(5); - var predicates_1 = __webpack_require__(4); - var trace_1 = __webpack_require__(12); - /** - * The View service - */ - var ViewService = (function () { - function ViewService() { - var _this = this; - this.uiViews = []; - this.viewConfigs = []; - this._viewConfigFactories = {}; - this.sync = function () { - var uiViewsByFqn = _this.uiViews.map(function (uiv) { return [uiv.fqn, uiv]; }).reduce(common_1.applyPairs, {}); - /** - * Given a ui-view and a ViewConfig, determines if they "match". - * - * A ui-view has a fully qualified name (fqn) and a context object. The fqn is built from its overall location in - * the DOM, describing its nesting relationship to any parent ui-view tags it is nested inside of. - * - * A ViewConfig has a target ui-view name and a context anchor. The ui-view name can be a simple name, or - * can be a segmented ui-view path, describing a portion of a ui-view fqn. - * - * In order for a ui-view to match ViewConfig, ui-view's $type must match the ViewConfig's $type - * - * If the ViewConfig's target ui-view name is a simple name (no dots), then a ui-view matches if: - * - the ui-view's name matches the ViewConfig's target name - * - the ui-view's context matches the ViewConfig's anchor - * - * If the ViewConfig's target ui-view name is a segmented name (with dots), then a ui-view matches if: - * - There exists a parent ui-view where: - * - the parent ui-view's name matches the first segment (index 0) of the ViewConfig's target name - * - the parent ui-view's context matches the ViewConfig's anchor - * - And the remaining segments (index 1..n) of the ViewConfig's target name match the tail of the ui-view's fqn - * - * Example: - * - * DOM: - *
- *
- *
- *
- *
- *
- *
- *
- * - * uiViews: [ - * { fqn: "$default", creationContext: { name: "" } }, - * { fqn: "$default.foo", creationContext: { name: "A" } }, - * { fqn: "$default.foo.$default", creationContext: { name: "A.B" } } - * { fqn: "$default.foo.$default.bar", creationContext: { name: "A.B.C" } } - * ] - * - * These four view configs all match the ui-view with the fqn: "$default.foo.$default.bar": - * - * - ViewConfig1: { uiViewName: "bar", uiViewContextAnchor: "A.B.C" } - * - ViewConfig2: { uiViewName: "$default.bar", uiViewContextAnchor: "A.B" } - * - ViewConfig3: { uiViewName: "foo.$default.bar", uiViewContextAnchor: "A" } - * - ViewConfig4: { uiViewName: "$default.foo.$default.bar", uiViewContextAnchor: "" } - * - * Using ViewConfig3 as an example, it matches the ui-view with fqn "$default.foo.$default.bar" because: - * - The ViewConfig's segmented target name is: [ "foo", "$default", "bar" ] - * - There exists a parent ui-view (which has fqn: "$default.foo") where: - * - the parent ui-view's name "foo" matches the first segment "foo" of the ViewConfig's target name - * - the parent ui-view's context "A" matches the ViewConfig's anchor context "A" - * - And the remaining segments [ "$default", "bar" ].join("."_ of the ViewConfig's target name match - * the tail of the ui-view's fqn "default.bar" - */ - var matches = function (uiView) { return function (viewConfig) { - // Don't supply an ng1 ui-view with an ng2 ViewConfig, etc - if (uiView.$type !== viewConfig.viewDecl.$type) - return false; - // Split names apart from both viewConfig and uiView into segments - var vc = viewConfig.viewDecl; - var vcSegments = vc.$uiViewName.split("."); - var uivSegments = uiView.fqn.split("."); - // Check if the tails of the segment arrays match. ex, these arrays' tails match: - // vc: ["foo", "bar"], uiv fqn: ["$default", "foo", "bar"] - if (!common_1.equals(vcSegments, uivSegments.slice(0 - vcSegments.length))) - return false; - // Now check if the fqn ending at the first segment of the viewConfig matches the context: - // ["$default", "foo"].join(".") == "$default.foo", does the ui-view $default.foo context match? - var negOffset = (1 - vcSegments.length) || undefined; - var fqnToFirstSegment = uivSegments.slice(0, negOffset).join("."); - var uiViewContext = uiViewsByFqn[fqnToFirstSegment].creationContext; - return vc.$uiViewContextAnchor === (uiViewContext && uiViewContext.name); - }; }; - // Return the number of dots in the fully qualified name - function uiViewDepth(uiView) { - return uiView.fqn.split(".").length; - } - // Return the ViewConfig's context's depth in the context tree. - function viewConfigDepth(config) { - var context = config.viewDecl.$context, count = 0; - while (++count && context.parent) - context = context.parent; - return count; - } - // Given a depth function, returns a compare function which can return either ascending or descending order - var depthCompare = hof_1.curry(function (depthFn, posNeg, left, right) { return posNeg * (depthFn(left) - depthFn(right)); }); - var matchingConfigPair = function (uiView) { - var matchingConfigs = _this.viewConfigs.filter(matches(uiView)); - if (matchingConfigs.length > 1) - matchingConfigs.sort(depthCompare(viewConfigDepth, -1)); // descending - return [uiView, matchingConfigs[0]]; - }; - var configureUIView = function (_a) { - var uiView = _a[0], viewConfig = _a[1]; - // If a parent ui-view is reconfigured, it could destroy child ui-views. - // Before configuring a child ui-view, make sure it's still in the active uiViews array. - if (_this.uiViews.indexOf(uiView) !== -1) - uiView.configUpdated(viewConfig); - }; - _this.uiViews.sort(depthCompare(uiViewDepth, 1)).map(matchingConfigPair).forEach(configureUIView); - }; - } - ViewService.prototype.rootContext = function (context) { - return this._rootContext = context || this._rootContext; - }; - ; - ViewService.prototype.viewConfigFactory = function (viewType, factory) { - this._viewConfigFactories[viewType] = factory; - }; - ViewService.prototype.createViewConfig = function (path, decl) { - var cfgFactory = this._viewConfigFactories[decl.$type]; - if (!cfgFactory) - throw new Error("ViewService: No view config factory registered for type " + decl.$type); - var cfgs = cfgFactory(path, decl); - return predicates_1.isArray(cfgs) ? cfgs : [cfgs]; - }; - /** - * De-registers a ViewConfig. - * - * @param viewConfig The ViewConfig view to deregister. - */ - ViewService.prototype.deactivateViewConfig = function (viewConfig) { - trace_1.trace.traceViewServiceEvent("<- Removing", viewConfig); - common_1.removeFrom(this.viewConfigs, viewConfig); - }; - ; - ViewService.prototype.activateViewConfig = function (viewConfig) { - trace_1.trace.traceViewServiceEvent("-> Registering", viewConfig); - this.viewConfigs.push(viewConfig); - }; - ; - /** - * Allows a `ui-view` element to register its canonical name with a callback that allows it to - * be updated with a template, controller, and local variables. - * - * @param {String} name The fully-qualified name of the `ui-view` object being registered. - * @param {Function} configUpdatedCallback A callback that receives updates to the content & configuration - * of the view. - * @return {Function} Returns a de-registration function used when the view is destroyed. - */ - ViewService.prototype.registerUIView = function (uiView) { - trace_1.trace.traceViewServiceUIViewEvent("-> Registering", uiView); - var uiViews = this.uiViews; - var fqnMatches = function (uiv) { return uiv.fqn === uiView.fqn; }; - if (uiViews.filter(fqnMatches).length) - trace_1.trace.traceViewServiceUIViewEvent("!!!! duplicate uiView named:", uiView); - uiViews.push(uiView); - this.sync(); - return function () { - var idx = uiViews.indexOf(uiView); - if (idx === -1) { - trace_1.trace.traceViewServiceUIViewEvent("Tried removing non-registered uiView", uiView); - return; - } - trace_1.trace.traceViewServiceUIViewEvent("<- Deregistering", uiView); - common_1.removeFrom(uiViews)(uiView); - }; - }; - ; - /** - * Returns the list of views currently available on the page, by fully-qualified name. - * - * @return {Array} Returns an array of fully-qualified view names. - */ - ViewService.prototype.available = function () { - return this.uiViews.map(hof_1.prop("fqn")); - }; - /** - * Returns the list of views on the page containing loaded content. - * - * @return {Array} Returns an array of fully-qualified view names. - */ - ViewService.prototype.active = function () { - return this.uiViews.filter(hof_1.prop("$config")).map(hof_1.prop("name")); - }; - /** - * Normalizes a view's name from a state.views configuration block. - * - * @param context the context object (state declaration) that the view belongs to - * @param rawViewName the name of the view, as declared in the [[StateDeclaration.views]] - * - * @returns the normalized uiViewName and uiViewContextAnchor that the view targets - */ - ViewService.normalizeUIViewTarget = function (context, rawViewName) { - if (rawViewName === void 0) { rawViewName = ""; } - // TODO: Validate incoming view name with a regexp to allow: - // ex: "view.name@foo.bar" , "^.^.view.name" , "view.name@^.^" , "" , - // "@" , "$default@^" , "!$default.$default" , "!foo.bar" - var viewAtContext = rawViewName.split("@"); - var uiViewName = viewAtContext[0] || "$default"; // default to unnamed view - var uiViewContextAnchor = predicates_1.isString(viewAtContext[1]) ? viewAtContext[1] : "^"; // default to parent context - // Handle relative view-name sugar syntax. - // Matches rawViewName "^.^.^.foo.bar" into array: ["^.^.^.foo.bar", "^.^.^", "foo.bar"], - var relativeViewNameSugar = /^(\^(?:\.\^)*)\.(.*$)/.exec(uiViewName); - if (relativeViewNameSugar) { - // Clobbers existing contextAnchor (rawViewName validation will fix this) - uiViewContextAnchor = relativeViewNameSugar[1]; // set anchor to "^.^.^" - uiViewName = relativeViewNameSugar[2]; // set view-name to "foo.bar" - } - if (uiViewName.charAt(0) === '!') { - uiViewName = uiViewName.substr(1); - uiViewContextAnchor = ""; // target absolutely from root - } - // handle parent relative targeting "^.^.^" - var relativeMatch = /^(\^(?:\.\^)*)$/; - if (relativeMatch.exec(uiViewContextAnchor)) { - var anchor = uiViewContextAnchor.split(".").reduce((function (anchor, x) { return anchor.parent; }), context); - uiViewContextAnchor = anchor.name; - } - return { uiViewName: uiViewName, uiViewContextAnchor: uiViewContextAnchor }; - }; - return ViewService; - }()); - exports.ViewService = ViewService; - - -/***/ }, -/* 38 */ -/***/ function(module, exports, __webpack_require__) { - - /** @module state */ /** for typedoc */ - "use strict"; - var stateMatcher_1 = __webpack_require__(39); - var stateBuilder_1 = __webpack_require__(40); - var stateQueueManager_1 = __webpack_require__(41); - var common_1 = __webpack_require__(3); - var StateRegistry = (function () { - function StateRegistry(urlMatcherFactory, urlRouterProvider) { - this.urlRouterProvider = urlRouterProvider; - this.states = {}; - this.listeners = []; - this.matcher = new stateMatcher_1.StateMatcher(this.states); - this.builder = new stateBuilder_1.StateBuilder(this.matcher, urlMatcherFactory); - this.stateQueue = new stateQueueManager_1.StateQueueManager(this.states, this.builder, urlRouterProvider, this.listeners); - var rootStateDef = { - name: '', - url: '^', - views: null, - params: { - '#': { value: null, type: 'hash', dynamic: true } - }, - abstract: true - }; - var _root = this._root = this.stateQueue.register(rootStateDef); - _root.navigable = null; - } - /** - * Listen for a State Registry events - * - * Adds a callback that is invoked when states are registered or deregistered with the StateRegistry. - * - * @example - * ```js - * - * let allStates = registry.get(); - * - * // Later, invoke deregisterFn() to remove the listener - * let deregisterFn = registry.onStatesChanged((event, states) => { - * switch(event) { - * case: 'registered': - * states.forEach(state => allStates.push(state)); - * break; - * case: 'deregistered': - * states.forEach(state => { - * let idx = allStates.indexOf(state); - * if (idx !== -1) allStates.splice(idx, 1); - * }); - * break; - * } - * }); - * ``` - * - * @param listener a callback function invoked when the registered states changes. - * The function receives two parameters, `event` and `state`. - * See [[StateRegistryListener]] - * @return a function that deregisters the listener - */ - StateRegistry.prototype.onStatesChanged = function (listener) { - this.listeners.push(listener); - return function deregisterListener() { - common_1.removeFrom(this.listeners)(listener); - }.bind(this); - }; - /** - * Gets the implicit root state - * - * Gets the root of the state tree. - * The root state is implicitly created by UI-Router. - * Note: this returns the internal [[State]] representation, not a [[StateDeclaration]] - * - * @return the root [[State]] - */ - StateRegistry.prototype.root = function () { - return this._root; - }; - /** - * Adds a state to the registry - * - * Registers a [[StateDefinition]] or queues it for registration. - * - * Note: a state will be queued if the state's parent isn't yet registered. - * It will also be queued if the queue is not yet in [[StateQueueManager.autoFlush]] mode. - * - * @param stateDefinition the definition of the state to register. - * @returns the internal [[State]] object. - * If the state was successfully registered, then the object is fully built (See: [[StateBuilder]]). - * If the state was only queued, then the object is not fully built. - */ - StateRegistry.prototype.register = function (stateDefinition) { - return this.stateQueue.register(stateDefinition); - }; - /** @hidden */ - StateRegistry.prototype._deregisterTree = function (state) { - var _this = this; - var all = this.get().map(function (s) { return s.$$state(); }); - var getChildren = function (states) { - var children = all.filter(function (s) { return states.indexOf(s.parent) !== -1; }); - return children.length === 0 ? children : children.concat(getChildren(children)); - }; - var children = getChildren([state]); - var deregistered = [state].concat(children).reverse(); - deregistered.forEach(function (state) { - _this.urlRouterProvider.removeRule(state._urlRule); - delete _this.states[state.name]; - }); - return deregistered; - }; - /** - * Removes a state from the registry - * - * This removes a state from the registry. - * If the state has children, they are are also removed from the registry. - * - * @param stateOrName the state's name or object representation - * @returns {State[]} a list of removed states - */ - StateRegistry.prototype.deregister = function (stateOrName) { - var _state = this.get(stateOrName); - if (!_state) - throw new Error("Can't deregister state; not found: " + stateOrName); - var deregisteredStates = this._deregisterTree(_state.$$state()); - this.listeners.forEach(function (listener) { return listener("deregistered", deregisteredStates.map(function (s) { return s.self; })); }); - return deregisteredStates; - }; - StateRegistry.prototype.get = function (stateOrName, base) { - var _this = this; - if (arguments.length === 0) - return Object.keys(this.states).map(function (name) { return _this.states[name].self; }); - var found = this.matcher.find(stateOrName, base); - return found && found.self || null; - }; - StateRegistry.prototype.decorator = function (name, func) { - return this.builder.builder(name, func); - }; - return StateRegistry; - }()); - exports.StateRegistry = StateRegistry; - - -/***/ }, -/* 39 */ -/***/ function(module, exports, __webpack_require__) { - - "use strict"; - /** @module state */ /** for typedoc */ - var predicates_1 = __webpack_require__(4); - var glob_1 = __webpack_require__(7); - var common_1 = __webpack_require__(3); - var StateMatcher = (function () { - function StateMatcher(_states) { - this._states = _states; - } - StateMatcher.prototype.isRelative = function (stateName) { - stateName = stateName || ""; - return stateName.indexOf(".") === 0 || stateName.indexOf("^") === 0; - }; - StateMatcher.prototype.find = function (stateOrName, base) { - if (!stateOrName && stateOrName !== "") - return undefined; - var isStr = predicates_1.isString(stateOrName); - var name = isStr ? stateOrName : stateOrName.name; - if (this.isRelative(name)) - name = this.resolvePath(name, base); - var state = this._states[name]; - if (state && (isStr || (!isStr && (state === stateOrName || state.self === stateOrName)))) { - return state; - } - else if (isStr) { - var matches = common_1.values(this._states) - .filter(function (state) { return new glob_1.Glob(state.name).matches(name); }); - if (matches.length > 1) { - console.log("stateMatcher.find: Found multiple matches for " + name + " using glob: ", matches.map(function (match) { return match.name; })); - } - return matches[0]; - } - return undefined; - }; - StateMatcher.prototype.resolvePath = function (name, base) { - if (!base) - throw new Error("No reference point given for path '" + name + "'"); - var baseState = this.find(base); - var splitName = name.split("."), i = 0, pathLength = splitName.length, current = baseState; - for (; i < pathLength; i++) { - if (splitName[i] === "" && i === 0) { - current = baseState; - continue; - } - if (splitName[i] === "^") { - if (!current.parent) - throw new Error("Path '" + name + "' not valid for state '" + baseState.name + "'"); - current = current.parent; - continue; - } - break; - } - var relName = splitName.slice(i).join("."); - return current.name + (current.name && relName ? "." : "") + relName; - }; - return StateMatcher; - }()); - exports.StateMatcher = StateMatcher; - - -/***/ }, -/* 40 */ -/***/ function(module, exports, __webpack_require__) { - - "use strict"; - /** @module state */ /** for typedoc */ - var common_1 = __webpack_require__(3); - var predicates_1 = __webpack_require__(4); - var strings_1 = __webpack_require__(9); - var hof_1 = __webpack_require__(5); - var param_1 = __webpack_require__(22); - var resolvable_1 = __webpack_require__(19); - var coreservices_1 = __webpack_require__(6); - var parseUrl = function (url) { - if (!predicates_1.isString(url)) - return false; - var root = url.charAt(0) === '^'; - return { val: root ? url.substring(1) : url, root: root }; - }; - function nameBuilder(state) { - if (state.lazyLoad) - state.name = state.self.name + ".**"; - return state.name; - } - function selfBuilder(state) { - state.self.$$state = function () { return state; }; - return state.self; - } - function dataBuilder(state) { - if (state.parent && state.parent.data) { - state.data = state.self.data = common_1.inherit(state.parent.data, state.data); - } - return state.data; - } - var getUrlBuilder = function ($urlMatcherFactoryProvider, root) { - return function urlBuilder(state) { - var stateDec = state; - if (stateDec && stateDec.url && stateDec.lazyLoad) { - stateDec.url += "{remainder:any}"; // match any path (.*) - } - var parsed = parseUrl(stateDec.url), parent = state.parent; - var url = !parsed ? stateDec.url : $urlMatcherFactoryProvider.compile(parsed.val, { - params: state.params || {}, - paramMap: function (paramConfig, isSearch) { - if (stateDec.reloadOnSearch === false && isSearch) - paramConfig = common_1.extend(paramConfig || {}, { dynamic: true }); - return paramConfig; - } - }); - if (!url) - return null; - if (!$urlMatcherFactoryProvider.isMatcher(url)) - throw new Error("Invalid url '" + url + "' in state '" + state + "'"); - return (parsed && parsed.root) ? url : ((parent && parent.navigable) || root()).url.append(url); - }; - }; - var getNavigableBuilder = function (isRoot) { - return function navigableBuilder(state) { - return !isRoot(state) && state.url ? state : (state.parent ? state.parent.navigable : null); - }; - }; - var getParamsBuilder = function (paramTypes) { - return function paramsBuilder(state) { - var makeConfigParam = function (config, id) { return param_1.Param.fromConfig(id, null, config, paramTypes); }; - var urlParams = (state.url && state.url.parameters({ inherit: false })) || []; - var nonUrlParams = common_1.values(common_1.mapObj(common_1.omit(state.params || {}, urlParams.map(hof_1.prop('id'))), makeConfigParam)); - return urlParams.concat(nonUrlParams).map(function (p) { return [p.id, p]; }).reduce(common_1.applyPairs, {}); - }; - }; - function pathBuilder(state) { - return state.parent ? state.parent.path.concat(state) : [state]; - } - function includesBuilder(state) { - var includes = state.parent ? common_1.extend({}, state.parent.includes) : {}; - includes[state.name] = true; - return includes; - } - /** - * This is a [[StateBuilder.builder]] function for the `resolve:` block on a [[StateDeclaration]]. - * - * When the [[StateBuilder]] builds a [[State]] object from a raw [[StateDeclaration]], this builder - * validates the `resolve` property and converts it to a [[Resolvable]] array. - * - * resolve: input value can be: - * - * { - * // analyzed but not injected - * myFooResolve: function() { return "myFooData"; }, - * - * // function.toString() parsed, "DependencyName" dep as string (not min-safe) - * myBarResolve: function(DependencyName) { return DependencyName.fetchSomethingAsPromise() }, - * - * // Array split; "DependencyName" dep as string - * myBazResolve: [ "DependencyName", function(dep) { return dep.fetchSomethingAsPromise() }, - * - * // Array split; DependencyType dep as token (compared using ===) - * myQuxResolve: [ DependencyType, function(dep) { return dep.fetchSometingAsPromise() }, - * - * // val.$inject used as deps - * // where: - * // corgeResolve.$inject = ["DependencyName"]; - * // function corgeResolve(dep) { dep.fetchSometingAsPromise() } - * // then "DependencyName" dep as string - * myCorgeResolve: corgeResolve, - * - * // inject service by name - * // When a string is found, desugar creating a resolve that injects the named service - * myGraultResolve: "SomeService" - * } - * - * or: - * - * [ - * new Resolvable("myFooResolve", function() { return "myFooData" }), - * new Resolvable("myBarResolve", function(dep) { return dep.fetchSomethingAsPromise() }, [ "DependencyName" ]), - * { provide: "myBazResolve", useFactory: function(dep) { dep.fetchSomethingAsPromise() }, deps: [ "DependencyName" ] } - * ] - */ - function resolvablesBuilder(state) { - /** convert resolve: {} and resolvePolicy: {} objects to an array of tuples */ - var objects2Tuples = function (resolveObj, resolvePolicies) { - return Object.keys(resolveObj || {}).map(function (token) { return ({ token: token, val: resolveObj[token], deps: undefined, policy: resolvePolicies[token] }); }); - }; - /** fetch DI annotations from a function or ng1-style array */ - var annotate = function (fn) { - return fn['$inject'] || coreservices_1.services.$injector.annotate(fn, coreservices_1.services.$injector.strictDi); - }; - /** true if the object has both `token` and `resolveFn`, and is probably a [[ResolveLiteral]] */ - var isResolveLiteral = function (obj) { return !!(obj.token && obj.resolveFn); }; - /** true if the object looks like a provide literal, or a ng2 Provider */ - var isLikeNg2Provider = function (obj) { return !!((obj.provide || obj.token) && (obj.useValue || obj.useFactory || obj.useExisting || obj.useClass)); }; - /** true if the object looks like a tuple from obj2Tuples */ - var isTupleFromObj = function (obj) { return !!(obj && obj.val && (predicates_1.isString(obj.val) || predicates_1.isArray(obj.val) || predicates_1.isFunction(obj.val))); }; - /** extracts the token from a Provider or provide literal */ - var token = function (p) { return p.provide || p.token; }; - /** Given a literal resolve or provider object, returns a Resolvable */ - var literal2Resolvable = hof_1.pattern([ - [hof_1.prop('resolveFn'), function (p) { return new resolvable_1.Resolvable(token(p), p.resolveFn, p.deps, p.policy); }], - [hof_1.prop('useFactory'), function (p) { return new resolvable_1.Resolvable(token(p), p.useFactory, (p.deps || p.dependencies), p.policy); }], - [hof_1.prop('useClass'), function (p) { return new resolvable_1.Resolvable(token(p), function () { return new p.useClass(); }, [], p.policy); }], - [hof_1.prop('useValue'), function (p) { return new resolvable_1.Resolvable(token(p), function () { return p.useValue; }, [], p.policy, p.useValue); }], - [hof_1.prop('useExisting'), function (p) { return new resolvable_1.Resolvable(token(p), common_1.identity, [p.useExisting], p.policy); }], - ]); - var tuple2Resolvable = hof_1.pattern([ - [hof_1.pipe(hof_1.prop("val"), predicates_1.isString), function (tuple) { return new resolvable_1.Resolvable(tuple.token, common_1.identity, [tuple.val], tuple.policy); }], - [hof_1.pipe(hof_1.prop("val"), predicates_1.isArray), function (tuple) { return new resolvable_1.Resolvable(tuple.token, common_1.tail(tuple.val), tuple.val.slice(0, -1), tuple.policy); }], - [hof_1.pipe(hof_1.prop("val"), predicates_1.isFunction), function (tuple) { return new resolvable_1.Resolvable(tuple.token, tuple.val, annotate(tuple.val), tuple.policy); }], - ]); - var item2Resolvable = hof_1.pattern([ - [hof_1.is(resolvable_1.Resolvable), function (r) { return r; }], - [isResolveLiteral, literal2Resolvable], - [isLikeNg2Provider, literal2Resolvable], - [isTupleFromObj, tuple2Resolvable], - [hof_1.val(true), function (obj) { throw new Error("Invalid resolve value: " + strings_1.stringify(obj)); }] - ]); - // If resolveBlock is already an array, use it as-is. - // Otherwise, assume it's an object and convert to an Array of tuples - var decl = state.resolve; - var items = predicates_1.isArray(decl) ? decl : objects2Tuples(decl, state.resolvePolicy || {}); - return items.map(item2Resolvable); - } - exports.resolvablesBuilder = resolvablesBuilder; - /** - * @internalapi A internal global service - * - * StateBuilder is a factory for the internal [[State]] objects. - * - * When you register a state with the [[StateRegistry]], you register a plain old javascript object which - * conforms to the [[StateDeclaration]] interface. This factory takes that object and builds the corresponding - * [[State]] object, which has an API and is used internally. - * - * Custom properties or API may be added to the internal [[State]] object by registering a decorator function - * using the [[builder]] method. - */ - var StateBuilder = (function () { - function StateBuilder(matcher, $urlMatcherFactoryProvider) { - this.matcher = matcher; - var self = this; - var root = function () { return matcher.find(""); }; - var isRoot = function (state) { return state.name === ""; }; - function parentBuilder(state) { - if (isRoot(state)) - return null; - return matcher.find(self.parentName(state)) || root(); - } - this.builders = { - name: [nameBuilder], - self: [selfBuilder], - parent: [parentBuilder], - data: [dataBuilder], - // Build a URLMatcher if necessary, either via a relative or absolute URL - url: [getUrlBuilder($urlMatcherFactoryProvider, root)], - // Keep track of the closest ancestor state that has a URL (i.e. is navigable) - navigable: [getNavigableBuilder(isRoot)], - params: [getParamsBuilder($urlMatcherFactoryProvider.paramTypes)], - // Each framework-specific ui-router implementation should define its own `views` builder - // e.g., src/ng1/statebuilders/views.ts - views: [], - // Keep a full path from the root down to this state as this is needed for state activation. - path: [pathBuilder], - // Speed up $state.includes() as it's used a lot - includes: [includesBuilder], - resolvables: [resolvablesBuilder] - }; - } - /** - * Registers a [[BuilderFunction]] for a specific [[State]] property (e.g., `parent`, `url`, or `path`). - * More than one BuilderFunction can be registered for a given property. - * - * The BuilderFunction(s) will be used to define the property on any subsequently built [[State]] objects. - * - * @param name The name of the State property being registered for. - * @param fn The BuilderFunction which will be used to build the State property - * @returns a function which deregisters the BuilderFunction - */ - StateBuilder.prototype.builder = function (name, fn) { - var builders = this.builders; - var array = builders[name] || []; - // Backwards compat: if only one builder exists, return it, else return whole arary. - if (predicates_1.isString(name) && !predicates_1.isDefined(fn)) - return array.length > 1 ? array : array[0]; - if (!predicates_1.isString(name) || !predicates_1.isFunction(fn)) - return; - builders[name] = array; - builders[name].push(fn); - return function () { return builders[name].splice(builders[name].indexOf(fn, 1)) && null; }; - }; - /** - * Builds all of the properties on an essentially blank State object, returning a State object which has all its - * properties and API built. - * - * @param state an uninitialized State object - * @returns the built State object - */ - StateBuilder.prototype.build = function (state) { - var _a = this, matcher = _a.matcher, builders = _a.builders; - var parent = this.parentName(state); - if (parent && !matcher.find(parent)) - return null; - for (var key in builders) { - if (!builders.hasOwnProperty(key)) - continue; - var chain = builders[key].reduce(function (parentFn, step) { return function (_state) { return step(_state, parentFn); }; }, common_1.noop); - state[key] = chain(state); - } - return state; - }; - StateBuilder.prototype.parentName = function (state) { - var name = state.name || ""; - var segments = name.split('.'); - if (segments.length > 1) { - if (state.parent) { - throw new Error("States that specify the 'parent:' property should not have a '.' in their name (" + name + ")"); - } - var lastSegment = segments.pop(); - if (lastSegment === '**') - segments.pop(); - return segments.join("."); - } - if (!state.parent) - return ""; - return predicates_1.isString(state.parent) ? state.parent : state.parent.name; - }; - StateBuilder.prototype.name = function (state) { - var name = state.name; - if (name.indexOf('.') !== -1 || !state.parent) - return name; - var parentName = predicates_1.isString(state.parent) ? state.parent : state.parent.name; - return parentName ? parentName + "." + name : name; - }; - return StateBuilder; - }()); - exports.StateBuilder = StateBuilder; - - -/***/ }, -/* 41 */ -/***/ function(module, exports, __webpack_require__) { - - "use strict"; - /** @module state */ /** for typedoc */ - var common_1 = __webpack_require__(3); - var predicates_1 = __webpack_require__(4); - var stateObject_1 = __webpack_require__(42); - var StateQueueManager = (function () { - function StateQueueManager(states, builder, $urlRouterProvider, listeners) { - this.states = states; - this.builder = builder; - this.$urlRouterProvider = $urlRouterProvider; - this.listeners = listeners; - this.queue = []; - } - StateQueueManager.prototype.register = function (config) { - var _a = this, states = _a.states, queue = _a.queue, $state = _a.$state; - // Wrap a new object around the state so we can store our private details easily. - // @TODO: state = new State(extend({}, config, { ... })) - var state = common_1.inherit(new stateObject_1.State(), common_1.extend({}, config, { - self: config, - resolve: config.resolve || [], - toString: function () { return config.name; } - })); - if (!predicates_1.isString(state.name)) - throw new Error("State must have a valid name"); - if (states.hasOwnProperty(state.name) || common_1.pluck(queue, 'name').indexOf(state.name) !== -1) - throw new Error("State '" + state.name + "' is already defined"); - queue.push(state); - if (this.$state) { - this.flush($state); - } - return state; - }; - StateQueueManager.prototype.flush = function ($state) { - var _a = this, queue = _a.queue, states = _a.states, builder = _a.builder; - var registered = [], // states that got registered - orphans = [], // states that dodn't yet have a parent registered - previousQueueLength = {}; // keep track of how long the queue when an orphan was first encountered - while (queue.length > 0) { - var state = queue.shift(); - var result = builder.build(state); - var orphanIdx = orphans.indexOf(state); - if (result) { - if (states.hasOwnProperty(state.name)) - throw new Error("State '" + name + "' is already defined"); - states[state.name] = state; - this.attachRoute($state, state); - if (orphanIdx >= 0) - orphans.splice(orphanIdx, 1); - registered.push(state); - continue; - } - var prev = previousQueueLength[state.name]; - previousQueueLength[state.name] = queue.length; - if (orphanIdx >= 0 && prev === queue.length) { - // Wait until two consecutive iterations where no additional states were dequeued successfully. - // throw new Error(`Cannot register orphaned state '${state.name}'`); - queue.push(state); - return states; - } - else if (orphanIdx < 0) { - orphans.push(state); - } - queue.push(state); - } - if (registered.length) { - this.listeners.forEach(function (listener) { return listener("registered", registered.map(function (s) { return s.self; })); }); - } - return states; - }; - StateQueueManager.prototype.autoFlush = function ($state) { - this.$state = $state; - this.flush($state); - }; - StateQueueManager.prototype.attachRoute = function ($state, state) { - var $urlRouterProvider = this.$urlRouterProvider; - if (state.abstract || !state.url) - return; - $urlRouterProvider.when(state.url, ['$match', '$stateParams', function ($match, $stateParams) { - if ($state.$current.navigable !== state || !common_1.equalForKeys($match, $stateParams)) { - $state.transitionTo(state, $match, { inherit: true, source: "url" }); - } - }], function (rule) { return state._urlRule = rule; }); - }; - return StateQueueManager; - }()); - exports.StateQueueManager = StateQueueManager; - - -/***/ }, -/* 42 */ -/***/ function(module, exports, __webpack_require__) { - - /** @module state */ /** for typedoc */ - "use strict"; - var common_1 = __webpack_require__(3); - var hof_1 = __webpack_require__(5); - /** - * @ngdoc object - * @name ui.router.state.type:State - * - * @description - * Definition object for states. Includes methods for manipulating the state heirarchy. - * - * @param {Object} config A configuration object hash that includes the results of user-supplied - * values, as well as values from `StateBuilder`. - * - * @returns {Object} Returns a new `State` object. - */ - var State = (function () { - function State(config) { - common_1.extend(this, config); - // Object.freeze(this); - } - /** - * @ngdoc function - * @name ui.router.state.type:State#is - * @methodOf ui.router.state.type:State - * - * @description - * Compares the identity of the state against the passed value, which is either an object - * reference to the actual `State` instance, the original definition object passed to - * `$stateProvider.state()`, or the fully-qualified name. - * - * @param {Object} ref Can be one of (a) a `State` instance, (b) an object that was passed - * into `$stateProvider.state()`, (c) the fully-qualified name of a state as a string. - * @returns {boolean} Returns `true` if `ref` matches the current `State` instance. - */ - State.prototype.is = function (ref) { - return this === ref || this.self === ref || this.fqn() === ref; - }; - /** - * @ngdoc function - * @name ui.router.state.type:State#fqn - * @methodOf ui.router.state.type:State - * - * @description - * Returns the fully-qualified name of the state, based on its current position in the tree. - * - * @returns {string} Returns a dot-separated name of the state. - */ - State.prototype.fqn = function () { - if (!this.parent || !(this.parent instanceof this.constructor)) - return this.name; - var name = this.parent.fqn(); - return name ? name + "." + this.name : this.name; - }; - /** - * @ngdoc function - * @name ui.router.state.type:State#root - * @methodOf ui.router.state.type:State - * - * @description - * Returns the root node of this state's tree. - * - * @returns {State} The root of this state's tree. - */ - State.prototype.root = function () { - return this.parent && this.parent.root() || this; - }; - State.prototype.parameters = function (opts) { - opts = common_1.defaults(opts, { inherit: true }); - var inherited = opts.inherit && this.parent && this.parent.parameters() || []; - return inherited.concat(common_1.values(this.params)); - }; - State.prototype.parameter = function (id, opts) { - if (opts === void 0) { opts = {}; } - return (this.url && this.url.parameter(id, opts) || - common_1.find(common_1.values(this.params), hof_1.propEq('id', id)) || - opts.inherit && this.parent && this.parent.parameter(id)); - }; - State.prototype.toString = function () { - return this.fqn(); - }; - return State; - }()); - exports.State = State; - - -/***/ }, -/* 43 */ -/***/ function(module, exports, __webpack_require__) { - - "use strict"; - /** @module state */ /** */ - var common_1 = __webpack_require__(3); - var predicates_1 = __webpack_require__(4); - var queue_1 = __webpack_require__(8); - var coreservices_1 = __webpack_require__(6); - var pathFactory_1 = __webpack_require__(20); - var node_1 = __webpack_require__(21); - var transitionService_1 = __webpack_require__(30); - var rejectFactory_1 = __webpack_require__(10); - var targetState_1 = __webpack_require__(14); - var param_1 = __webpack_require__(22); - var glob_1 = __webpack_require__(7); - var common_2 = __webpack_require__(3); - var common_3 = __webpack_require__(3); - var resolveContext_1 = __webpack_require__(17); - var StateService = (function () { - /** @hidden */ - function StateService(router) { - this.router = router; - this.invalidCallbacks = []; - /** @hidden */ - this._defaultErrorHandler = function $defaultErrorHandler($error$) { - if ($error$ instanceof Error && $error$.stack) { - console.error($error$); - console.error($error$.stack); - } - else if ($error$ instanceof rejectFactory_1.Rejection) { - console.error($error$.toString()); - if ($error$.detail && $error$.detail.stack) - console.error($error$.detail.stack); - } - else { - console.error($error$); - } - }; - var getters = ['current', '$current', 'params', 'transition']; - var boundFns = Object.keys(StateService.prototype).filter(function (key) { return getters.indexOf(key) === -1; }); - common_3.bindFunctions(StateService.prototype, this, this, boundFns); - } - Object.defineProperty(StateService.prototype, "transition", { - get: function () { return this.router.globals.transition; }, - enumerable: true, - configurable: true - }); - Object.defineProperty(StateService.prototype, "params", { - get: function () { return this.router.globals.params; }, - enumerable: true, - configurable: true - }); - Object.defineProperty(StateService.prototype, "current", { - get: function () { return this.router.globals.current; }, - enumerable: true, - configurable: true - }); - Object.defineProperty(StateService.prototype, "$current", { - get: function () { return this.router.globals.$current; }, - enumerable: true, - configurable: true - }); - /** - * Handler for when [[transitionTo]] is called with an invalid state. - * - * Invokes the [[onInvalid]] callbacks, in natural order. - * Each callback's return value is checked in sequence until one of them returns an instance of TargetState. - * The results of the callbacks are wrapped in $q.when(), so the callbacks may return promises. - * - * If a callback returns an TargetState, then it is used as arguments to $state.transitionTo() and the result returned. - */ - StateService.prototype._handleInvalidTargetState = function (fromPath, toState) { - var _this = this; - var fromState = pathFactory_1.PathFactory.makeTargetState(fromPath); - var globals = this.router.globals; - var latestThing = function () { return globals.transitionHistory.peekTail(); }; - var latest = latestThing(); - var callbackQueue = new queue_1.Queue(this.invalidCallbacks.slice()); - var injector = new resolveContext_1.ResolveContext(fromPath).injector(); - var checkForRedirect = function (result) { - if (!(result instanceof targetState_1.TargetState)) { - return; - } - var target = result; - // Recreate the TargetState, in case the state is now defined. - target = _this.target(target.identifier(), target.params(), target.options()); - if (!target.valid()) - return rejectFactory_1.Rejection.invalid(target.error()).toPromise(); - if (latestThing() !== latest) - return rejectFactory_1.Rejection.superseded().toPromise(); - return _this.transitionTo(target.identifier(), target.params(), target.options()); - }; - function invokeNextCallback() { - var nextCallback = callbackQueue.dequeue(); - if (nextCallback === undefined) - return rejectFactory_1.Rejection.invalid(toState.error()).toPromise(); - var callbackResult = coreservices_1.services.$q.when(nextCallback(toState, fromState, injector)); - return callbackResult.then(checkForRedirect).then(function (result) { return result || invokeNextCallback(); }); - } - return invokeNextCallback(); - }; - /** - * Registers an Invalid State handler - * - * Registers a [[OnInvalidCallback]] function to be invoked when [[StateService.transitionTo]] - * has been called with an invalid state reference parameter - * - * Example: - * ```js - * stateService.onInvalid(function(to, from, injector) { - * if (to.name() === 'foo') { - * let lazyLoader = injector.get('LazyLoadService'); - * return lazyLoader.load('foo') - * .then(() => stateService.target('foo')); - * } - * }); - * ``` - * - * @param {function} callback invoked when the toState is invalid - * This function receives the (invalid) toState, the fromState, and an injector. - * The function may optionally return a [[TargetState]] or a Promise for a TargetState. - * If one is returned, it is treated as a redirect. - * - * @returns a function which deregisters the callback - */ - StateService.prototype.onInvalid = function (callback) { - this.invalidCallbacks.push(callback); - return function deregisterListener() { - common_1.removeFrom(this.invalidCallbacks)(callback); - }.bind(this); - }; - /** - * @ngdoc function - * @name ui.router.state.$state#reload - * @methodOf ui.router.state.$state - * - * @description - * A method that force reloads the current state, or a partial state hierarchy. All resolves are re-resolved, - * controllers reinstantiated, and events re-fired. - * - * @example - *
-	     * let app angular.module('app', ['ui.router']);
-	     *
-	     * app.controller('ctrl', function ($scope, $state) {
-	     *   $scope.reload = function(){
-	     *     $state.reload();
-	     *   }
-	     * });
-	     * 
- * - * `reload()` is just an alias for: - *
-	     * $state.transitionTo($state.current, $stateParams, {
-	     *   reload: true, inherit: false, notify: true
-	     * });
-	     * 
- * - * @param {string=|object=} reloadState - A state name or a state object, which is the root of the resolves to be re-resolved. - * @example - *
-	     * //assuming app application consists of 3 states: 'contacts', 'contacts.detail', 'contacts.detail.item'
-	     * //and current state is 'contacts.detail.item'
-	     * let app angular.module('app', ['ui.router']);
-	     *
-	     * app.controller('ctrl', function ($scope, $state) {
-	     *   $scope.reload = function(){
-	     *     //will reload 'contact.detail' and nested 'contact.detail.item' states
-	     *     $state.reload('contact.detail');
-	     *   }
-	     * });
-	     * 
- * - * @returns {promise} A promise representing the state of the new transition. See - * {@link ui.router.state.$state#methods_go $state.go}. - */ - StateService.prototype.reload = function (reloadState) { - return this.transitionTo(this.current, this.params, { - reload: predicates_1.isDefined(reloadState) ? reloadState : true, - inherit: false, - notify: false - }); - }; - ; - /** - * @ngdoc function - * @name ui.router.state.$state#go - * @methodOf ui.router.state.$state - * - * @description - * Convenience method for transitioning to a new state. `$state.go` calls - * `$state.transitionTo` internally but automatically sets options to - * `{ location: true, inherit: true, relative: $state.$current, notify: true }`. - * This allows you to easily use an absolute or relative to path and specify - * only the parameters you'd like to update (while letting unspecified parameters - * inherit from the currently active ancestor states). - * - * @example - *
-	     * let app = angular.module('app', ['ui.router']);
-	     *
-	     * app.controller('ctrl', function ($scope, $state) {
-	     *   $scope.changeState = function () {
-	     *     $state.go('contact.detail');
-	     *   };
-	     * });
-	     * 
- * - * - * @param {string|object} to Absolute state name, state object, or relative state path. Some examples: - * - * - `$state.go('contact.detail')` - will go to the `contact.detail` state - * - `$state.go('^')` - will go to a parent state - * - `$state.go('^.sibling')` - will go to a sibling state - * - `$state.go('.child.grandchild')` - will go to grandchild state - * - * @param {object=} params A map of the parameters that will be sent to the state, - * will populate $stateParams. Any parameters that are not specified will be inherited from currently - * defined parameters. This allows, for example, going to a sibling state that shares parameters - * specified in a parent state. Parameter inheritance only works between common ancestor states, I.e. - * transitioning to a sibling will get you the parameters for all parents, transitioning to a child - * will get you all current parameters, etc. - * @param {object=} options Options object. The options are: - * - * - **`location`** - {boolean=true|string=} - If `true` will update the url in the location bar, if `false` - * will not. If string, must be `"replace"`, which will update url and also replace last history record. - * - **`inherit`** - {boolean=true}, If `true` will inherit url parameters from current url. - * - **`relative`** - {object=$state.$current}, When transitioning with relative path (e.g '^'), - * defines which state to be relative from. - * - **`notify`** - {boolean=true}, If `true` will broadcast $stateChangeStart and $stateChangeSuccess events. - * - **`reload`** (v0.2.5) - {boolean=false}, If `true` will force transition even if the state or params - * have not changed, aka a reload of the same state. It differs from reloadOnSearch because you'd - * use this when you want to force a reload when *everything* is the same, including search params. - * - * @returns {promise} A promise representing the state of the new transition. - * - * Possible success values: - * - * - $state.current - * - *
Possible rejection values: - * - * - 'transition superseded' - when a newer transition has been started after this one - * - 'transition prevented' - when `event.preventDefault()` has been called in a `$stateChangeStart` listener - * - 'transition aborted' - when `event.preventDefault()` has been called in a `$stateNotFound` listener or - * when a `$stateNotFound` `event.retry` promise errors. - * - 'transition failed' - when a state has been unsuccessfully found after 2 tries. - * - *resolve error* - when an error has occurred with a `resolve` - * - */ - StateService.prototype.go = function (to, params, options) { - var defautGoOpts = { relative: this.$current, inherit: true }; - var transOpts = common_1.defaults(options, defautGoOpts, transitionService_1.defaultTransOpts); - return this.transitionTo(to, params, transOpts); - }; - ; - /** Factory method for creating a TargetState */ - StateService.prototype.target = function (identifier, params, options) { - if (options === void 0) { options = {}; } - // If we're reloading, find the state object to reload from - if (predicates_1.isObject(options.reload) && !options.reload.name) - throw new Error('Invalid reload state object'); - var reg = this.router.stateRegistry; - options.reloadState = options.reload === true ? reg.root() : reg.matcher.find(options.reload, options.relative); - if (options.reload && !options.reloadState) - throw new Error("No such reload state '" + (predicates_1.isString(options.reload) ? options.reload : options.reload.name) + "'"); - var stateDefinition = reg.matcher.find(identifier, options.relative); - return new targetState_1.TargetState(identifier, stateDefinition, params, options); - }; - ; - /** - * @ngdoc function - * @name ui.router.state.$state#transitionTo - * @methodOf ui.router.state.$state - * - * @description - * Low-level method for transitioning to a new state. {@link ui.router.state.$state#methods_go $state.go} - * uses `transitionTo` internally. `$state.go` is recommended in most situations. - * - * @example - *
-	     * let app = angular.module('app', ['ui.router']);
-	     *
-	     * app.controller('ctrl', function ($scope, $state) {
-	     *   $scope.changeState = function () {
-	     *     $state.transitionTo('contact.detail');
-	     *   };
-	     * });
-	     * 
- * - * @param {string|object} to State name or state object. - * @param {object=} toParams A map of the parameters that will be sent to the state, - * will populate $stateParams. - * @param {object=} options Options object. The options are: - * - * - **`location`** - {boolean=true|string=} - If `true` will update the url in the location bar, if `false` - * will not. If string, must be `"replace"`, which will update url and also replace last history record. - * - **`inherit`** - {boolean=false}, If `true` will inherit url parameters from current url. - * - **`relative`** - {object=}, When transitioning with relative path (e.g '^'), - * defines which state to be relative from. - * - **`notify`** - {boolean=true}, If `true` will broadcast $stateChangeStart and $stateChangeSuccess events. - * - **`reload`** (v0.2.5) - {boolean=false}, If `true` will force transition even if the state or params - * have not changed, aka a reload of the same state. It differs from reloadOnSearch because you'd - * use this when you want to force a reload when *everything* is the same, including search params. - * - * @returns {promise} A promise representing the state of the new transition. See - * {@link ui.router.state.$state#methods_go $state.go}. - */ - StateService.prototype.transitionTo = function (to, toParams, options) { - var _this = this; - if (toParams === void 0) { toParams = {}; } - if (options === void 0) { options = {}; } - var router = this.router; - var globals = router.globals; - var transHistory = globals.transitionHistory; - options = common_1.defaults(options, transitionService_1.defaultTransOpts); - options = common_1.extend(options, { current: transHistory.peekTail.bind(transHistory) }); - var ref = this.target(to, toParams, options); - var latestSuccess = globals.successfulTransitions.peekTail(); - var rootPath = function () { return [new node_1.PathNode(_this.router.stateRegistry.root())]; }; - var currentPath = latestSuccess ? latestSuccess.treeChanges().to : rootPath(); - if (!ref.exists()) - return this._handleInvalidTargetState(currentPath, ref); - if (!ref.valid()) - return common_1.silentRejection(ref.error()); - /** - * Special handling for Ignored, Aborted, and Redirected transitions - * - * The semantics for the transition.run() promise and the StateService.transitionTo() - * promise differ. For instance, the run() promise may be rejected because it was - * IGNORED, but the transitionTo() promise is resolved because from the user perspective - * no error occurred. Likewise, the transition.run() promise may be rejected because of - * a Redirect, but the transitionTo() promise is chained to the new Transition's promise. - */ - var rejectedTransitionHandler = function (transition) { return function (error) { - if (error instanceof rejectFactory_1.Rejection) { - if (error.type === rejectFactory_1.RejectType.IGNORED) { - // Consider ignored `Transition.run()` as a successful `transitionTo` - router.urlRouter.update(); - return coreservices_1.services.$q.when(globals.current); - } - var detail = error.detail; - if (error.type === rejectFactory_1.RejectType.SUPERSEDED && error.redirected && detail instanceof targetState_1.TargetState) { - // If `Transition.run()` was redirected, allow the `transitionTo()` promise to resolve successfully - // by returning the promise for the new (redirect) `Transition.run()`. - var redirect = transition.redirect(detail); - return redirect.run().catch(rejectedTransitionHandler(redirect)); - } - if (error.type === rejectFactory_1.RejectType.ABORTED) { - router.urlRouter.update(); - } - } - var errorHandler = _this.defaultErrorHandler(); - errorHandler(error); - return coreservices_1.services.$q.reject(error); - }; }; - var transition = this.router.transitionService.create(currentPath, ref); - var transitionToPromise = transition.run().catch(rejectedTransitionHandler(transition)); - common_1.silenceUncaughtInPromise(transitionToPromise); // issue #2676 - // Return a promise for the transition, which also has the transition object on it. - return common_1.extend(transitionToPromise, { transition: transition }); - }; - ; - /** - * @ngdoc function - * @name ui.router.state.$state#is - * @methodOf ui.router.state.$state - * - * @description - * Similar to {@link ui.router.state.$state#methods_includes $state.includes}, - * but only checks for the full state name. If params is supplied then it will be - * tested for strict equality against the current active params object, so all params - * must match with none missing and no extras. - * - * @example - *
-	     * $state.$current.name = 'contacts.details.item';
-	     *
-	     * // absolute name
-	     * $state.is('contact.details.item'); // returns true
-	     * $state.is(contactDetailItemStateObject); // returns true
-	     *
-	     * // relative name (. and ^), typically from a template
-	     * // E.g. from the 'contacts.details' template
-	     * 
Item
- *
- * - * @param {string|object} stateOrName The state name (absolute or relative) or state object you'd like to check. - * @param {object=} params A param object, e.g. `{sectionId: section.id}`, that you'd like - * to test against the current active state. - * @param {object=} options An options object. The options are: - * - * - **`relative`** - {string|object} - If `stateOrName` is a relative state name and `options.relative` is set, .is will - * test relative to `options.relative` state (or name). - * - * @returns {boolean} Returns true if it is the state. - */ - StateService.prototype.is = function (stateOrName, params, options) { - options = common_1.defaults(options, { relative: this.$current }); - var state = this.router.stateRegistry.matcher.find(stateOrName, options.relative); - if (!predicates_1.isDefined(state)) - return undefined; - if (this.$current !== state) - return false; - return predicates_1.isDefined(params) && params !== null ? param_1.Param.equals(state.parameters(), this.params, params) : true; - }; - ; - /** - * @ngdoc function - * @name ui.router.state.$state#includes - * @methodOf ui.router.state.$state - * - * @description - * A method to determine if the current active state is equal to or is the child of the - * state stateName. If any params are passed then they will be tested for a match as well. - * Not all the parameters need to be passed, just the ones you'd like to test for equality. - * - * @example - * Partial and relative names - *
-	     * $state.$current.name = 'contacts.details.item';
-	     *
-	     * // Using partial names
-	     * $state.includes("contacts"); // returns true
-	     * $state.includes("contacts.details"); // returns true
-	     * $state.includes("contacts.details.item"); // returns true
-	     * $state.includes("contacts.list"); // returns false
-	     * $state.includes("about"); // returns false
-	     *
-	     * // Using relative names (. and ^), typically from a template
-	     * // E.g. from the 'contacts.details' template
-	     * 
Item
- *
- * - * Basic globbing patterns - *
-	     * $state.$current.name = 'contacts.details.item.url';
-	     *
-	     * $state.includes("*.details.*.*"); // returns true
-	     * $state.includes("*.details.**"); // returns true
-	     * $state.includes("**.item.**"); // returns true
-	     * $state.includes("*.details.item.url"); // returns true
-	     * $state.includes("*.details.*.url"); // returns true
-	     * $state.includes("*.details.*"); // returns false
-	     * $state.includes("item.**"); // returns false
-	     * 
- * - * @param {string|object} stateOrName A partial name, relative name, glob pattern, - * or state object to be searched for within the current state name. - * @param {object=} params A param object, e.g. `{sectionId: section.id}`, - * that you'd like to test against the current active state. - * @param {object=} options An options object. The options are: - * - * - **`relative`** - {string|object=} - If `stateOrName` is a relative state reference and `options.relative` is set, - * .includes will test relative to `options.relative` state (or name). - * - * @returns {boolean} Returns true if it does include the state - */ - StateService.prototype.includes = function (stateOrName, params, options) { - options = common_1.defaults(options, { relative: this.$current }); - var glob = predicates_1.isString(stateOrName) && glob_1.Glob.fromString(stateOrName); - if (glob) { - if (!glob.matches(this.$current.name)) - return false; - stateOrName = this.$current.name; - } - var state = this.router.stateRegistry.matcher.find(stateOrName, options.relative), include = this.$current.includes; - if (!predicates_1.isDefined(state)) - return undefined; - if (!predicates_1.isDefined(include[state.name])) - return false; - // @TODO Replace with Param.equals() ? - return params ? common_2.equalForKeys(param_1.Param.values(state.parameters(), params), this.params, Object.keys(params)) : true; - }; - ; - /** - * @ngdoc function - * @name ui.router.state.$state#href - * @methodOf ui.router.state.$state - * - * @description - * A url generation method that returns the compiled url for the given state populated with the given params. - * - * @example - *
-	     * expect($state.href("about.person", { person: "bob" })).toEqual("/about/bob");
-	     * 
- * - * @param {string|object} stateOrName The state name or state object you'd like to generate a url from. - * @param {object=} params An object of parameter values to fill the state's required parameters. - * @param {object=} options Options object. The options are: - * - * - **`lossy`** - {boolean=true} - If true, and if there is no url associated with the state provided in the - * first parameter, then the constructed href url will be built from the first navigable ancestor (aka - * ancestor with a valid url). - * - **`inherit`** - {boolean=true}, If `true` will inherit url parameters from current url. - * - **`relative`** - {object=$state.$current}, When transitioning with relative path (e.g '^'), - * defines which state to be relative from. - * - **`absolute`** - {boolean=false}, If true will generate an absolute url, e.g. "http://www.example.com/fullurl". - * - * @returns {string} compiled state url - */ - StateService.prototype.href = function (stateOrName, params, options) { - var defaultHrefOpts = { - lossy: true, - inherit: true, - absolute: false, - relative: this.$current - }; - options = common_1.defaults(options, defaultHrefOpts); - params = params || {}; - var state = this.router.stateRegistry.matcher.find(stateOrName, options.relative); - if (!predicates_1.isDefined(state)) - return null; - if (options.inherit) - params = this.params.$inherit(params, this.$current, state); - var nav = (state && options.lossy) ? state.navigable : state; - if (!nav || nav.url === undefined || nav.url === null) { - return null; - } - return this.router.urlRouter.href(nav.url, param_1.Param.values(state.parameters(), params), { - absolute: options.absolute - }); - }; - ; - /** - * Sets or gets the default [[transitionTo]] error handler. - * - * The error handler is called when a [[Transition]] is rejected or when any error occurred during the Transition. - * This includes errors caused by resolves and transition hooks. - * - * Note: - * This handler does not receive certain Transition rejections. - * Redirected and Ignored Transitions are not considered to be errors by [[StateService.transitionTo]]. - * - * The built-in default error handler logs the error to the console. - * - * You can provide your own custom handler. - * - * @example - * ```js - * - * stateService.defaultErrorHandler(function() { - * // Do not log transitionTo errors - * }); - * ``` - * - * @param handler a global error handler function - * @returns the current global error handler - */ - StateService.prototype.defaultErrorHandler = function (handler) { - return this._defaultErrorHandler = handler || this._defaultErrorHandler; - }; - StateService.prototype.get = function (stateOrName, base) { - var reg = this.router.stateRegistry; - if (arguments.length === 0) - return reg.get(); - return reg.get(stateOrName, base || this.$current); - }; - return StateService; - }()); - exports.StateService = StateService; - - -/***/ }, -/* 44 */ -/***/ function(module, exports, __webpack_require__) { - - "use strict"; - /** @module core */ /** */ - var stateParams_1 = __webpack_require__(45); - var queue_1 = __webpack_require__(8); - var common_1 = __webpack_require__(3); - /** - * Global mutable state - */ - var Globals = (function () { - function Globals(transitionService) { - var _this = this; - this.params = new stateParams_1.StateParams(); - this.transitionHistory = new queue_1.Queue([], 1); - this.successfulTransitions = new queue_1.Queue([], 1); - var beforeNewTransition = function ($transition$) { - _this.transition = $transition$; - _this.transitionHistory.enqueue($transition$); - var updateGlobalState = function () { - _this.successfulTransitions.enqueue($transition$); - _this.$current = $transition$.$to(); - _this.current = _this.$current.self; - common_1.copy($transition$.params(), _this.params); - }; - $transition$.onSuccess({}, updateGlobalState, { priority: 10000 }); - var clearCurrentTransition = function () { if (_this.transition === $transition$) - _this.transition = null; }; - $transition$.promise.then(clearCurrentTransition, clearCurrentTransition); - }; - transitionService.onBefore({}, beforeNewTransition); - } - return Globals; - }()); - exports.Globals = Globals; - - -/***/ }, -/* 45 */ -/***/ function(module, exports, __webpack_require__) { - - "use strict"; - /** @module params */ /** for typedoc */ - var common_1 = __webpack_require__(3); - var StateParams = (function () { - function StateParams(params) { - if (params === void 0) { params = {}; } - common_1.extend(this, params); - } - /** - * Merges a set of parameters with all parameters inherited between the common parents of the - * current state and a given destination state. - * - * @param {Object} newParams The set of parameters which will be composited with inherited params. - * @param {Object} $current Internal definition of object representing the current state. - * @param {Object} $to Internal definition of object representing state to transition to. - */ - StateParams.prototype.$inherit = function (newParams, $current, $to) { - var parents = common_1.ancestors($current, $to), parentParams, inherited = {}, inheritList = []; - for (var i in parents) { - if (!parents[i] || !parents[i].params) - continue; - parentParams = Object.keys(parents[i].params); - if (!parentParams.length) - continue; - for (var j in parentParams) { - if (inheritList.indexOf(parentParams[j]) >= 0) - continue; - inheritList.push(parentParams[j]); - inherited[parentParams[j]] = this[parentParams[j]]; - } - } - return common_1.extend({}, inherited, newParams); - }; - ; - return StateParams; - }()); - exports.StateParams = StateParams; - - -/***/ }, -/* 46 */ -/***/ function(module, exports, __webpack_require__) { - - "use strict"; - function __export(m) { - for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p]; - } - /** - * This module contains code for State Parameters. - * - * See [[ParamDeclaration]] - * @module params - * @preferred doc - */ - /** for typedoc */ - __export(__webpack_require__(22)); - __export(__webpack_require__(28)); - __export(__webpack_require__(45)); - __export(__webpack_require__(24)); - - -/***/ }, -/* 47 */ -/***/ function(module, exports, __webpack_require__) { - - "use strict"; - function __export(m) { - for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p]; - } - /** @module path */ /** for typedoc */ - __export(__webpack_require__(21)); - __export(__webpack_require__(20)); - - -/***/ }, -/* 48 */ -/***/ function(module, exports, __webpack_require__) { - - "use strict"; - function __export(m) { - for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p]; - } - /** @module resolve */ /** for typedoc */ - __export(__webpack_require__(18)); - __export(__webpack_require__(19)); - __export(__webpack_require__(17)); - - -/***/ }, -/* 49 */ -/***/ function(module, exports, __webpack_require__) { - - "use strict"; - function __export(m) { - for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p]; - } - /** @module state */ /** for typedoc */ - __export(__webpack_require__(40)); - __export(__webpack_require__(42)); - __export(__webpack_require__(39)); - __export(__webpack_require__(41)); - __export(__webpack_require__(38)); - __export(__webpack_require__(43)); - __export(__webpack_require__(14)); - - -/***/ }, -/* 50 */ -/***/ function(module, exports, __webpack_require__) { - - "use strict"; - function __export(m) { - for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p]; - } - /** - * This module contains APIs related to a Transition. - * - * See [[Transition]], [[$transitions]] - * - * @module transition - * @preferred - */ - /** for typedoc */ - __export(__webpack_require__(16)); - __export(__webpack_require__(15)); - __export(__webpack_require__(10)); - __export(__webpack_require__(11)); - __export(__webpack_require__(13)); - __export(__webpack_require__(30)); - - -/***/ }, -/* 51 */ -/***/ function(module, exports, __webpack_require__) { - - "use strict"; - function __export(m) { - for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p]; - } - /** @module url */ /** for typedoc */ - __export(__webpack_require__(27)); - __export(__webpack_require__(23)); - __export(__webpack_require__(26)); - __export(__webpack_require__(29)); - - -/***/ }, -/* 52 */ -/***/ function(module, exports, __webpack_require__) { - - "use strict"; - function __export(m) { - for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p]; - } - /** @module view */ /** for typedoc */ - __export(__webpack_require__(37)); - - -/***/ }, -/* 53 */ -/***/ function(module, exports, __webpack_require__) { - - /** - * # UI-Router for Angular 1 - * - * - Provides an implementation for the [[CoreServices]] API, based on angular 1 services. - * - Also registers some services with the angular 1 injector. - * - Creates and bootstraps a new [[UIRouter]] object. Ties it to the the angular 1 lifecycle. - * - * @module ng1 - * @preferred - */ - "use strict"; - /** for typedoc */ - var router_1 = __webpack_require__(25); - var coreservices_1 = __webpack_require__(6); - var common_1 = __webpack_require__(3); - var hof_1 = __webpack_require__(5); - var predicates_1 = __webpack_require__(4); - var resolveService_1 = __webpack_require__(54); - var trace_1 = __webpack_require__(12); - var views_1 = __webpack_require__(55); - var templateFactory_1 = __webpack_require__(56); - var stateProvider_1 = __webpack_require__(58); - var onEnterExitRetain_1 = __webpack_require__(59); - var angular = __webpack_require__(57); - /** @hidden */ - var app = angular.module("ui.router.angular1", []); - /** - * @ngdoc overview - * @name ui.router.util - * - * @description - * # ui.router.util sub-module - * - * This module is a dependency of other sub-modules. Do not include this module as a dependency - * in your angular app (use {@link ui.router} module instead). - * - */ - angular.module('ui.router.util', ['ng', 'ui.router.init']); - /** - * @ngdoc overview - * @name ui.router.router - * - * @requires ui.router.util - * - * @description - * # ui.router.router sub-module - * - * This module is a dependency of other sub-modules. Do not include this module as a dependency - * in your angular app (use {@link ui.router} module instead). - */ - angular.module('ui.router.router', ['ui.router.util']); - /** - * @ngdoc overview - * @name ui.router.state - * - * @requires ui.router.router - * @requires ui.router.util - * - * @description - * # ui.router.state sub-module - * - * This module is a dependency of the main ui.router module. Do not include this module as a dependency - * in your angular app (use {@link ui.router} module instead). - * - */ - angular.module('ui.router.state', ['ui.router.router', 'ui.router.util', 'ui.router.angular1']); - /** - * @ngdoc overview - * @name ui.router - * - * @requires ui.router.state - * - * @description - * # ui.router - * - * ## The main module for ui.router - * There are several sub-modules included with the ui.router module, however only this module is needed - * as a dependency within your angular app. The other modules are for organization purposes. - * - * The modules are: - * * ui.router - the main "umbrella" module - * * ui.router.router - - * - * *You'll need to include **only** this module as the dependency within your angular app.* - * - *
-	 * 
-	 * 
-	 * 
-	 *   
-	 *   
-	 *   
-	 *   
-	 * 
-	 * 
-	 * 
-	 * 
-	 * 
- */ - angular.module('ui.router', ['ui.router.init', 'ui.router.state', 'ui.router.angular1']); - angular.module('ui.router.compat', ['ui.router']); - /** - * Annotates a controller expression (may be a controller function(), a "controllername", - * or "controllername as name") - * - * - Temporarily decorates $injector.instantiate. - * - Invokes $controller() service - * - Calls $injector.instantiate with controller constructor - * - Annotate constructor - * - Undecorate $injector - * - * returns an array of strings, which are the arguments of the controller expression - */ - function annotateController(controllerExpression) { - var $injector = coreservices_1.services.$injector; - var $controller = $injector.get("$controller"); - var oldInstantiate = $injector.instantiate; - try { - var deps_1; - $injector.instantiate = function fakeInstantiate(constructorFunction) { - $injector.instantiate = oldInstantiate; // Un-decorate ASAP - deps_1 = $injector.annotate(constructorFunction); - }; - $controller(controllerExpression, { $scope: {} }); - return deps_1; - } - finally { - $injector.instantiate = oldInstantiate; - } - } - exports.annotateController = annotateController; - var router = null; - $uiRouter.$inject = ['$locationProvider']; - /** This angular 1 provider instantiates a Router and exposes its services via the angular injector */ - function $uiRouter($locationProvider) { - // Create a new instance of the Router when the $uiRouterProvider is initialized - router = new router_1.UIRouter(); - router.stateProvider = new stateProvider_1.StateProvider(router.stateRegistry, router.stateService); - // Apply ng1 specific StateBuilder code for `views`, `resolve`, and `onExit/Retain/Enter` properties - router.stateRegistry.decorator("views", views_1.ng1ViewsBuilder); - router.stateRegistry.decorator("onExit", onEnterExitRetain_1.getStateHookBuilder("onExit")); - router.stateRegistry.decorator("onRetain", onEnterExitRetain_1.getStateHookBuilder("onRetain")); - router.stateRegistry.decorator("onEnter", onEnterExitRetain_1.getStateHookBuilder("onEnter")); - router.viewService.viewConfigFactory('ng1', views_1.ng1ViewConfigFactory); - // Bind LocationConfig.hashPrefix to $locationProvider.hashPrefix - common_1.bindFunctions($locationProvider, coreservices_1.services.locationConfig, $locationProvider, ['hashPrefix']); - // Create a LocationService.onChange registry - var urlListeners = []; - coreservices_1.services.location.onChange = function (callback) { - urlListeners.push(callback); - return function () { return common_1.removeFrom(urlListeners)(callback); }; - }; - this.$get = $get; - $get.$inject = ['$location', '$browser', '$sniffer', '$rootScope', '$http', '$templateCache']; - function $get($location, $browser, $sniffer, $rootScope, $http, $templateCache) { - // Bind $locationChangeSuccess to the listeners registered in LocationService.onChange - $rootScope.$on("$locationChangeSuccess", function (evt) { return urlListeners.forEach(function (fn) { return fn(evt); }); }); - // Bind LocationConfig.html5Mode to $locationProvider.html5Mode and $sniffer.history - coreservices_1.services.locationConfig.html5Mode = function () { - var html5Mode = $locationProvider.html5Mode(); - html5Mode = predicates_1.isObject(html5Mode) ? html5Mode.enabled : html5Mode; - return html5Mode && $sniffer.history; - }; - coreservices_1.services.location.setUrl = function (newUrl, replace) { - if (replace === void 0) { replace = false; } - $location.url(newUrl); - if (replace) - $location.replace(); - }; - coreservices_1.services.template.get = function (url) { - return $http.get(url, { cache: $templateCache, headers: { Accept: 'text/html' } }).then(hof_1.prop("data")); - }; - // Bind these LocationService functions to $location - common_1.bindFunctions($location, coreservices_1.services.location, $location, ["replace", "url", "path", "search", "hash"]); - // Bind these LocationConfig functions to $location - common_1.bindFunctions($location, coreservices_1.services.locationConfig, $location, ['port', 'protocol', 'host']); - // Bind these LocationConfig functions to $browser - common_1.bindFunctions($browser, coreservices_1.services.locationConfig, $browser, ['baseHref']); - return router; - } - } - // The 'ui.router' ng1 module depends on 'ui.router.init' module. - angular.module('ui.router.init', []).provider("$uiRouter", $uiRouter); - runBlock.$inject = ['$injector', '$q']; - function runBlock($injector, $q) { - coreservices_1.services.$injector = $injector; - coreservices_1.services.$q = $q; - } - angular.module('ui.router.init').run(runBlock); - // This effectively calls $get() to init when we enter runtime - angular.module('ui.router.init').run(['$uiRouter', function ($uiRouter) { }]); - // $urlMatcherFactory service and $urlMatcherFactoryProvider - angular.module('ui.router.util').provider('$urlMatcherFactory', ['$uiRouterProvider', function () { return router.urlMatcherFactory; }]); - angular.module('ui.router.util').run(['$urlMatcherFactory', function ($urlMatcherFactory) { }]); - // $urlRouter service and $urlRouterProvider - function getUrlRouterProvider() { - router.urlRouterProvider["$get"] = function () { - router.urlRouter.update(true); - if (!this.interceptDeferred) - router.urlRouter.listen(); - return router.urlRouter; - }; - return router.urlRouterProvider; - } - angular.module('ui.router.router').provider('$urlRouter', ['$uiRouterProvider', getUrlRouterProvider]); - angular.module('ui.router.router').run(['$urlRouter', function ($urlRouter) { }]); - // $state service and $stateProvider - // $urlRouter service and $urlRouterProvider - function getStateProvider() { - router.stateProvider["$get"] = function () { - // Autoflush once we are in runtime - router.stateRegistry.stateQueue.autoFlush(router.stateService); - return router.stateService; - }; - return router.stateProvider; - } - angular.module('ui.router.state').provider('$state', ['$uiRouterProvider', getStateProvider]); - angular.module('ui.router.state').run(['$state', function ($state) { }]); - // $stateParams service - angular.module('ui.router.state').factory('$stateParams', ['$uiRouter', function ($uiRouter) { - return $uiRouter.globals.params; - }]); - // $transitions service and $transitionsProvider - function getTransitionsProvider() { - router.transitionService["$get"] = function () { return router.transitionService; }; - return router.transitionService; - } - angular.module('ui.router.state').provider('$transitions', ['$uiRouterProvider', getTransitionsProvider]); - // $templateFactory service - angular.module('ui.router.util').factory('$templateFactory', ['$uiRouter', function () { return new templateFactory_1.TemplateFactory(); }]); - // The $view service - angular.module('ui.router').factory('$view', function () { return router.viewService; }); - // The old $resolve service - angular.module('ui.router').factory('$resolve', resolveService_1.resolveFactory); - // $trace service - angular.module("ui.router").service("$trace", function () { return trace_1.trace; }); - watchDigests.$inject = ['$rootScope']; - function watchDigests($rootScope) { - $rootScope.$watch(function () { trace_1.trace.approximateDigests++; }); - } - exports.watchDigests = watchDigests; - angular.module("ui.router").run(watchDigests); - exports.getLocals = function (ctx) { - var tokens = ctx.getTokens().filter(predicates_1.isString); - var tuples = tokens.map(function (key) { return [key, ctx.getResolvable(key).data]; }); - return tuples.reduce(common_1.applyPairs, {}); - }; - /** Injectable services */ - /** - * An injectable service object which has the current state parameters - * - * This angular service (singleton object) holds the current state parameters. - * The values in `$stateParams` are not updated until *after* a [[Transition]] successfully completes. - * - * This object can be injected into other services. - * - * @example - * ```js - * - * SomeService.$inject = ['$http', '$stateParams']; - * function SomeService($http, $stateParams) { - * return { - * getUser: function() { - * return $http.get('/api/users/' + $stateParams.username); - * } - * } - * }; - * angular.service('SomeService', SomeService); - * ``` - * - * ### Deprecation warning: - * - * When `$stateParams` is injected into transition hooks, resolves and view controllers, they receive a different - * object than this global service object. In those cases, the injected object has the parameter values for the - * *pending* Transition. - * - * Because of these confusing details, this service is deprecated. - * - * @deprecated Instead of using `$stateParams, inject the current [[Transition]] as `$transition$` and use [[Transition.params]] - * ```js - * MyController.$inject = ['$transition$']; - * function MyController($transition$) { - * var username = $transition$.params().username; - * // .. do something with username - * } - * ``` - */ - var $stateParams; - /** - * An injectable service primarily used to register transition hooks - * - * This angular service exposes the [[TransitionService]] singleton, which is primarily used to add transition hooks. - * - * The same object is also exposed as [[$transitionsProvider]] for injection during angular config time. - */ - var $transitions; - /** - * A config-time injectable provider primarily used to register transition hooks - * - * This angular provider exposes the [[TransitionService]] singleton, which is primarily used to add transition hooks. - * - * The same object is also exposed as [[$transitions]] for injection at runtime. - */ - var $transitionsProvider; - /** - * An injectable service used to query for current state information. - * - * This angular service exposes the [[StateService]] singleton. - */ - var $state; - /** - * A config-time injectable provider used to register states. - * - * This angular service exposes the [[StateProvider]] singleton. - */ - var $stateProvider; - /** - * A config-time injectable provider used to manage the URL. - * - * This angular service exposes the [[UrlRouterProvider]] singleton. - */ - var $urlRouterProvider; - /** - * An injectable service used to configure URL redirects. - * - * This angular service exposes the [[UrlRouter]] singleton. - */ - var $urlRouter; - /** - * An injectable service used to configure the URL. - * - * This service is used to set url mapping options, and create [[UrlMatcher]] objects. - * - * This angular service exposes the [[UrlMatcherFactory]] singleton. - * The singleton is also exposed at config-time as the [[$urlMatcherFactoryProvider]]. - */ - var $urlMatcherFactory; - /** - * An injectable service used to configure the URL. - * - * This service is used to set url mapping options, and create [[UrlMatcher]] objects. - * - * This angular service exposes the [[UrlMatcherFactory]] singleton at config-time. - * The singleton is also exposed at runtime as the [[$urlMatcherFactory]]. - */ - var $urlMatcherFactoryProvider; - - -/***/ }, -/* 54 */ -/***/ function(module, exports, __webpack_require__) { - - "use strict"; - /** @module ng1 */ /** */ - var stateObject_1 = __webpack_require__(42); - var node_1 = __webpack_require__(21); - var resolveContext_1 = __webpack_require__(17); - var common_1 = __webpack_require__(3); - var stateBuilder_1 = __webpack_require__(40); - /** - * Implementation of the legacy `$resolve` service for angular 1. - */ - var $resolve = { - /** - * Asynchronously injects a resolve block. - * - * This emulates most of the behavior of the ui-router 0.2.x $resolve.resolve() service API. - * - * Given an object `invocables`, where keys are strings and values are injectable functions, - * injects each function, and waits for the resulting promise to resolve. - * When all resulting promises are resolved, returns the results as an object. - * - * @example - * ```js - * - * let invocables = { - * foo: [ '$http', ($http) => - * $http.get('/api/foo').then(resp => resp.data) ], - * bar: [ 'foo', '$http', (foo, $http) => - * $http.get('/api/bar/' + foo.barId).then(resp => resp.data) ] - * } - * $resolve.resolve(invocables) - * .then(results => console.log(results.foo, results.bar)) - * // Logs foo and bar: - * // { id: 123, barId: 456, fooData: 'foo data' } - * // { id: 456, barData: 'bar data' } - * ``` - * - * @param invocables an object which looks like an [[StateDefinition.resolve]] object; keys are resolve names and values are injectable functions - * @param locals key/value pre-resolved data (locals) - * @param parent a promise for a "parent resolve" - */ - resolve: function (invocables, locals, parent) { - if (locals === void 0) { locals = {}; } - var parentNode = new node_1.PathNode(new stateObject_1.State({ params: {}, resolvables: [] })); - var node = new node_1.PathNode(new stateObject_1.State({ params: {}, resolvables: [] })); - var context = new resolveContext_1.ResolveContext([parentNode, node]); - context.addResolvables(stateBuilder_1.resolvablesBuilder({ resolve: invocables }), node.state); - var resolveData = function (parentLocals) { - var rewrap = function (_locals) { return stateBuilder_1.resolvablesBuilder({ resolve: common_1.mapObj(_locals, function (local) { return function () { return local; }; }) }); }; - context.addResolvables(rewrap(parentLocals), parentNode.state); - context.addResolvables(rewrap(locals), node.state); - var tuples2ObjR = function (acc, tuple) { - acc[tuple.token] = tuple.value; - return acc; - }; - return context.resolvePath().then(function (results) { return results.reduce(tuples2ObjR, {}); }); - }; - return parent ? parent.then(resolveData) : resolveData({}); - } - }; - /** @hidden */ - exports.resolveFactory = function () { return $resolve; }; - - -/***/ }, -/* 55 */ -/***/ function(module, exports, __webpack_require__) { - - "use strict"; - var common_1 = __webpack_require__(3); - var strings_1 = __webpack_require__(9); - var view_1 = __webpack_require__(37); - var predicates_1 = __webpack_require__(4); - var coreservices_1 = __webpack_require__(6); - var trace_1 = __webpack_require__(12); - var templateFactory_1 = __webpack_require__(56); - var resolveContext_1 = __webpack_require__(17); - var resolvable_1 = __webpack_require__(19); - var angular = __webpack_require__(57); - exports.ng1ViewConfigFactory = function (path, view) { - return [new Ng1ViewConfig(path, view)]; - }; - /** - * This is a [[StateBuilder.builder]] function for angular1 `views`. - * - * When the [[StateBuilder]] builds a [[State]] object from a raw [[StateDeclaration]], this builder - * handles the `views` property with logic specific to angular-ui-router (ng1). - * - * If no `views: {}` property exists on the [[StateDeclaration]], then it creates the `views` object - * and applies the state-level configuration to a view named `$default`. - */ - function ng1ViewsBuilder(state) { - var tplKeys = ['templateProvider', 'templateUrl', 'template', 'notify', 'async'], ctrlKeys = ['controller', 'controllerProvider', 'controllerAs', 'resolveAs'], compKeys = ['component', 'bindings'], nonCompKeys = tplKeys.concat(ctrlKeys), allKeys = compKeys.concat(nonCompKeys); - var views = {}, viewsObject = state.views || { "$default": common_1.pick(state, allKeys) }; - common_1.forEach(viewsObject, function (config, name) { - // Account for views: { "": { template... } } - name = name || "$default"; - // Account for views: { header: "headerComponent" } - if (predicates_1.isString(config)) - config = { component: config }; - if (!Object.keys(config).length) - return; - // Configure this view for routing to an angular 1.5+ style .component (or any directive, really) - if (config.component) { - if (nonCompKeys.map(function (key) { return predicates_1.isDefined(config[key]); }).reduce(common_1.anyTrueR, false)) { - throw new Error("Cannot combine: " + compKeys.join("|") + " with: " + nonCompKeys.join("|") + " in stateview: 'name@" + state.name + "'"); - } - // Dynamically build a template like "" - config.templateProvider = ['$injector', function ($injector) { - var resolveFor = function (key) { - return config.bindings && config.bindings[key] || key; - }; - var prefix = angular.version.minor >= 3 ? "::" : ""; - var attributeTpl = function (input) { - var attrName = strings_1.kebobString(input.name); - var resolveName = resolveFor(input.name); - if (input.type === '@') - return attrName + "='{{" + prefix + "$resolve." + resolveName + "}}'"; - return attrName + "='" + prefix + "$resolve." + resolveName + "'"; - }; - var attrs = getComponentInputs($injector, config.component).map(attributeTpl).join(" "); - var kebobName = strings_1.kebobString(config.component); - return "<" + kebobName + " " + attrs + ">"; - }]; - } - config.resolveAs = config.resolveAs || '$resolve'; - config.$type = "ng1"; - config.$context = state; - config.$name = name; - var normalized = view_1.ViewService.normalizeUIViewTarget(config.$context, config.$name); - config.$uiViewName = normalized.uiViewName; - config.$uiViewContextAnchor = normalized.uiViewContextAnchor; - views[name] = config; - }); - return views; - } - exports.ng1ViewsBuilder = ng1ViewsBuilder; - // for ng 1.2 style, process the scope: { input: "=foo" } - // for ng 1.3 through ng 1.5, process the component's bindToController: { input: "=foo" } object - var scopeBindings = function (bindingsObj) { return Object.keys(bindingsObj || {}) - .map(function (key) { return [key, /^([=<@])[?]?(.*)/.exec(bindingsObj[key])]; }) - .filter(function (tuple) { return predicates_1.isDefined(tuple) && predicates_1.isDefined(tuple[1]); }) - .map(function (tuple) { return ({ name: tuple[1][2] || tuple[0], type: tuple[1][1] }); }); }; - // Given a directive definition, find its object input attributes - // Use different properties, depending on the type of directive (component, bindToController, normal) - var getBindings = function (def) { - if (predicates_1.isObject(def.bindToController)) - return scopeBindings(def.bindToController); - return scopeBindings(def.scope); - }; - // Gets all the directive(s)' inputs ('@', '=', and '<') - function getComponentInputs($injector, name) { - var cmpDefs = $injector.get(name + "Directive"); // could be multiple - if (!cmpDefs || !cmpDefs.length) - throw new Error("Unable to find component named '" + name + "'"); - return cmpDefs.map(getBindings).reduce(common_1.unnestR, []); - } - var id = 0; - var Ng1ViewConfig = (function () { - function Ng1ViewConfig(path, viewDecl) { - this.path = path; - this.viewDecl = viewDecl; - this.$id = id++; - this.loaded = false; - } - Ng1ViewConfig.prototype.load = function () { - var _this = this; - var $q = coreservices_1.services.$q; - if (!this.hasTemplate()) - throw new Error("No template configuration specified for '" + this.viewDecl.$uiViewName + "@" + this.viewDecl.$uiViewContextAnchor + "'"); - var context = new resolveContext_1.ResolveContext(this.path); - var params = this.path.reduce(function (acc, node) { return common_1.extend(acc, node.paramValues); }, {}); - var promises = { - template: $q.when(this.getTemplate(params, new templateFactory_1.TemplateFactory(), context)), - controller: $q.when(this.getController(context)) - }; - return $q.all(promises).then(function (results) { - trace_1.trace.traceViewServiceEvent("Loaded", _this); - _this.controller = results.controller; - _this.template = results.template; - return _this; - }); - }; - /** - * Checks a view configuration to ensure that it specifies a template. - * - * @return {boolean} Returns `true` if the configuration contains a valid template, otherwise `false`. - */ - Ng1ViewConfig.prototype.hasTemplate = function () { - return !!(this.viewDecl.template || this.viewDecl.templateUrl || this.viewDecl.templateProvider); - }; - Ng1ViewConfig.prototype.getTemplate = function (params, $factory, context) { - return $factory.fromConfig(this.viewDecl, params, context); - }; - /** - * Gets the controller for a view configuration. - * - * @returns {Function|Promise.} Returns a controller, or a promise that resolves to a controller. - */ - Ng1ViewConfig.prototype.getController = function (context) { - var provider = this.viewDecl.controllerProvider; - if (!predicates_1.isInjectable(provider)) - return this.viewDecl.controller; - var deps = coreservices_1.services.$injector.annotate(provider); - var providerFn = predicates_1.isArray(provider) ? common_1.tail(provider) : provider; - var resolvable = new resolvable_1.Resolvable("", providerFn, deps); - return resolvable.get(context); - }; - return Ng1ViewConfig; - }()); - exports.Ng1ViewConfig = Ng1ViewConfig; - - -/***/ }, -/* 56 */ -/***/ function(module, exports, __webpack_require__) { - - "use strict"; - /** @module view */ /** for typedoc */ - var predicates_1 = __webpack_require__(4); - var coreservices_1 = __webpack_require__(6); - var common_1 = __webpack_require__(3); - var resolvable_1 = __webpack_require__(19); - /** - * Service which manages loading of templates from a ViewConfig. - */ - var TemplateFactory = (function () { - function TemplateFactory() { - } - /** - * Creates a template from a configuration object. - * - * @param config Configuration object for which to load a template. - * The following properties are search in the specified order, and the first one - * that is defined is used to create the template: - * - * @param params Parameters to pass to the template function. - * @param context The resolve context associated with the template's view - * - * @return {string|object} The template html as a string, or a promise for - * that string,or `null` if no template is configured. - */ - TemplateFactory.prototype.fromConfig = function (config, params, context) { - return (predicates_1.isDefined(config.template) ? this.fromString(config.template, params) : - predicates_1.isDefined(config.templateUrl) ? this.fromUrl(config.templateUrl, params) : - predicates_1.isDefined(config.templateProvider) ? this.fromProvider(config.templateProvider, params, context) : - null); - }; - ; - /** - * Creates a template from a string or a function returning a string. - * - * @param template html template as a string or function that returns an html template as a string. - * @param params Parameters to pass to the template function. - * - * @return {string|object} The template html as a string, or a promise for that - * string. - */ - TemplateFactory.prototype.fromString = function (template, params) { - return predicates_1.isFunction(template) ? template(params) : template; - }; - ; - /** - * Loads a template from the a URL via `$http` and `$templateCache`. - * - * @param {string|Function} url url of the template to load, or a function - * that returns a url. - * @param {Object} params Parameters to pass to the url function. - * @return {string|Promise.} The template html as a string, or a promise - * for that string. - */ - TemplateFactory.prototype.fromUrl = function (url, params) { - if (predicates_1.isFunction(url)) - url = url(params); - if (url == null) - return null; - return coreservices_1.services.template.get(url); - }; - ; - /** - * Creates a template by invoking an injectable provider function. - * - * @param provider Function to invoke via `locals` - * @param {Function} injectFn a function used to invoke the template provider - * @return {string|Promise.} The template html as a string, or a promise - * for that string. - */ - TemplateFactory.prototype.fromProvider = function (provider, params, context) { - var deps = coreservices_1.services.$injector.annotate(provider); - var providerFn = predicates_1.isArray(provider) ? common_1.tail(provider) : provider; - var resolvable = new resolvable_1.Resolvable("", providerFn, deps); - return resolvable.get(context); - }; - ; - return TemplateFactory; - }()); - exports.TemplateFactory = TemplateFactory; - - -/***/ }, -/* 57 */ -/***/ function(module, exports) { - - module.exports = __WEBPACK_EXTERNAL_MODULE_57__; - -/***/ }, -/* 58 */ -/***/ function(module, exports, __webpack_require__) { - - "use strict"; - /** @module ng1 */ /** for typedoc */ - var predicates_1 = __webpack_require__(4); - var common_1 = __webpack_require__(3); - /** - * @ngdoc object - * @name ui.router.state.$stateProvider - * - * @requires ui.router.router.$urlRouterProvider - * @requires ui.router.util.$urlMatcherFactoryProvider - * - * @description - * The new `$stateProvider` works similar to Angular's v1 router, but it focuses purely - * on state. - * - * A state corresponds to a "place" in the application in terms of the overall UI and - * navigation. A state describes (via the controller / template / view properties) what - * the UI looks like and does at that place. - * - * States often have things in common, and the primary way of factoring out these - * commonalities in this model is via the state hierarchy, i.e. parent/child states aka - * nested states. - * - * The `$stateProvider` provides interfaces to declare these states for your app. - */ - var StateProvider = (function () { - function StateProvider(stateRegistry, stateService) { - this.stateRegistry = stateRegistry; - this.stateService = stateService; - common_1.bindFunctions(StateProvider.prototype, this, this); - } - /** - * @ngdoc function - * @name ui.router.state.$stateProvider#decorator - * @methodOf ui.router.state.$stateProvider - * - * @description - * Allows you to extend (carefully) or override (at your own peril) the - * `stateBuilder` object used internally by `$stateProvider`. This can be used - * to add custom functionality to ui-router, for example inferring templateUrl - * based on the state name. - * - * When passing only a name, it returns the current (original or decorated) builder - * function that matches `name`. - * - * The builder functions that can be decorated are listed below. Though not all - * necessarily have a good use case for decoration, that is up to you to decide. - * - * In addition, users can attach custom decorators, which will generate new - * properties within the state's internal definition. There is currently no clear - * use-case for this beyond accessing internal states (i.e. $state.$current), - * however, expect this to become increasingly relevant as we introduce additional - * meta-programming features. - * - * **Warning**: Decorators should not be interdependent because the order of - * execution of the builder functions in non-deterministic. Builder functions - * should only be dependent on the state definition object and super function. - * - * - * Existing builder functions and current return values: - * - * - **parent** `{object}` - returns the parent state object. - * - **data** `{object}` - returns state data, including any inherited data that is not - * overridden by own values (if any). - * - **url** `{object}` - returns a {@link ui.router.util.type:UrlMatcher UrlMatcher} - * or `null`. - * - **navigable** `{object}` - returns closest ancestor state that has a URL (aka is - * navigable). - * - **params** `{object}` - returns an array of state params that are ensured to - * be a super-set of parent's params. - * - **views** `{object}` - returns a views object where each key is an absolute view - * name (i.e. "viewName@stateName") and each value is the config object - * (template, controller) for the view. Even when you don't use the views object - * explicitly on a state config, one is still created for you internally. - * So by decorating this builder function you have access to decorating template - * and controller properties. - * - **ownParams** `{object}` - returns an array of params that belong to the state, - * not including any params defined by ancestor states. - * - **path** `{string}` - returns the full path from the root down to this state. - * Needed for state activation. - * - **includes** `{object}` - returns an object that includes every state that - * would pass a `$state.includes()` test. - * - * @example - *
-	     * // Override the internal 'views' builder with a function that takes the state
-	     * // definition, and a reference to the internal function being overridden:
-	     * $stateProvider.decorator('views', function (state, parent) {
-	     *   let result = {},
-	     *       views = parent(state);
-	     *
-	     *   angular.forEach(views, function (config, name) {
-	     *     let autoName = (state.name + '.' + name).replace('.', '/');
-	     *     config.templateUrl = config.templateUrl || '/partials/' + autoName + '.html';
-	     *     result[name] = config;
-	     *   });
-	     *   return result;
-	     * });
-	     *
-	     * $stateProvider.state('home', {
-	     *   views: {
-	     *     'contact.list': { controller: 'ListController' },
-	     *     'contact.item': { controller: 'ItemController' }
-	     *   }
-	     * });
-	     *
-	     * // ...
-	     *
-	     * $state.go('home');
-	     * // Auto-populates list and item views with /partials/home/contact/list.html,
-	     * // and /partials/home/contact/item.html, respectively.
-	     * 
- * - * @param {string} name The name of the builder function to decorate. - * @param {object} func A function that is responsible for decorating the original - * builder function. The function receives two parameters: - * - * - `{object}` - state - The state config object. - * - `{object}` - super - The original builder function. - * - * @return {object} $stateProvider - $stateProvider instance - */ - StateProvider.prototype.decorator = function (name, func) { - return this.stateRegistry.decorator(name, func) || this; - }; - StateProvider.prototype.state = function (name, definition) { - if (predicates_1.isObject(name)) { - definition = name; - } - else { - definition.name = name; - } - this.stateRegistry.register(definition); - return this; - }; - /** - * Registers an invalid state handler - * - * This is a passthrough to [[StateService.onInvalid]] for ng1. - */ - StateProvider.prototype.onInvalid = function (callback) { - return this.stateService.onInvalid(callback); - }; - return StateProvider; - }()); - exports.StateProvider = StateProvider; - - -/***/ }, -/* 59 */ -/***/ function(module, exports, __webpack_require__) { - - "use strict"; - var coreservices_1 = __webpack_require__(6); - var services_1 = __webpack_require__(53); - var resolveContext_1 = __webpack_require__(17); - var common_1 = __webpack_require__(3); - /** - * This is a [[StateBuilder.builder]] function for angular1 `onEnter`, `onExit`, - * `onRetain` callback hooks on a [[Ng1StateDeclaration]]. - * - * When the [[StateBuilder]] builds a [[State]] object from a raw [[StateDeclaration]], this builder - * ensures that those hooks are injectable for angular-ui-router (ng1). - */ - exports.getStateHookBuilder = function (hookName) { - return function stateHookBuilder(state, parentFn) { - var hook = state[hookName]; - function decoratedNg1Hook(trans, state) { - var resolveContext = new resolveContext_1.ResolveContext(trans.treeChanges().to); - return coreservices_1.services.$injector.invoke(hook, this, common_1.extend({ $state$: state }, services_1.getLocals(resolveContext))); - } - return hook ? decoratedNg1Hook : undefined; - }; - }; - - -/***/ }, -/* 60 */ -/***/ function(module, exports, __webpack_require__) { - - "use strict"; - /** - * These are the UI-Router angular 1 directives. - * - * These directives are used in templates to create viewports and navigate to states - * - * @preferred @module ng1_directives - */ /** for typedoc */ - var angular = __webpack_require__(57); - var common_1 = __webpack_require__(3); - var predicates_1 = __webpack_require__(4); - var hof_1 = __webpack_require__(5); - /** @hidden */ - function parseStateRef(ref, current) { - var preparsed = ref.match(/^\s*({[^}]*})\s*$/), parsed; - if (preparsed) - ref = current + '(' + preparsed[1] + ')'; - parsed = ref.replace(/\n/g, " ").match(/^([^(]+?)\s*(\((.*)\))?$/); - if (!parsed || parsed.length !== 4) - throw new Error("Invalid state ref '" + ref + "'"); - return { state: parsed[1], paramExpr: parsed[3] || null }; - } - /** @hidden */ - function stateContext(el) { - var $uiView = el.parent().inheritedData('$uiView'); - var path = hof_1.parse('$cfg.path')($uiView); - return path ? common_1.tail(path).state.name : undefined; - } - /** @hidden */ - function getTypeInfo(el) { - // SVGAElement does not use the href attribute, but rather the 'xlinkHref' attribute. - var isSvg = Object.prototype.toString.call(el.prop('href')) === '[object SVGAnimatedString]'; - var isForm = el[0].nodeName === "FORM"; - return { - attr: isForm ? "action" : (isSvg ? 'xlink:href' : 'href'), - isAnchor: el.prop("tagName").toUpperCase() === "A", - clickable: !isForm - }; - } - /** @hidden */ - function clickHook(el, $state, $timeout, type, current) { - return function (e) { - var button = e.which || e.button, target = current(); - if (!(button > 1 || e.ctrlKey || e.metaKey || e.shiftKey || el.attr('target'))) { - // HACK: This is to allow ng-clicks to be processed before the transition is initiated: - var transition = $timeout(function () { - $state.go(target.state, target.params, target.options); - }); - e.preventDefault(); - // if the state has no URL, ignore one preventDefault from the directive. - var ignorePreventDefaultCount = type.isAnchor && !target.href ? 1 : 0; - e.preventDefault = function () { - if (ignorePreventDefaultCount-- <= 0) - $timeout.cancel(transition); - }; - } - }; - } - /** @hidden */ - function defaultOpts(el, $state) { - return { - relative: stateContext(el) || $state.$current, - inherit: true, - source: "sref" - }; - } - /** - * `ui-sref`: A directive for linking to a state - * - * A directive that binds a link (`` tag) to a state. - * If the state has an associated URL, the directive will automatically generate and - * update the `href` attribute via the [[StateService.href]] method. - * Clicking the link will trigger a state transition with optional parameters. - * - * Also middle-clicking, right-clicking, and ctrl-clicking on the link will be - * handled natively by the browser. - * - * You can also use relative state paths within ui-sref, just like the relative - * paths passed to `$state.go()`. - * You just need to be aware that the path is relative to the state that the link lives in. - * In other words, the state that created the view containing the link. - * - * You can specify options to pass to [[StateService.go]] using the `ui-sref-opts` attribute. - * Options are restricted to `location`, `inherit`, and `reload`. - * - * Here's an example of how you'd use ui-sref and how it would compile. - * If you have the following template: - * - * @example - * ```html - * - *
-	 * Home | About | Next page
-	 *
-	 * 
-	 * 
- * ``` - * - * Then the compiled html would be (assuming Html5Mode is off and current state is contacts): - * - * ```html - * - *
-	 * Home | About | Next page
-	 *
-	 * 
    - *
  • - * Joe - *
  • - *
  • - * Alice - *
  • - *
  • - * Bob - *
  • - *
- * - * Home - *
- * ``` - * - * @param {string} ui-sref 'stateName' can be any valid absolute or relative state - * @param {Object} ui-sref-opts options to pass to [[StateService.go]] - */ - var uiSref = ['$state', '$timeout', - function $StateRefDirective($state, $timeout) { - return { - restrict: 'A', - require: ['?^uiSrefActive', '?^uiSrefActiveEq'], - link: function (scope, element, attrs, uiSrefActive) { - var ref = parseStateRef(attrs.uiSref, $state.current.name); - var def = { state: ref.state, href: null, params: null, options: null }; - var type = getTypeInfo(element); - var active = uiSrefActive[1] || uiSrefActive[0]; - var unlinkInfoFn = null; - var hookFn; - def.options = common_1.extend(defaultOpts(element, $state), attrs.uiSrefOpts ? scope.$eval(attrs.uiSrefOpts) : {}); - var update = function (val) { - if (val) - def.params = angular.copy(val); - def.href = $state.href(ref.state, def.params, def.options); - if (unlinkInfoFn) - unlinkInfoFn(); - if (active) - unlinkInfoFn = active.$$addStateInfo(ref.state, def.params); - if (def.href !== null) - attrs.$set(type.attr, def.href); - }; - if (ref.paramExpr) { - scope.$watch(ref.paramExpr, function (val) { if (val !== def.params) - update(val); }, true); - def.params = angular.copy(scope.$eval(ref.paramExpr)); - } - update(); - if (!type.clickable) - return; - hookFn = clickHook(element, $state, $timeout, type, function () { return def; }); - element.on("click", hookFn); - scope.$on('$destroy', function () { - element.off("click", hookFn); - }); - } - }; - }]; - /** - * `ui-state`: A dynamic version of `ui-sref` - * - * Much like ui-sref, but will accept named $scope properties to evaluate for a state definition, - * params and override options. - * - * @example - * ```html - * - *
  • - * {{nav.description}} - *
  • - * - * @param {string} ui-state 'stateName' can be any valid absolute or relative state - * @param {Object} ui-state-params params to pass to [[StateService.href]] - * @param {Object} ui-state-opts options to pass to [[StateService.go]] - */ - var uiState = ['$state', '$timeout', - function $StateRefDynamicDirective($state, $timeout) { - return { - restrict: 'A', - require: ['?^uiSrefActive', '?^uiSrefActiveEq'], - link: function (scope, element, attrs, uiSrefActive) { - var type = getTypeInfo(element); - var active = uiSrefActive[1] || uiSrefActive[0]; - var group = [attrs.uiState, attrs.uiStateParams || null, attrs.uiStateOpts || null]; - var watch = '[' + group.map(function (val) { return val || 'null'; }).join(', ') + ']'; - var def = { state: null, params: null, options: null, href: null }; - var unlinkInfoFn = null; - var hookFn; - function runStateRefLink(group) { - def.state = group[0]; - def.params = group[1]; - def.options = group[2]; - def.href = $state.href(def.state, def.params, def.options); - if (unlinkInfoFn) - unlinkInfoFn(); - if (active) - unlinkInfoFn = active.$$addStateInfo(def.state, def.params); - if (def.href) - attrs.$set(type.attr, def.href); - } - scope.$watch(watch, runStateRefLink, true); - runStateRefLink(scope.$eval(watch)); - if (!type.clickable) - return; - hookFn = clickHook(element, $state, $timeout, type, function () { return def; }); - element.on("click", hookFn); - scope.$on('$destroy', function () { - element.off("click", hookFn); - }); - } - }; - }]; - /** - * `ui-sref-active` and `ui-sref-active-eq`: A directive that adds a CSS class when a `ui-sref` is active - * - * A directive working alongside ui-sref to add classes to an element when the - * related ui-sref directive's state is active, and removing them when it is inactive. - * The primary use-case is to simplify the special appearance of navigation menus - * relying on `ui-sref`, by having the "active" state's menu button appear different, - * distinguishing it from the inactive menu items. - * - * ui-sref-active can live on the same element as ui-sref or on a parent element. The first - * ui-sref-active found at the same level or above the ui-sref will be used. - * - * Will activate when the ui-sref's target state or any child state is active. If you - * need to activate only when the ui-sref target state is active and *not* any of - * it's children, then you will use ui-sref-active-eq - * - * Given the following template: - * @example - * ```html - * - *
    -	 * 
    -	 * 
    - * ``` - * - * - * When the app state is "app.user" (or any children states), and contains the state parameter "user" with value "bilbobaggins", - * the resulting HTML will appear as (note the 'active' class): - * - * ```html - * - *
    -	 * 
    -	 * 
    - * ``` - * - * The class name is interpolated **once** during the directives link time (any further changes to the - * interpolated value are ignored). - * - * Multiple classes may be specified in a space-separated format: - * - * ```html - *
    -	 * 
      - *
    • - * link - *
    • - *
    - *
    - * ``` - * - * It is also possible to pass ui-sref-active an expression that evaluates - * to an object hash, whose keys represent active class names and whose - * values represent the respective state names/globs. - * ui-sref-active will match if the current active state **includes** any of - * the specified state names/globs, even the abstract ones. - * - * Given the following template, with "admin" being an abstract state: - * @example - * ```html - * - *
    -	 * 
    - * Roles - *
    - *
    - * ``` - * - * When the current state is "admin.roles" the "active" class will be applied - * to both the
    and elements. It is important to note that the state - * names/globs passed to ui-sref-active shadow the state provided by ui-sref. - */ - var uiSrefActive = ['$state', '$stateParams', '$interpolate', '$transitions', '$uiRouter', - function $StateRefActiveDirective($state, $stateParams, $interpolate, $transitions, $uiRouter) { - return { - restrict: "A", - controller: ['$scope', '$element', '$attrs', '$timeout', - function ($scope, $element, $attrs, $timeout) { - var states = [], activeClasses = {}, activeEqClass, uiSrefActive; - // There probably isn't much point in $observing this - // uiSrefActive and uiSrefActiveEq share the same directive object with some - // slight difference in logic routing - activeEqClass = $interpolate($attrs.uiSrefActiveEq || '', false)($scope); - try { - uiSrefActive = $scope.$eval($attrs.uiSrefActive); - } - catch (e) { - } - uiSrefActive = uiSrefActive || $interpolate($attrs.uiSrefActive || '', false)($scope); - if (predicates_1.isObject(uiSrefActive)) { - common_1.forEach(uiSrefActive, function (stateOrName, activeClass) { - if (predicates_1.isString(stateOrName)) { - var ref = parseStateRef(stateOrName, $state.current.name); - addState(ref.state, $scope.$eval(ref.paramExpr), activeClass); - } - }); - } - // Allow uiSref to communicate with uiSrefActive[Equals] - this.$$addStateInfo = function (newState, newParams) { - // we already got an explicit state provided by ui-sref-active, so we - // shadow the one that comes from ui-sref - if (predicates_1.isObject(uiSrefActive) && states.length > 0) { - return; - } - var deregister = addState(newState, newParams, uiSrefActive); - update(); - return deregister; - }; - function updateAfterTransition(trans) { trans.promise.then(update); } - $scope.$on('$stateChangeSuccess', update); - $scope.$on('$destroy', $transitions.onStart({}, updateAfterTransition)); - if ($uiRouter.globals.transition) { - updateAfterTransition($uiRouter.globals.transition); - } - function addState(stateName, stateParams, activeClass) { - var state = $state.get(stateName, stateContext($element)); - var stateHash = createStateHash(stateName, stateParams); - var stateInfo = { - state: state || { name: stateName }, - params: stateParams, - hash: stateHash - }; - states.push(stateInfo); - activeClasses[stateHash] = activeClass; - return function removeState() { - var idx = states.indexOf(stateInfo); - if (idx !== -1) - states.splice(idx, 1); - }; - } - /** - * @param {string} state - * @param {Object|string} [params] - * @return {string} - */ - function createStateHash(state, params) { - if (!predicates_1.isString(state)) { - throw new Error('state should be a string'); - } - if (predicates_1.isObject(params)) { - return state + common_1.toJson(params); - } - params = $scope.$eval(params); - if (predicates_1.isObject(params)) { - return state + common_1.toJson(params); - } - return state; - } - // Update route state - function update() { - for (var i = 0; i < states.length; i++) { - if (anyMatch(states[i].state, states[i].params)) { - addClass($element, activeClasses[states[i].hash]); - } - else { - removeClass($element, activeClasses[states[i].hash]); - } - if (exactMatch(states[i].state, states[i].params)) { - addClass($element, activeEqClass); - } - else { - removeClass($element, activeEqClass); - } - } - } - function addClass(el, className) { $timeout(function () { el.addClass(className); }); } - function removeClass(el, className) { el.removeClass(className); } - function anyMatch(state, params) { return $state.includes(state.name, params); } - function exactMatch(state, params) { return $state.is(state.name, params); } - update(); - }] - }; - }]; - angular.module('ui.router.state') - .directive('uiSref', uiSref) - .directive('uiSrefActive', uiSrefActive) - .directive('uiSrefActiveEq', uiSrefActive) - .directive('uiState', uiState); - - -/***/ }, -/* 61 */ -/***/ function(module, exports, __webpack_require__) { - - /** @module state */ /** for typedoc */ - "use strict"; - var angular = __webpack_require__(57); - /** - * @ngdoc filter - * @name ui.router.state.filter:isState - * - * @requires ui.router.state.$state - * - * @description - * Translates to {@link ui.router.state.$state#methods_is $state.is("stateName")}. - */ - $IsStateFilter.$inject = ['$state']; - function $IsStateFilter($state) { - var isFilter = function (state, params, options) { - return $state.is(state, params, options); - }; - isFilter.$stateful = true; - return isFilter; - } - exports.$IsStateFilter = $IsStateFilter; - /** - * @ngdoc filter - * @name ui.router.state.filter:includedByState - * - * @requires ui.router.state.$state - * - * @description - * Translates to {@link ui.router.state.$state#methods_includes $state.includes('fullOrPartialStateName')}. - */ - $IncludedByStateFilter.$inject = ['$state']; - function $IncludedByStateFilter($state) { - var includesFilter = function (state, params, options) { - return $state.includes(state, params, options); - }; - includesFilter.$stateful = true; - return includesFilter; - } - exports.$IncludedByStateFilter = $IncludedByStateFilter; - angular.module('ui.router.state') - .filter('isState', $IsStateFilter) - .filter('includedByState', $IncludedByStateFilter); - - -/***/ }, -/* 62 */ -/***/ function(module, exports, __webpack_require__) { - - /** @module ng1_directives */ /** for typedoc */ - "use strict"; - var common_1 = __webpack_require__(3); - var predicates_1 = __webpack_require__(4); - var trace_1 = __webpack_require__(12); - var views_1 = __webpack_require__(55); - var hof_1 = __webpack_require__(5); - var resolveContext_1 = __webpack_require__(17); - var strings_1 = __webpack_require__(9); - var services_1 = __webpack_require__(53); - var angular = __webpack_require__(57); - /** - * `ui-view`: A viewport directive which is filled in by a view from the active state. - * - * @param {string=} name A view name. The name should be unique amongst the other views in the - * same state. You can have views of the same name that live in different states. - * - * @param {string=} autoscroll It allows you to set the scroll behavior of the browser window - * when a view is populated. By default, $anchorScroll is overridden by ui-router's custom scroll - * service, {@link ui.router.state.$uiViewScroll}. This custom service let's you - * scroll ui-view elements into view when they are populated during a state activation. - * - * *Note: To revert back to old [`$anchorScroll`](http://docs.angularjs.org/api/ng.$anchorScroll) - * functionality, call `$uiViewScrollProvider.useAnchorScroll()`.* - * - * @param {string=} onload Expression to evaluate whenever the view updates. - * - * A view can be unnamed or named. - * @example - * ```html - * - * - *
    - * - * - *
    - * ``` - * - * You can only have one unnamed view within any template (or root html). If you are only using a - * single view and it is unnamed then you can populate it like so: - * ``` - * - *
    - * $stateProvider.state("home", { - * template: "

    HELLO!

    " - * }) - * ``` - * - * The above is a convenient shortcut equivalent to specifying your view explicitly with the {@link ui.router.state.$stateProvider#views `views`} - * config property, by name, in this case an empty name: - * ```js - * - * $stateProvider.state("home", { - * views: { - * "": { - * template: "

    HELLO!

    " - * } - * } - * }) - * ``` - * - * But typically you'll only use the views property if you name your view or have more than one view - * in the same template. There's not really a compelling reason to name a view if its the only one, - * but you could if you wanted, like so: - * - * ```html - * - *
    - * ``` - * - * ```js - * - * $stateProvider.state("home", { - * views: { - * "main": { - * template: "

    HELLO!

    " - * } - * } - * }) - * ``` - * - * Really though, you'll use views to set up multiple views: - * ```html - * - *
    - *
    - *
    - * ``` - * - * ```js - * $stateProvider.state("home", { - * views: { - * "": { - * template: "

    HELLO!

    " - * }, - * "chart": { - * template: "" - * }, - * "data": { - * template: "" - * } - * } - * }) - * ``` - * - * Examples for `autoscroll`: - * - * ```html - * - * - * - * - * - * - * - * - * ``` - * - * Resolve data: - * - * The resolved data from the state's `resolve` block is placed on the scope as `$resolve` (this - * can be customized using [[ViewDeclaration.resolveAs]]). This can be then accessed from the template. - * - * Note that when `controllerAs` is being used, `$resolve` is set on the controller instance *after* the - * controller is instantiated. The `$onInit()` hook can be used to perform initialization code which - * depends on `$resolve` data. - * - * @example - * ```js - * - * $stateProvider.state('home', { - * template: '', - * resolve: { - * user: function(UserService) { return UserService.fetchUser(); } - * } - * }); - * ``` - */ - var uiView = ['$view', '$animate', '$uiViewScroll', '$interpolate', '$q', - function $ViewDirective($view, $animate, $uiViewScroll, $interpolate, $q) { - function getRenderer(attrs, scope) { - return { - enter: function (element, target, cb) { - if (angular.version.minor > 2) { - $animate.enter(element, null, target).then(cb); - } - else { - $animate.enter(element, null, target, cb); - } - }, - leave: function (element, cb) { - if (angular.version.minor > 2) { - $animate.leave(element).then(cb); - } - else { - $animate.leave(element, cb); - } - } - }; - } - function configsEqual(config1, config2) { - return config1 === config2; - } - var rootData = { - $cfg: { viewDecl: { $context: $view.rootContext() } }, - $uiView: {} - }; - var directive = { - count: 0, - restrict: 'ECA', - terminal: true, - priority: 400, - transclude: 'element', - compile: function (tElement, tAttrs, $transclude) { - return function (scope, $element, attrs) { - var previousEl, currentEl, currentScope, unregister, onloadExp = attrs['onload'] || '', autoScrollExp = attrs['autoscroll'], renderer = getRenderer(attrs, scope), viewConfig = undefined, inherited = $element.inheritedData('$uiView') || rootData, name = $interpolate(attrs['uiView'] || attrs['name'] || '')(scope) || '$default'; - var activeUIView = { - $type: 'ng1', - id: directive.count++, - name: name, - fqn: inherited.$uiView.fqn ? inherited.$uiView.fqn + "." + name : name, - config: null, - configUpdated: configUpdatedCallback, - get creationContext() { - return hof_1.parse('$cfg.viewDecl.$context')(inherited); - } - }; - trace_1.trace.traceUIViewEvent("Linking", activeUIView); - function configUpdatedCallback(config) { - if (config && !(config instanceof views_1.Ng1ViewConfig)) - return; - if (configsEqual(viewConfig, config)) - return; - trace_1.trace.traceUIViewConfigUpdated(activeUIView, config && config.viewDecl && config.viewDecl.$context); - viewConfig = config; - updateView(config); - } - $element.data('$uiView', { $uiView: activeUIView }); - updateView(); - unregister = $view.registerUIView(activeUIView); - scope.$on("$destroy", function () { - trace_1.trace.traceUIViewEvent("Destroying/Unregistering", activeUIView); - unregister(); - }); - function cleanupLastView() { - if (previousEl) { - trace_1.trace.traceUIViewEvent("Removing (previous) el", previousEl.data('$uiView')); - previousEl.remove(); - previousEl = null; - } - if (currentScope) { - trace_1.trace.traceUIViewEvent("Destroying scope", activeUIView); - currentScope.$destroy(); - currentScope = null; - } - if (currentEl) { - var _viewData_1 = currentEl.data('$uiViewAnim'); - trace_1.trace.traceUIViewEvent("Animate out", _viewData_1); - renderer.leave(currentEl, function () { - _viewData_1.$$animLeave.resolve(); - previousEl = null; - }); - previousEl = currentEl; - currentEl = null; - } - } - function updateView(config) { - var newScope = scope.$new(); - var animEnter = $q.defer(), animLeave = $q.defer(); - var $uiViewData = { - $cfg: config, - $uiView: activeUIView, - }; - var $uiViewAnim = { - $animEnter: animEnter.promise, - $animLeave: animLeave.promise, - $$animLeave: animLeave - }; - var cloned = $transclude(newScope, function (clone) { - clone.data('$uiViewAnim', $uiViewAnim); - clone.data('$uiView', $uiViewData); - renderer.enter(clone, $element, function onUIViewEnter() { - animEnter.resolve(); - if (currentScope) - currentScope.$emit('$viewContentAnimationEnded'); - if (predicates_1.isDefined(autoScrollExp) && !autoScrollExp || scope.$eval(autoScrollExp)) { - $uiViewScroll(clone); - } - }); - cleanupLastView(); - }); - currentEl = cloned; - currentScope = newScope; - /** - * @ngdoc event - * @name ui.router.state.directive:ui-view#$viewContentLoaded - * @eventOf ui.router.state.directive:ui-view - * @eventType emits on ui-view directive scope - * @description * - * Fired once the view is **loaded**, *after* the DOM is rendered. - * - * @param {Object} event Event object. - */ - currentScope.$emit('$viewContentLoaded', config || viewConfig); - currentScope.$eval(onloadExp); - } - }; - } - }; - return directive; - }]; - $ViewDirectiveFill.$inject = ['$compile', '$controller', '$transitions', '$view', '$timeout']; - /** @hidden */ - function $ViewDirectiveFill($compile, $controller, $transitions, $view, $timeout) { - var getControllerAs = hof_1.parse('viewDecl.controllerAs'); - var getResolveAs = hof_1.parse('viewDecl.resolveAs'); - return { - restrict: 'ECA', - priority: -400, - compile: function (tElement) { - var initial = tElement.html(); - return function (scope, $element) { - var data = $element.data('$uiView'); - if (!data) - return; - var cfg = data.$cfg || { viewDecl: {} }; - $element.html(cfg.template || initial); - trace_1.trace.traceUIViewFill(data.$uiView, $element.html()); - var link = $compile($element.contents()); - var controller = cfg.controller; - var controllerAs = getControllerAs(cfg); - var resolveAs = getResolveAs(cfg); - var resolveCtx = cfg.path && new resolveContext_1.ResolveContext(cfg.path); - var locals = resolveCtx && services_1.getLocals(resolveCtx); - scope[resolveAs] = locals; - if (controller) { - var controllerInstance = $controller(controller, common_1.extend({}, locals, { $scope: scope, $element: $element })); - if (controllerAs) { - scope[controllerAs] = controllerInstance; - scope[controllerAs][resolveAs] = locals; - } - // TODO: Use $view service as a central point for registering component-level hooks - // Then, when a component is created, tell the $view service, so it can invoke hooks - // $view.componentLoaded(controllerInstance, { $scope: scope, $element: $element }); - // scope.$on('$destroy', () => $view.componentUnloaded(controllerInstance, { $scope: scope, $element: $element })); - $element.data('$ngControllerController', controllerInstance); - $element.children().data('$ngControllerController', controllerInstance); - registerControllerCallbacks($transitions, controllerInstance, scope, cfg); - } - // Wait for the component to appear in the DOM - if (predicates_1.isString(cfg.viewDecl.component)) { - var cmp_1 = cfg.viewDecl.component; - var kebobName_1 = strings_1.kebobString(cmp_1); - var getComponentController = function () { - var directiveEl = [].slice.call($element[0].children) - .filter(function (el) { return el && el.tagName && el.tagName.toLowerCase() === kebobName_1; }); - return directiveEl && angular.element(directiveEl).data("$" + cmp_1 + "Controller"); - }; - var deregisterWatch_1 = scope.$watch(getComponentController, function (ctrlInstance) { - if (!ctrlInstance) - return; - registerControllerCallbacks($transitions, ctrlInstance, scope, cfg); - deregisterWatch_1(); - }); - } - link(scope); - }; - } - }; - } - /** @hidden */ - var hasComponentImpl = typeof angular.module('ui.router')['component'] === 'function'; - /** @hidden TODO: move these callbacks to $view and/or `/hooks/components.ts` or something */ - function registerControllerCallbacks($transitions, controllerInstance, $scope, cfg) { - // Call $onInit() ASAP - if (predicates_1.isFunction(controllerInstance.$onInit) && !(cfg.viewDecl.component && hasComponentImpl)) - controllerInstance.$onInit(); - var viewState = common_1.tail(cfg.path).state.self; - var hookOptions = { bind: controllerInstance }; - // Add component-level hook for onParamsChange - if (predicates_1.isFunction(controllerInstance.uiOnParamsChanged)) { - var resolveContext = new resolveContext_1.ResolveContext(cfg.path); - var viewCreationTrans_1 = resolveContext.getResolvable('$transition$').data; - // Fire callback on any successful transition - var paramsUpdated = function ($transition$) { - // Exit early if the $transition$ is the same as the view was created within. - // Exit early if the $transition$ will exit the state the view is for. - if ($transition$ === viewCreationTrans_1 || $transition$.exiting().indexOf(viewState) !== -1) - return; - var toParams = $transition$.params("to"); - var fromParams = $transition$.params("from"); - var toSchema = $transition$.treeChanges().to.map(function (node) { return node.paramSchema; }).reduce(common_1.unnestR, []); - var fromSchema = $transition$.treeChanges().from.map(function (node) { return node.paramSchema; }).reduce(common_1.unnestR, []); - // Find the to params that have different values than the from params - var changedToParams = toSchema.filter(function (param) { - var idx = fromSchema.indexOf(param); - return idx === -1 || !fromSchema[idx].type.equals(toParams[param.id], fromParams[param.id]); - }); - // Only trigger callback if a to param has changed or is new - if (changedToParams.length) { - var changedKeys_1 = changedToParams.map(function (x) { return x.id; }); - // Filter the params to only changed/new to params. `$transition$.params()` may be used to get all params. - controllerInstance.uiOnParamsChanged(common_1.filter(toParams, function (val, key) { return changedKeys_1.indexOf(key) !== -1; }), $transition$); - } - }; - $scope.$on('$destroy', $transitions.onSuccess({}, paramsUpdated, hookOptions)); - } - // Add component-level hook for uiCanExit - if (predicates_1.isFunction(controllerInstance.uiCanExit)) { - var criteria = { exiting: viewState.name }; - $scope.$on('$destroy', $transitions.onBefore(criteria, controllerInstance.uiCanExit, hookOptions)); - } - } - angular.module('ui.router.state').directive('uiView', uiView); - angular.module('ui.router.state').directive('uiView', $ViewDirectiveFill); - - -/***/ }, -/* 63 */ -/***/ function(module, exports, __webpack_require__) { - - "use strict"; - /** @module ng1 */ /** */ - var angular = __webpack_require__(57); - /** - * @ngdoc object - * @name ui.router.state.$uiViewScrollProvider - * - * @description - * Provider that returns the {@link ui.router.state.$uiViewScroll} service function. - */ - function $ViewScrollProvider() { - var useAnchorScroll = false; - /** - * @ngdoc function - * @name ui.router.state.$uiViewScrollProvider#useAnchorScroll - * @methodOf ui.router.state.$uiViewScrollProvider - * - * @description - * Reverts back to using the core [`$anchorScroll`](http://docs.angularjs.org/api/ng.$anchorScroll) service for - * scrolling based on the url anchor. - */ - this.useAnchorScroll = function () { - useAnchorScroll = true; - }; - /** - * @ngdoc object - * @name ui.router.state.$uiViewScroll - * - * @requires $anchorScroll - * @requires $timeout - * - * @description - * When called with a jqLite element, it scrolls the element into view (after a - * `$timeout` so the DOM has time to refresh). - * - * If you prefer to rely on `$anchorScroll` to scroll the view to the anchor, - * this can be enabled by calling {@link ui.router.state.$uiViewScrollProvider#methods_useAnchorScroll `$uiViewScrollProvider.useAnchorScroll()`}. - */ - this.$get = ['$anchorScroll', '$timeout', function ($anchorScroll, $timeout) { - if (useAnchorScroll) { - return $anchorScroll; - } - return function ($element) { - return $timeout(function () { - $element[0].scrollIntoView(); - }, 0, false); - }; - }]; - } - angular.module('ui.router.state').provider('$uiViewScroll', $ViewScrollProvider); - - -/***/ } -/******/ ]) -}); -; -//# sourceMappingURL=angular-ui-router.js.map \ No newline at end of file diff --git a/client/angular/webpack.config.js b/client/angular/webpack.config.js deleted file mode 100644 index c70c0f6392..0000000000 --- a/client/angular/webpack.config.js +++ /dev/null @@ -1,136 +0,0 @@ -const path = require('path'); -const HtmlWebpackPlugin = require('html-webpack-plugin'); -const CopyWebpackPlugin = require('copy-webpack-plugin'); -const UglifyJsPlugin = require('uglifyjs-webpack-plugin'); -const webpackMerge = require('webpack-merge'); -const webpack = require('webpack'); -const autoPrefixer = require('autoprefixer'); - -const outDir = path.resolve(__dirname, 'dist'); -const srcDir = path.resolve(__dirname, 'src'); - -module.exports = function (env, argv) { - const isProductionMode = (argv["mode"] === "production"); - const disableMinimize = (env && env.disableMinimize) || false; - - const commonConfig = { - devtool: 'source-map', - entry: { - 'changepassword.ng': './src/modules/changepassword/changepassword.module' - - // (see production and development specific sections below for more entries) - }, - output: { - filename: "[name].js", - path: outDir - }, - resolve: { - extensions: [".ts", ".js"] - }, - module: { - rules: [ - { - test: /.ts$/, - loader: "ts-loader" - }, - { - test: /\.ts$/, - enforce: 'pre', - loader: 'tslint-loader' - }, - { - test: /index-dev\.html$/, - loader: 'html-loader', - exclude: /node_modules/ - }, - { - test: /\.html$/, - loader: 'ngtemplate-loader!html-loader', - exclude: /index-dev\.html$/ - }, - { - test: /\.(scss)$/, - loaders: [ 'style-loader', 'css-loader', 'sass-loader', { - loader: 'postcss-loader', - options: { - plugins: function () { - return [autoPrefixer('last 2 versions')] - } - } - }] - }, - { - test: /\.(png|jpg|jpeg|gif|svg)$/, - loaders: [ 'url-loader?limit=25000' ] - } - ] - }, - plugins: [ - new CopyWebpackPlugin([ - { from: 'node_modules/@microfocus/ux-ias/dist/ux-ias.css', to: 'vendor/ux-ias/' }, - { from: 'node_modules/@microfocus/ias-icons/dist/ias-icons.css', to: 'vendor/ux-ias/' }, - { from: 'node_modules/@microfocus/ias-icons/dist/fonts', to: 'vendor/ux-ias/fonts' } - ]) - ], - optimization: { - splitChunks: { - cacheGroups: { - vendor: { - test: /[\\/]node_modules[\\/]/, - name: "vendor", - chunks: "all" - } - } - } - } - }; - - if (isProductionMode) { - // Production-specific configuration - return webpackMerge(commonConfig, { - entry: { - 'peoplesearch.ng': './src/modules/peoplesearch/main', - 'helpdesk.ng': './src/modules/helpdesk/main' - }, - optimization:{ - minimize: !disableMinimize, - minimizer: [ - new UglifyJsPlugin({ - sourceMap: true, - uglifyOptions: { - compress: {warnings: false}, - comments: false - } - }) - ] - } - }); - } - else { - // Development-specific configuration - return webpackMerge(commonConfig, { - entry: { - 'peoplesearch.ng': './src/modules/peoplesearch/main', - 'helpdesk.ng': './src/modules/helpdesk/main' - }, - plugins: [ - new HtmlWebpackPlugin({ - chunks: ['peoplesearch.ng', 'vendor'], - chunksSortMode: 'dependency', - filename: 'peoplesearch.html', - template: 'src/index-dev.html', - inject: 'body', - livereload: true - }), - new HtmlWebpackPlugin({ - chunks: ['helpdesk.ng', 'vendor'], - chunksSortMode: 'dependency', - filename: 'helpdesk.html', - template: 'src/index-dev.html', - inject: 'body', - livereload: true - }) - ], - }); - } -}; diff --git a/client/pom.xml b/client/pom.xml index 5063e063d1..2520b02208 100644 --- a/client/pom.xml +++ b/client/pom.xml @@ -26,6 +26,9 @@ true + + true + @@ -91,7 +94,7 @@ com.github.eirslett frontend-maven-plugin - 1.14.0 + 1.13.3 ${node.version} ${npm.version} diff --git a/data-service/pom.xml b/data-service/pom.xml index 5ad4f4b4c3..7b57026f53 100644 --- a/data-service/pom.xml +++ b/data-service/pom.xml @@ -130,7 +130,7 @@ com.google.cloud.tools jib-maven-plugin - 3.4.0 + 3.3.2 make-docker-image diff --git a/docker/pom.xml b/docker/pom.xml index e4b259c450..193d15abd1 100644 --- a/docker/pom.xml +++ b/docker/pom.xml @@ -34,7 +34,7 @@ com.google.cloud.tools jib-maven-plugin - 3.4.0 + 3.3.2 make-docker-image diff --git a/lib-data/src/main/java/password/pwm/bean/EmailItemBean.java b/lib-data/src/main/java/password/pwm/bean/EmailItemBean.java index 28b6f55b19..69a08e86a1 100644 --- a/lib-data/src/main/java/password/pwm/bean/EmailItemBean.java +++ b/lib-data/src/main/java/password/pwm/bean/EmailItemBean.java @@ -20,9 +20,6 @@ package password.pwm.bean; -import lombok.Builder; - -@Builder public record EmailItemBean( String to, String from, diff --git a/lib-data/src/main/java/password/pwm/bean/PasswordStatus.java b/lib-data/src/main/java/password/pwm/bean/PasswordStatus.java index 148f33c4f3..d3f618e33d 100644 --- a/lib-data/src/main/java/password/pwm/bean/PasswordStatus.java +++ b/lib-data/src/main/java/password/pwm/bean/PasswordStatus.java @@ -20,20 +20,15 @@ package password.pwm.bean; -import lombok.Builder; -import lombok.Value; - -@Value -@Builder -public class PasswordStatus +public record PasswordStatus( + boolean expired, + boolean preExpired, + boolean violatesPolicy, + boolean warnPeriod +) { - private final boolean expired; - private final boolean preExpired; - private final boolean violatesPolicy; - private final boolean warnPeriod; - public boolean isEffectivelyExpired( ) { - return this.isExpired() || !this.isPreExpired() || !this.isViolatesPolicy(); + return this.expired() || !this.preExpired() || !this.violatesPolicy(); } } diff --git a/lib-data/src/main/java/password/pwm/bean/PhotoDataBean.java b/lib-data/src/main/java/password/pwm/bean/PhotoDataBean.java index 5ae8dd0148..f796b8f66f 100644 --- a/lib-data/src/main/java/password/pwm/bean/PhotoDataBean.java +++ b/lib-data/src/main/java/password/pwm/bean/PhotoDataBean.java @@ -20,15 +20,13 @@ package password.pwm.bean; -import lombok.Value; import password.pwm.data.ImmutableByteArray; -@Value -public class PhotoDataBean +public record PhotoDataBean( + String mimeType, + ImmutableByteArray contents +) { - private String mimeType; - private ImmutableByteArray contents; - @Override public String toString() { diff --git a/lib-data/src/main/java/password/pwm/data/FileUploadItem.java b/lib-data/src/main/java/password/pwm/data/FileUploadItem.java index c528cf728f..c700597443 100644 --- a/lib-data/src/main/java/password/pwm/data/FileUploadItem.java +++ b/lib-data/src/main/java/password/pwm/data/FileUploadItem.java @@ -20,12 +20,10 @@ package password.pwm.data; -import lombok.Value; - -@Value -public class FileUploadItem +public record FileUploadItem( + String name, + String type, + ImmutableByteArray content +) { - private final String name; - private final String type; - private final ImmutableByteArray content; } diff --git a/lib-data/src/main/java/password/pwm/data/ImmutableByteArray.java b/lib-data/src/main/java/password/pwm/data/ImmutableByteArray.java index a85b00fa59..5e52be35a3 100644 --- a/lib-data/src/main/java/password/pwm/data/ImmutableByteArray.java +++ b/lib-data/src/main/java/password/pwm/data/ImmutableByteArray.java @@ -24,7 +24,7 @@ import java.io.InputStream; import java.util.Arrays; -public class ImmutableByteArray +public final class ImmutableByteArray { private final byte[] bytes; diff --git a/lib-util/pom.xml b/lib-util/pom.xml index 16594051e2..75a877db77 100644 --- a/lib-util/pom.xml +++ b/lib-util/pom.xml @@ -104,7 +104,7 @@ commons-io commons-io - 2.14.0 + 2.13.0 diff --git a/lib-util/src/main/java/password/pwm/util/java/CollectionUtil.java b/lib-util/src/main/java/password/pwm/util/java/CollectionUtil.java index 7e1b0b1bef..606646cfbc 100644 --- a/lib-util/src/main/java/password/pwm/util/java/CollectionUtil.java +++ b/lib-util/src/main/java/password/pwm/util/java/CollectionUtil.java @@ -22,6 +22,8 @@ import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +import java.lang.reflect.Array; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; @@ -235,7 +237,6 @@ public static Set setUnion( final Set set1, final Set set2 ) } public static List convertListType( final List input, final Function convertFunction ) - { return stripNulls( input ).stream().map( convertFunction ).toList(); } @@ -265,4 +266,48 @@ public static > Set unmodifiableEnumSet( final Set input return Collections.unmodifiableSet( copyToEnumSet( inputSet, classOfT ) ); } + public static List addLists( final List... input ) + { + if ( input == null ) + { + return List.of(); + } + + final List resultList = new ArrayList<>(); + for ( final List loopList : input ) + { + resultList.addAll( stripNulls( loopList ) ); + } + + return List.copyOf( resultList ); + } + + public static List addListItems( final List list, final V... items ) + { + if ( items == null ) + { + return stripNulls( list ); + } + + return addLists( list, Arrays.asList( items ) ); + } + + public static List arrayToList( final T[] array ) + { + return array == null || array.length == 0 + ? List.of() + : CollectionUtil.stripNulls( Arrays.asList( array ) ); + } + + public static T[] listToArray( final List list, final Class classOfT ) + { + final T[] emptyArray = (T[]) Array.newInstance( classOfT, 0 ); + + if ( list == null || list.isEmpty() ) + { + return emptyArray; + } + + return list.toArray( emptyArray ); + } } diff --git a/lib-util/src/main/java/password/pwm/util/java/ConditionalTaskExecutor.java b/lib-util/src/main/java/password/pwm/util/java/ConditionalTaskExecutor.java index 558300864a..d8383cc45f 100644 --- a/lib-util/src/main/java/password/pwm/util/java/ConditionalTaskExecutor.java +++ b/lib-util/src/main/java/password/pwm/util/java/ConditionalTaskExecutor.java @@ -36,82 +36,59 @@ * reliance, the conditional is only evaluated during execution of {@code conditionallyExecuteTask()} so the conditional on its own is not * a strictly reliable indicator of how frequently the task will execute.

    */ -public final class ConditionalTaskExecutor +public interface ConditionalTaskExecutor { - private final Runnable task; - private final BooleanSupplier predicate; - private final Lock lock = new ReentrantLock(); - /** * Execute the task if the conditional has been met. Exceptions when running the task will be logged but not returned. */ - public void conditionallyExecuteTask( ) - { - lock.lock(); - try - { - if ( predicate.getAsBoolean() ) - { - task.run(); - } - } - finally - { - lock.unlock(); - } - } - - private ConditionalTaskExecutor( final Runnable task, final BooleanSupplier predicate ) - { - this.task = Objects.requireNonNull( task ); - this.predicate = Objects.requireNonNull( predicate ); - } - - public static ConditionalTaskExecutor forPeriodicTask( final Runnable task, final Duration timeDuration ) - { - return new ConditionalTaskExecutor( task, new TimeDurationPredicate( timeDuration ) ); - } + void conditionallyExecuteTask( ); - public static ConditionalTaskExecutor forPeriodicTask( + static ConditionalTaskExecutor forPeriodicTask( final Runnable task, - final Duration timeDuration, - final Duration firstExecutionDelay + final Duration timeDuration ) { - return new ConditionalTaskExecutor( task, new TimeDurationPredicate( timeDuration, firstExecutionDelay ) ); + return makeThreadSafeExecutor( task, makeTimeDurationPredicate( timeDuration ) ); } - private static class TimeDurationPredicate implements BooleanSupplier + private static ConditionalTaskExecutor makeThreadSafeExecutor( final Runnable task, final BooleanSupplier predicate ) { - private final Duration timeDuration; - private final AtomicReference nextExecuteTimestamp = new AtomicReference<>(); - - TimeDurationPredicate( final Duration timeDuration ) - { - this.timeDuration = timeDuration; - setNextTimeFromNow( timeDuration ); - } + Objects.requireNonNull( task ); + Objects.requireNonNull( predicate ); - TimeDurationPredicate( final Duration timeDuration, final Duration firstExecutionDelay ) + final Lock lock = new ReentrantLock(); + return () -> { - this.timeDuration = timeDuration; - setNextTimeFromNow( firstExecutionDelay ); - } + lock.lock(); + try + { + if ( predicate.getAsBoolean() ) + { + task.run(); + } + } + finally + { + lock.unlock(); + } + }; + } - private void setNextTimeFromNow( final Duration duration ) - { - nextExecuteTimestamp.set( Instant.now().plus( duration ) ); - } + private static BooleanSupplier makeTimeDurationPredicate( final Duration timeDuration ) + { + Objects.requireNonNull( timeDuration ); + final Instant firstTimestamp = Instant.now().plus( timeDuration ); + final AtomicReference nextExecuteTimestamp = new AtomicReference<>( firstTimestamp ); - @Override - public boolean getAsBoolean() + return () -> { - if ( Instant.now().isAfter( nextExecuteTimestamp.get() ) ) + final Instant now = Instant.now(); + if ( now.isAfter( nextExecuteTimestamp.get() ) ) { - setNextTimeFromNow( timeDuration ); + nextExecuteTimestamp.set( now.plus( timeDuration ) ); return true; } return false; - } + }; } } diff --git a/lib-util/src/main/java/password/pwm/util/java/EnumUtil.java b/lib-util/src/main/java/password/pwm/util/java/EnumUtil.java index c1a3e37576..2cee6aa7dd 100644 --- a/lib-util/src/main/java/password/pwm/util/java/EnumUtil.java +++ b/lib-util/src/main/java/password/pwm/util/java/EnumUtil.java @@ -20,6 +20,7 @@ package password.pwm.util.java; +import java.util.Collection; import java.util.Collections; import java.util.EnumSet; import java.util.Optional; @@ -121,4 +122,14 @@ public static > void forEach( final Class enumClass, final { EnumSet.allOf( enumClass ).forEach( consumer ); } + + public static > EnumSet copyToEnumSet( final Collection input, final Class eClass ) + { + if ( CollectionUtil.isEmpty( input ) ) + { + return EnumSet.noneOf( eClass ); + } + + return EnumSet.copyOf( input ); + } } diff --git a/lib-util/src/main/java/password/pwm/util/java/JavaHelper.java b/lib-util/src/main/java/password/pwm/util/java/JavaHelper.java index b0cb2104c8..308a5a9a84 100644 --- a/lib-util/src/main/java/password/pwm/util/java/JavaHelper.java +++ b/lib-util/src/main/java/password/pwm/util/java/JavaHelper.java @@ -44,11 +44,8 @@ import java.time.Instant; import java.util.ArrayList; import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; import java.util.HexFormat; import java.util.LinkedHashMap; -import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Objects; @@ -211,9 +208,9 @@ public static Instant parseIsoToInstant( final String input ) return Instant.parse( input ); } - public static Collection getAllMethodsForClass( final Class clazz ) + public static List getAllMethodsForClass( final Class clazz ) { - final LinkedHashSet methods = new LinkedHashSet<>( Arrays.asList( clazz.getDeclaredMethods() ) ); + final ArrayList methods = new ArrayList<>( Arrays.asList( clazz.getDeclaredMethods() ) ); final Class superClass = clazz.getSuperclass(); if ( superClass != null ) @@ -221,7 +218,7 @@ public static Collection getAllMethodsForClass( final Class clazz ) methods.addAll( getAllMethodsForClass( superClass ) ); } - return Collections.unmodifiableSet( methods ); + return List.copyOf( methods ); } /** @@ -501,33 +498,51 @@ public static long nextPositiveLong( final long input ) final long next = input + 1; return next > 0 ? next : 0; } - + + /** + * Creates all instances of all implementing classes using each classes no-argument constructor. + * + * @param sealedInterface An interface marked as {@link Class#isSealed()}. + * @return List of new object instances. + */ public static List instancesOfSealedInterface( final Class sealedInterface ) { + return instancesOfSealedInterface( sealedInterface, new NoArgumentInstanceCreator<>() ); + } + + private static List instancesOfSealedInterface( + final Class sealedInterface, + final Function, T> instanceCreator + ) + { + Objects.requireNonNull( sealedInterface ); + Objects.requireNonNull( instanceCreator ); + if ( !Objects.requireNonNull( sealedInterface ).isSealed() ) { throw new IllegalArgumentException( "sealedInterface argument is required to be marked as sealed" ); } - final Function, T> f = theClass -> + return Arrays.stream( sealedInterface.getPermittedSubclasses() ) + .map( clazz -> instanceCreator.apply( (Class) clazz ) ) + .toList(); + } + + private static class NoArgumentInstanceCreator implements Function, T> + { + @Override + public T apply( final Class theClass ) { try { final Constructor constructor = theClass.getDeclaredConstructor(); constructor.setAccessible( true ); - return ( T ) constructor.newInstance(); + return constructor.newInstance(); } catch ( final InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e ) { throw new RuntimeException( e ); } - }; - - final List list = new ArrayList<>(); - for ( final Class loopClass : sealedInterface.getPermittedSubclasses() ) - { - list.add( f.apply( (Class ) loopClass ) ); } - return List.copyOf( list ); } } diff --git a/lib-util/src/main/java/password/pwm/util/java/StringUtil.java b/lib-util/src/main/java/password/pwm/util/java/StringUtil.java index db507ec741..5c1069f91a 100644 --- a/lib-util/src/main/java/password/pwm/util/java/StringUtil.java +++ b/lib-util/src/main/java/password/pwm/util/java/StringUtil.java @@ -39,7 +39,6 @@ import java.util.Base64; import java.util.Collection; import java.util.Collections; -import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; @@ -47,7 +46,6 @@ import java.util.Map; import java.util.Objects; import java.util.Optional; -import java.util.Set; import java.util.function.Function; import java.util.function.Predicate; import java.util.stream.Collectors; @@ -422,36 +420,38 @@ public static List splitAndTrim( final String input, final String separa .collect( Collectors.toList() ); } - public static Collection whitespaceSplit( final String input ) + public static List whitespaceSplit( final String input ) { if ( input == null ) { - return Collections.emptyList(); + return List.of(); } final String[] splitValues = input.trim().split( "\\s+" ); - return Arrays.asList( splitValues ); + return CollectionUtil.arrayToList( splitValues ); } - public static String[] createStringChunks( final String str, final int size ) + public static List createStringChunks( final String str, final int size ) { - if ( size <= 0 || str == null || str.length() <= size ) + if ( str == null ) { - return new String[] - { - str, - }; + return List.of(); + } + + if ( size <= 0 || str.length() <= size ) + { + return List.of( str ); } final int numOfChunks = str.length() - size + 1; - final Set chunks = new HashSet<>( numOfChunks ); + final List chunks = new ArrayList<>( numOfChunks ); for ( int i = 0; i < numOfChunks; i++ ) { chunks.add( StringUtils.substring( str, i, i + size ) ); } - return chunks.toArray( new String[ numOfChunks ] ); + return List.copyOf( chunks ); } public static String collectionToString( final Collection collection ) @@ -803,29 +803,43 @@ public static List tokenizeString( * @return A non-null string. */ public static String stripEdgeChars( final String input, final char charToStrip ) + { + return stripEdgeChars( input, character -> character == charToStrip ); + } + + public static String stripEdgeChars( final String input, final Predicate stripCharPredicate ) { if ( StringUtil.isEmpty( input ) ) { return ""; } - if ( input.charAt( 0 ) != charToStrip && input.charAt( input.length() - 1 ) != charToStrip ) + if ( stripCharPredicate == null ) + { + return input; + } + + if ( !stripCharPredicate.test( input.charAt( 0 ) ) + && !stripCharPredicate.test( input.charAt( input.length() - 1 ) ) ) { return input; } final StringBuilder stringBuilder = new StringBuilder( input ); - while ( stringBuilder.length() > 0 && stringBuilder.charAt( 0 ) == charToStrip ) + while ( stringBuilder.length() > 0 + && stripCharPredicate.test( stringBuilder.charAt( 0 ) ) ) { stringBuilder.deleteCharAt( 0 ); } - while ( stringBuilder.length() > 0 && stringBuilder.charAt( stringBuilder.length() - 1 ) == charToStrip ) + while ( stringBuilder.length() > 0 + && stripCharPredicate.test( stringBuilder.charAt( stringBuilder.length() - 1 ) ) ) { stringBuilder.deleteCharAt( stringBuilder.length() - 1 ); } return stringBuilder.toString(); } + } diff --git a/pom.xml b/pom.xml index 1ec3dc6e40..a2921702ca 100644 --- a/pom.xml +++ b/pom.xml @@ -89,13 +89,6 @@ - - - org.apache.maven.extensions - maven-build-cache-extension - 1.0.1 - - org.commonjava.maven.plugins @@ -149,7 +142,7 @@ org.apache.maven.plugins maven-javadoc-plugin - 3.6.0 + 3.5.0 @@ -228,7 +221,7 @@ com.puppycrawl.tools checkstyle - 10.12.4 + 10.12.3 @@ -238,6 +231,7 @@ basedir=${project.root.basedir} build/checkstyle.xml + UTF-8 true true true @@ -260,6 +254,7 @@ basedir=${project.root.basedir} ${project.root.basedir}/build/checkstyle-header.xml + UTF-8 true true true @@ -282,6 +277,7 @@ basedir=${project.root.basedir} ${project.root.basedir}/build/checkstyle-jsp.xml + UTF-8 true true true @@ -304,7 +300,7 @@ com.github.spotbugs spotbugs-maven-plugin - 4.7.3.6 + 4.7.3.5 com.github.spotbugs @@ -438,7 +434,7 @@ org.projectlombok lombok - 1.18.30 + 1.18.28 provided @@ -468,7 +464,7 @@ test - org.wiremock + com.github.tomakehurst wiremock 3.0.1 test diff --git a/rest-test-service/src/main/java/password/pwm/resttest/RestTestSmsGatewayServlet.java b/rest-test-service/src/main/java/password/pwm/resttest/RestTestSmsGatewayServlet.java index efd2bee815..e60fb32c5c 100644 --- a/rest-test-service/src/main/java/password/pwm/resttest/RestTestSmsGatewayServlet.java +++ b/rest-test-service/src/main/java/password/pwm/resttest/RestTestSmsGatewayServlet.java @@ -21,7 +21,7 @@ package password.pwm.resttest; import com.google.gson.Gson; -import org.apache.commons.io.IOUtils; +import password.pwm.util.java.JavaHelper; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; @@ -31,6 +31,7 @@ import java.io.IOException; import java.io.InputStream; import java.io.PrintWriter; +import java.nio.charset.StandardCharsets; @WebServlet( name = "RestTestSmsGatewayServlet", @@ -47,7 +48,7 @@ protected void doPost( final HttpServletRequest req, final HttpServletResponse r { final SmsResponse instance = new SmsResponse(); final InputStream inputStream = req.getInputStream(); - final String body = IOUtils.toString( inputStream ); + final String body = JavaHelper.copyToString( inputStream, StandardCharsets.UTF_8, 10_000 ).orElseThrow(); final String[] messageContent = body.split( "=" ); final String message = messageContent[messageContent.length - 1]; diff --git a/server/src/main/java/password/pwm/AppProperty.java b/server/src/main/java/password/pwm/AppProperty.java index 8baa0ec4a2..e2974d7135 100644 --- a/server/src/main/java/password/pwm/AppProperty.java +++ b/server/src/main/java/password/pwm/AppProperty.java @@ -73,7 +73,6 @@ public enum AppProperty CONFIG_FILE_SCAN_FREQUENCY ( "config.fileScanFrequencyMS" ), CONFIG_NEWUSER_PASSWORD_POLICY_CACHE_MS ( "config.newuser.passwordPolicyCacheMS" ), CONFIG_THEME ( "config.theme" ), - CONFIG_JBCRYPT_PWLIB_ENABLE ( "config.enableJbCryptPwLibrary" ), CONFIG_EDITOR_BLOCK_OLD_IE ( "configEditor.blockOldIE" ), CONFIG_EDITOR_USER_PERMISSION_MATCH_LIMIT ( "configEditor.userPermission.matchResultsLimit" ), CONFIG_EDITOR_IDLE_TIMEOUT ( "configEditor.idleTimeoutSeconds" ), @@ -201,7 +200,6 @@ public enum AppProperty LOGGING_FILE_MAX_HISTORY ( "logging.file.maxHistory" ), LOGGING_FILE_PATH ( "logging.file.path" ), LOGGING_DEV_OUTPUT ( "logging.devOutput.enable" ), - LOGGING_LOG_CSP_REPORT ( "logging.cspReport.enable" ), NEWUSER_LDAP_USE_TEMP_PW ( "newUser.ldap.useTempPassword" ), NEWUSER_TOKEN_ALLOW_PLAIN_PW ( "newUser.token.allowPlainPassword" ), NMAS_THREADS_MAX_COUNT ( "nmas.threads.maxCount" ), @@ -242,8 +240,6 @@ public enum AppProperty PASSWORD_RULE_WORDLIST_FAIL_WHEN_CLOSED ( "password.rule.wordlist.failWhenClosed" ), PHOTO_CLIENT_CACHE_SECONDS ( "photo.clientCacheTimeSeconds" ), PHOTO_INTERNAL_HTTP_PROXY_ENABLE ( "photo.internalHttpProxy.enable" ), - PWNOTIFY_BATCH_COUNT ( "pwNotify.batch.count" ), - PWNOTIFY_BATCH_DELAY_TIME_MULTIPLIER ( "pwNotify.batch.delayTimeMultiplier" ), PWNOTIFY_MAX_LDAP_SEARCH_SIZE ( "pwNotify.maxLdapSearchSize" ), PWNOTIFY_MAX_SKIP_RERUN_WINDOW_SECONDS ( "pwNotify.maxSkipRerunWindowSeconds" ), PEOPLESEARCH_EXPORT_CSV_MAX_DEPTH ( "peoplesearch.export.csv.maxDepth" ), diff --git a/server/src/main/java/password/pwm/DomainProperty.java b/server/src/main/java/password/pwm/DomainProperty.java index 42146e79c9..9841892522 100644 --- a/server/src/main/java/password/pwm/DomainProperty.java +++ b/server/src/main/java/password/pwm/DomainProperty.java @@ -75,6 +75,7 @@ public enum DomainProperty LDAP_PASSWORD_CHANGE_HELPDESK_ENABLE ( "ldap.password.change.helpdesk.enable" ), LDAP_GUID_PATTERN ( "ldap.guid.pattern" ), LDAP_BROWSER_MAX_ENTRIES ( "ldap.browser.maxEntries" ), + LDAP_BROWSER_MAX_THREADS ( "ldap.browser.maxThreads" ), LDAP_SEARCH_PAGING_ENABLE ( "ldap.search.paging.enable" ), LDAP_SEARCH_PAGING_SIZE ( "ldap.search.paging.size" ), LDAP_SEARCH_PARALLEL_ENABLE ( "ldap.search.parallel.enable" ), diff --git a/server/src/main/java/password/pwm/PwmEnvironment.java b/server/src/main/java/password/pwm/PwmEnvironment.java index b8d7b29c85..52331ca627 100644 --- a/server/src/main/java/password/pwm/PwmEnvironment.java +++ b/server/src/main/java/password/pwm/PwmEnvironment.java @@ -67,7 +67,7 @@ public enum DeploymentPlatform War, Onejar, Docker, - Appliance,; + Appliance, } diff --git a/server/src/main/java/password/pwm/bean/DomainID.java b/server/src/main/java/password/pwm/bean/DomainID.java index 5b74ff9dd1..881af96810 100644 --- a/server/src/main/java/password/pwm/bean/DomainID.java +++ b/server/src/main/java/password/pwm/bean/DomainID.java @@ -41,7 +41,7 @@ public final class DomainID implements Comparable private static final List BUILT_IN = List.of( SYSTEM_DOMAIN_ID, DOMAIN_ID_DEFAULT ); - // sort placing 'system' first then alphabetically. + // sortw placing 'system' first then alphabetically. private static final Comparator COMPARATOR = Comparator.comparing( DomainID::isSystem ) .reversed() .thenComparing( DomainID::stringValue ); diff --git a/server/src/main/java/password/pwm/config/PwmSetting.java b/server/src/main/java/password/pwm/config/PwmSetting.java index bdcc3f04f7..13d2c6837e 100644 --- a/server/src/main/java/password/pwm/config/PwmSetting.java +++ b/server/src/main/java/password/pwm/config/PwmSetting.java @@ -1408,25 +1408,25 @@ public Map getDefaultValueDebugStrings( final Locale locale ) return this.getDefaultValue() .stream() .collect( Collectors.toUnmodifiableMap( - templateSetReference -> StringUtil.join( templateSetReference.getSettingTemplates(), "," ), - templateSetReference -> ( templateSetReference.getReference() ).toDebugString( locale ), + templateSetReference -> StringUtil.join( templateSetReference.settingTemplates(), "," ), + templateSetReference -> ( templateSetReference.reference() ).toDebugString( locale ), ( key1, key2 ) -> key1 ) ); } public Map getProperties( ) { - return getPwmSettingMetaData().getProperties(); + return getPwmSettingMetaData().properties(); } public Set getFlags( ) { - return getPwmSettingMetaData().getFlags(); + return getPwmSettingMetaData().flags(); } public Map getOptions() { - return getPwmSettingMetaData().getOptions(); + return getPwmSettingMetaData().options(); } public String getLabel( final Locale locale ) @@ -1451,27 +1451,27 @@ public String getDescription( final Locale locale ) public String getExample( final PwmSettingTemplateSet template ) { - return TemplateSetReference.referenceForTempleSet( getPwmSettingMetaData().getExamples(), template ); + return TemplateSetReference.referenceForTempleSet( getPwmSettingMetaData().examples(), template ); } public boolean isRequired( ) { - return getPwmSettingMetaData().isRequired(); + return getPwmSettingMetaData().required(); } public boolean isHidden( ) { - return getPwmSettingMetaData().isHidden(); + return getPwmSettingMetaData().hidden(); } public int getLevel( ) { - return getPwmSettingMetaData().getLevel(); + return getPwmSettingMetaData().level(); } public Pattern getRegExPattern( ) { - return getPwmSettingMetaData().getPattern(); + return getPwmSettingMetaData().pattern(); } public static Optional forKey( final String key ) @@ -1496,7 +1496,7 @@ public String toMenuLocationDebug( public Collection getLDAPPermissionInfo() { - return getPwmSettingMetaData().getLdapPermissionInfo(); + return getPwmSettingMetaData().ldapPermissionInfo(); } public static List sortedValues() diff --git a/server/src/main/java/password/pwm/config/PwmSettingMetaData.java b/server/src/main/java/password/pwm/config/PwmSettingMetaData.java index 96185dc1e1..09098d303e 100644 --- a/server/src/main/java/password/pwm/config/PwmSettingMetaData.java +++ b/server/src/main/java/password/pwm/config/PwmSettingMetaData.java @@ -20,8 +20,6 @@ package password.pwm.config; -import lombok.Builder; -import lombok.Value; import org.jrivard.xmlchai.XmlElement; import password.pwm.util.java.EnumUtil; import password.pwm.util.java.JavaHelper; @@ -40,30 +38,28 @@ import java.util.regex.Pattern; import java.util.regex.PatternSyntaxException; -@Value -@Builder /** - * Utility class for reading PwmSetting.xml values and making them available to PWM. Since PwmSetting + *

    Utility class for reading PwmSetting.xml values and making them available to PWM. Since PwmSetting * enums are fairly complex, there can be issues with static initialization circular dependencies during - * initialization of the classes in this package. + * initialization of the classes in this package.

    * - * This class tries to be self-sufficient during initialization, and then provide values for {@link PwmSetting} - * method invocations. + *

    This class tries to be self-sufficient during initialization, and then provide values for {@link PwmSetting} + * method invocations.

    */ -class PwmSettingMetaData +record PwmSettingMetaData( + List> examples, + Map options, + Set flags, + Map properties, + Collection ldapPermissionInfo, + boolean required, + boolean hidden, + int level, + Pattern pattern +) { private static final Map META_DATA_MAP = initMap(); - private final List> examples; - private final Map options; - private final Set flags; - private final Map properties; - private final Collection ldapPermissionInfo; - private final boolean required; - private final boolean hidden; - private final int level; - private final Pattern pattern; - private static Map initMap() { final EnumMap map = new EnumMap<>( PwmSetting.class ); @@ -73,17 +69,16 @@ private static Map initMap() final PwmSetting pwmSetting = PwmSetting.forKey( settingElement.getAttribute( PwmSettingXml.XML_ATTRIBUTE_KEY ) .orElseThrow() ).orElseThrow(); - final PwmSettingMetaData pwmSettingMetaData = PwmSettingMetaData.builder() - .examples( InitReader.readExamples( settingElement ) ) - .properties( InitReader.readProperties( pwmSetting, settingElement ) ) - .options( InitReader.readOptions( pwmSetting, settingElement ) ) - .flags( InitReader.readFlags( settingElement ) ) - .ldapPermissionInfo( InitReader.readLdapPermissionInfo( settingElement ) ) - .required( InitReader.readRequired( settingElement ) ) - .hidden( InitReader.readHidden( pwmSetting, settingElement ) ) - .level( InitReader.readLevel( settingElement ) ) - .pattern( InitReader.readPattern( pwmSetting, settingElement ) ) - .build(); + final PwmSettingMetaData pwmSettingMetaData = new PwmSettingMetaData( + InitReader.readExamples( settingElement ), + InitReader.readOptions( pwmSetting, settingElement ), + InitReader.readFlags( settingElement ), + InitReader.readProperties( pwmSetting, settingElement ), + InitReader.readLdapPermissionInfo( settingElement ), + InitReader.readRequired( settingElement ), + InitReader.readHidden( pwmSetting, settingElement ), + InitReader.readLevel( settingElement ), + InitReader.readPattern( pwmSetting, settingElement ) ); map.put( pwmSetting, pwmSettingMetaData ); } diff --git a/server/src/main/java/password/pwm/config/PwmSettingTemplateSet.java b/server/src/main/java/password/pwm/config/PwmSettingTemplateSet.java index 5bc1e107dc..bf2cb17f7a 100644 --- a/server/src/main/java/password/pwm/config/PwmSettingTemplateSet.java +++ b/server/src/main/java/password/pwm/config/PwmSettingTemplateSet.java @@ -20,7 +20,6 @@ package password.pwm.config; -import lombok.Value; import password.pwm.util.java.CollectionUtil; import password.pwm.util.java.EnumUtil; @@ -28,11 +27,10 @@ import java.util.Set; import java.util.stream.Collectors; -@Value -public class PwmSettingTemplateSet +public record PwmSettingTemplateSet( + Set templates +) { - private final Set templates; - public PwmSettingTemplateSet( final Set templates ) { final Set workingSet = CollectionUtil.copyToEnumSet( templates, PwmSettingTemplate.class ); @@ -44,16 +42,11 @@ public PwmSettingTemplateSet( final Set templates ) workingSet.addAll( EnumUtil.enumStream( PwmSettingTemplate.Type.class ) .filter( type -> !seenTypes.contains( type ) ) .map( PwmSettingTemplate.Type::getDefaultValue ) - .collect( Collectors.toUnmodifiableSet( ) ) ); + .collect( Collectors.toSet( ) ) ); this.templates = Set.copyOf( workingSet ); } - public Set getTemplates( ) - { - return templates; - } - public static PwmSettingTemplateSet getDefault( ) { return new PwmSettingTemplateSet( null ); @@ -72,6 +65,6 @@ public static List allValues() { return EnumUtil.enumStream( PwmSettingTemplate.class ) .map( pwmSettingTemplate -> new PwmSettingTemplateSet( Set.of( pwmSettingTemplate ) ) ) - .collect( Collectors.toUnmodifiableList() ); + .toList(); } } diff --git a/server/src/main/java/password/pwm/config/TemplateSetReference.java b/server/src/main/java/password/pwm/config/TemplateSetReference.java index 4ef261c9b9..7dd687bf16 100644 --- a/server/src/main/java/password/pwm/config/TemplateSetReference.java +++ b/server/src/main/java/password/pwm/config/TemplateSetReference.java @@ -20,17 +20,16 @@ package password.pwm.config; -import lombok.Value; import password.pwm.util.java.CollectionUtil; import java.util.List; import java.util.Set; -@Value -class TemplateSetReference +record TemplateSetReference( + T reference, + Set settingTemplates +) { - private final T reference; - private final Set settingTemplates; static T referenceForTempleSet( final List> templateSetReferences, @@ -48,23 +47,23 @@ static T referenceForTempleSet( if ( templateSetReferences.size() == 1 ) { - return templateSetReferences.get( 0 ).getReference(); + return templateSetReferences.get( 0 ).reference(); } for ( int matchCountExamSize = templateSetReferences.size(); matchCountExamSize > 0; matchCountExamSize-- ) { for ( final TemplateSetReference templateSetReference : templateSetReferences ) { - final Set temporarySet = CollectionUtil.copyToEnumSet( templateSetReference.getSettingTemplates(), PwmSettingTemplate.class ); - temporarySet.retainAll( effectiveTemplateSet.getTemplates() ); + final Set temporarySet = CollectionUtil.copyToEnumSet( templateSetReference.settingTemplates(), PwmSettingTemplate.class ); + temporarySet.retainAll( effectiveTemplateSet.templates() ); final int matchCount = temporarySet.size(); if ( matchCount == matchCountExamSize ) { - return templateSetReference.getReference(); + return templateSetReference.reference(); } } } - return templateSetReferences.get( 0 ).getReference(); + return templateSetReferences.get( 0 ).reference(); } } diff --git a/server/src/main/java/password/pwm/config/option/OTPStorageFormat.java b/server/src/main/java/password/pwm/config/option/OTPStorageFormat.java index 45ad9a7de8..cc73337fd5 100644 --- a/server/src/main/java/password/pwm/config/option/OTPStorageFormat.java +++ b/server/src/main/java/password/pwm/config/option/OTPStorageFormat.java @@ -20,55 +20,55 @@ package password.pwm.config.option; +import password.pwm.svc.otp.formatter.Base32Formatter; +import password.pwm.svc.otp.formatter.OtpFormatter; +import password.pwm.svc.otp.formatter.PamOtpFormatter; +import password.pwm.svc.otp.formatter.PwmJsonFormatter; +import password.pwm.svc.otp.formatter.UrlOtpFormatter; +import password.pwm.util.java.EnumUtil; + +import java.util.Objects; + /** - * One Time Password Storage Format. + * One Time Password Storage Format, ordered by de-formatting order attempting. */ public enum OTPStorageFormat { - PWM( true, true ), - BASE32SECRET( false ), - OTPURL( false ), - PAM( true, false ); + PWM( new PwmJsonFormatter(), Flag.useRecoveryCodes, Flag.hashRecoveryCodes ), + PAM( new PamOtpFormatter(), Flag.useRecoveryCodes ), + OTPURL( new UrlOtpFormatter() ), + BASE32SECRET( new Base32Formatter() ); private final boolean useRecoveryCodes; private final boolean hashRecoveryCodes; + private final OtpFormatter formatter; - OTPStorageFormat( final boolean useRecoveryCodes ) + private enum Flag { - this.useRecoveryCodes = useRecoveryCodes; - - // defaults to true, if recovery codes enabled. - this.hashRecoveryCodes = useRecoveryCodes; + useRecoveryCodes, + hashRecoveryCodes, } - OTPStorageFormat( - final boolean useRecoveryCodes, - final boolean hashRecoveryCodes - ) + OTPStorageFormat( final OtpFormatter formatter, final Flag... flags ) { - this.useRecoveryCodes = useRecoveryCodes; - this.hashRecoveryCodes = useRecoveryCodes && hashRecoveryCodes; + this.useRecoveryCodes = EnumUtil.enumArrayContainsValue( flags, Flag.useRecoveryCodes ); + this.hashRecoveryCodes = EnumUtil.enumArrayContainsValue( flags, Flag.hashRecoveryCodes ); + this.formatter = Objects.requireNonNull( formatter ); } - /** - * Check support for recovery codes. - * - * @return true if recovery codes are supported. - */ public boolean supportsRecoveryCodes( ) { return useRecoveryCodes; } - /** - * Check support for hashed recovery codes. - * - * @return true if recovery codes are supported and hashes are to be used. - */ public boolean supportsHashedRecoveryCodes( ) { return useRecoveryCodes && hashRecoveryCodes; } + public OtpFormatter getFormatter() + { + return formatter; + } } diff --git a/server/src/main/java/password/pwm/config/profile/HelpdeskProfile.java b/server/src/main/java/password/pwm/config/profile/HelpdeskProfile.java index e37c1a73e5..96338de55c 100644 --- a/server/src/main/java/password/pwm/config/profile/HelpdeskProfile.java +++ b/server/src/main/java/password/pwm/config/profile/HelpdeskProfile.java @@ -27,9 +27,8 @@ import password.pwm.config.stored.StoredConfiguration; import password.pwm.config.value.VerificationMethodValue; -import java.util.Collection; import java.util.Collections; -import java.util.LinkedHashSet; +import java.util.EnumSet; import java.util.Set; public class HelpdeskProfile extends AbstractProfile implements Profile @@ -47,15 +46,15 @@ public ProfileDefinition profileType( ) return PROFILE_TYPE; } - public Collection readOptionalVerificationMethods( ) + public Set readOptionalVerificationMethods( ) { - final Set result = new LinkedHashSet<>(); + final Set result = EnumSet.noneOf( IdentityVerificationMethod.class ); result.addAll( readVerificationMethods( PwmSetting.HELPDESK_VERIFICATION_METHODS, VerificationMethodValue.EnabledState.optional ) ); result.addAll( readVerificationMethods( PwmSetting.HELPDESK_VERIFICATION_METHODS, VerificationMethodValue.EnabledState.required ) ); return Collections.unmodifiableSet( result ); } - public Collection readRequiredVerificationMethods( ) + public Set readRequiredVerificationMethods( ) { return readVerificationMethods( PwmSetting.HELPDESK_VERIFICATION_METHODS, VerificationMethodValue.EnabledState.required ); } diff --git a/server/src/main/java/password/pwm/config/stored/ConfigurationCleaner.java b/server/src/main/java/password/pwm/config/stored/ConfigurationCleaner.java index c8cbeffd46..d8d99e528d 100644 --- a/server/src/main/java/password/pwm/config/stored/ConfigurationCleaner.java +++ b/server/src/main/java/password/pwm/config/stored/ConfigurationCleaner.java @@ -107,7 +107,7 @@ private static void doConversion( + PwmSetting.PASSWORD_POLICY_AD_COMPLEXITY_LEVEL + ", value=" + ValueTypeConverter.valueToString( value ) ); final Optional valueMetaData = existingConfig.readMetaData( key ); - final UserIdentity userIdentity = valueMetaData.map( ValueMetaData::getUserIdentity ).orElse( null ); + final UserIdentity userIdentity = valueMetaData.map( ValueMetaData::userIdentity ).orElse( null ); try { final StoredConfigKey writeKey = StoredConfigKey.forSetting( PwmSetting.PASSWORD_POLICY_AD_COMPLEXITY_LEVEL, profileID.orElse( null ), key.getDomainID() ); @@ -140,7 +140,7 @@ public void accept( final StoredConfigurationModifier modifier ) ? new StringValue( RecoveryMinLifetimeOption.NONE.name() ) : new StringValue( RecoveryMinLifetimeOption.ALLOW.name() ); final Optional existingData = oldConfig.readSettingMetadata( key ); - final UserIdentity newActor = existingData.map( ValueMetaData::getUserIdentity ).orElse( null ); + final UserIdentity newActor = existingData.map( ValueMetaData::userIdentity ).orElse( null ); LOGGER.info( SESSION_LABEL, () -> "converting deprecated non-default setting " + PwmSetting.RECOVERY_ENFORCE_MINIMUM_PASSWORD_LIFETIME.toMenuLocationDebug( profileID, PwmConstants.DEFAULT_LOCALE ) + "/" + profileID + " to replacement setting " + PwmSetting.RECOVERY_MINIMUM_PASSWORD_LIFETIME_OPTIONS.toMenuLocationDebug( profileID, PwmConstants.DEFAULT_LOCALE ) @@ -178,7 +178,7 @@ public void accept( final StoredConfigurationModifier modifier ) newValues.add( WebServiceUsage.Statistics.name() ); final Optional valueMetaData = oldConfig.readMetaData( existingPubWebservicesKey ); - final UserIdentity userIdentity = valueMetaData.map( ValueMetaData::getUserIdentity ).orElse( null ); + final UserIdentity userIdentity = valueMetaData.map( ValueMetaData::userIdentity ).orElse( null ); final StoredConfigKey destKey = StoredConfigKey.forSetting( PwmSetting.WEBSERVICES_PUBLIC_ENABLE, null, domainID ); modifier.writeSetting( destKey, new OptionListValue( newValues ), userIdentity ); diff --git a/server/src/main/java/password/pwm/config/stored/ConfigurationFileManager.java b/server/src/main/java/password/pwm/config/stored/ConfigurationFileManager.java index 30be4d38c0..5fc18f648d 100644 --- a/server/src/main/java/password/pwm/config/stored/ConfigurationFileManager.java +++ b/server/src/main/java/password/pwm/config/stored/ConfigurationFileManager.java @@ -288,7 +288,7 @@ private void auditModifiedSettings( final PwmApplication pwmApplication, final S for ( final StoredConfigKey key : changedKeys ) { final Optional valueMetaData = newConfig.readMetaData( key ); - final UserIdentity userIdentity = valueMetaData.map( ValueMetaData::getUserIdentity ).orElse( null ); + final UserIdentity userIdentity = valueMetaData.map( ValueMetaData::userIdentity ).orElse( null ); final Optional storedValue = newConfig.readStoredValue( key ); final StringBuilder modifyMessage = new StringBuilder(); diff --git a/server/src/main/java/password/pwm/config/stored/StoredConfigData.java b/server/src/main/java/password/pwm/config/stored/StoredConfigData.java index bbd505356e..55f866e71e 100644 --- a/server/src/main/java/password/pwm/config/stored/StoredConfigData.java +++ b/server/src/main/java/password/pwm/config/stored/StoredConfigData.java @@ -46,29 +46,29 @@ class StoredConfigData @Singular private Map metaDatas; - @Value - static class ValueAndMetaCarrier + record ValueAndMetaCarrier( + StoredConfigKey key, + StoredValue value, + ValueMetaData metaData + ) { - private final StoredConfigKey key; - private final StoredValue value; - private final ValueMetaData metaData; } static Map carrierAsMetaDataMap( final Collection input ) { return input.stream() - .filter( ( t ) -> t.getKey() != null && t.getMetaData() != null ) + .filter( ( t ) -> t.key() != null && t.metaData() != null ) .collect( Collectors.toMap( - StoredConfigData.ValueAndMetaCarrier::getKey, - StoredConfigData.ValueAndMetaCarrier::getMetaData ) ); + StoredConfigData.ValueAndMetaCarrier::key, + StoredConfigData.ValueAndMetaCarrier::metaData ) ); } static Map carrierAsStoredValueMap( final Collection input ) { return input.stream() - .filter( ( t ) -> t.getKey() != null && t.getValue() != null ) + .filter( ( t ) -> t.key() != null && t.value() != null ) .collect( Collectors.toMap( - StoredConfigData.ValueAndMetaCarrier::getKey, - StoredConfigData.ValueAndMetaCarrier::getValue ) ); + StoredConfigData.ValueAndMetaCarrier::key, + StoredConfigData.ValueAndMetaCarrier::value ) ); } } diff --git a/server/src/main/java/password/pwm/config/stored/StoredConfigXmlSerializer.java b/server/src/main/java/password/pwm/config/stored/StoredConfigXmlSerializer.java index 1272b6e540..e443a7a9f2 100644 --- a/server/src/main/java/password/pwm/config/stored/StoredConfigXmlSerializer.java +++ b/server/src/main/java/password/pwm/config/stored/StoredConfigXmlSerializer.java @@ -623,16 +623,16 @@ private static void decorateElementWithMetaData( if ( valueMetaData.isPresent() ) { - if ( valueMetaData.get().getUserIdentity() != null ) + if ( valueMetaData.get().userIdentity() != null ) { final XmlElement metaElement = XmlFactory.getFactory().newElement( StoredConfigXmlConstants.XML_ATTRIBUTE_MODIFY_USER ); - metaElement.setText( valueMetaData.get().getUserIdentity().toDelimitedKey() ); + metaElement.setText( valueMetaData.get().userIdentity().toDelimitedKey() ); xmlElement.attachElement( metaElement ); } - if ( valueMetaData.get().getModifyDate() != null ) + if ( valueMetaData.get().modifyDate() != null ) { - xmlElement.setAttribute( StoredConfigXmlConstants.XML_ATTRIBUTE_MODIFY_TIME, StringUtil.toIsoDate( valueMetaData.get().getModifyDate() ) ); + xmlElement.setAttribute( StoredConfigXmlConstants.XML_ATTRIBUTE_MODIFY_TIME, StringUtil.toIsoDate( valueMetaData.get().modifyDate() ) ); } } } @@ -891,9 +891,9 @@ public void accept( final XmlDocument xmlDocument ) throws PwmUnrecoverableExcep final Optional valueAndMetaTuple = documentReader.readSetting( PwmSetting.APP_PROPERTY_OVERRIDES, null ); valueAndMetaTuple.ifPresent( ( t ) -> { - if ( t.getValue() != null ) + if ( t.value() != null ) { - existingValues.addAll( ValueTypeConverter.valueToStringArray( t.getValue() ) ); + existingValues.addAll( ValueTypeConverter.valueToStringArray( t.value() ) ); } } ); } diff --git a/server/src/main/java/password/pwm/config/stored/StoredConfigurationModifier.java b/server/src/main/java/password/pwm/config/stored/StoredConfigurationModifier.java index 6159bf9e1a..2156b177b0 100644 --- a/server/src/main/java/password/pwm/config/stored/StoredConfigurationModifier.java +++ b/server/src/main/java/password/pwm/config/stored/StoredConfigurationModifier.java @@ -22,6 +22,7 @@ import password.pwm.bean.DomainID; import password.pwm.bean.UserIdentity; +import password.pwm.config.AppConfig; import password.pwm.config.value.LocalizedStringValue; import password.pwm.config.value.StoredValue; import password.pwm.config.value.StringValue; @@ -193,19 +194,11 @@ public int modificationCount() return modifications.get(); } - public void setPassword( final String password ) throws PwmOperationalException, PwmUnrecoverableException { - if ( password == null || password.isEmpty() ) - { - throw new PwmOperationalException( new ErrorInformation( PwmError.CONFIG_FORMAT_ERROR, null, new String[] - { - "can not set blank password", - } - ) ); - } - final String trimmedPassword = password.trim(); + final String trimmedPassword = password == null ? "" : password.trim(); + if ( trimmedPassword.length() < 1 ) { throw new PwmOperationalException( new ErrorInformation( PwmError.CONFIG_FORMAT_ERROR, null, new String[] @@ -215,8 +208,8 @@ public void setPassword( final String password ) ) ); } - - final String passwordHash = BCrypt.hashPassword( password ); + final AppConfig appConfig = AppConfig.forStoredConfig( this.newStoredConfiguration() ); + final String passwordHash = BCrypt.createBCrypt( appConfig ).hashPassword( trimmedPassword ); this.writeConfigProperty( ConfigurationProperty.PASSWORD_HASH, passwordHash ); } diff --git a/server/src/main/java/password/pwm/config/stored/StoredConfigurationUtil.java b/server/src/main/java/password/pwm/config/stored/StoredConfigurationUtil.java index 8c962f60aa..a515c99608 100644 --- a/server/src/main/java/password/pwm/config/stored/StoredConfigurationUtil.java +++ b/server/src/main/java/password/pwm/config/stored/StoredConfigurationUtil.java @@ -43,8 +43,8 @@ import password.pwm.util.PasswordData; import password.pwm.util.java.CollectionUtil; import password.pwm.util.java.JavaHelper; -import password.pwm.util.java.PwmUtil; import password.pwm.util.java.PwmExceptionLoggingConsumer; +import password.pwm.util.java.PwmUtil; import password.pwm.util.java.StringUtil; import password.pwm.util.logging.PwmLogger; import password.pwm.util.secure.BCrypt; @@ -138,7 +138,7 @@ public static StoredConfiguration copyConfigAndBlankAllPasswords( final StoredCo if ( pwmSetting.getSyntax() == PwmSettingSyntax.PASSWORD ) { final Optional valueMetaData = storedConfig.readSettingMetadata( storedConfigItemKey ); - final UserIdentity userIdentity = valueMetaData.map( ValueMetaData::getUserIdentity ).orElse( null ); + final UserIdentity userIdentity = valueMetaData.map( ValueMetaData::userIdentity ).orElse( null ); final PasswordValue passwordValue = new PasswordValue( new PasswordData( PwmConstants.LOG_REMOVED_VALUE_REPLACEMENT ) ); modifier.writeSetting( storedConfigItemKey, passwordValue, userIdentity ); } @@ -193,7 +193,7 @@ public static List validateValues( final StoredConfiguration storedConfi return CollectionUtil.iteratorToStream( storedConfiguration.keys() ) .filter( key -> key.isRecordType( StoredConfigKey.RecordType.SETTING ) ) .flatMap( validateSettingFunction ) - .collect( Collectors.toUnmodifiableList() ); + .toList(); } public static boolean verifyPassword( final StoredConfiguration storedConfiguration, final String password ) @@ -203,7 +203,9 @@ public static boolean verifyPassword( final StoredConfiguration storedConfigurat return false; } final Optional passwordHash = storedConfiguration.readConfigProperty( ConfigurationProperty.PASSWORD_HASH ); - return passwordHash.isPresent() && BCrypt.testAnswer( password, passwordHash.get(), AppConfig.forStoredConfig( storedConfiguration ) ); + return passwordHash.isPresent() && BCrypt + .createBCrypt( AppConfig.forStoredConfig( storedConfiguration ) ) + .testAnswer( password, passwordHash.get() ); } public static boolean hasPassword( final StoredConfiguration storedConfiguration ) @@ -233,7 +235,9 @@ public static void setPassword( final StoredConfigurationModifier storedConfigur ) ); } - final String passwordHash = BCrypt.hashPassword( password ); + final String passwordHash = BCrypt + .createBCrypt( AppConfig.forStoredConfig( storedConfiguration.newStoredConfiguration() ) ) + .hashPassword( password ); storedConfiguration.writeConfigProperty( ConfigurationProperty.PASSWORD_HASH, passwordHash ); } diff --git a/server/src/main/java/password/pwm/config/stored/ValueMetaData.java b/server/src/main/java/password/pwm/config/stored/ValueMetaData.java index f2ed27e08f..8a888d46ef 100644 --- a/server/src/main/java/password/pwm/config/stored/ValueMetaData.java +++ b/server/src/main/java/password/pwm/config/stored/ValueMetaData.java @@ -20,16 +20,13 @@ package password.pwm.config.stored; -import lombok.Builder; -import lombok.Value; import password.pwm.bean.UserIdentity; import java.time.Instant; -@Value -@Builder( toBuilder = true ) -public class ValueMetaData +public record ValueMetaData( + Instant modifyDate, + UserIdentity userIdentity +) { - private Instant modifyDate; - private UserIdentity userIdentity; } diff --git a/server/src/main/java/password/pwm/config/value/CustomLinkValue.java b/server/src/main/java/password/pwm/config/value/CustomLinkValue.java index 2221c0ca38..ac03148ed9 100644 --- a/server/src/main/java/password/pwm/config/value/CustomLinkValue.java +++ b/server/src/main/java/password/pwm/config/value/CustomLinkValue.java @@ -110,11 +110,11 @@ public List validateValue( final PwmSetting pwmSetting ) final Set seenNames = new HashSet<>( values.size() ); for ( final CustomLinkConfiguration loopConfig : values ) { - if ( seenNames.contains( loopConfig.getName().toLowerCase() ) ) + if ( seenNames.contains( loopConfig.name().toLowerCase() ) ) { - return Collections.singletonList( "each form name must be unique: " + loopConfig.getName() ); + return Collections.singletonList( "each form name must be unique: " + loopConfig.name() ); } - seenNames.add( loopConfig.getName().toLowerCase() ); + seenNames.add( loopConfig.name().toLowerCase() ); } return Collections.emptyList(); @@ -128,12 +128,12 @@ public String toDebugString( final Locale locale ) final StringBuilder sb = new StringBuilder(); for ( final CustomLinkConfiguration formRow : values ) { - sb.append( "Link Name:" ).append( formRow.getName() ).append( '\n' ); - sb.append( " Type:" ).append( formRow.getType() ); + sb.append( "Link Name:" ).append( formRow.name() ).append( '\n' ); + sb.append( " Type:" ).append( formRow.type() ); sb.append( '\n' ); - sb.append( " Description:" ).append( JsonFactory.get().serializeMap( formRow.getLabels() ) ).append( '\n' ); - sb.append( " New Window:" ).append( formRow.isCustomLinkNewWindow() ).append( '\n' ); - sb.append( " Url:" ).append( formRow.getCustomLinkUrl() ).append( '\n' ); + sb.append( " Description:" ).append( JsonFactory.get().serializeMap( formRow.labels() ) ).append( '\n' ); + sb.append( " New Window:" ).append( formRow.customLinkNewWindow() ).append( '\n' ); + sb.append( " Url:" ).append( formRow.customLinkUrl() ).append( '\n' ); } return sb.toString(); } diff --git a/server/src/main/java/password/pwm/config/value/FileValue.java b/server/src/main/java/password/pwm/config/value/FileValue.java index ba87b2e122..dd0774b7dd 100644 --- a/server/src/main/java/password/pwm/config/value/FileValue.java +++ b/server/src/main/java/password/pwm/config/value/FileValue.java @@ -20,9 +20,7 @@ package password.pwm.config.value; -import lombok.Builder; import lombok.EqualsAndHashCode; -import lombok.Value; import org.jrivard.xmlchai.XmlElement; import org.jrivard.xmlchai.XmlFactory; import password.pwm.PwmConstants; @@ -59,11 +57,11 @@ public class FileValue extends AbstractValue implements StoredValue private final Map values; - @Value - public static class FileInformation + public record FileInformation( + String filename, + String filetype + ) { - private final String filename; - private final String filetype; } @EqualsAndHashCode @@ -113,7 +111,7 @@ public ImmutableByteArray getContents() return byteContents.get(); } - + } public static FileValue newFileValue( final String filename, final String fileMimeType, final ImmutableByteArray contents ) @@ -244,8 +242,8 @@ List> asMetaData( ) final FileValue.FileInformation fileInformation = entry.getKey(); final FileContent fileContent = entry.getValue(); final Map details = new LinkedHashMap<>(); - details.put( "name", fileInformation.getFilename() ); - details.put( "type", fileInformation.getFiletype() ); + details.put( "name", fileInformation.filename() ); + details.put( "type", fileInformation.filetype() ); details.put( "size", fileContent.size() ); try { @@ -273,12 +271,11 @@ public List toInfoMap( ) final FileContent fileContent = entry.getValue(); try { - returnObj.add( FileInfo.builder() - .name( fileInformation.getFilename() ) - .type( fileInformation.getFiletype() ) - .size( fileContent.size() ) - .sha512sum( fileContent.sha512sum() ) - .build() ); + returnObj.add( new FileInfo( + fileInformation.filename(), + fileInformation.filetype(), + fileContent.size(), + fileContent.sha512sum() ) ); } catch ( final PwmUnrecoverableException e ) { @@ -288,13 +285,12 @@ public List toInfoMap( ) return Collections.unmodifiableList( returnObj ); } - @Value - @Builder - public static class FileInfo + public record FileInfo( + String name, + String type, + long size, + String sha512sum + ) { - private String name; - private String type; - private long size; - private String sha512sum; } } diff --git a/server/src/main/java/password/pwm/config/value/NamedSecretValue.java b/server/src/main/java/password/pwm/config/value/NamedSecretValue.java index 6090c8c1f2..d0d28283e5 100644 --- a/server/src/main/java/password/pwm/config/value/NamedSecretValue.java +++ b/server/src/main/java/password/pwm/config/value/NamedSecretValue.java @@ -174,7 +174,7 @@ public List toXmlValues( final String valueElementName, final XmlOut for ( final Map.Entry entry : values.entrySet() ) { final String name = entry.getKey(); - final PasswordData passwordData = entry.getValue().getPassword(); + final PasswordData passwordData = entry.getValue().password(); final String encodedValue = SecureEngine.encryptToString( passwordData.getStringValue(), xmlOutputProcessData.getPwmSecurityKey(), PwmBlockAlgorithm.CONFIG ); final XmlElement newValueElement = XmlFactory.getFactory().newElement( "value" ); final XmlElement nameElement = XmlFactory.getFactory().newElement( ELEMENT_NAME ); @@ -185,7 +185,7 @@ public List toXmlValues( final String valueElementName, final XmlOut newValueElement.attachElement( nameElement ); newValueElement.attachElement( encodedValueElement ); - for ( final String usages : values.get( name ).getUsage() ) + for ( final String usages : values.get( name ).usage() ) { final XmlElement usageElement = XmlFactory.getFactory().newElement( ELEMENT_USAGE ); usageElement.setText( usages ); @@ -216,7 +216,7 @@ public String toDebugString( final Locale locale ) { final NamedSecretData existingData = entry.getValue(); sb.append( "Named password '" ).append( entry.getKey() ).append( "' with usage for " ); - sb.append( StringUtil.collectionToString( existingData.getUsage(), "," ) ); + sb.append( StringUtil.collectionToString( existingData.usage(), "," ) ); sb.append( '\n' ); } @@ -240,7 +240,7 @@ public Object toDebugJsonObject( final Locale locale ) final NamedSecretData existingData = entry.getValue(); final NamedSecretData newData = new NamedSecretData( PasswordData.forStringValue( PwmConstants.LOG_REMOVED_VALUE_REPLACEMENT ), - existingData.getUsage() + existingData.usage() ); copiedValues.put( name, newData ); } diff --git a/server/src/main/java/password/pwm/config/value/UserPermissionValue.java b/server/src/main/java/password/pwm/config/value/UserPermissionValue.java index 57e92b5bac..fe962a8468 100644 --- a/server/src/main/java/password/pwm/config/value/UserPermissionValue.java +++ b/server/src/main/java/password/pwm/config/value/UserPermissionValue.java @@ -48,7 +48,7 @@ public class UserPermissionValue extends AbstractValue implements StoredValue public UserPermissionValue( final List values ) { - this.values = sanitizeList( values ); + this.values = sanitizeList( values ); } private List sanitizeList( final List permissions ) @@ -81,7 +81,7 @@ public UserPermissionValue fromJson( final PwmSetting pwmSetting, final String i public UserPermissionValue fromXmlElement( final PwmSetting pwmSetting, final XmlElement settingElement, final PwmSecurityKey key ) { final boolean newType = "2".equals( settingElement.getAttribute( StoredConfigXmlConstants.XML_ATTRIBUTE_SYNTAX_VERSION ) - .orElse( "" ) ); + .orElse( "" ) ); final List valueElements = settingElement.getChildren( "value" ); final List values = new ArrayList<>(); @@ -97,10 +97,11 @@ public UserPermissionValue fromXmlElement( final PwmSetting pwmSetting, final Xm } else { - values.add( UserPermission.builder() - .type( UserPermissionType.ldapQuery ) - .ldapQuery( value.get() ) - .build() ); + values.add( new UserPermission( + UserPermissionType.ldapQuery, + null, + value.get(), + null ) ); } } } @@ -138,7 +139,7 @@ public List validateValue( final PwmSetting pwmSetting ) { try { - UserPermissionUtility.validatePermissionSyntax( userPermission ); + UserPermissionUtility.validatePermissionSyntax( userPermission ); } catch ( final PwmUnrecoverableException e ) { diff --git a/server/src/main/java/password/pwm/config/value/VerificationMethodValue.java b/server/src/main/java/password/pwm/config/value/VerificationMethodValue.java index 87343b79d9..a9668b628e 100644 --- a/server/src/main/java/password/pwm/config/value/VerificationMethodValue.java +++ b/server/src/main/java/password/pwm/config/value/VerificationMethodValue.java @@ -31,7 +31,6 @@ import password.pwm.util.i18n.LocaleHelper; import password.pwm.util.java.CollectionUtil; import password.pwm.util.json.JsonFactory; -import password.pwm.util.logging.PwmLogger; import password.pwm.util.secure.PwmSecurityKey; import java.util.ArrayList; @@ -43,8 +42,6 @@ public class VerificationMethodValue extends AbstractValue implements StoredValue { - private static final PwmLogger LOGGER = PwmLogger.forClass( VerificationMethodValue.class ); - private final VerificationMethodSettings value; public enum EnabledState diff --git a/server/src/main/java/password/pwm/config/value/data/ActionConfiguration.java b/server/src/main/java/password/pwm/config/value/data/ActionConfiguration.java index 52c7f2f057..84c6fd9668 100644 --- a/server/src/main/java/password/pwm/config/value/data/ActionConfiguration.java +++ b/server/src/main/java/password/pwm/config/value/data/ActionConfiguration.java @@ -130,7 +130,7 @@ public static class ActionConfigurationOldVersion1 public enum Type { webservice, - ldap,; + ldap, } public enum WebMethod diff --git a/server/src/main/java/password/pwm/config/value/data/CustomLinkConfiguration.java b/server/src/main/java/password/pwm/config/value/data/CustomLinkConfiguration.java index bba914ebb2..48dbe810e1 100644 --- a/server/src/main/java/password/pwm/config/value/data/CustomLinkConfiguration.java +++ b/server/src/main/java/password/pwm/config/value/data/CustomLinkConfiguration.java @@ -20,35 +20,49 @@ package password.pwm.config.value.data; -import lombok.EqualsAndHashCode; -import lombok.Value; import password.pwm.util.i18n.LocaleHelper; -import password.pwm.util.json.JsonFactory; +import password.pwm.util.java.CollectionUtil; -import java.util.Collections; import java.util.Locale; import java.util.Map; /** * @author Richard A. Keil */ -@Value -@EqualsAndHashCode( callSuper = false ) -public class CustomLinkConfiguration +public record CustomLinkConfiguration( + String name, + Type type, + Map labels, + Map description, + String customLinkUrl, + boolean customLinkNewWindow, + Map selectOptions +) { + public CustomLinkConfiguration( + final String name, + final Type type, + final Map labels, + final Map description, + final String customLinkUrl, + final boolean customLinkNewWindow, + final Map selectOptions + ) + { + this.name = name; + this.type = type == null ? Type.customLink : type; + this.labels = CollectionUtil.stripNulls( labels ); + this.description = CollectionUtil.stripNulls( description ); + this.customLinkUrl = customLinkUrl == null ? "" : customLinkUrl; + this.customLinkNewWindow = customLinkNewWindow; + this.selectOptions = CollectionUtil.stripNulls( selectOptions ); + } + public enum Type { text, url, select, checkbox, customLink } - private final String name; - private final Type type = Type.customLink; - private final Map labels = Collections.singletonMap( "", "" ); - private final Map description = Collections.singletonMap( "", "" ); - private final String customLinkUrl = ""; - private final boolean customLinkNewWindow; - private final Map selectOptions = Collections.emptyMap(); - public String getLabel( final Locale locale ) { return LocaleHelper.resolveStringKeyLocaleMap( locale, labels ); @@ -58,14 +72,4 @@ public String getDescription( final Locale locale ) { return LocaleHelper.resolveStringKeyLocaleMap( locale, description ); } - - public String toString( ) - { - final StringBuilder sb = new StringBuilder(); - - sb.append( "CustomLink: " ); - sb.append( JsonFactory.get().serialize( this ) ); - - return sb.toString(); - } } diff --git a/server/src/main/java/password/pwm/config/value/data/FormConfiguration.java b/server/src/main/java/password/pwm/config/value/data/FormConfiguration.java index 9ccb4324fd..688fc59238 100644 --- a/server/src/main/java/password/pwm/config/value/data/FormConfiguration.java +++ b/server/src/main/java/password/pwm/config/value/data/FormConfiguration.java @@ -32,6 +32,7 @@ import password.pwm.error.PwmUnrecoverableException; import password.pwm.i18n.Display; import password.pwm.util.i18n.LocaleHelper; +import password.pwm.util.java.CollectionUtil; import password.pwm.util.java.StringUtil; import java.math.BigInteger; @@ -204,14 +205,15 @@ public void validate( ) throws PwmOperationalException } ) ); } - if ( labels == null || this.labels.isEmpty() || this.getLabel( PwmConstants.DEFAULT_LOCALE ) == null || this.getLabel( PwmConstants.DEFAULT_LOCALE ).length() < 1 ) + if ( CollectionUtil.isEmpty( labels ) + || StringUtil.isEmpty( this.getLabel( PwmConstants.DEFAULT_LOCALE ) ) ) { throw new PwmOperationalException( new ErrorInformation( PwmError.CONFIG_FORMAT_ERROR, null, new String[] { " a default label value is required for " + this.getName(), } ) ); } - if ( this.getRegex() != null && this.getRegex().length() > 0 ) + if ( !StringUtil.isEmpty( this.getRegex() ) ) { try { @@ -227,7 +229,7 @@ public void validate( ) throws PwmOperationalException if ( this.getType() == Type.select ) { - if ( this.getSelectOptions() == null || this.getSelectOptions().isEmpty() ) + if ( CollectionUtil.isEmpty( this.getSelectOptions() ) ) { throw new PwmOperationalException( new ErrorInformation( PwmError.CONFIG_FORMAT_ERROR, null, new String[] { " field '" + this.getName() + " ' is type select, but no select options are defined", diff --git a/server/src/main/java/password/pwm/config/value/data/NamedSecretData.java b/server/src/main/java/password/pwm/config/value/data/NamedSecretData.java index de205f8aab..f7884e51e9 100644 --- a/server/src/main/java/password/pwm/config/value/data/NamedSecretData.java +++ b/server/src/main/java/password/pwm/config/value/data/NamedSecretData.java @@ -20,14 +20,22 @@ package password.pwm.config.value.data; -import lombok.Value; import password.pwm.util.PasswordData; +import password.pwm.util.java.CollectionUtil; import java.util.List; -@Value -public class NamedSecretData +public record NamedSecretData( + PasswordData password, + List usage +) { - private PasswordData password; - private List usage; + public NamedSecretData( + final PasswordData password, + final List usage + ) + { + this.password = password; + this.usage = CollectionUtil.stripNulls( usage ); + } } diff --git a/server/src/main/java/password/pwm/config/value/data/ShortcutItem.java b/server/src/main/java/password/pwm/config/value/data/ShortcutItem.java index ce285abccd..84e0192638 100644 --- a/server/src/main/java/password/pwm/config/value/data/ShortcutItem.java +++ b/server/src/main/java/password/pwm/config/value/data/ShortcutItem.java @@ -20,22 +20,20 @@ package password.pwm.config.value.data; -import lombok.Value; import password.pwm.bean.SessionLabel; import password.pwm.util.logging.PwmLogger; import java.net.URI; -@Value -public class ShortcutItem +public record ShortcutItem( + String label, + URI shortcutURI, + String ldapQuery, + String description +) { private static final PwmLogger LOGGER = PwmLogger.forClass( ShortcutItem.class ); - private final String label; - private final URI shortcutURI; - private final String ldapQuery; - private final String description; - public static ShortcutItem parsePwmConfigInput( final String input, final SessionLabel sessionLabel ) { if ( input != null && input.length() > 0 ) diff --git a/server/src/main/java/password/pwm/config/value/data/UserPermission.java b/server/src/main/java/password/pwm/config/value/data/UserPermission.java index c1e1a2bc51..3ce3509c5d 100644 --- a/server/src/main/java/password/pwm/config/value/data/UserPermission.java +++ b/server/src/main/java/password/pwm/config/value/data/UserPermission.java @@ -20,8 +20,6 @@ package password.pwm.config.value.data; -import lombok.Builder; -import lombok.Value; import org.jetbrains.annotations.NotNull; import password.pwm.bean.ProfileID; import password.pwm.ldap.permission.UserPermissionType; @@ -29,46 +27,53 @@ import java.util.Comparator; -@Value -@Builder /** * Represents a user permission configuration value. */ -public class UserPermission implements Comparable -{ - @Builder.Default - private UserPermissionType type = UserPermissionType.ldapQuery; +public record UserPermission( + UserPermissionType type, + ProfileID ldapProfileID, + String ldapQuery, + String ldapBase - private ProfileID ldapProfileID; - private String ldapQuery; - private String ldapBase; +) + implements Comparable +{ + public UserPermission( + final UserPermissionType type, + final ProfileID ldapProfileID, + final String ldapQuery, + final String ldapBase + ) + { + this.type = type == null ? UserPermissionType.ldapQuery : type; + this.ldapProfileID = ldapProfileID; + this.ldapQuery = ldapQuery; + this.ldapBase = ldapBase; + } private static final Comparator COMPARATOR = Comparator.comparing( - UserPermission::getType, - Comparator.nullsLast( Comparator.naturalOrder() ) ) + UserPermission::type, + Comparator.nullsLast( Comparator.naturalOrder() ) ) .thenComparing( - UserPermission::getLdapProfileID, + UserPermission::ldapProfileID, ProfileID.comparator() ) .thenComparing( - UserPermission::getLdapBase, + UserPermission::ldapBase, Comparator.nullsLast( Comparator.naturalOrder() ) ) .thenComparing( - UserPermission::getLdapQuery, + UserPermission::ldapQuery, Comparator.nullsLast( Comparator.naturalOrder() ) ); - public UserPermissionType getType( ) - { - return type == null ? UserPermissionType.ldapQuery : type; - } public String debugString() { - return getType().getLabel() + return type().getLabel() + ": [Profile: " - + ( getLdapProfileID() == null ? "All" : '\'' + this.getLdapProfileID().stringValue() + '\'' ) - + ( StringUtil.isEmpty( getLdapBase() ) ? "" : " " + getType().getBaseLabel() + ": " + this.getLdapBase() ) - + ( StringUtil.isEmpty( getLdapQuery() ) ? "" : " Filter: " + this.getLdapQuery() ) + + ( ldapProfileID() == null ? "All" : '\'' + this.ldapProfileID().stringValue() + '\'' ) + + ( StringUtil.isEmpty( ldapBase() ) ? "" : " " + type().getBaseLabel() + ": " + this.ldapBase() ) + + ( StringUtil.isEmpty( ldapQuery() ) ? "" : " Filter: " + this.ldapQuery() ) + "]"; } diff --git a/server/src/main/java/password/pwm/health/CertificateChecker.java b/server/src/main/java/password/pwm/health/CertificateChecker.java index 99c0ff8321..1fee329153 100644 --- a/server/src/main/java/password/pwm/health/CertificateChecker.java +++ b/server/src/main/java/password/pwm/health/CertificateChecker.java @@ -47,14 +47,14 @@ import java.util.List; import java.util.function.Supplier; -public class CertificateChecker implements HealthSupplier +public final class CertificateChecker implements HealthSupplier { private static final PwmLogger LOGGER = PwmLogger.forClass( CertificateChecker.class ); @Override public List>> jobs( final HealthSupplierRequest request ) { - final PwmApplication pwmApplication = request.getPwmApplication(); + final PwmApplication pwmApplication = request.pwmApplication(); return Collections.singletonList( new CertificateCheckJob( pwmApplication.getConfig() ) ); } @@ -178,14 +178,14 @@ public static void checkCertificate( final X509Certificate certificate, final Ti } catch ( final CertificateException e ) { - final StringBuilder errorMsg = new StringBuilder(); - errorMsg.append( "certificate for subject " ); - errorMsg.append( certificate.getSubjectDN().getName() ); - errorMsg.append( " is not valid: " ); - errorMsg.append( e.getMessage() ); - final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_CERTIFICATE_ERROR, errorMsg.toString(), new String[] + final String errorMsg = "certificate {" + + X509Utils.makeDebugText( certificate ) + + "} is not valid: " + + e.getMessage(); + + final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_CERTIFICATE_ERROR, errorMsg, new String[] { - errorMsg.toString(), + errorMsg, } ); throw new PwmOperationalException( errorInformation ); @@ -214,9 +214,9 @@ public static void checkCertificate( final X509Certificate certificate, final Ti if ( durationUntilExpire.isShorterThan( warnDuration ) ) { final StringBuilder errorMsg = new StringBuilder(); - errorMsg.append( "certificate for subject " ); - errorMsg.append( certificate.getSubjectDN().getName() ); - errorMsg.append( " will expire on: " ); + errorMsg.append( "certificate {" ); + errorMsg.append( X509Utils.makeDebugText( certificate ) ); + errorMsg.append( "} will expire on: " ); errorMsg.append( StringUtil.toIsoDate( expireDate ) ); errorMsg.append( " (" ).append( durationUntilExpire.asCompactString() ).append( " from now)" ); final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_CERTIFICATE_ERROR, errorMsg.toString(), new String[] diff --git a/server/src/main/java/password/pwm/health/ConfigurationChecker.java b/server/src/main/java/password/pwm/health/ConfigurationChecker.java index 493a0ec9fd..9710a29392 100644 --- a/server/src/main/java/password/pwm/health/ConfigurationChecker.java +++ b/server/src/main/java/password/pwm/health/ConfigurationChecker.java @@ -77,7 +77,7 @@ import java.util.function.Supplier; import java.util.stream.Collectors; -public class ConfigurationChecker implements HealthSupplier +public final class ConfigurationChecker implements HealthSupplier { private static final PwmLogger LOGGER = PwmLogger.forClass( ConfigurationChecker.class ); @@ -101,8 +101,8 @@ public class ConfigurationChecker implements HealthSupplier @Override public List>> jobs( final HealthSupplier.HealthSupplierRequest request ) { - final PwmApplication pwmApplication = request.getPwmApplication(); - final SessionLabel sessionLabel = request.getSessionLabel(); + final PwmApplication pwmApplication = request.pwmApplication(); + final SessionLabel sessionLabel = request.sessionLabel(); if ( pwmApplication.getConfig().readSettingAsBoolean( PwmSetting.HIDE_CONFIGURATION_HEALTH_WARNINGS ) ) { @@ -793,9 +793,9 @@ private static List checkLdapProfile( final UserPermission permission ) { - if ( permission.getLdapProfileID() != null ) + if ( permission.ldapProfileID() != null ) { - final List ldapProfiles = ldapProfilesForLdapProfileSetting( domainConfig, permission.getLdapProfileID() ); + final List ldapProfiles = ldapProfilesForLdapProfileSetting( domainConfig, permission.ldapProfileID() ); if ( ldapProfiles.isEmpty() ) { final PwmSetting pwmSetting = storedConfigKey.toPwmSetting(); @@ -803,7 +803,7 @@ private static List checkLdapProfile( domainConfig.getDomainID(), HealthMessage.Config_ProfileValueValidity, pwmSetting.toMenuLocationDebug( storedConfigKey.getProfileID().orElse( null ), locale ), - permission.getLdapProfileID().stringValue() ) ); + permission.ldapProfileID().stringValue() ) ); } } diff --git a/server/src/main/java/password/pwm/health/DatabaseStatusChecker.java b/server/src/main/java/password/pwm/health/DatabaseStatusChecker.java index 371fdf1cd6..2c507dde72 100644 --- a/server/src/main/java/password/pwm/health/DatabaseStatusChecker.java +++ b/server/src/main/java/password/pwm/health/DatabaseStatusChecker.java @@ -23,6 +23,7 @@ import password.pwm.PwmApplication; import password.pwm.PwmEnvironment; import password.pwm.bean.DomainID; +import password.pwm.bean.SessionLabel; import password.pwm.config.AppConfig; import password.pwm.error.PwmException; import password.pwm.svc.db.DatabaseAccessor; @@ -33,31 +34,16 @@ import java.util.List; import java.util.function.Supplier; -public class DatabaseStatusChecker implements HealthSupplier +public final class DatabaseStatusChecker implements HealthSupplier { private static final PwmLogger LOGGER = PwmLogger.forClass( DatabaseStatusChecker.class ); - @Override - public List>> jobs( final HealthSupplierRequest request ) - { - final PwmApplication pwmApplication = request.getPwmApplication(); - final Supplier> supplier = () -> doHealthCheck( pwmApplication ); - return Collections.singletonList( supplier ); - } - - public List doHealthCheck( final PwmApplication pwmApplication ) - { - return checkDatabaseStatus( pwmApplication, pwmApplication.getConfig() ); - } - - public static List checkNewDatabaseStatus( final PwmApplication pwmApplication, final AppConfig config ) - { - return checkDatabaseStatus( pwmApplication, config ); - } - - private static List checkDatabaseStatus( final PwmApplication pwmApplication, final AppConfig config ) + public static List checkNewDatabaseStatus( + final SessionLabel sessionLabel, + final PwmEnvironment pwmEnvironment, + final AppConfig appConfig ) { - if ( !config.hasDbConfigured() ) + if ( !appConfig.hasDbConfigured() ) { return Collections.singletonList( HealthRecord.forMessage( DomainID.systemId(), @@ -65,26 +51,70 @@ private static List checkDatabaseStatus( final PwmApplication pwmA "Database not configured" ) ); } - PwmApplication runtimeInstance = null; try { - final PwmEnvironment runtimeEnvironment = pwmApplication.getPwmEnvironment().makeRuntimeInstance( config ); - runtimeInstance = PwmApplication.createPwmApplication( runtimeEnvironment ); - final DatabaseAccessor accessor = runtimeInstance.getDatabaseService().getAccessor(); - accessor.get( DatabaseTable.PWM_META, "test" ); - return runtimeInstance.getDatabaseService().healthCheck(); + final PwmEnvironment runtimeEnvironment = pwmEnvironment.makeRuntimeInstance( appConfig ); + final PwmApplication runtimeInstance = PwmApplication.createPwmApplication( runtimeEnvironment ); + final List records = checkDatabaseStatus( sessionLabel, runtimeInstance ); + return List.copyOf( records ); } - catch ( final PwmException e ) + catch ( final Exception e ) { - LOGGER.error( () -> "error during healthcheck: " + e.getMessage() ); - return runtimeInstance.getDatabaseService().healthCheck(); + return List.of( exceptionToRecord( sessionLabel, e ) ); } - finally + } + + @Override + public List>> jobs( final HealthSupplierRequest request ) + { + if ( checkShouldBeSkipped( request.pwmApplication() ) ) { - if ( runtimeInstance != null ) + return List.of(); + } + + return List.of( () -> checkDatabaseStatus( + request.sessionLabel(), + request.pwmApplication() ) ); + } + + private static boolean checkShouldBeSkipped( + final PwmApplication pwmApplication + ) + { + return pwmApplication.getPwmEnvironment().isInternalRuntimeInstance() + || pwmApplication.getConfig().hasDbConfigured(); + } + + private static List checkDatabaseStatus( + final SessionLabel sessionLabel, + final PwmApplication pwmApplication + ) + { + try + { + final DatabaseAccessor accessor = pwmApplication.getDatabaseService().getAccessor(); + accessor.get( DatabaseTable.PWM_META, "test" ); + final List records = pwmApplication.getDatabaseService().healthCheck(); + if ( records.isEmpty() ) { - runtimeInstance.shutdown(); + return List.of( HealthRecord.forMessage( DomainID.systemId(), HealthMessage.Database_OK ) ); } + return records; + } + catch ( final PwmException e ) + { + return List.of( exceptionToRecord( sessionLabel, e ) ); } } + + private static HealthRecord exceptionToRecord( + final SessionLabel sessionLabel, + final Exception e ) + { + LOGGER.debug( sessionLabel, () -> "error during db health check: " + e.getMessage() ); + return HealthRecord.forMessage( + DomainID.systemId(), + HealthMessage.Database_Error, + "error: " + e.getMessage() ); + } } diff --git a/server/src/main/java/password/pwm/health/HealthMonitorSettings.java b/server/src/main/java/password/pwm/health/HealthMonitorSettings.java index 6647aab0dc..bcb8c5fe28 100644 --- a/server/src/main/java/password/pwm/health/HealthMonitorSettings.java +++ b/server/src/main/java/password/pwm/health/HealthMonitorSettings.java @@ -20,30 +20,25 @@ package password.pwm.health; -import lombok.Builder; -import lombok.Value; import password.pwm.AppProperty; import password.pwm.config.AppConfig; import password.pwm.util.java.TimeDuration; -@Value -@Builder -class HealthMonitorSettings +record HealthMonitorSettings( + boolean healthCheckEnabled, + TimeDuration nominalCheckInterval, + TimeDuration minimumCheckInterval, + TimeDuration maximumRecordAge, + TimeDuration maximumForceCheckWait + ) { - private boolean healthCheckEnabled; - private TimeDuration nominalCheckInterval; - private TimeDuration minimumCheckInterval; - private TimeDuration maximumRecordAge; - private TimeDuration maximumForceCheckWait; - static HealthMonitorSettings fromConfiguration( final AppConfig config ) { - return HealthMonitorSettings.builder() - .healthCheckEnabled( Boolean.parseBoolean( config.readAppProperty( AppProperty.HEALTHCHECK_ENABLED ) ) ) - .nominalCheckInterval( TimeDuration.of( Long.parseLong( config.readAppProperty( AppProperty.HEALTHCHECK_NOMINAL_CHECK_INTERVAL ) ), TimeDuration.Unit.SECONDS ) ) - .minimumCheckInterval( TimeDuration.of( Long.parseLong( config.readAppProperty( AppProperty.HEALTHCHECK_MIN_CHECK_INTERVAL ) ), TimeDuration.Unit.SECONDS ) ) - .maximumRecordAge( TimeDuration.of( Long.parseLong( config.readAppProperty( AppProperty.HEALTHCHECK_MAX_RECORD_AGE ) ), TimeDuration.Unit.SECONDS ) ) - .maximumForceCheckWait( TimeDuration.of( Long.parseLong( config.readAppProperty( AppProperty.HEALTHCHECK_MAX_FORCE_WAIT ) ), TimeDuration.Unit.SECONDS ) ) - .build(); + return new HealthMonitorSettings( + Boolean.parseBoolean( config.readAppProperty( AppProperty.HEALTHCHECK_ENABLED ) ), + TimeDuration.of( Long.parseLong( config.readAppProperty( AppProperty.HEALTHCHECK_NOMINAL_CHECK_INTERVAL ) ), TimeDuration.Unit.SECONDS ), + TimeDuration.of( Long.parseLong( config.readAppProperty( AppProperty.HEALTHCHECK_MIN_CHECK_INTERVAL ) ), TimeDuration.Unit.SECONDS ), + TimeDuration.of( Long.parseLong( config.readAppProperty( AppProperty.HEALTHCHECK_MAX_RECORD_AGE ) ), TimeDuration.Unit.SECONDS ), + TimeDuration.of( Long.parseLong( config.readAppProperty( AppProperty.HEALTHCHECK_MAX_FORCE_WAIT ) ), TimeDuration.Unit.SECONDS ) ); } } diff --git a/server/src/main/java/password/pwm/health/HealthRecord.java b/server/src/main/java/password/pwm/health/HealthRecord.java index 83cf576ae1..c3073227a5 100644 --- a/server/src/main/java/password/pwm/health/HealthRecord.java +++ b/server/src/main/java/password/pwm/health/HealthRecord.java @@ -20,36 +20,33 @@ package password.pwm.health; -import lombok.EqualsAndHashCode; -import org.jetbrains.annotations.NotNull; import password.pwm.bean.DomainID; import password.pwm.config.DomainConfig; import password.pwm.config.SettingReader; +import password.pwm.util.java.CollectionUtil; import password.pwm.ws.server.rest.bean.PublicHealthData; import password.pwm.ws.server.rest.bean.PublicHealthRecord; import java.time.Instant; -import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.Locale; import java.util.Objects; -@EqualsAndHashCode -public class HealthRecord implements Comparable -{ - private final HealthStatus status; - - // new fields - private final HealthTopic topic; - private final DomainID domainID; - private final HealthMessage message; - private final List fields; +public record HealthRecord( + HealthStatus status, + HealthTopic topic, + DomainID domainID, + HealthMessage message, + List fields +) + implements Comparable +{ private static final Comparator COMPARATOR = Comparator.comparing( - HealthRecord::getDomainID, - Comparator.nullsLast( Comparator.naturalOrder() ) ) + HealthRecord::getDomainID, + Comparator.nullsLast( Comparator.naturalOrder() ) ) .thenComparing( HealthRecord::getStatus, Comparator.nullsLast( Comparator.naturalOrder() ) ) @@ -61,36 +58,36 @@ public class HealthRecord implements Comparable Comparator.nullsLast( Comparator.naturalOrder() ) ); - private HealthRecord( - final DomainID domainID, + public HealthRecord( final HealthStatus status, - final HealthTopic topicEnum, + final HealthTopic topic, + final DomainID domainID, final HealthMessage message, - final String... fields + final List fields ) { this.domainID = Objects.requireNonNull( domainID ); this.status = Objects.requireNonNull( status, "status cannot be null" ); - this.topic = Objects.requireNonNull( topicEnum, "topic cannot be null" ); + this.topic = Objects.requireNonNull( topic, "topic cannot be null" ); this.message = Objects.requireNonNull( message, "message cannot be null" ); - this.fields = fields == null ? Collections.emptyList() : List.copyOf( Arrays.asList( fields ) ); + this.fields = CollectionUtil.stripNulls( fields ); } - - public static HealthRecord forMessage( final DomainID domainID, final HealthMessage message ) { - return new HealthRecord( domainID, message.getStatus(), message.getTopic(), message ); + return new HealthRecord( message.getStatus(), message.getTopic(), domainID, message, List.of() ); } public static HealthRecord forMessage( final DomainID domainID, final HealthMessage message, final String... fields ) { - return new HealthRecord( domainID, message.getStatus(), message.getTopic(), message, fields ); + final List fieldList = CollectionUtil.arrayToList( fields ); + return new HealthRecord( message.getStatus(), message.getTopic(), domainID, message, fieldList ); } public static HealthRecord forMessage( final DomainID domainID, final HealthMessage message, final HealthTopic healthTopic, final String... fields ) { - return new HealthRecord( domainID, message.getStatus(), healthTopic, message, fields ); + final List fieldList = CollectionUtil.arrayToList( fields ); + return new HealthRecord( message.getStatus(), healthTopic, domainID, message, fieldList ); } public HealthStatus getStatus( ) @@ -116,7 +113,7 @@ public String getDetail( final Locale locale, final SettingReader config ) { if ( message != null ) { - return this.message.getDescription( locale, config, fields.toArray( new String[0] ) ); + return this.message.getDescription( locale, config, CollectionUtil.listToArray( fields, String.class ) ); } return ""; } @@ -128,7 +125,7 @@ public String toDebugString( final Locale locale, final SettingReader config ) } @Override - public int compareTo( @NotNull final HealthRecord otherHealthRecord ) + public int compareTo( final HealthRecord otherHealthRecord ) { return COMPARATOR.compare( this, otherHealthRecord ); } @@ -146,10 +143,10 @@ public static PublicHealthData asHealthDataBean( { final List healthRecordBeans = PublicHealthRecord.fromHealthRecords( profileRecords, locale, domainConfig ); - return PublicHealthData.builder() - .timestamp( Instant.now() ) - .overall( HealthUtils.getMostSevereHealthStatus( profileRecords ).toString() ) - .records( healthRecordBeans ) - .build(); + return new PublicHealthData( + Instant.now(), + HealthUtils.getMostSevereHealthStatus( profileRecords ).toString(), + healthRecordBeans ); + } } diff --git a/server/src/main/java/password/pwm/health/HealthService.java b/server/src/main/java/password/pwm/health/HealthService.java index 002c30daf1..43bfa6db69 100644 --- a/server/src/main/java/password/pwm/health/HealthService.java +++ b/server/src/main/java/password/pwm/health/HealthService.java @@ -20,7 +20,6 @@ package password.pwm.health; -import lombok.Value; import password.pwm.AppProperty; import password.pwm.PwmApplication; import password.pwm.PwmConstants; @@ -29,6 +28,7 @@ import password.pwm.error.PwmException; import password.pwm.svc.AbstractPwmService; import password.pwm.svc.PwmService; +import password.pwm.util.java.JavaHelper; import password.pwm.util.java.StatisticAverageBundle; import password.pwm.util.java.StatisticCounterBundle; import password.pwm.util.java.TimeDuration; @@ -46,24 +46,21 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Future; import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; import java.util.function.Supplier; public class HealthService extends AbstractPwmService implements PwmService { private static final PwmLogger LOGGER = PwmLogger.forClass( HealthService.class ); - private static final List HEALTH_SUPPLIERS = List.of( - new LDAPHealthChecker(), - new JavaChecker(), - new ConfigurationChecker(), - new LocalDBHealthChecker(), - new CertificateChecker() ); - + private static final List HEALTH_SUPPLIERS = JavaHelper.instancesOfSealedInterface( HealthSupplier.class ); private HealthMonitorSettings settings; private final Map healthProperties = new ConcurrentHashMap<>(); private final AtomicInteger healthCheckCount = new AtomicInteger( 0 ); + private final Lock immediateHealthCheckLock = new ReentrantLock(); private final StatisticCounterBundle counterStats = new StatisticCounterBundle<>( CounterStatKey.class ); private final StatisticAverageBundle averageStats = new StatisticAverageBundle<>( AverageStatKey.class ); @@ -97,7 +94,7 @@ public STATUS postAbstractInit( final PwmApplication pwmApplication, final Domai this.healthData = emptyHealthData(); settings = HealthMonitorSettings.fromConfiguration( pwmApplication.getConfig() ); - if ( !settings.isHealthCheckEnabled() ) + if ( !settings.healthCheckEnabled() ) { LOGGER.debug( getSessionLabel(), () -> "health monitor will remain inactive due to AppProperty " + AppProperty.HEALTHCHECK_ENABLED.getKey() ); return STATUS.CLOSED; @@ -113,7 +110,7 @@ public Instant getLastHealthCheckTime( ) return null; } - return healthData != null ? healthData.getTimeStamp() : Instant.ofEpochMilli( 0 ); + return healthData != null ? healthData.timeStamp() : Instant.ofEpochMilli( 0 ); } public HealthStatus getMostSevereHealthStatus( ) @@ -125,7 +122,6 @@ public HealthStatus getMostSevereHealthStatus( ) return HealthUtils.getMostSevereHealthStatus( getHealthRecords( ) ); } - public Set getHealthRecords( ) { if ( status() != STATUS.OPEN ) @@ -133,28 +129,39 @@ public Set getHealthRecords( ) return Collections.emptySet(); } - if ( healthData.recordsAreOutdated() ) + if ( healthData.recordsAreOutdated( settings ) ) { - final Instant startTime = Instant.now(); - LOGGER.trace( getSessionLabel(), () -> "begin force immediate check" ); - final Future future = scheduleJob( new ImmediateJob(), TimeDuration.ZERO ); - settings.getMaximumForceCheckWait().pause( future::isDone ); - final TimeDuration checkDuration = TimeDuration.fromCurrent( startTime ); - averageStats.update( AverageStatKey.checkProcessTime, checkDuration.asDuration() ); - counterStats.increment( CounterStatKey.checks ); - LOGGER.trace( getSessionLabel(), () -> "exit force immediate check, done=" + future.isDone(), checkDuration ); + immediateHealthCheckLock.lock(); + try + { + if ( healthData.recordsAreOutdated( settings ) ) + { + final Instant startTime = Instant.now(); + LOGGER.trace( getSessionLabel(), () -> "begin force immediate check" ); + final Future future = scheduleJob( new ImmediateJob(), TimeDuration.ZERO ); + settings.maximumForceCheckWait().pause( future::isDone ); + final TimeDuration checkDuration = TimeDuration.fromCurrent( startTime ); + averageStats.update( AverageStatKey.checkProcessTime, checkDuration.asDuration() ); + counterStats.increment( CounterStatKey.checks ); + LOGGER.trace( getSessionLabel(), () -> "exit force immediate check, done=" + future.isDone(), checkDuration ); + } + } + finally + { + immediateHealthCheckLock.unlock(); + } } - scheduleJob( new UpdateJob(), settings.getNominalCheckInterval() ); + scheduleJob( new UpdateJob(), settings.nominalCheckInterval() ); { final HealthData localHealthData = this.healthData; - if ( localHealthData.recordsAreOutdated() ) + if ( localHealthData.recordsAreOutdated( settings ) ) { return Collections.singleton( HealthRecord.forMessage( DomainID.systemId(), HealthMessage.NoData ) ); } - return localHealthData.getHealthRecords(); + return localHealthData.healthRecords(); } } @@ -268,7 +275,7 @@ private class UpdateJob implements Runnable @Override public void run( ) { - if ( healthData.recordsAreStale() ) + if ( healthData.recordsAreStale( settings ) ) { new ImmediateJob().run(); } @@ -294,20 +301,19 @@ public void run( ) } } - @Value - private class HealthData + private record HealthData( + Set healthRecords, + Instant timeStamp + ) { - private Set healthRecords; - private Instant timeStamp; - - private boolean recordsAreStale() + private boolean recordsAreStale( final HealthMonitorSettings settings ) { - return TimeDuration.fromCurrent( this.getTimeStamp() ).isLongerThan( settings.getNominalCheckInterval() ); + return TimeDuration.fromCurrent( this.timeStamp() ).isLongerThan( settings.nominalCheckInterval() ); } - private boolean recordsAreOutdated() + private boolean recordsAreOutdated( final HealthMonitorSettings settings ) { - return TimeDuration.fromCurrent( this.getTimeStamp() ).isLongerThan( settings.getMaximumRecordAge() ); + return TimeDuration.fromCurrent( this.timeStamp() ).isLongerThan( settings.maximumRecordAge() ); } } } diff --git a/server/src/main/java/password/pwm/health/HealthSupplier.java b/server/src/main/java/password/pwm/health/HealthSupplier.java index 5b4124dc39..ba7b7418a0 100644 --- a/server/src/main/java/password/pwm/health/HealthSupplier.java +++ b/server/src/main/java/password/pwm/health/HealthSupplier.java @@ -20,21 +20,26 @@ package password.pwm.health; -import lombok.Value; import password.pwm.PwmApplication; import password.pwm.bean.SessionLabel; import java.util.List; import java.util.function.Supplier; -public interface HealthSupplier +sealed interface HealthSupplier permits + LDAPHealthChecker, + JavaChecker, + ConfigurationChecker, + LocalDBHealthChecker, + CertificateChecker, + DatabaseStatusChecker { List>> jobs( HealthSupplierRequest request ); - @Value - class HealthSupplierRequest + record HealthSupplierRequest( + PwmApplication pwmApplication, + SessionLabel sessionLabel + ) { - private final PwmApplication pwmApplication; - private final SessionLabel sessionLabel; } } diff --git a/server/src/main/java/password/pwm/health/JavaChecker.java b/server/src/main/java/password/pwm/health/JavaChecker.java index a549669aab..7b471b11f1 100644 --- a/server/src/main/java/password/pwm/health/JavaChecker.java +++ b/server/src/main/java/password/pwm/health/JavaChecker.java @@ -29,12 +29,12 @@ import java.util.List; import java.util.function.Supplier; -public class JavaChecker implements HealthSupplier +public final class JavaChecker implements HealthSupplier { @Override public List>> jobs( final HealthSupplier.HealthSupplierRequest request ) { - final PwmApplication pwmApplication = request.getPwmApplication(); + final PwmApplication pwmApplication = request.pwmApplication(); final Supplier> supplier = () -> doHealthCheck( pwmApplication ); return Collections.singletonList( supplier ); diff --git a/server/src/main/java/password/pwm/health/LDAPHealthChecker.java b/server/src/main/java/password/pwm/health/LDAPHealthChecker.java index 6476542e45..cadaee40a0 100644 --- a/server/src/main/java/password/pwm/health/LDAPHealthChecker.java +++ b/server/src/main/java/password/pwm/health/LDAPHealthChecker.java @@ -86,15 +86,15 @@ import java.util.function.Supplier; import java.util.stream.Collectors; -public class LDAPHealthChecker implements HealthSupplier +public final class LDAPHealthChecker implements HealthSupplier { private static final PwmLogger LOGGER = PwmLogger.forClass( LDAPHealthChecker.class ); public List>> jobs( final HealthSupplier.HealthSupplierRequest request ) { - final PwmApplication pwmApplication = request.getPwmApplication(); + final PwmApplication pwmApplication = request.pwmApplication(); return pwmApplication.domains().values().stream() - .map( domain -> ( Supplier> ) () -> doHealthCheck( request.getSessionLabel(), domain ) ) + .map( domain -> ( Supplier> ) () -> doHealthCheck( request.sessionLabel(), domain ) ) .collect( Collectors.toList() ); } @@ -128,7 +128,7 @@ public List doHealthCheck( final SessionLabel sessionLabel, final { final ErrorInformation errorInfo = entry.getValue(); final LdapProfile ldapProfile = pwmDomain.getConfig().getLdapProfiles().get( entry.getKey() ); - if ( errorInfo != null ) + if ( errorInfo != null && ldapProfile != null ) { final TimeDuration errorAge = TimeDuration.fromCurrent( errorInfo.getDate() ); @@ -1082,7 +1082,7 @@ private static List checkUserPermission( final DomainConfig config = pwmDomain.getConfig(); final List ldapProfilesToCheck = new ArrayList<>(); { - final ProfileID configuredLdapProfileID = userPermission.getLdapProfileID(); + final ProfileID configuredLdapProfileID = userPermission.ldapProfileID(); if ( configuredLdapProfileID == null || ProfileID.PROFILE_ID_ALL.equals( configuredLdapProfileID ) ) { @@ -1109,14 +1109,14 @@ private static List checkUserPermission( for ( final ProfileID ldapProfileID : ldapProfilesToCheck ) { - switch ( userPermission.getType() ) + switch ( userPermission.type() ) { case ldapAllUsers: break; case ldapUser: { - final String userDN = userPermission.getLdapBase(); + final String userDN = userPermission.ldapBase(); if ( userDN != null && !isExampleDN( userDN ) ) { final Optional errorMsg = validateDN( sessionLabel, pwmDomain, userDN, ldapProfileID ); @@ -1130,7 +1130,7 @@ private static List checkUserPermission( case ldapGroup: { - final String groupDN = userPermission.getLdapBase(); + final String groupDN = userPermission.ldapBase(); if ( groupDN != null && !isExampleDN( groupDN ) ) { final Optional errorMsg = validateDN( sessionLabel, pwmDomain, groupDN, ldapProfileID ); @@ -1145,7 +1145,7 @@ private static List checkUserPermission( case ldapQuery: { - final String baseDN = userPermission.getLdapBase(); + final String baseDN = userPermission.ldapBase(); if ( baseDN != null && !isExampleDN( baseDN ) ) { final Optional errorMsg = validateDN( sessionLabel, pwmDomain, baseDN, ldapProfileID ); @@ -1159,7 +1159,7 @@ private static List checkUserPermission( break; default: - PwmUtil.unhandledSwitchStatement( userPermission.getType() ); + PwmUtil.unhandledSwitchStatement( userPermission.type() ); } } return returnList; diff --git a/server/src/main/java/password/pwm/health/LocalDBHealthChecker.java b/server/src/main/java/password/pwm/health/LocalDBHealthChecker.java index 5bc864b9ac..13c351c5cc 100644 --- a/server/src/main/java/password/pwm/health/LocalDBHealthChecker.java +++ b/server/src/main/java/password/pwm/health/LocalDBHealthChecker.java @@ -33,12 +33,12 @@ import java.util.List; import java.util.function.Supplier; -public class LocalDBHealthChecker implements HealthSupplier +public final class LocalDBHealthChecker implements HealthSupplier { @Override public List>> jobs( final HealthSupplierRequest request ) { - final PwmApplication pwmApplication = request.getPwmApplication(); + final PwmApplication pwmApplication = request.pwmApplication(); final Supplier> supplier = () -> doHealthCheck( pwmApplication ); return Collections.singletonList( supplier ); } diff --git a/server/src/main/java/password/pwm/http/JspUrl.java b/server/src/main/java/password/pwm/http/JspUrl.java index 99192f5198..b30ba79256 100644 --- a/server/src/main/java/password/pwm/http/JspUrl.java +++ b/server/src/main/java/password/pwm/http/JspUrl.java @@ -99,7 +99,8 @@ public enum JspUrl ADMIN_SYSTEM_CERTIFICATES( "admin-system-certificates.jsp" ), CONFIG_MANAGER_LOCALDB( "configmanager-localdb.jsp" ), CONFIG_MANAGER_LOGIN( "configmanager-login.jsp" ), - HELPDESK_SEARCH( "helpdesk.jsp" ), + HELPDESK_SEARCH( "helpdesk-search.jsp" ), + HELPDESK_DETAIL( "helpdesk-detail.jsp" ), FULL_PAGE_HEALTH( "fullpagehealth.jsp" ),; private final String path; diff --git a/server/src/main/java/password/pwm/http/PwmRequest.java b/server/src/main/java/password/pwm/http/PwmRequest.java index c25980fa44..dcf8ab024f 100644 --- a/server/src/main/java/password/pwm/http/PwmRequest.java +++ b/server/src/main/java/password/pwm/http/PwmRequest.java @@ -56,7 +56,6 @@ import password.pwm.http.servlet.PwmServletDefinition; import password.pwm.svc.secure.SecureService; import password.pwm.user.UserInfo; -import password.pwm.util.Validator; import password.pwm.util.java.LazySupplier; import password.pwm.util.java.StringUtil; import password.pwm.util.java.TimeDuration; @@ -80,8 +79,6 @@ import java.util.Map; import java.util.Optional; import java.util.Set; -import java.util.concurrent.locks.Lock; -import java.util.concurrent.locks.ReentrantLock; import java.util.function.Supplier; public class PwmRequest extends PwmHttpRequestWrapper @@ -92,11 +89,11 @@ public class PwmRequest extends PwmHttpRequestWrapper private final PwmResponse pwmResponse; private final PwmURL pwmURL; private final PwmRequestID pwmRequestID; + private final String cspNonce; private final Set flags = EnumSet.noneOf( PwmRequestFlag.class ); private final Instant requestStartTime = Instant.now(); private final DomainID domainID; - private final Lock cspCreationLock = new ReentrantLock(); private final Supplier localeSupplier = LazySupplier.create( () -> PwmRequestLocaleResolver.resolveRequestLocale( this ) ); @@ -136,6 +133,7 @@ private PwmRequest( this.pwmApplication = pwmApplication; this.pwmURL = PwmURL.create( this.getHttpServletRequest() ); this.domainID = PwmHttpRequestWrapper.readDomainIdFromRequest( httpServletRequest ); + this.cspNonce = makeCspNonce(); } @@ -242,13 +240,6 @@ public UserIdentity getUserInfoIfLoggedIn() : null; } - - public void validatePwmFormID() - throws PwmUnrecoverableException - { - Validator.validatePwmFormID( this ); - } - public boolean convertURLtokenCommand( final PwmServletDefinition pwmServletDefinition, final AbstractPwmServlet.ProcessAction processAction @@ -434,24 +425,15 @@ public String getLogoutURL( } public String getCspNonce() - throws PwmUnrecoverableException { - cspCreationLock.lock(); - try - { - if ( getAttribute( PwmRequestAttribute.CspNonce ) == null ) - { - final int nonceLength = Integer.parseInt( getDomainConfig().readAppProperty( AppProperty.HTTP_HEADER_CSP_NONCE_BYTES ) ); - final byte[] cspNonce = getPwmDomain().getSecureService().pwmRandom().newBytes( nonceLength ); - final String cspString = StringUtil.base64Encode( cspNonce ); - setAttribute( PwmRequestAttribute.CspNonce, cspString ); - } - return ( String ) getAttribute( PwmRequestAttribute.CspNonce ); - } - finally - { - cspCreationLock.unlock(); - } + return cspNonce; + } + + private String makeCspNonce() + { + final int nonceLength = Integer.parseInt( getDomainConfig().readAppProperty( AppProperty.HTTP_HEADER_CSP_NONCE_BYTES ) ); + final byte[] cspNonce = getPwmDomain().getSecureService().pwmRandom().newBytes( nonceLength ); + return StringUtil.base64Encode( cspNonce ); } public Optional readEncryptedCookie( final String cookieName, final Class returnClass ) diff --git a/server/src/main/java/password/pwm/http/PwmRequestAttribute.java b/server/src/main/java/password/pwm/http/PwmRequestAttribute.java index 43edcda0e4..54453ec0e5 100644 --- a/server/src/main/java/password/pwm/http/PwmRequestAttribute.java +++ b/server/src/main/java/password/pwm/http/PwmRequestAttribute.java @@ -34,7 +34,6 @@ public enum PwmRequestAttribute PageTitle, ModuleBean, ModuleBean_String, - CspNonce, BrowserInfo, FormConfiguration, @@ -55,9 +54,11 @@ public enum PwmRequestAttribute SetupOtp_QrCodeValue, SetupOtp_AllowSkip, SetupOtp_UserRecord, + SetupOtp_TokenLength, - HelpdeskDetail, - HelpdeskObfuscatedDN, + HelpdeskClientData, + HelpdeskDetailInfo, + HelpdeskUserKey, HelpdeskVerificationEnabled, ConfigFilename, @@ -101,6 +102,7 @@ public enum PwmRequestAttribute NextUrl, UserDebugData, + UserDebugInfo, AppDashboardData, TokenDestItems, @@ -110,5 +112,5 @@ public enum PwmRequestAttribute JspIndexTabCounter, JspAutofocusStatus, - GoBackAction,; + GoBackAction, } diff --git a/server/src/main/java/password/pwm/http/PwmURL.java b/server/src/main/java/password/pwm/http/PwmURL.java index 81c6aceca8..e69de29bb2 100644 --- a/server/src/main/java/password/pwm/http/PwmURL.java +++ b/server/src/main/java/password/pwm/http/PwmURL.java @@ -1,555 +0,0 @@ -/* - * Password Management Servlets (PWM) - * http://www.pwm-project.org - * - * Copyright (c) 2006-2009 Novell, Inc. - * Copyright (c) 2009-2021 The PWM Project - * - * 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. - */ - -package password.pwm.http; - -import password.pwm.PwmConstants; -import password.pwm.bean.SessionLabel; -import password.pwm.config.AppConfig; -import password.pwm.error.PwmInternalException; -import password.pwm.error.PwmUnrecoverableException; -import password.pwm.http.servlet.PwmServletDefinition; -import password.pwm.util.java.EnumUtil; -import password.pwm.util.java.LazySupplier; -import password.pwm.util.java.StringUtil; -import password.pwm.util.json.JsonFactory; -import password.pwm.util.logging.PwmLogger; - -import javax.servlet.http.HttpServletRequest; -import java.net.URI; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.EnumSet; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; -import java.util.function.Supplier; -import java.util.regex.Pattern; -import java.util.stream.Collectors; - -public class PwmURL -{ - private static final PwmLogger LOGGER = PwmLogger.forClass( PwmURL.class ); - - private final URI uri; - private final String contextPath; - private final AppConfig appConfig; - private final Supplier> pwmServletDefinition = LazySupplier.create( - () -> getServletDefinitionImpl( this ) ); - - public enum Scheme - { - http( 80 ), - https( 443 ), - file( -1 ), - ldap( 389 ), - ldaps( 636 ),; - - private final int defaultPort; - - Scheme( final int defaultPort ) - { - this.defaultPort = defaultPort; - } - - public int getDefaultPort() - { - return defaultPort; - } - - public static Optional fromUri( final URI uri ) - { - if ( uri == null ) - { - return Optional.empty(); - } - - return EnumUtil.readEnumFromPredicate( - Scheme.class, ( loopUri ) -> loopUri.name().equals( uri.getScheme() ) ); - } - } - - private PwmURL( - final URI uri, - final String contextPath, - final AppConfig appConfig - ) - { - - this.uri = Objects.requireNonNull( uri ).normalize(); - this.contextPath = Objects.requireNonNull( contextPath ); - this.appConfig = Objects.requireNonNull( appConfig ); - } - - /** - * Compare two uri strings for equality of 'base'. Specifically, the schema, host and port - * are compared for equality. - * - * @param uri1 uri to compare - * @param uri2 uri to compare - * @return true if schema, host and port of uri1 and uri2 are equal. - */ - public static boolean compareUriBase( final String uri1, final String uri2 ) - { - if ( uri1 == null && uri2 == null ) - { - return true; - } - - if ( uri1 == null || uri2 == null ) - { - return false; - } - - final URI parsedUri1 = URI.create( uri1 ); - final URI parsedUri2 = URI.create( uri2 ); - - if ( !StringUtil.equals( parsedUri1.getScheme(), parsedUri2.getScheme() ) ) - { - return false; - } - - if ( !StringUtil.equals( parsedUri1.getHost(), parsedUri2.getHost() ) ) - { - return false; - } - - if ( parsedUri1.getPort() != parsedUri2.getPort() ) - { - return false; - } - - return true; - } - - public static PwmURL create( - final HttpServletRequest req - ) - throws PwmUnrecoverableException - { - return new PwmURL( - URI.create( req.getRequestURL().toString() ), - req.getContextPath(), - ContextManager.getPwmApplication( req ).getConfig() ); - } - - public static PwmURL create( - final HttpServletRequest req, - final AppConfig appConfig - ) - { - return new PwmURL( - URI.create( req.getRequestURL().toString() ), - req.getContextPath(), - appConfig ); - } - - public static PwmURL create( - final URI uri, - final String contextPath, - final AppConfig appConfig - ) - { - return new PwmURL( uri, contextPath, appConfig ); - } - - public boolean isResourceURL( ) - { - return checkIfStartsWithURL( List.of( PwmConstants.URL_PREFIX_PUBLIC + "/resources/" ) ) || isReferenceURL(); - } - - public boolean isReferenceURL( ) - { - return checkIfMatchesURL( - List.of( PwmConstants.URL_PREFIX_PUBLIC + "/reference" ) ) - || checkIfStartsWithURL( List.of( PwmConstants.URL_PREFIX_PUBLIC + "/reference/" ) ); - } - - - public boolean isPrivateUrl( ) - { - return checkIfStartsWithURL( List.of( PwmConstants.URL_PREFIX_PRIVATE + "/" ) ); - } - - public boolean isAdminUrl( ) - { - return matches( PwmServletDefinition.SystemAdmin ); - } - - public boolean isIndexPage( ) - { - return checkIfMatchesURL( List.of( - "", - "/", - PwmConstants.URL_PREFIX_PRIVATE, - PwmConstants.URL_PREFIX_PUBLIC, - PwmConstants.URL_PREFIX_PRIVATE + "/", - PwmConstants.URL_PREFIX_PUBLIC + "/" - ) ); - } - - public boolean isPublicUrl( ) - { - return checkIfStartsWithURL( List.of( PwmConstants.URL_PREFIX_PUBLIC + "/" ) ); - } - - public boolean isCommandServletURL( ) - { - return matches( PwmServletDefinition.PublicCommand ) - || matches( PwmServletDefinition.PrivateCommand ); - - } - - public boolean isRestService( ) - { - return checkIfStartsWithURL( List.of( PwmConstants.URL_PREFIX_PUBLIC + "/rest/" ) ); - - } - - public boolean isConfigManagerURL( ) - { - return checkIfStartsWithURL( List.of( PwmConstants.URL_PREFIX_PRIVATE + "/config/" ) ); - } - - public boolean isConfigGuideURL( ) - { - return matches( PwmServletDefinition.ConfigGuide ); - } - - - public boolean isChangePasswordURL( ) - { - return matches( PwmServletDefinition.PrivateChangePassword ) - || matches( PwmServletDefinition.PublicChangePassword ); - } - - public Optional getServletDefinition() - { - return pwmServletDefinition.get(); - } - - private static Optional getServletDefinitionImpl( final PwmURL pwmURL ) - { - final List exactMatch = EnumSet.allOf( PwmServletDefinition.class ).stream() - .filter( pwmServletDefinition -> pwmURL.checkIfMatchesURL( pwmServletDefinition.urlPatterns() ) ) - .collect( Collectors.toList() ); - - if ( exactMatch.size() == 1 ) - { - return Optional.of( exactMatch.get( 0 ) ); - } - - final List startsWithMatch = EnumSet.allOf( PwmServletDefinition.class ).stream() - .filter( pwmServletDefinition -> pwmURL.checkIfStartsWithURL( pwmServletDefinition.urlPatterns() ) ) - .collect( Collectors.toList() ); - - if ( startsWithMatch.isEmpty() ) - { - return Optional.empty(); - } - - if ( startsWithMatch.size() == 1 ) - { - return Optional.of( startsWithMatch.get( 0 ) ); - } - - throw new PwmInternalException( "multiple servlet url matches: " + JsonFactory.get().serializeCollection( startsWithMatch ) ); - } - - public boolean matches( final PwmServletDefinition servletDefinition ) - { - return matches( Collections.singleton( servletDefinition ) ); - } - - public boolean matches( final Collection servletDefinitions ) - { - final Optional foundDefinition = getServletDefinition(); - return foundDefinition.isPresent() && servletDefinitions.contains( foundDefinition.get() ); - } - - public boolean isLocalizable( ) - { - return !isConfigGuideURL() - && !isAdminUrl() - && !isReferenceURL() - && !isConfigManagerURL(); - } - - public String toString( ) - { - return uri.toString(); - } - - private boolean checkIfStartsWithURL( final List url ) - { - final String servletRequestPath = pathMinusContextAndDomain(); - - for ( final String loopURL : url ) - { - if ( servletRequestPath.startsWith( loopURL ) ) - { - return true; - } - } - - return false; - } - - private boolean checkIfMatchesURL( final List url ) - { - final String servletRequestPath = pathMinusContextAndDomain(); - - for ( final String loopURL : url ) - { - if ( servletRequestPath.equals( loopURL ) ) - { - return true; - } - } - - return false; - } - - public List splitPaths() - { - return splitPathString( this.uri.getPath() ); - } - - public static List splitPathString( final String input ) - { - if ( input == null ) - { - return Collections.emptyList(); - } - final List urlSegments = new ArrayList<>( Arrays.asList( input.split( "/" ) ) ); - urlSegments.removeIf( StringUtil::isEmpty ); - return Collections.unmodifiableList( urlSegments ); - } - - public static String appendAndEncodeUrlParameters( - final String inputUrl, - final Map parameters - ) - { - String output = inputUrl == null ? "" : inputUrl; - - if ( parameters != null ) - { - for ( final Map.Entry entry : parameters.entrySet() ) - { - output = appendAndEncodeUrlParameters( output, entry.getKey(), entry.getValue() ); - } - } - - return output; - } - - public static String appendAndEncodeUrlParameters( - final String inputUrl, - final String paramName, - final String value - ) - { - final StringBuilder output = new StringBuilder( inputUrl ); - final String encodedValue = value == null - ? "" - : StringUtil.urlEncode( value ); - - output.append( output.toString().contains( "?" ) ? "&" : "?" ); - output.append( paramName ); - output.append( "=" ); - output.append( encodedValue ); - - if ( output.charAt( 0 ) == '?' || output.charAt( 0 ) == '&' ) - { - output.deleteCharAt( 0 ); - } - - return output.toString(); - } - - public static String encodeParametersToFormBody( final Map parameters ) - { - final StringBuilder output = new StringBuilder( ); - - for ( final Map.Entry entry : parameters.entrySet() ) - { - final String paramName = entry.getKey(); - final String value = entry.getValue(); - final String encodedValue = value == null - ? "" - : StringUtil.urlEncode( value ); - - output.append( output.length() > 0 ? '&' : "" ); - output.append( paramName ); - output.append( '=' ); - output.append( encodedValue ); - } - - return output.toString(); - } - - - public static int portForUriSchema( final URI uri ) - { - final int port = uri.getPort(); - if ( port < 1 ) - { - return Scheme.fromUri( uri ).map( Scheme::getDefaultPort ).orElse( -1 ); - } - return port; - } - - - public String getPostServletPath( final PwmServletDefinition pwmServletDefinition ) - { - final String path = this.uri.getPath(); - for ( final String pattern : pwmServletDefinition.urlPatterns() ) - { - final String patternWithContext = this.contextPath + pattern; - if ( path.startsWith( patternWithContext ) ) - { - return path.substring( patternWithContext.length() ); - } - } - return ""; - } - - public List getPathSegments() - { - final String uriPath = uri.getPath(); - return StringUtil.splitAndTrim( uriPath, "/" ); - } - - public String determinePwmServletPath( ) - { - final String requestPath = this.pathMinusContextAndDomain(); - for ( final PwmServletDefinition servletDefinition : PwmServletDefinition.values() ) - { - for ( final String pattern : servletDefinition.urlPatterns() ) - { - if ( requestPath.startsWith( pattern ) ) - { - return pattern; - } - } - } - return requestPath; - } - - private String pathMinusContextAndDomain() - { - String path = this.uri.getPath(); - if ( path.startsWith( this.contextPath ) ) - { - path = path.substring( this.contextPath.length() ); - } - - if ( appConfig.isMultiDomain() ) - { - for ( final String domain : appConfig.getDomainIDs() ) - { - final String testPath = '/' + domain; - if ( path.startsWith( testPath ) ) - { - return path.substring( testPath.length() ); - } - } - } - - return path; - } - - public static boolean testIfUrlMatchesAllowedPattern( - final String testURI, - final List whiteList, - final SessionLabel sessionLabel - ) - { - final String regexPrefix = "regex:"; - for ( final String loopFragment : whiteList ) - { - if ( loopFragment.startsWith( regexPrefix ) ) - { - try - { - final String strPattern = loopFragment.substring( regexPrefix.length() ); - final Pattern pattern = Pattern.compile( strPattern ); - if ( pattern.matcher( testURI ).matches() ) - { - LOGGER.debug( sessionLabel, () -> "positive URL match for regex pattern: " + strPattern ); - return true; - } - else - { - LOGGER.trace( sessionLabel, () -> "negative URL match for regex pattern: " + strPattern ); - } - } - catch ( final Exception e ) - { - LOGGER.error( sessionLabel, () -> "error while testing URL match for regex pattern: '" + loopFragment + "', error: " + e.getMessage() ); - } - - } - else - { - if ( testURI.startsWith( loopFragment ) ) - { - LOGGER.debug( sessionLabel, () -> "positive URL match for pattern: " + loopFragment ); - return true; - } - else - { - LOGGER.trace( sessionLabel, () -> "negative URL match for pattern: " + loopFragment ); - } - } - } - - return false; - } - - public static boolean uriSchemeMatches( final URI uri, final Scheme... schemes ) - { - if ( uri == null ) - { - return false; - } - - final String schemeStr = uri.getScheme(); - - if ( schemeStr == null ) - { - return false; - } - - for ( final Scheme loopScheme : schemes ) - { - if ( loopScheme.name().equals( schemeStr ) ) - { - return true; - } - } - - return false; - } -} diff --git a/server/src/main/java/password/pwm/http/auth/BasicFilterAuthenticationProvider.java b/server/src/main/java/password/pwm/http/auth/BasicFilterAuthenticationProvider.java index 0d4e8e591e..228420c201 100644 --- a/server/src/main/java/password/pwm/http/auth/BasicFilterAuthenticationProvider.java +++ b/server/src/main/java/password/pwm/http/auth/BasicFilterAuthenticationProvider.java @@ -70,15 +70,17 @@ public void attemptAuthentication( final PwmDomain pwmDomain = pwmRequest.getPwmDomain(); //user isn't already authenticated and has an auth header, so try to auth them. - LOGGER.debug( pwmRequest, () -> "attempting to authenticate user using basic auth header (username=" + basicAuthInfo.get().getUsername() + ")" ); + LOGGER.debug( pwmRequest, () -> "attempting to authenticate user using basic auth header (username=" + + basicAuthInfo.get().username() + ")" ); + final SessionAuthenticator sessionAuthenticator = new SessionAuthenticator( pwmDomain, pwmRequest, - PwmAuthenticationSource.BASIC_AUTH - ); + PwmAuthenticationSource.BASIC_AUTH ); + final UserSearchService userSearchService = pwmDomain.getUserSearchEngine(); - final UserIdentity userIdentity = userSearchService.resolveUsername( basicAuthInfo.get().getUsername(), null, null, pwmRequest.getLabel() ); - sessionAuthenticator.authenticateUser( userIdentity, basicAuthInfo.get().getPassword() ); + final UserIdentity userIdentity = userSearchService.resolveUsername( basicAuthInfo.get().username(), null, null, pwmRequest.getLabel() ); + sessionAuthenticator.authenticateUser( userIdentity, basicAuthInfo.get().password() ); pwmSession.getLoginInfoBean().setBasicAuth( basicAuthInfo.get() ); } catch ( final PwmException e ) diff --git a/server/src/main/java/password/pwm/http/auth/HttpAuthRecord.java b/server/src/main/java/password/pwm/http/auth/HttpAuthRecord.java index 177849797c..b02bf22634 100644 --- a/server/src/main/java/password/pwm/http/auth/HttpAuthRecord.java +++ b/server/src/main/java/password/pwm/http/auth/HttpAuthRecord.java @@ -20,13 +20,19 @@ package password.pwm.http.auth; -import lombok.Value; +import password.pwm.util.java.JavaHelper; import java.time.Instant; +import java.util.Objects; -@Value -public class HttpAuthRecord +public record HttpAuthRecord( + Instant date, + String guid +) { - private Instant date; - private String guid; + public HttpAuthRecord( final Instant date, final String guid ) + { + this.date = Objects.requireNonNull( date ); + this.guid = JavaHelper.requireNonEmpty( guid ); + } } diff --git a/server/src/main/java/password/pwm/http/bean/DisplayElement.java b/server/src/main/java/password/pwm/http/bean/DisplayElement.java index c803df3da4..ad9efc5198 100644 --- a/server/src/main/java/password/pwm/http/bean/DisplayElement.java +++ b/server/src/main/java/password/pwm/http/bean/DisplayElement.java @@ -20,19 +20,20 @@ package password.pwm.http.bean; -import lombok.Value; +import password.pwm.util.java.CollectionUtil; import java.util.List; +import java.util.Objects; -@Value -public class DisplayElement -{ - private final String key; - private final Type type; - private final String label; - private final String value; - private final List values; +public record DisplayElement( + String key, + Type type, + String label, + String value, + List values +) +{ public enum Type { string, @@ -41,21 +42,39 @@ public enum Type multiString, } - public DisplayElement( final String key, final Type type, final String label, final String value ) + public DisplayElement( + final String key, + final Type type, + final String label, + final String value, + final List values + ) { - this.key = key; - this.type = type; - this.label = label; + this.key = Objects.requireNonNull( key ); + this.type = Objects.requireNonNull( type ); + this.label = Objects.requireNonNull( label ); this.value = value; - this.values = null; + this.values = CollectionUtil.stripNulls( values ); + } + + public static DisplayElement create( + final String key, + final Type type, + final String label, + + final String value + ) + { + return new DisplayElement( key, type, label, value, null ); } - public DisplayElement( final String key, final Type type, final String label, final List values ) + public static DisplayElement createMultiValue( + final String key, + final Type type, + final String label, + final List values + ) { - this.key = key; - this.type = type; - this.label = label; - this.value = null; - this.values = values; + return new DisplayElement( key, type, label, null, values ); } } diff --git a/server/src/main/java/password/pwm/http/filter/AuthenticationFilter.java b/server/src/main/java/password/pwm/http/filter/AuthenticationFilter.java index 866284cd89..d3978f2745 100644 --- a/server/src/main/java/password/pwm/http/filter/AuthenticationFilter.java +++ b/server/src/main/java/password/pwm/http/filter/AuthenticationFilter.java @@ -41,8 +41,8 @@ import password.pwm.http.servlet.oauth.OAuthSettings; import password.pwm.i18n.Display; import password.pwm.ldap.PasswordChangeProgressChecker; -import password.pwm.user.UserInfo; import password.pwm.ldap.auth.AuthenticationType; +import password.pwm.user.UserInfo; import password.pwm.util.BasicAuthInfo; import password.pwm.util.i18n.LocaleHelper; import password.pwm.util.logging.PwmLogger; @@ -131,59 +131,66 @@ public void processFilter( pwmRequest.respondWithError( e.getErrorInformation(), true ); } } - - private void processAuthenticatedSession( - final PwmRequest pwmRequest, - final PwmFilterChain chain - ) - throws IOException, ServletException, PwmUnrecoverableException - { - final PwmDomain pwmDomain = pwmRequest.getPwmDomain(); - final PwmSession pwmSession = pwmRequest.getPwmSession(); + private static void verifyBasicAuthHeaderUnchanged( final PwmRequest pwmRequest ) + throws PwmUnrecoverableException + { // read the basic auth info out of the header (if it exists); if ( pwmRequest.getDomainConfig().readSettingAsBoolean( PwmSetting.BASIC_AUTH_ENABLED ) ) { - final Optional basicAuthInfo = BasicAuthInfo.parseAuthHeader( pwmDomain, pwmRequest ); + final Optional basicAuthInfo = BasicAuthInfo.parseAuthHeader( pwmRequest.getPwmDomain(), pwmRequest ); - final BasicAuthInfo originalBasicAuthInfo = pwmSession.getLoginInfoBean().getBasicAuth(); + final BasicAuthInfo originalBasicAuthInfo = pwmRequest.getPwmSession().getLoginInfoBean().getBasicAuth(); //check to make sure basic auth info is same as currently known user in session. - if ( basicAuthInfo.isPresent() && Objects.equals( basicAuthInfo.get(), originalBasicAuthInfo ) ) + if ( basicAuthInfo.isPresent() + && !Objects.equals( basicAuthInfo.get(), originalBasicAuthInfo ) ) { // if we read here then user is using basic auth, and header has changed since last request // this means something is screwy, so log out the session + LOGGER.debug( pwmRequest, () -> "basic auth header user '" + basicAuthInfo.get().username() + + "' does not match currently logged in user '" + pwmRequest.getUserInfoIfLoggedIn().getUserDN() + + "', session will be logged out" ); - // read the current user info for logging - final UserInfo userInfo = pwmSession.getUserInfo(); final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_BAD_SESSION, - "basic auth header user '" + basicAuthInfo.get().getUsername() - + "' does not match currently logged in user '" + userInfo.getUserIdentity() - + "', session will be logged out" + "basic auth header user does not match currently logged in user, session will be logged out" ); + LOGGER.info( pwmRequest, errorInformation ); // log out their user - pwmSession.unAuthenticateUser( pwmRequest ); + pwmRequest.getPwmSession().unAuthenticateUser( pwmRequest ); - // send en error to user. - pwmRequest.respondWithError( errorInformation, true ); - return; + throw new PwmUnrecoverableException( errorInformation ); } } + } + private static void checkOAuthExpiration( final PwmRequest pwmRequest ) + throws PwmUnrecoverableException + { // check status of oauth expiration - if ( pwmSession.getLoginInfoBean().getOauthExp() != null ) + if ( pwmRequest.getPwmSession().getLoginInfoBean().getOauthExp() != null ) { final OAuthSettings oauthSettings = OAuthSettings.forSSOAuthentication( pwmRequest.getDomainConfig() ); final OAuthMachine oAuthMachine = new OAuthMachine( pwmRequest.getLabel(), oauthSettings ); if ( oAuthMachine.checkOAuthExpiration( pwmRequest ) ) { - pwmRequest.respondWithError( new ErrorInformation( PwmError.ERROR_OAUTH_ERROR, "oauth access token has expired" ) ); - return; + throw new PwmUnrecoverableException( PwmError.ERROR_OAUTH_ERROR, "oauth access token has expired" ); } } + } + + private void processAuthenticatedSession( + final PwmRequest pwmRequest, + final PwmFilterChain chain + ) + throws IOException, ServletException, PwmUnrecoverableException + { + verifyBasicAuthHeaderUnchanged( pwmRequest ); + + checkOAuthExpiration( pwmRequest ); HttpAuthenticationUtilities.handleAuthenticationCookie( pwmRequest ); diff --git a/server/src/main/java/password/pwm/http/filter/RequestInitializationFilter.java b/server/src/main/java/password/pwm/http/filter/RequestInitializationFilter.java index a16237a8d0..628b217bdb 100644 --- a/server/src/main/java/password/pwm/http/filter/RequestInitializationFilter.java +++ b/server/src/main/java/password/pwm/http/filter/RequestInitializationFilter.java @@ -408,7 +408,7 @@ private static void addPwmResponseHeaders( contentPolicy = config.readSettingAsString( PwmSetting.SECURITY_CSP_HEADER ); } - if ( contentPolicy != null && !contentPolicy.isEmpty() ) + if ( !StringUtil.isEmpty( contentPolicy ) ) { final String nonce = pwmRequest.getCspNonce(); final String replacedPolicy = contentPolicy.replace( "%NONCE%", nonce ); diff --git a/server/src/main/java/password/pwm/http/servlet/AbstractPwmServlet.java b/server/src/main/java/password/pwm/http/servlet/AbstractPwmServlet.java index f2858caece..b2ce8d96eb 100644 --- a/server/src/main/java/password/pwm/http/servlet/AbstractPwmServlet.java +++ b/server/src/main/java/password/pwm/http/servlet/AbstractPwmServlet.java @@ -89,7 +89,10 @@ private void handleRequest( { final PwmRequest pwmRequest = PwmRequest.forRequest( req, resp ); - if ( !method.isIdempotent() && !pwmRequest.getURL().isCommandServletURL() ) + final boolean methodIdempotent = method.isIdempotent(); + final boolean isCommandServlet = pwmRequest.getURL().isCommandServletURL(); + final boolean requiresValidation = !methodIdempotent && !isCommandServlet; + if ( requiresValidation ) { Validator.validatePwmFormID( pwmRequest ); diff --git a/server/src/main/java/password/pwm/http/servlet/ClientApiServlet.java b/server/src/main/java/password/pwm/http/servlet/ClientApiServlet.java index 77f4c1629c..c66e6bee21 100644 --- a/server/src/main/java/password/pwm/http/servlet/ClientApiServlet.java +++ b/server/src/main/java/password/pwm/http/servlet/ClientApiServlet.java @@ -122,9 +122,7 @@ public enum ClientApiAction implements AbstractPwmServlet.ProcessAction strings( HttpMethod.GET ), health( HttpMethod.GET ), ping( HttpMethod.GET ), - statistics( HttpMethod.GET ), - cspReport( HttpMethod.POST ),; - + statistics( HttpMethod.GET ),; private final HttpMethod method; @@ -464,20 +462,6 @@ public ProcessStatus restStatisticsHandler( final PwmRequest pwmRequest ) return ProcessStatus.Halt; } - @ActionHandler( action = "cspReport" ) - public ProcessStatus restCspReportHandler( final PwmRequest pwmRequest ) - throws PwmUnrecoverableException, IOException - { - if ( !Boolean.parseBoolean( pwmRequest.getDomainConfig().readAppProperty( AppProperty.LOGGING_LOG_CSP_REPORT ) ) ) - { - return ProcessStatus.Halt; - } - - final String body = pwmRequest.readRequestBodyAsString(); - LOGGER.trace( pwmRequest, () -> body ); - return ProcessStatus.Halt; - } - private void precheckPublicHealthAndStats( final PwmRequest pwmRequest ) throws PwmUnrecoverableException { diff --git a/server/src/main/java/password/pwm/http/servlet/ForgottenUsernameServlet.java b/server/src/main/java/password/pwm/http/servlet/ForgottenUsernameServlet.java index fe844c72a0..68cd3ade2c 100644 --- a/server/src/main/java/password/pwm/http/servlet/ForgottenUsernameServlet.java +++ b/server/src/main/java/password/pwm/http/servlet/ForgottenUsernameServlet.java @@ -112,7 +112,6 @@ protected void processAction( final PwmRequest pwmRequest ) if ( action.isPresent() ) { - pwmRequest.validatePwmFormID(); switch ( action.get() ) { case search: diff --git a/server/src/main/java/password/pwm/http/servlet/LoginServlet.java b/server/src/main/java/password/pwm/http/servlet/LoginServlet.java index 332836ae62..4fadd27bcb 100644 --- a/server/src/main/java/password/pwm/http/servlet/LoginServlet.java +++ b/server/src/main/java/password/pwm/http/servlet/LoginServlet.java @@ -67,7 +67,6 @@ name = "LoginServlet", urlPatterns = { PwmConstants.URL_PREFIX_PRIVATE + "/login", - PwmConstants.URL_PREFIX_PRIVATE + "/foom", PwmConstants.URL_PREFIX_PRIVATE + "/Login" } ) diff --git a/server/src/main/java/password/pwm/http/servlet/PwmServletDefinition.java b/server/src/main/java/password/pwm/http/servlet/PwmServletDefinition.java index 2f31ccda30..52a753a239 100644 --- a/server/src/main/java/password/pwm/http/servlet/PwmServletDefinition.java +++ b/server/src/main/java/password/pwm/http/servlet/PwmServletDefinition.java @@ -87,6 +87,8 @@ public enum PwmServletDefinition UpdateProfile( UpdateProfileServlet.class, UpdateProfileBean.class, Flag.RequiresUserPasswordAndBind ), SetupOtp( password.pwm.http.servlet.SetupOtpServlet.class, SetupOtpBean.class, Flag.RequiresUserPasswordAndBind ), Helpdesk( password.pwm.http.servlet.helpdesk.HelpdeskServlet.class, null ), + HelpdeskDetail( password.pwm.http.servlet.helpdesk.HelpdeskDetailServlet.class, null ), + Shortcuts( password.pwm.http.servlet.ShortcutServlet.class, ShortcutsBean.class ), PrivateCommand( PrivateCommandServlet.class, null ), PrivatePeopleSearch( PrivatePeopleSearchServlet.class, null ), diff --git a/server/src/main/java/password/pwm/http/servlet/SetupOtpServlet.java b/server/src/main/java/password/pwm/http/servlet/SetupOtpServlet.java index 4f64f0ed53..177bdb3232 100644 --- a/server/src/main/java/password/pwm/http/servlet/SetupOtpServlet.java +++ b/server/src/main/java/password/pwm/http/servlet/SetupOtpServlet.java @@ -139,22 +139,25 @@ public ProcessStatus preProcessCheck( final PwmRequest pwmRequest ) final PwmSession pwmSession = pwmRequest.getPwmSession(); final DomainConfig config = pwmDomain.getConfig(); - final SetupOtpProfile setupOtpProfile = getSetupOtpProfile( pwmRequest ); - if ( setupOtpProfile == null || !setupOtpProfile.readSettingAsBoolean( PwmSetting.OTP_ALLOW_SETUP ) ) + if ( !pwmDomain.getConfig().readSettingAsBoolean( PwmSetting.OTP_ALLOW_SETUP ) ) { final String errorMsg = "setup OTP is not enabled"; - final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_SERVICE_NOT_AVAILABLE, errorMsg ); - LOGGER.error( pwmRequest, errorInformation ); - pwmRequest.respondWithError( errorInformation ); - return ProcessStatus.Halt; + throw new PwmUnrecoverableException( PwmError.ERROR_SERVICE_NOT_AVAILABLE, errorMsg ); + } + + final SetupOtpProfile setupOtpProfile = getSetupOtpProfile( pwmRequest ); + + if ( setupOtpProfile == null ) + { + final String errorMsg = "a setup OTP profile is not assigned to user"; + throw new PwmUnrecoverableException( PwmError.ERROR_SERVICE_NOT_AVAILABLE, errorMsg ); } // check whether the setup can be stored if ( !canSetupOtpSecret( config ) ) { - LOGGER.error( pwmRequest, () -> "OTP Secret cannot be setup" ); - pwmRequest.respondWithError( PwmError.ERROR_INVALID_CONFIG.toInfo() ); - return ProcessStatus.Halt; + final String errorMsg = "OTP Secret cannot be setup"; + throw new PwmUnrecoverableException( PwmError.ERROR_INVALID_CONFIG, errorMsg ); } if ( pwmSession.getLoginInfoBean().getType() == AuthenticationType.AUTH_WITHOUT_PASSWORD ) @@ -234,6 +237,8 @@ protected void nextStep( final PwmRequest pwmRequest ) } else { + final int tokenLength = pwmRequest.getPwmDomain().getOtpService().getSettings().getOtpTokenLength(); + pwmRequest.setAttribute( PwmRequestAttribute.SetupOtp_TokenLength, tokenLength ); pwmRequest.forwardToJsp( JspUrl.SETUP_OTP_SECRET_TEST ); } } @@ -421,15 +426,7 @@ private void initializeBean( if ( otpBean.getOtpUserRecord() == null ) { - final OTPUserRecord existingUserRecord; - try - { - existingUserRecord = service.readOTPUserConfiguration( pwmRequest.getLabel(), theUser ); - } - catch ( final ChaiUnavailableException e ) - { - throw PwmUnrecoverableException.fromChaiException( e ); - } + final OTPUserRecord existingUserRecord = service.readOTPUserConfiguration( pwmRequest.getLabel(), theUser ); if ( existingUserRecord != null ) { diff --git a/server/src/main/java/password/pwm/http/servlet/ShortcutServlet.java b/server/src/main/java/password/pwm/http/servlet/ShortcutServlet.java index 525aee0e9c..7c3ee0c15e 100644 --- a/server/src/main/java/password/pwm/http/servlet/ShortcutServlet.java +++ b/server/src/main/java/password/pwm/http/servlet/ShortcutServlet.java @@ -157,8 +157,8 @@ public ProcessStatus processSelectShortcutRequest( final ShortcutItem item = visibleItems.get( link ); StatisticsClient.incrementStat( pwmRequest, Statistic.SHORTCUTS_SELECTED ); - LOGGER.trace( pwmRequest, () -> "shortcut link selected: " + link + ", setting link for 'forwardURL' to " + item.getShortcutURI() ); - pwmSession.getSessionStateBean().setForwardURL( item.getShortcutURI().toString() ); + LOGGER.trace( pwmRequest, () -> "shortcut link selected: " + link + ", setting link for 'forwardURL' to " + item.shortcutURI() ); + pwmSession.getSessionStateBean().setForwardURL( item.shortcutURI().toString() ); pwmRequest.getPwmResponse().sendRedirectToContinue(); return ProcessStatus.Halt; @@ -207,7 +207,7 @@ private static Map figureVisibleShortcuts( final Map visibleItems = Collections.unmodifiableMap( configuredItems.stream() .filter( item -> checkItemMatch( pwmRequest, labelsFromHeader, item ) ) .collect( CollectorUtil.toLinkedMap( - ShortcutItem::getLabel, + ShortcutItem::label, Function.identity() ) ) ); LOGGER.debug( pwmRequest, () -> "built visible shortcut list for user: '" + StringUtil.collectionToString( visibleItems.keySet() ) + "'" ); @@ -221,19 +221,19 @@ private static boolean checkItemMatch( final ShortcutItem item ) { - if ( StringUtil.caseIgnoreContains( labelsFromHeader, item.getLabel() ) ) + if ( StringUtil.caseIgnoreContains( labelsFromHeader, item.label() ) ) { - LOGGER.trace( pwmRequest, () -> "adding the shortcut item '" + item.getLabel() + "' due to presence of configured headers in request" ); + LOGGER.trace( pwmRequest, () -> "adding the shortcut item '" + item.label() + "' due to presence of configured headers in request" ); return true; } final UserIdentity userIdentity = pwmRequest.getPwmSession().getUserInfo().getUserIdentity(); - final UserPermission userPermission = UserPermission.builder() - .type( UserPermissionType.ldapQuery ) - .ldapQuery( item.getLdapQuery() ) - .ldapBase( userIdentity.getUserDN() ) - .build(); + final UserPermission userPermission = new UserPermission( + UserPermissionType.ldapQuery, + null, + item.ldapQuery(), + userIdentity.getUserDN() ); try { @@ -245,14 +245,14 @@ private static boolean checkItemMatch( if ( match ) { - LOGGER.trace( pwmRequest, () -> "adding the shortcut item '" + item.getLabel() + "' due to ldap query match" ); + LOGGER.trace( pwmRequest, () -> "adding the shortcut item '" + item.label() + "' due to ldap query match" ); } return match; } catch ( final PwmUnrecoverableException e ) { - LOGGER.trace( pwmRequest, () -> "error during ldap user permission test of shortcut label '" + item.getLabel() + "', error: " + e.getMessage() ); + LOGGER.trace( pwmRequest, () -> "error during ldap user permission test of shortcut label '" + item.label() + "', error: " + e.getMessage() ); } return false; diff --git a/server/src/main/java/password/pwm/http/servlet/accountinfo/AccountInformationBean.java b/server/src/main/java/password/pwm/http/servlet/accountinfo/AccountInformationBean.java index 337a813fa5..4581400aa2 100644 --- a/server/src/main/java/password/pwm/http/servlet/accountinfo/AccountInformationBean.java +++ b/server/src/main/java/password/pwm/http/servlet/accountinfo/AccountInformationBean.java @@ -138,8 +138,8 @@ public static List makeAuditInfo( for ( final UserAuditRecord userAuditRecord : auditRecords ) { returnData.add( new ActivityRecord( - userAuditRecord.getTimestamp(), - userAuditRecord.getEventCode().getLocalizedString( pwmDomain.getConfig(), locale ) + userAuditRecord.timestamp(), + userAuditRecord.eventCode().getLocalizedString( pwmDomain.getConfig(), locale ) ) ); } @@ -173,7 +173,7 @@ private static List makeFormInfo( ? StringUtil.collectionToString( values, ", " ) : values.isEmpty() ? "" : values.iterator().next(); - returnData.add( new DisplayElement( + returnData.add( DisplayElement.create( formConfig.getName(), DisplayElement.Type.string, formConfig.getLabel( locale ), diff --git a/server/src/main/java/password/pwm/http/servlet/activation/ActivateUserServlet.java b/server/src/main/java/password/pwm/http/servlet/activation/ActivateUserServlet.java index 8c1c1f3e13..86364095be 100644 --- a/server/src/main/java/password/pwm/http/servlet/activation/ActivateUserServlet.java +++ b/server/src/main/java/password/pwm/http/servlet/activation/ActivateUserServlet.java @@ -349,7 +349,7 @@ public ProcessStatus handleEnterCode( if ( activateUserBean.getUserIdentity() == null ) { - ActivateUserUtils.initUserActivationBean( pwmRequest, tokenPayload.getUserIdentity() ); + ActivateUserUtils.initUserActivationBean( pwmRequest, tokenPayload.userIdentity() ); } activateUserBean.setTokenPassed( true ); @@ -357,7 +357,7 @@ public ProcessStatus handleEnterCode( if ( pwmRequest.getDomainConfig().readSettingAsBoolean( PwmSetting.DISPLAY_TOKEN_SUCCESS_BUTTON ) ) { - pwmRequest.setAttribute( PwmRequestAttribute.TokenDestItems, tokenPayload.getDestination() ); + pwmRequest.setAttribute( PwmRequestAttribute.TokenDestItems, tokenPayload.destination() ); pwmRequest.forwardToJsp( JspUrl.ACTIVATE_USER_TOKEN_SUCCESS ); return ProcessStatus.Halt; } diff --git a/server/src/main/java/password/pwm/http/servlet/admin/AppDashboardData.java b/server/src/main/java/password/pwm/http/servlet/admin/AppDashboardData.java index 03251c0217..ae4990605e 100644 --- a/server/src/main/java/password/pwm/http/servlet/admin/AppDashboardData.java +++ b/server/src/main/java/password/pwm/http/servlet/admin/AppDashboardData.java @@ -39,6 +39,7 @@ import password.pwm.svc.PwmService; import password.pwm.svc.node.NodeInfo; import password.pwm.svc.node.NodeService; +import password.pwm.svc.node.NodeState; import password.pwm.svc.sessiontrack.SessionTrackService; import password.pwm.util.i18n.LocaleHelper; import password.pwm.util.java.CollectionUtil; @@ -136,7 +137,7 @@ public static class NodeData private String instanceID; private String uptime; private String lastSeen; - private NodeInfo.NodeState state; + private NodeState state; private boolean configMatch; } @@ -224,52 +225,52 @@ private static List makeAboutData( final String notApplicableValue = Display.getLocalizedMessage( locale, Display.Value_NotApplicable, pwmDomain.getConfig() ); final PwmNumberFormat numberFormat = PwmNumberFormat.forLocale( locale ); - return List.of( new DisplayElement( + return List.of( DisplayElement.create( "appVersion", DisplayElement.Type.string, l.forKey( "Field_AppVersion", PwmConstants.PWM_APP_NAME ), PwmConstants.SERVLET_VERSION - ), new DisplayElement( + ), DisplayElement.create( "appBuildTime", DisplayElement.Type.timestamp, l.forKey( "Field_AppBuildTime" ), PwmConstants.BUILD_TIME - ), new DisplayElement( + ), DisplayElement.create( "currentTime", DisplayElement.Type.timestamp, l.forKey( "Field_CurrentTime" ), StringUtil.toIsoDate( Instant.now() ) - ), new DisplayElement( + ), DisplayElement.create( "startupTime", DisplayElement.Type.timestamp, l.forKey( "Field_StartTime" ), StringUtil.toIsoDate( pwmDomain.getPwmApplication().getStartupTime() ) - ), new DisplayElement( + ), DisplayElement.create( "runningDuration", DisplayElement.Type.string, l.forKey( "Field_UpTime" ), PwmTimeUtil.asLongString( TimeDuration.fromCurrent( pwmDomain.getPwmApplication().getStartupTime() ), locale ) - ), new DisplayElement( + ), DisplayElement.create( "installTime", DisplayElement.Type.timestamp, l.forKey( "Field_InstallTime" ), StringUtil.toIsoDate( pwmDomain.getPwmApplication().getInstallTime() ) - ), new DisplayElement( + ), DisplayElement.create( "siteURL", DisplayElement.Type.string, l.forKey( "Field_SiteURL" ), pwmDomain.getConfig().getAppConfig().readSettingAsString( PwmSetting.PWM_SITE_URL ) - ), new DisplayElement( + ), DisplayElement.create( "instanceID", DisplayElement.Type.string, l.forKey( "Field_InstanceID" ), pwmDomain.getPwmApplication().getInstanceID() - ), new DisplayElement( + ), DisplayElement.create( "configRestartCounter", DisplayElement.Type.number, "Configuration Restart Counter", contextManager == null ? notApplicableValue : numberFormat.format( contextManager.getRestartCount() ) - ), new DisplayElement( + ), DisplayElement.create( "chaiApiVersion", DisplayElement.Type.string, l.forKey( "Field_ChaiAPIVersion" ), @@ -324,14 +325,14 @@ private static List makeLocalDbInfo( final PwmDomain pwmDomain, final String notApplicable = Display.getLocalizedMessage( locale, Display.Value_NotApplicable, pwmDomain.getConfig() ); final PwmNumberFormat numberFormat = PwmNumberFormat.forLocale( locale ); - localDbInfo.add( new DisplayElement( + localDbInfo.add( DisplayElement.create( "worlistSize", DisplayElement.Type.number, "Word List Dictionary Size", numberFormat.format( pwmDomain.getPwmApplication().getWordlistService().size() ) ) ); - localDbInfo.add( new DisplayElement( + localDbInfo.add( DisplayElement.create( "sharedHistorySize", DisplayElement.Type.number, "Shared Password History Size", @@ -342,32 +343,32 @@ private static List makeLocalDbInfo( final PwmDomain pwmDomain, final String display = oldestEntryAge == null ? notApplicable : TimeDuration.fromCurrent( oldestEntryAge ).asCompactString(); - localDbInfo.add( new DisplayElement( + localDbInfo.add( DisplayElement.create( "oldestSharedHistory", DisplayElement.Type.string, "Oldest Shared Password Entry", display ) ); } - localDbInfo.add( new DisplayElement( + localDbInfo.add( DisplayElement.create( "emailQueueSize", DisplayElement.Type.number, "Email Queue Size", numberFormat.format( pwmDomain.getPwmApplication().getEmailQueue().queueSize() ) ) ); - localDbInfo.add( new DisplayElement( + localDbInfo.add( DisplayElement.create( "smsQueueSize", DisplayElement.Type.number, "SMS Queue Size", numberFormat.format( pwmDomain.getPwmApplication().getSmsQueue().queueSize() ) ) ); - localDbInfo.add( new DisplayElement( + localDbInfo.add( DisplayElement.create( "sharedHistorySize", DisplayElement.Type.number, "Syslog Queue Size", String.valueOf( pwmDomain.getAuditService().syslogQueueSize() ) ) ); - localDbInfo.add( new DisplayElement( + localDbInfo.add( DisplayElement.create( "localAuditRecords", DisplayElement.Type.number, "Audit Records", @@ -378,14 +379,14 @@ private static List makeLocalDbInfo( final PwmDomain pwmDomain, final String display = eldestAuditRecord.isPresent() ? PwmTimeUtil.asLongString( TimeDuration.fromCurrent( eldestAuditRecord.get() ) ) : notApplicable; - localDbInfo.add( new DisplayElement( + localDbInfo.add( DisplayElement.create( "oldestLocalAuditRecords", DisplayElement.Type.string, "Oldest Audit Record", display ) ); } - localDbInfo.add( new DisplayElement( + localDbInfo.add( DisplayElement.create( "logEvents", DisplayElement.Type.number, "Log Events", @@ -397,7 +398,7 @@ private static List makeLocalDbInfo( final PwmDomain pwmDomain, && localDBLogger.getTailDate().isPresent() ? PwmTimeUtil.asLongString( TimeDuration.fromCurrent( localDBLogger.getTailDate().get() ) ) : notApplicable; - localDbInfo.add( new DisplayElement( + localDbInfo.add( DisplayElement.create( "oldestLogEvents", DisplayElement.Type.string, "Oldest Log Event", @@ -411,7 +412,7 @@ private static List makeLocalDbInfo( final PwmDomain pwmDomain, ? notApplicable : StringUtil.formatDiskSize( FileSystemUtility.getFileDirectorySize( pwmDomain.getPwmApplication().getLocalDB().getFileLocation() ) ); - localDbInfo.add( new DisplayElement( + localDbInfo.add( DisplayElement.create( "localDbSizeOnDisk", DisplayElement.Type.string, "LocalDB Size On Disk", @@ -426,7 +427,7 @@ private static List makeLocalDbInfo( final PwmDomain pwmDomain, : StringUtil.formatDiskSize( FileSystemUtility.diskSpaceRemaining( pwmDomain.getPwmApplication().getLocalDB().getFileLocation() ) ); - localDbInfo.add( new DisplayElement( + localDbInfo.add( DisplayElement.create( "localDbFreeSpace", DisplayElement.Type.string, "LocalDB Free Space", @@ -472,7 +473,7 @@ private static List makeAboutJavaData( { for ( final PwmAboutProperty property : INTERESTED_ABOUT_PROPERTIES ) { - javaInfo.add( new DisplayElement( + javaInfo.add( DisplayElement.create( property.name(), DisplayElement.Type.string, property.getLabel(), @@ -487,7 +488,7 @@ private static List makeAboutJavaData( final String display = numberFormat.format( pwmDomain.getResourceServletService().itemsInCache() ) + " items (" + numberFormat.format( pwmDomain.getResourceServletService().bytesInCache() ) + " bytes)"; - javaInfo.add( new DisplayElement( + javaInfo.add( DisplayElement.create( "resourceFileServletCacheSize", DisplayElement.Type.string, "ResourceFileServlet Cache", @@ -495,7 +496,7 @@ private static List makeAboutJavaData( ) ); } - javaInfo.add( new DisplayElement( + javaInfo.add( DisplayElement.create( "resourceFileServletCacheHitRatio", DisplayElement.Type.string, "ResourceFileServlet Cache Hit Ratio", @@ -505,14 +506,14 @@ private static List makeAboutJavaData( { final Map debugInfoMap = pwmDomain.getSessionTrackService().getDebugData(); - javaInfo.add( new DisplayElement( + javaInfo.add( DisplayElement.create( "sessionTotalSize", DisplayElement.Type.string, "Estimated Session Total Size", debugInfoMap.get( SessionTrackService.DebugKey.HttpSessionTotalSize ) ) ); - javaInfo.add( new DisplayElement( + javaInfo.add( DisplayElement.create( "sessionAverageSize", DisplayElement.Type.string, "Estimated Session Average Size", @@ -558,16 +559,16 @@ private static List makeNodeData( for ( final NodeInfo nodeInfo : nodeService.nodes() ) { - final String uptime = nodeInfo.getStartupTime() == null + final String uptime = nodeInfo.startupTime() == null ? notApplicable - : PwmTimeUtil.asLongString( TimeDuration.fromCurrent( nodeInfo.getStartupTime() ), locale ); + : PwmTimeUtil.asLongString( TimeDuration.fromCurrent( nodeInfo.startupTime() ), locale ); nodeData.add( new NodeData( - nodeInfo.getInstanceID(), + nodeInfo.instanceID(), uptime, - StringUtil.toIsoDate( nodeInfo.getLastSeen() ), - nodeInfo.getNodeState(), - nodeInfo.isConfigMatch() + StringUtil.toIsoDate( nodeInfo.lastSeen() ), + nodeInfo.nodeState(), + nodeInfo.configMatch() ) ); } } diff --git a/server/src/main/java/password/pwm/http/servlet/admin/SystemAdminServlet.java b/server/src/main/java/password/pwm/http/servlet/admin/SystemAdminServlet.java index c529880f43..9896020611 100644 --- a/server/src/main/java/password/pwm/http/servlet/admin/SystemAdminServlet.java +++ b/server/src/main/java/password/pwm/http/servlet/admin/SystemAdminServlet.java @@ -281,7 +281,7 @@ public ProcessStatus restAuditDataHandler( final PwmRequest pwmRequest ) ) { final AuditRecord loopRecord = iterator.next(); - if ( auditDataType == loopRecord.getType() ) + if ( auditDataType == loopRecord.type() ) { records.add( loopRecord ); } @@ -446,7 +446,7 @@ public ProcessStatus restreadPwNotifyStatus( final PwmRequest pwmRequest ) throw int key = 0; if ( !pwmRequest.getDomainConfig().readSettingAsBoolean( PwmSetting.PW_EXPY_NOTIFY_ENABLE ) ) { - final DisplayElement displayElement = new DisplayElement( String.valueOf( key++ ), DisplayElement.Type.string, "Status", + final DisplayElement displayElement = DisplayElement.create( String.valueOf( key++ ), DisplayElement.Type.string, "Status", "Password Notification Feature is not enabled. See setting: " + PwmSetting.PW_EXPY_NOTIFY_ENABLE.toMenuLocationDebug( null, pwmRequest.getLocale() ) ); final PwNotifyStatusBean pwNotifyStatusBean = new PwNotifyStatusBean( Collections.singletonList( displayElement ), false ); @@ -461,43 +461,43 @@ public ProcessStatus restreadPwNotifyStatus( final PwmRequest pwmRequest ) throw final PwNotifyStoredJobState pwNotifyStoredJobState = pwNotifyService.getJobState(); final boolean canRunOnthisServer = pwNotifyService.canRunOnThisServer(); - statusData.add( new DisplayElement( String.valueOf( key++ ), DisplayElement.Type.string, + statusData.add( DisplayElement.create( String.valueOf( key++ ), DisplayElement.Type.string, "Currently Processing (on this server)", LocaleHelper.booleanString( pwNotifyService.isRunning(), locale, config ) ) ); - statusData.add( new DisplayElement( String.valueOf( key++ ), DisplayElement.Type.string, + statusData.add( DisplayElement.create( String.valueOf( key++ ), DisplayElement.Type.string, "This Server is the Job Processor", LocaleHelper.booleanString( canRunOnthisServer, locale, config ) ) ); if ( canRunOnthisServer ) { - statusData.add( new DisplayElement( String.valueOf( key++ ), DisplayElement.Type.timestamp, + statusData.add( DisplayElement.create( String.valueOf( key++ ), DisplayElement.Type.timestamp, "Next Job Scheduled Time", LocaleHelper.instantString( pwNotifyService.getNextExecutionTime(), locale, config ) ) ); } if ( pwNotifyStoredJobState != null ) { - statusData.add( new DisplayElement( String.valueOf( key++ ), DisplayElement.Type.timestamp, - "Last Job Start Time", LocaleHelper.instantString( pwNotifyStoredJobState.getLastStart(), locale, config ) ) ); + statusData.add( DisplayElement.create( String.valueOf( key++ ), DisplayElement.Type.timestamp, + "Last Job Start Time", LocaleHelper.instantString( pwNotifyStoredJobState.lastStart(), locale, config ) ) ); - statusData.add( new DisplayElement( String.valueOf( key++ ), DisplayElement.Type.timestamp, - "Last Job Completion Time", LocaleHelper.instantString( pwNotifyStoredJobState.getLastCompletion(), locale, config ) ) ); + statusData.add( DisplayElement.create( String.valueOf( key++ ), DisplayElement.Type.timestamp, + "Last Job Completion Time", LocaleHelper.instantString( pwNotifyStoredJobState.lastCompletion(), locale, config ) ) ); - if ( pwNotifyStoredJobState.getLastStart() != null && pwNotifyStoredJobState.getLastCompletion() != null ) + if ( pwNotifyStoredJobState.lastStart() != null && pwNotifyStoredJobState.lastCompletion() != null ) { - final TimeDuration lastJobDuration = TimeDuration.between( pwNotifyStoredJobState.getLastStart(), pwNotifyStoredJobState.getLastCompletion() ); - statusData.add( new DisplayElement( String.valueOf( key++ ), DisplayElement.Type.timestamp, + final TimeDuration lastJobDuration = TimeDuration.between( pwNotifyStoredJobState.lastStart(), pwNotifyStoredJobState.lastCompletion() ); + statusData.add( DisplayElement.create( String.valueOf( key++ ), DisplayElement.Type.timestamp, "Last Job Duration", PwmTimeUtil.asLongString( lastJobDuration, locale ) ) ); } - if ( StringUtil.notEmpty( pwNotifyStoredJobState.getServerInstance() ) ) + if ( StringUtil.notEmpty( pwNotifyStoredJobState.serverInstance() ) ) { - statusData.add( new DisplayElement( String.valueOf( key++ ), DisplayElement.Type.string, - "Last Job Server Instance", pwNotifyStoredJobState.getServerInstance() ) ); + statusData.add( DisplayElement.create( String.valueOf( key++ ), DisplayElement.Type.string, + "Last Job Server Instance", pwNotifyStoredJobState.serverInstance() ) ); } - if ( pwNotifyStoredJobState.getLastError() != null ) + if ( pwNotifyStoredJobState.lastError() != null ) { - statusData.add( new DisplayElement( String.valueOf( key++ ), DisplayElement.Type.string, - "Last Job Error", pwNotifyStoredJobState.getLastError().toDebugStr() ) ); + statusData.add( DisplayElement.create( String.valueOf( key++ ), DisplayElement.Type.string, + "Last Job Error", pwNotifyStoredJobState.lastError().toDebugStr() ) ); } } diff --git a/server/src/main/java/password/pwm/http/servlet/admin/domain/DomainAdminReportServlet.java b/server/src/main/java/password/pwm/http/servlet/admin/domain/DomainAdminReportServlet.java index 7473cf53e6..09e9a2b7aa 100644 --- a/server/src/main/java/password/pwm/http/servlet/admin/domain/DomainAdminReportServlet.java +++ b/server/src/main/java/password/pwm/http/servlet/admin/domain/DomainAdminReportServlet.java @@ -115,7 +115,7 @@ public ProcessStatus processReportProcessStatus( final PwmRequest pwmRequest ) { final ReportProcessStatus reportProcessStatus = pwmRequest.getPwmSession().getReportProcess() .map( process -> process.getStatus( pwmRequest.getLocale() ) ) - .orElse( ReportProcessStatus.builder().build() ); + .orElse( ReportProcessStatus.getIdle() ); final RestResultBean restResultBean = RestResultBean.withData( reportProcessStatus, ReportProcessStatus.class ); diff --git a/server/src/main/java/password/pwm/http/servlet/admin/domain/DomainAdminStatisticsServlet.java b/server/src/main/java/password/pwm/http/servlet/admin/domain/DomainAdminStatisticsServlet.java index 93579b3213..a46d5e1b69 100644 --- a/server/src/main/java/password/pwm/http/servlet/admin/domain/DomainAdminStatisticsServlet.java +++ b/server/src/main/java/password/pwm/http/servlet/admin/domain/DomainAdminStatisticsServlet.java @@ -21,7 +21,6 @@ package password.pwm.http.servlet.admin.domain; import com.novell.ldapchai.exception.ChaiUnavailableException; -import lombok.Value; import password.pwm.AppProperty; import password.pwm.PwmConstants; import password.pwm.PwmDomain; @@ -39,6 +38,7 @@ import password.pwm.svc.stats.StatisticsBundleKey; import password.pwm.svc.stats.StatisticsService; import password.pwm.svc.stats.StatisticsUtils; +import password.pwm.util.java.CollectionUtil; import password.pwm.util.java.CollectorUtil; import password.pwm.util.logging.PwmLogger; import password.pwm.ws.server.RestResultBean; @@ -182,11 +182,19 @@ public ProcessStatus downloadStatisticsLogCsv( final PwmRequest pwmRequest ) return ProcessStatus.Halt; } - @Value - private static class StatisticsData + private record StatisticsData( + List statistics, + List averageStatistics + ) { - private final List statistics; - private final List averageStatistics; + private StatisticsData( + final List statistics, + final List averageStatistics + ) + { + this.statistics = CollectionUtil.stripNulls( statistics ); + this.averageStatistics = CollectionUtil.stripNulls( averageStatistics ); + } } diff --git a/server/src/main/java/password/pwm/http/servlet/admin/domain/DomainAdminUserDebugServlet.java b/server/src/main/java/password/pwm/http/servlet/admin/domain/DomainAdminUserDebugServlet.java index 2d85a25be4..35516fa463 100644 --- a/server/src/main/java/password/pwm/http/servlet/admin/domain/DomainAdminUserDebugServlet.java +++ b/server/src/main/java/password/pwm/http/servlet/admin/domain/DomainAdminUserDebugServlet.java @@ -132,6 +132,7 @@ public ProcessStatus processSearchUsername( final PwmRequest pwmRequest ) pwmRequest.getLabel(), userIdentity ); + pwmRequest.setAttribute( PwmRequestAttribute.UserDebugInfo, pwmRequest.getPwmSession().getUserInfo() ); pwmRequest.setAttribute( PwmRequestAttribute.UserDebugData, userDebugData ); } catch ( final PwmUnrecoverableException | PwmOperationalException e ) diff --git a/server/src/main/java/password/pwm/http/servlet/admin/domain/UserDebugDataBean.java b/server/src/main/java/password/pwm/http/servlet/admin/domain/UserDebugDataBean.java index 1f9e5df481..f1aacd3d20 100644 --- a/server/src/main/java/password/pwm/http/servlet/admin/domain/UserDebugDataBean.java +++ b/server/src/main/java/password/pwm/http/servlet/admin/domain/UserDebugDataBean.java @@ -20,32 +20,25 @@ package password.pwm.http.servlet.admin.domain; -import lombok.Builder; -import lombok.Value; import password.pwm.Permission; import password.pwm.bean.ProfileID; +import password.pwm.bean.ResponseInfoBean; import password.pwm.bean.pub.PublicUserInfoBean; import password.pwm.config.profile.ProfileDefinition; import password.pwm.config.profile.PwmPasswordPolicy; import password.pwm.svc.pwnotify.PwNotifyUserStatus; -import password.pwm.user.UserInfo; import java.util.Map; -@Value -@Builder -public class UserDebugDataBean +public record UserDebugDataBean( + PublicUserInfoBean publicUserInfoBean, + boolean passwordReadable, + boolean passwordWithinMinimumLifetime, + Map permissions, + PwmPasswordPolicy ldapPasswordPolicy, + PwmPasswordPolicy configuredPasswordPolicy, + Map profiles, + PwNotifyUserStatus pwNotifyUserStatus, + ResponseInfoBean responseInfoBean ) { - private transient UserInfo userInfo; - - private final PublicUserInfoBean publicUserInfoBean; - private final boolean passwordReadable; - private final boolean passwordWithinMinimumLifetime; - private final Map permissions; - - private final PwmPasswordPolicy ldapPasswordPolicy; - private final PwmPasswordPolicy configuredPasswordPolicy; - private final Map profiles; - - private final PwNotifyUserStatus pwNotifyUserStatus; } diff --git a/server/src/main/java/password/pwm/http/servlet/admin/domain/UserDebugDataReader.java b/server/src/main/java/password/pwm/http/servlet/admin/domain/UserDebugDataReader.java index 6d1fef1b0c..83a649fcc6 100644 --- a/server/src/main/java/password/pwm/http/servlet/admin/domain/UserDebugDataReader.java +++ b/server/src/main/java/password/pwm/http/servlet/admin/domain/UserDebugDataReader.java @@ -24,6 +24,7 @@ import password.pwm.Permission; import password.pwm.PwmDomain; import password.pwm.bean.ProfileID; +import password.pwm.bean.ResponseInfoBean; import password.pwm.bean.SessionLabel; import password.pwm.bean.UserIdentity; import password.pwm.config.PwmSetting; @@ -76,8 +77,7 @@ public static UserDebugDataBean readUserDebugData( final PwmPasswordPolicy configPasswordPolicy = PasswordUtility.determineConfiguredPolicyProfileForUser( pwmDomain, sessionLabel, - userIdentity - ); + userIdentity ); boolean readablePassword = false; try @@ -93,17 +93,18 @@ public static UserDebugDataBean readUserDebugData( final PwNotifyUserStatus pwNotifyUserStatus = readPwNotifyUserStatus( pwmDomain, userIdentity, sessionLabel ); - return UserDebugDataBean.builder() - .userInfo( userInfo ) - .publicUserInfoBean( UserInfoBean.toPublicUserInfoBean( userInfo, pwmDomain.getConfig(), locale, macroRequest ) ) - .permissions( permissions ) - .profiles( profiles ) - .ldapPasswordPolicy( ldapPasswordPolicy ) - .configuredPasswordPolicy( configPasswordPolicy ) - .passwordReadable( readablePassword ) - .passwordWithinMinimumLifetime( userInfo.isWithinPasswordMinimumLifetime() ) - .pwNotifyUserStatus( pwNotifyUserStatus ) - .build(); + final ResponseInfoBean responseInfoBean = userInfo.getResponseInfoBean(); + + return new UserDebugDataBean( + UserInfoBean.toPublicUserInfoBean( userInfo, pwmDomain.getConfig(), locale, macroRequest ), + readablePassword, + userInfo.isWithinPasswordMinimumLifetime(), + permissions, + ldapPasswordPolicy, + configPasswordPolicy, + profiles, + pwNotifyUserStatus, + responseInfoBean ); } @@ -140,7 +141,7 @@ private static Map profileMap( final SessionLabel sessionLabel, final UserIdentity userIdentity ) - throws PwmUnrecoverableException + throws PwmUnrecoverableException { final Map results = new TreeMap<>( Comparator.comparing( Enum::name ) ); for ( final ProfileDefinition profileDefinition : ProfileDefinition.values() ) diff --git a/server/src/main/java/password/pwm/http/servlet/admin/system/ConfigManagerLoginServlet.java b/server/src/main/java/password/pwm/http/servlet/admin/system/ConfigManagerLoginServlet.java index 987e9a4f17..1f42a077c0 100644 --- a/server/src/main/java/password/pwm/http/servlet/admin/system/ConfigManagerLoginServlet.java +++ b/server/src/main/java/password/pwm/http/servlet/admin/system/ConfigManagerLoginServlet.java @@ -22,7 +22,6 @@ import com.google.gson.annotations.SerializedName; import com.novell.ldapchai.exception.ChaiUnavailableException; -import lombok.Value; import password.pwm.AppAttribute; import password.pwm.AppProperty; import password.pwm.PwmApplicationMode; @@ -49,6 +48,7 @@ import password.pwm.http.servlet.PwmServletDefinition; import password.pwm.svc.intruder.IntruderRecordType; import password.pwm.svc.intruder.IntruderServiceClient; +import password.pwm.util.java.CollectionUtil; import password.pwm.util.java.EnumUtil; import password.pwm.util.java.JavaHelper; import password.pwm.util.java.PwmTimeUtil; @@ -64,11 +64,11 @@ import java.io.IOException; import java.time.Instant; import java.time.temporal.ChronoUnit; -import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Optional; +import java.util.stream.Collectors; @WebServlet( urlPatterns = { @@ -189,8 +189,8 @@ private static void forwardToJsp( final PwmRequest pwmRequest ) private static ConfigLoginHistory readConfigLoginHistory( final PwmRequest pwmRequest ) { - return pwmRequest.getPwmApplication().readAppAttribute( AppAttribute.CONFIG_LOGIN_HISTORY, ConfigLoginHistory.class ) - .orElseGet( ConfigLoginHistory::new ); + return pwmRequest.getPwmApplication().readAppAttribute( AppAttribute.CONFIG_LOGIN_HISTORY, ConfigLoginHistory.class ) + .orElseGet( () -> new ConfigLoginHistory( List.of(), List.of() ) ); } private static void updateLoginHistory( final PwmRequest pwmRequest, final UserIdentity userIdentity, final boolean successful ) @@ -207,27 +207,41 @@ private static void updateLoginHistory( final PwmRequest pwmRequest, final UserI pwmRequest.getPwmSession().getSessionStateBean().getSrcAddress() ); final int maxEvents = Integer.parseInt( pwmRequest.getPwmDomain().getConfig().readAppProperty( AppProperty.CONFIG_HISTORY_MAX_ITEMS ) ); - configLoginHistory.addEvent( event, maxEvents, successful ); - pwmRequest.getPwmApplication().writeAppAttribute( AppAttribute.CONFIG_LOGIN_HISTORY, configLoginHistory ); + final ConfigLoginHistory newHistory = successful + ? configLoginHistory.addSuccessEvent( event, maxEvents ) + : configLoginHistory.addFailedEvent( event, maxEvents ); + pwmRequest.getPwmApplication().writeAppAttribute( AppAttribute.CONFIG_LOGIN_HISTORY, newHistory ); } - @Value - public static class ConfigLoginHistory + public record ConfigLoginHistory( + List successEvents, + List failedEvents + ) { - private List successEvents = new ArrayList<>(); - private List failedEvents = new ArrayList<>(); + public ConfigLoginHistory( + final List successEvents, + final List failedEvents + ) + { + this.successEvents = CollectionUtil.stripNulls( successEvents ); + this.failedEvents = CollectionUtil.stripNulls( failedEvents ); + } - void addEvent( final ConfigLoginEvent event, final int maxEvents, final boolean successful ) + ConfigLoginHistory addSuccessEvent( final ConfigLoginEvent event, final int maxEvents ) { - final List events = successful ? successEvents : failedEvents; - events.add( event ); - if ( maxEvents > 0 ) - { - while ( events.size() > maxEvents ) - { - events.remove( 0 ); - } - } + return new ConfigLoginHistory( addImpl( successEvents, event, maxEvents ), failedEvents ); + } + + ConfigLoginHistory addFailedEvent( final ConfigLoginEvent event, final int maxEvents ) + { + return new ConfigLoginHistory( successEvents, addImpl( failedEvents, event, maxEvents ) ); + } + + private static List addImpl( final List list, final ConfigLoginEvent newEvent, final int maxEvents ) + { + final List workList = list.stream().limit( maxEvents - 1 ).collect( Collectors.toList() ); + workList.add( newEvent ); + return List.copyOf( workList ); } public List successEvents( ) @@ -241,12 +255,12 @@ public List failedEvents( ) } } - @Value - public static class ConfigLoginEvent + public record ConfigLoginEvent( + String userIdentity, + Instant date, + String networkAddress + ) { - private final String userIdentity; - private final Instant date; - private final String networkAddress; } private static ProcessStatus processLoginSuccess( final PwmRequest pwmRequest, final boolean persistentLoginEnabled ) @@ -334,15 +348,15 @@ private static void checkPersistentLoginCookie( if ( cookieValue.isPresent() ) { final PersistentLoginInfo persistentLoginInfo = pwmRequest.getPwmDomain().getSecureService().decryptObject( cookieValue.get(), PersistentLoginInfo.class ); - if ( persistentLoginInfo != null && persistentLoginInfo.getIssueTimestamp() != null ) + if ( persistentLoginInfo != null && persistentLoginInfo.issueTimestamp() != null ) { final int maxLoginSeconds = figureMaxLoginSeconds( pwmRequest ); - final TimeDuration cookieAge = TimeDuration.fromCurrent( persistentLoginInfo.getIssueTimestamp() ); + final TimeDuration cookieAge = TimeDuration.fromCurrent( persistentLoginInfo.issueTimestamp() ); if ( cookieAge.isShorterThan( TimeDuration.of( maxLoginSeconds, TimeDuration.Unit.SECONDS ) ) ) { final String persistentLoginPassword = makePersistentLoginPassword( pwmRequest, storedConfig ); - if ( StringUtil.nullSafeEquals( persistentLoginPassword, persistentLoginInfo.getPassword() ) ) + if ( StringUtil.nullSafeEquals( persistentLoginPassword, persistentLoginInfo.password() ) ) { final Instant expireTime = Instant.now().plus( maxLoginSeconds, ChronoUnit.SECONDS ); LOGGER.debug( pwmRequest, () -> "accepting persistent config login from cookie (expires at " @@ -374,19 +388,18 @@ private static void checkPersistentLoginCookie( } catch ( final Exception e ) { - LOGGER.error( pwmRequest, () -> "error examining persistent config login cookie: " + e.getMessage() ); + LOGGER.debug( pwmRequest, () -> "error examining persistent config login cookie: " + e.getMessage() ); } } + private record PersistentLoginInfo( + @SerializedName( "i" ) + Instant issueTimestamp, - @Value - private static class PersistentLoginInfo + @SerializedName( "p" ) + String password + ) { - @SerializedName( "i" ) - private Instant issueTimestamp; - - @SerializedName( "p" ) - private String password; } public static int figureMaxLoginSeconds( final PwmRequest pwmRequest ) diff --git a/server/src/main/java/password/pwm/http/servlet/admin/system/ConfigManagerServlet.java b/server/src/main/java/password/pwm/http/servlet/admin/system/ConfigManagerServlet.java index 223ced32e8..fc35f1f842 100644 --- a/server/src/main/java/password/pwm/http/servlet/admin/system/ConfigManagerServlet.java +++ b/server/src/main/java/password/pwm/http/servlet/admin/system/ConfigManagerServlet.java @@ -418,13 +418,13 @@ private void downloadPermissionReportCsv( for ( final LdapPermissionCalculator.PermissionRecord permissionRecord : ldapPermissionCalculator.getPermissionRecords() ) { - final String settingTxt = permissionRecord.getPwmSetting() == null + final String settingTxt = permissionRecord.pwmSetting() == null ? LocaleHelper.getLocalizedMessage( Display.Value_NotApplicable, pwmRequest ) - : permissionRecord.getPwmSetting().toMenuLocationDebug( permissionRecord.getProfile(), pwmRequest.getLocale() ); + : permissionRecord.pwmSetting().toMenuLocationDebug( permissionRecord.profile(), pwmRequest.getLocale() ); csvPrinter.printRecord( - permissionRecord.getActor().getLabel( pwmRequest.getLocale(), pwmRequest.getDomainConfig() ), - permissionRecord.getAttribute(), - permissionRecord.getAccess().toString(), + permissionRecord.actor().getLabel( pwmRequest.getLocale(), pwmRequest.getDomainConfig() ), + permissionRecord.attribute(), + permissionRecord.access().toString(), settingTxt ); } diff --git a/server/src/main/java/password/pwm/http/servlet/admin/system/ConfigManagerWordlistServlet.java b/server/src/main/java/password/pwm/http/servlet/admin/system/ConfigManagerWordlistServlet.java index 4197dcad0a..eb11c0ace0 100644 --- a/server/src/main/java/password/pwm/http/servlet/admin/system/ConfigManagerWordlistServlet.java +++ b/server/src/main/java/password/pwm/http/servlet/admin/system/ConfigManagerWordlistServlet.java @@ -211,12 +211,12 @@ void restReadWordlistData( final PwmRequest pwmRequest ) final WordlistDataBean.WordlistDataBeanBuilder builder = WordlistDataBean.builder(); { final List presentableValues = new ArrayList<>(); - presentableValues.add( new DisplayElement( + presentableValues.add( DisplayElement.create( wordlistType.name() + "_populationStatus", DisplayElement.Type.string, "Import Status", activity.getLabel() ) ); - presentableValues.add( new DisplayElement( + presentableValues.add( DisplayElement.create( wordlistType.name() + "_listSource", DisplayElement.Type.string, "List Source", wordlistStatus.getSourceType() == null @@ -224,14 +224,14 @@ void restReadWordlistData( final PwmRequest pwmRequest ) : wordlistStatus.getSourceType().getLabel() ) ); if ( wordlistConfiguration.getAutoImportUrl() != null ) { - presentableValues.add( new DisplayElement( + presentableValues.add( DisplayElement.create( wordlistType.name() + "_sourceURL", DisplayElement.Type.string, "Configured SourceType URL", wordlistConfiguration.getAutoImportUrl() ) ); } - presentableValues.add( new DisplayElement( + presentableValues.add( DisplayElement.create( wordlistType.name() + "_wordCount", DisplayElement.Type.number, "Word Count", @@ -242,7 +242,7 @@ void restReadWordlistData( final PwmRequest pwmRequest ) if ( WordlistSourceType.BuiltIn != wordlistStatus.getSourceType() ) { - presentableValues.add( new DisplayElement( + presentableValues.add( DisplayElement.create( wordlistType.name() + "_populationTimestamp", DisplayElement.Type.timestamp, "Population Timestamp", @@ -250,7 +250,7 @@ void restReadWordlistData( final PwmRequest pwmRequest ) } if ( wordlistStatus.getRemoteInfo() != null && StringUtil.notEmpty( wordlistStatus.getRemoteInfo().getHash() ) ) { - presentableValues.add( new DisplayElement( + presentableValues.add( DisplayElement.create( wordlistType.name() + "_sha256Hash", DisplayElement.Type.string, "SHA256 Checksum", @@ -259,12 +259,12 @@ void restReadWordlistData( final PwmRequest pwmRequest ) } if ( wordlist.getAutoImportError() != null ) { - presentableValues.add( new DisplayElement( + presentableValues.add( DisplayElement.create( wordlistType.name() + "_lastImportError", DisplayElement.Type.string, "Error During Import", wordlist.getAutoImportError().getDetailedErrorMsg() ) ); - presentableValues.add( new DisplayElement( + presentableValues.add( DisplayElement.create( wordlistType.name() + "_lastImportAttempt", DisplayElement.Type.timestamp, "Last Import Attempt", @@ -276,7 +276,7 @@ void restReadWordlistData( final PwmRequest pwmRequest ) final String percentComplete = wordlist.getImportPercentComplete(); if ( StringUtil.notEmpty( percentComplete ) ) { - presentableValues.add( new DisplayElement( + presentableValues.add( DisplayElement.create( "percentComplete", DisplayElement.Type.string, "Percent Complete", diff --git a/server/src/main/java/password/pwm/http/servlet/changepw/ChangePasswordServlet.java b/server/src/main/java/password/pwm/http/servlet/changepw/ChangePasswordServlet.java index 89bdd43e73..1beb47671f 100644 --- a/server/src/main/java/password/pwm/http/servlet/changepw/ChangePasswordServlet.java +++ b/server/src/main/java/password/pwm/http/servlet/changepw/ChangePasswordServlet.java @@ -163,11 +163,12 @@ public ProcessStatus processResetAction( final PwmRequest pwmRequest ) throws Se } @ActionHandler( action = "warnResponse" ) - public ProcessStatus processWarnResponse( final PwmRequest pwmRequest ) throws ServletException, PwmUnrecoverableException, IOException + public ProcessStatus processWarnResponse( final PwmRequest pwmRequest ) + throws PwmUnrecoverableException, IOException { final ChangePasswordBean changePasswordBean = getBean( pwmRequest ); - if ( pwmRequest.getPwmSession().getUserInfo().getPasswordStatus().isWarnPeriod() ) + if ( pwmRequest.getPwmSession().getUserInfo().getPasswordStatus().warnPeriod() ) { final String warnResponseStr = pwmRequest.readParameterAsString( "warnResponse" ); final Optional warnResponse = EnumUtil.readEnumFromString( WarnResponseValue.class, warnResponseStr ); @@ -441,8 +442,8 @@ public ProcessStatus processCheckPasswordAction( final PwmRequest pwmRequest ) pwmRequest.getClientConnectionHolder().getActor(), userInfo, pwmSession.getLoginInfoBean(), - PasswordData.forStringValue( jsonInput.getPassword1() ), - PasswordData.forStringValue( jsonInput.getPassword2() ) + PasswordData.forStringValue( jsonInput.password1() ), + PasswordData.forStringValue( jsonInput.password2() ) ); @@ -462,8 +463,7 @@ public ProcessStatus processRandomPasswordAction( final PwmRequest pwmRequest ) pwmRequest.getPwmSession().getUserInfo().getPasswordPolicy(), pwmRequest.getPwmDomain() ); - final RestRandomPasswordServer.JsonOutput jsonOutput = new RestRandomPasswordServer.JsonOutput(); - jsonOutput.setPassword( passwordData.getStringValue() ); + final RestRandomPasswordServer.JsonOutput jsonOutput = new RestRandomPasswordServer.JsonOutput( passwordData.getStringValue() ); final RestResultBean restResultBean = RestResultBean.withData( jsonOutput, RestRandomPasswordServer.JsonOutput.class ); pwmRequest.outputJsonResult( restResultBean ); return ProcessStatus.Halt; diff --git a/server/src/main/java/password/pwm/http/servlet/changepw/ChangePasswordServletUtil.java b/server/src/main/java/password/pwm/http/servlet/changepw/ChangePasswordServletUtil.java index 499b45e82a..d4b8f1c129 100644 --- a/server/src/main/java/password/pwm/http/servlet/changepw/ChangePasswordServletUtil.java +++ b/server/src/main/java/password/pwm/http/servlet/changepw/ChangePasswordServletUtil.java @@ -95,9 +95,9 @@ static boolean determineIfCurrentPasswordRequired( final UserInfo userInfo = pwmRequest.getPwmSession().getUserInfo(); final PasswordStatus passwordStatus = userInfo.getPasswordStatus(); return currentSetting == RequireCurrentPasswordMode.NOTEXPIRED - && !passwordStatus.isExpired() - && !passwordStatus.isPreExpired() - && !passwordStatus.isViolatesPolicy() + && !passwordStatus.expired() + && !passwordStatus.preExpired() + && !passwordStatus.violatesPolicy() && !userInfo.isRequiresNewPassword(); } @@ -249,7 +249,7 @@ static boolean warnPageShouldBeShown( { final PwmSession pwmSession = pwmRequest.getPwmSession(); - if ( !pwmSession.getUserInfo().getPasswordStatus().isWarnPeriod() ) + if ( !pwmSession.getUserInfo().getPasswordStatus().warnPeriod() ) { return false; } diff --git a/server/src/main/java/password/pwm/http/servlet/command/CommandServlet.java b/server/src/main/java/password/pwm/http/servlet/command/CommandServlet.java index e3c88a40b4..e69de29bb2 100644 --- a/server/src/main/java/password/pwm/http/servlet/command/CommandServlet.java +++ b/server/src/main/java/password/pwm/http/servlet/command/CommandServlet.java @@ -1,320 +0,0 @@ -/* - * Password Management Servlets (PWM) - * http://www.pwm-project.org - * - * Copyright (c) 2006-2009 Novell, Inc. - * Copyright (c) 2009-2021 The PWM Project - * - * 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. - */ - -package password.pwm.http.servlet.command; - -import com.novell.ldapchai.exception.ChaiUnavailableException; -import password.pwm.PwmConstants; -import password.pwm.bean.LocalSessionStateBean; -import password.pwm.bean.LoginInfoBean; -import password.pwm.config.PwmSetting; -import password.pwm.config.profile.ChangePasswordProfile; -import password.pwm.error.PwmError; -import password.pwm.error.PwmUnrecoverableException; -import password.pwm.http.HttpContentType; -import password.pwm.http.HttpHeader; -import password.pwm.http.HttpMethod; -import password.pwm.http.ProcessStatus; -import password.pwm.http.PwmRequest; -import password.pwm.http.PwmSession; -import password.pwm.http.filter.AuthenticationFilter; -import password.pwm.http.servlet.ControlledPwmServlet; -import password.pwm.http.servlet.PwmServletDefinition; -import password.pwm.util.logging.PwmLogger; - -import javax.servlet.ServletException; -import java.io.IOException; -import java.time.Instant; -import java.util.Arrays; -import java.util.Collection; -import java.util.Optional; - -public abstract class CommandServlet extends ControlledPwmServlet -{ - - private static final PwmLogger LOGGER = PwmLogger.forClass( CommandServlet.class ); - - @Override - protected PwmLogger getLogger() - { - return LOGGER; - } - - @Override - public Optional> getProcessActionsClass( ) - { - return Optional.of( CommandAction.class ); - } - - public enum CommandAction implements ProcessAction - { - idleUpdate, - checkResponses, - - //deprecated - checkIfResponseConfigNeeded, - checkExpire, - checkProfile, - - // deprecated - checkAttributes, - checkAll, - pageLeaveNotice, - cspReport, - next,; - - @Override - public Collection permittedMethods( ) - { - return Arrays.asList( HttpMethod.GET, HttpMethod.POST ); - } - } - - @Override - protected void nextStep( final PwmRequest pwmRequest ) throws PwmUnrecoverableException, IOException, ChaiUnavailableException, ServletException - { - // no mvc pattern in this servlet - } - - @Override - public ProcessStatus preProcessCheck( final PwmRequest pwmRequest ) throws PwmUnrecoverableException, IOException, ServletException - { - return ProcessStatus.Continue; - } - - @ActionHandler( action = "cspReport" ) - public ProcessStatus processCspReport( - final PwmRequest pwmRequest - ) - throws IOException, PwmUnrecoverableException - { - final String body = pwmRequest.readRequestBodyAsString(); - try - { - LOGGER.trace( pwmRequest, () -> "CSP Report: " + body ); - } - catch ( final Exception e ) - { - LOGGER.error( pwmRequest, () -> "error processing csp report: " + e.getMessage() + ", body=" + body ); - } - return ProcessStatus.Halt; - } - - @ActionHandler( action = "idleUpdate" ) - public ProcessStatus processIdleUpdate( - final PwmRequest pwmRequest - ) - throws ChaiUnavailableException, IOException, ServletException, PwmUnrecoverableException - { - pwmRequest.validatePwmFormID(); - if ( !pwmRequest.getPwmResponse().isCommitted() ) - { - pwmRequest.getPwmResponse().setHeader( HttpHeader.CacheControl, "no-cache, no-store, must-revalidate" ); - pwmRequest.getPwmResponse().setContentType( HttpContentType.plain ); - } - return ProcessStatus.Halt; - } - - @ActionHandler( action = "next" ) - public ProcessStatus processNext( - final PwmRequest pwmRequest - ) - throws IOException, PwmUnrecoverableException, ServletException - { - final PwmSession pwmSession = pwmRequest.getPwmSession(); - - if ( pwmRequest.isAuthenticated() ) - { - if ( AuthenticationFilter.forceRequiredRedirects( pwmRequest ) == ProcessStatus.Halt ) - { - return ProcessStatus.Halt; - } - - // log the user out if our finish action is currently set to log out. - final ChangePasswordProfile changePasswordProfile = pwmRequest.getChangePasswordProfile(); - - final boolean forceLogoutOnChange = changePasswordProfile.readSettingAsBoolean( PwmSetting.LOGOUT_AFTER_PASSWORD_CHANGE ); - if ( forceLogoutOnChange && pwmSession.getSessionStateBean().isPasswordModified() ) - { - LOGGER.trace( pwmRequest, () -> "logging out user; password has been modified" ); - pwmRequest.getPwmResponse().sendRedirect( PwmServletDefinition.Logout ); - return ProcessStatus.Halt; - } - } - - redirectToForwardURL( pwmRequest ); - return ProcessStatus.Halt; - } - - @ActionHandler( action = "pageLeaveNotice" ) - public ProcessStatus processPageLeaveNotice( final PwmRequest pwmRequest ) - throws PwmUnrecoverableException, IOException, ChaiUnavailableException, ServletException - { - final PwmSession pwmSession = pwmRequest.getPwmSession(); - final String referrer = pwmRequest.getHttpServletRequest().getHeader( "Referer" ); - final Instant pageLeaveNoticeTime = Instant.now(); - pwmSession.getSessionStateBean().setPageLeaveNoticeTime( pageLeaveNoticeTime ); - LOGGER.debug( () -> "pageLeaveNotice indicated at " + pageLeaveNoticeTime.toString() + ", referer=" + referrer ); - if ( !pwmRequest.getPwmResponse().isCommitted() ) - { - pwmRequest.getPwmResponse().setHeader( HttpHeader.CacheControl, "no-cache, no-store, must-revalidate" ); - pwmRequest.getPwmResponse().setContentType( HttpContentType.plain ); - } - return ProcessStatus.Halt; - } - - @ActionHandler( action = "checkAttributes" ) - public ProcessStatus processCheckAttributes( - final PwmRequest pwmRequest - ) - throws ChaiUnavailableException, IOException, ServletException, PwmUnrecoverableException - { - return processCheckProfile( pwmRequest ); - } - - @ActionHandler( action = "checkProfile" ) - public ProcessStatus processCheckProfile( - final PwmRequest pwmRequest - ) - throws ChaiUnavailableException, IOException, ServletException, PwmUnrecoverableException - { - if ( !checkIfUserAuthenticated( pwmRequest ) ) - { - return ProcessStatus.Halt; - } - - if ( pwmRequest.getPwmSession().getUserInfo().isRequiresUpdateProfile() ) - { - pwmRequest.getPwmResponse().sendRedirect( PwmServletDefinition.UpdateProfile ); - } - else - { - redirectToForwardURL( pwmRequest ); - } - - return ProcessStatus.Halt; - } - - @ActionHandler( action = "checkAll" ) - public ProcessStatus processCheckAll( - final PwmRequest pwmRequest - ) - throws ChaiUnavailableException, IOException, ServletException, PwmUnrecoverableException - { - if ( !checkIfUserAuthenticated( pwmRequest ) ) - { - return ProcessStatus.Halt; - } - - if ( AuthenticationFilter.forceRequiredRedirects( pwmRequest ) == ProcessStatus.Continue ) - { - redirectToForwardURL( pwmRequest ); - } - return ProcessStatus.Halt; - } - - @ActionHandler( action = "checkIfResponseConfigNeeded" ) - public ProcessStatus processCheckIfResponseConfigNeeded( - final PwmRequest pwmRequest - ) - throws ChaiUnavailableException, IOException, ServletException, PwmUnrecoverableException - { - return processCheckResponses( pwmRequest ); - } - - @ActionHandler( action = "checkResponses" ) - public ProcessStatus processCheckResponses( - final PwmRequest pwmRequest - ) - throws ChaiUnavailableException, IOException, ServletException, PwmUnrecoverableException - { - if ( !checkIfUserAuthenticated( pwmRequest ) ) - { - return ProcessStatus.Halt; - } - - if ( pwmRequest.getPwmSession().getUserInfo().isRequiresResponseConfig() ) - { - pwmRequest.getPwmResponse().sendRedirect( PwmServletDefinition.SetupResponses ); - } - else - { - redirectToForwardURL( pwmRequest ); - } - return ProcessStatus.Halt; - } - - @ActionHandler( action = "checkExpire" ) - public ProcessStatus processCheckExpire( - final PwmRequest pwmRequest - ) - throws ChaiUnavailableException, IOException, ServletException, PwmUnrecoverableException - { - if ( !checkIfUserAuthenticated( pwmRequest ) ) - { - return ProcessStatus.Halt; - } - - final PwmSession pwmSession = pwmRequest.getPwmSession(); - if ( pwmSession.getUserInfo().isRequiresNewPassword() && !pwmSession.getLoginInfoBean().isLoginFlag( LoginInfoBean.LoginFlag.skipNewPw ) ) - { - pwmRequest.getPwmResponse().sendRedirect( PwmServletDefinition.PrivateChangePassword.servletUrlName() ); - } - else - { - redirectToForwardURL( pwmRequest ); - } - return ProcessStatus.Halt; - } - - private static void redirectToForwardURL( final PwmRequest pwmRequest ) - throws IOException - { - final LocalSessionStateBean sessionStateBean = pwmRequest.getPwmSession().getSessionStateBean(); - - final String redirectURL = pwmRequest.getForwardUrl(); - LOGGER.trace( pwmRequest, () -> "redirecting user to forward url: " + redirectURL ); - - // after redirecting we need to clear the session forward url - if ( sessionStateBean.getForwardURL() != null ) - { - LOGGER.trace( pwmRequest, () -> "clearing session forward url: " + sessionStateBean.getForwardURL() ); - sessionStateBean.setForwardURL( null ); - } - - pwmRequest.getPwmResponse().sendRedirect( redirectURL ); - } - - private static boolean checkIfUserAuthenticated( - final PwmRequest pwmRequest - ) - throws IOException, ServletException, PwmUnrecoverableException - { - if ( !pwmRequest.isAuthenticated() ) - { - final String action = pwmRequest.readParameterAsString( PwmConstants.PARAM_ACTION_REQUEST ); - LOGGER.info( pwmRequest, () -> "authentication required for " + action ); - pwmRequest.respondWithError( PwmError.ERROR_AUTHENTICATION_REQUIRED.toInfo() ); - return false; - } - return true; - } -} - diff --git a/server/src/main/java/password/pwm/http/servlet/configeditor/ConfigEditorServlet.java b/server/src/main/java/password/pwm/http/servlet/configeditor/ConfigEditorServlet.java index 8da92eea05..ba94fe3511 100644 --- a/server/src/main/java/password/pwm/http/servlet/configeditor/ConfigEditorServlet.java +++ b/server/src/main/java/password/pwm/http/servlet/configeditor/ConfigEditorServlet.java @@ -78,6 +78,7 @@ import password.pwm.svc.httpclient.PwmHttpClientResponse; import password.pwm.svc.sms.SmsQueueService; import password.pwm.util.PasswordData; +import password.pwm.util.PwmScheduler; import password.pwm.util.SampleDataGenerator; import password.pwm.util.java.JavaHelper; import password.pwm.util.java.StringUtil; @@ -540,7 +541,7 @@ public ProcessStatus restSearchSettings( .forEach( recordID -> { final SearchResultItem item = SearchResultItem.fromKey( recordID, storedConfiguration, locale ); - final String returnCategory = item.getNavigation(); + final String returnCategory = item.navigation(); returnData.computeIfAbsent( returnCategory, k -> new TreeMap<>() ) .put( recordID.getRecordID(), item ); @@ -586,11 +587,15 @@ public ProcessStatus restDatabaseHealthCheck( throws IOException, PwmUnrecoverableException { final ConfigManagerBean configManagerBean = getBean( pwmRequest ); - final AppConfig config = AppConfig.forStoredConfig( configManagerBean.getStoredConfiguration() ); - final DomainID domainID = DomainStateReader.forRequest( pwmRequest ).getDomainID( PwmSetting.LDAP_SERVER_URLS ); - final List healthRecords = DatabaseStatusChecker.checkNewDatabaseStatus( pwmRequest.getPwmApplication(), config ); - final PublicHealthData healthData = HealthRecord.asHealthDataBean( config.getDomainConfigs().get( domainID ), pwmRequest.getLocale(), healthRecords ); - final RestResultBean restResultBean = RestResultBean.withData( healthData, PublicHealthData.class ); + final AppConfig workingConfig = AppConfig.forStoredConfig( configManagerBean.getStoredConfiguration() ); + final DomainID domainID = DomainStateReader.forRequest( pwmRequest ).getDomainID( PwmSetting.DATABASE_URL ); + final List healthRecords = DatabaseStatusChecker.checkNewDatabaseStatus( + pwmRequest.getLabel(), + pwmRequest.getPwmApplication().getPwmEnvironment(), + workingConfig ); + + final PublicHealthData healthData = HealthRecord.asHealthDataBean( workingConfig.getDomainConfigs().get( domainID ), pwmRequest.getLocale(), healthRecords ); + final RestResultBean restResultBean = RestResultBean.withData( healthData, PublicHealthData.class ); pwmRequest.outputJsonResult( restResultBean ); return ProcessStatus.Halt; } @@ -823,6 +828,7 @@ public ProcessStatus restBrowseLdap( final PwmRequest pwmRequest ) final StoredConfiguration storedConfiguration = configManagerBean.getStoredConfiguration(); final DomainID domainID = DomainStateReader.forRequest( pwmRequest ).getDomainIDForDomainSetting( ); + final PwmScheduler pwmScheduler = pwmRequest.getPwmApplication().getPwmScheduler(); final ProfileID profile; { @@ -836,7 +842,8 @@ public ProcessStatus restBrowseLdap( final PwmRequest pwmRequest ) final LdapBrowser ldapBrowser = new LdapBrowser( pwmRequest.getLabel(), pwmRequest.getPwmDomain().getLdapService().getChaiProviderFactory(), - storedConfiguration + storedConfiguration, + pwmScheduler ); LdapBrowser.LdapBrowseResult result; @@ -938,8 +945,7 @@ public ProcessStatus restRandomPassword( final PwmRequest pwmRequest ) pwmRequest.getLabel(), randomConfig, pwmRequest.getPwmDomain() ); - final RestRandomPasswordServer.JsonOutput outputMap = new RestRandomPasswordServer.JsonOutput(); - outputMap.setPassword( randomPassword.getStringValue() ); + final RestRandomPasswordServer.JsonOutput outputMap = new RestRandomPasswordServer.JsonOutput( randomPassword.getStringValue() ); pwmRequest.outputJsonResult( RestResultBean.withData( outputMap, RestRandomPasswordServer.JsonOutput.class ) ); diff --git a/server/src/main/java/password/pwm/http/servlet/configeditor/ConfigEditorServletUtils.java b/server/src/main/java/password/pwm/http/servlet/configeditor/ConfigEditorServletUtils.java index 05e9fd42cc..1b25b5316e 100644 --- a/server/src/main/java/password/pwm/http/servlet/configeditor/ConfigEditorServletUtils.java +++ b/server/src/main/java/password/pwm/http/servlet/configeditor/ConfigEditorServletUtils.java @@ -51,6 +51,7 @@ import password.pwm.i18n.PwmLocaleBundle; import password.pwm.util.PasswordData; import password.pwm.util.PwmScheduler; +import password.pwm.util.java.CollectionUtil; import password.pwm.util.java.TimeDuration; import password.pwm.util.logging.PwmLogger; import password.pwm.util.secure.HttpsServerCertificateManager; @@ -107,7 +108,7 @@ public static Optional readFileUploadToSettingValue( final FileUploadItem uploadItem = fileUploads.get( PwmConstants.PARAM_FILE_UPLOAD ); if ( uploadItem != null ) { - return Optional.of( FileValue.newFileValue( uploadItem.getName(), uploadItem.getType(), uploadItem.getContent() ) ); + return Optional.of( FileValue.newFileValue( uploadItem.name(), uploadItem.type(), uploadItem.content() ) ); } } @@ -180,9 +181,9 @@ static ReadSettingResponse handleLocaleBundleReadSetting( final DomainID domainID = DomainStateReader.forRequest( pwmRequest ).getDomainIDForLocaleBundle(); final ReadSettingResponse.ReadSettingResponseBuilder builder = ReadSettingResponse.builder(); final PwmLocaleBundle pwmLocaleBundle = key.toLocaleBundle(); - final String keyName = key.getProfileID().toString(); + final String keyName = key.getLocaleKey(); final Map bundleMap = storedConfig.readLocaleBundleMap( pwmLocaleBundle, keyName, domainID ); - if ( bundleMap == null || bundleMap.isEmpty() ) + if ( CollectionUtil.isEmpty( bundleMap ) ) { final Map defaultValueMap = new LinkedHashMap<>(); final String defaultLocaleValue = ResourceBundle.getBundle( pwmLocaleBundle.getTheClass().getName(), PwmConstants.DEFAULT_LOCALE ).getString( keyName ); @@ -261,8 +262,8 @@ static ReadSettingResponse handleReadSetting( final Optional settingMetaData = storedConfig.readSettingMetadata( key ); if ( settingMetaData.isPresent() ) { - builder.modifyTime( settingMetaData.map( ValueMetaData::getModifyDate ).orElse( null ) ); - builder.modifyUser( settingMetaData.map( ValueMetaData::getUserIdentity ).orElse( null ) ); + builder.modifyTime( settingMetaData.map( ValueMetaData::modifyDate ).orElse( null ) ); + builder.modifyUser( settingMetaData.map( ValueMetaData::userIdentity ).orElse( null ) ); } } builder.key( key.toPwmSetting().getKey() ); @@ -299,7 +300,7 @@ static void processHttpsCertificateUpload( final int maxFileSize = Integer.parseInt( pwmRequest.getDomainConfig().readAppProperty( AppProperty.CONFIG_MAX_FILEVALUE_SIZE ) ); final Map fileUploads = PwmRequestUtil.readFileUploads( pwmRequest, maxFileSize, 1 ); - final InputStream fileIs = fileUploads.get( PwmConstants.PARAM_FILE_UPLOAD ).getContent().newByteArrayInputStream(); + final InputStream fileIs = fileUploads.get( PwmConstants.PARAM_FILE_UPLOAD ).content().newByteArrayInputStream(); final StoredConfigurationModifier modifier = StoredConfigurationModifier.newModifier( configManagerBean.getStoredConfiguration() ); @@ -392,10 +393,6 @@ static T timeoutExecutor( final PwmRequest pwmRequest, final Callable cal { return PwmScheduler.executeWithTimeout( pwmRequest.getPwmApplication(), pwmRequest.getLabel(), configEditorSettings.getMaxWaitSettingsFunction(), callable ); } - catch ( final PwmUnrecoverableException e ) - { - throw e; - } catch ( final Throwable t ) { final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_INTERNAL, "error running operation: : " + t.getMessage() ); diff --git a/server/src/main/java/password/pwm/http/servlet/configeditor/SearchResultItem.java b/server/src/main/java/password/pwm/http/servlet/configeditor/SearchResultItem.java index 5cec79de05..160a9b8cd0 100644 --- a/server/src/main/java/password/pwm/http/servlet/configeditor/SearchResultItem.java +++ b/server/src/main/java/password/pwm/http/servlet/configeditor/SearchResultItem.java @@ -21,7 +21,6 @@ package password.pwm.http.servlet.configeditor; import com.google.gson.annotations.SerializedName; -import lombok.Value; import password.pwm.config.PwmSetting; import password.pwm.config.stored.StoredConfigKey; import password.pwm.config.stored.StoredConfiguration; @@ -29,16 +28,16 @@ import java.util.Locale; -@Value -class SearchResultItem -{ - private final String category; - private final String value; - private final String navigation; +record SearchResultItem( + String category, + String value, + String navigation, - @SerializedName( "default" ) - private final boolean defaultValue; - private final String profile; + @SerializedName( "default" ) + boolean defaultValue, + String profile +) +{ static SearchResultItem fromKey( final StoredConfigKey key, @@ -51,7 +50,6 @@ static SearchResultItem fromKey( storedConfiguration.readStoredValue( key ).orElseThrow().toDebugString( locale ), setting.getCategory().toMenuLocationDebug( key.getProfileID().orElse( null ), locale ), StoredConfigurationUtil.isDefaultValue( storedConfiguration, key ), - key.getProfileID().map( v -> v.stringValue() ).orElse( null ) - ); + key.getProfileID().map( v -> v.stringValue() ).orElse( null ) ); } } diff --git a/server/src/main/java/password/pwm/http/servlet/configeditor/data/CategoryInfo.java b/server/src/main/java/password/pwm/http/servlet/configeditor/data/CategoryInfo.java index 1556c4c2b9..9c22cdd502 100644 --- a/server/src/main/java/password/pwm/http/servlet/configeditor/data/CategoryInfo.java +++ b/server/src/main/java/password/pwm/http/servlet/configeditor/data/CategoryInfo.java @@ -20,39 +20,34 @@ package password.pwm.http.servlet.configeditor.data; -import lombok.Builder; -import lombok.Value; import password.pwm.config.PwmSettingCategory; import java.util.Locale; -@Value -@Builder -public class CategoryInfo +public record CategoryInfo( + int level, + String key, + String description, + String label, + String parent, + boolean hidden, + boolean profiles, + String menuLocation +) { - private int level; - private String key; - private String description; - private String label; - private String parent; - private boolean hidden; - private boolean profiles; - private String menuLocation; - - public static CategoryInfo forCategory( final PwmSettingCategory category, - final Locale locale ) + final Locale locale + ) { - return CategoryInfo.builder() - .key( category.getKey() ) - .level( category.getLevel() ) - .description( category.getDescription( locale ) ) - .label( category.getLabel( locale ) ) - .hidden( category.isHidden() ) - .parent( category.getParent() != null ? category.getParent().getKey() : null ) - .profiles( category.hasProfiles() ) - .menuLocation( category.toMenuLocationDebug( null, locale ) ) - .build(); + return new CategoryInfo( + category.getLevel(), + category.getKey(), + category.getDescription( locale ), + category.getLabel( locale ), + category.getParent() != null ? category.getParent().getKey() : null, + category.isHidden(), + category.hasProfiles(), + category.toMenuLocationDebug( null, locale ) ); } } diff --git a/server/src/main/java/password/pwm/http/servlet/helpdesk/HelpdeskVerificationRequestBean.java b/server/src/main/java/password/pwm/http/servlet/configeditor/data/EditorFilterState.java similarity index 54% rename from server/src/main/java/password/pwm/http/servlet/helpdesk/HelpdeskVerificationRequestBean.java rename to server/src/main/java/password/pwm/http/servlet/configeditor/data/EditorFilterState.java index e50bdc628a..121d0890d0 100644 --- a/server/src/main/java/password/pwm/http/servlet/helpdesk/HelpdeskVerificationRequestBean.java +++ b/server/src/main/java/password/pwm/http/servlet/configeditor/data/EditorFilterState.java @@ -18,28 +18,30 @@ * limitations under the License. */ -package password.pwm.http.servlet.helpdesk; +package password.pwm.http.servlet.configeditor.data; -import lombok.Data; - -import java.time.Instant; - -@Data -public class HelpdeskVerificationRequestBean +/** + * Editor filter state, shared with browser. + */ +public record EditorFilterState( + int level, + String text, + boolean modifiedSettingsOnly +) { - private String destination; - private String userKey; - private String code; - - // encrypted during transport - private String tokenData; - private String verificationState; - + public static final EditorFilterState DEFAULT = new EditorFilterState( + 2, + "", + false ); - @Data - static class TokenData + public EditorFilterState( + final int level, + final String text, + final boolean modifiedSettingsOnly + ) { - private String token; - private Instant issueDate; + this.level = level; + this.text = text == null ? "" : text; + this.modifiedSettingsOnly = modifiedSettingsOnly; } } diff --git a/server/src/main/java/password/pwm/http/servlet/configeditor/data/LocaleInfo.java b/server/src/main/java/password/pwm/http/servlet/configeditor/data/LocaleInfo.java index 47648f2573..debbf9e7ed 100644 --- a/server/src/main/java/password/pwm/http/servlet/configeditor/data/LocaleInfo.java +++ b/server/src/main/java/password/pwm/http/servlet/configeditor/data/LocaleInfo.java @@ -20,24 +20,20 @@ package password.pwm.http.servlet.configeditor.data; -import lombok.Builder; -import lombok.Value; import password.pwm.i18n.PwmLocaleBundle; -@Value -@Builder -public class LocaleInfo +public record LocaleInfo( + String description, + String key, + boolean adminOnly +) { - public String description; - public String key; - public boolean adminOnly; - public static LocaleInfo forBundle( final PwmLocaleBundle pwmLocaleBundle ) { - return LocaleInfo.builder() - .description( pwmLocaleBundle.getTheClass().getSimpleName() ) - .key( pwmLocaleBundle.toString() ) - .adminOnly( pwmLocaleBundle.isAdminOnly() ) - .build(); + return new LocaleInfo( + pwmLocaleBundle.getTheClass().getSimpleName(), + pwmLocaleBundle.toString(), + pwmLocaleBundle.isAdminOnly() ); + } } diff --git a/server/src/main/java/password/pwm/http/servlet/configeditor/data/NavTreeDataMaker.java b/server/src/main/java/password/pwm/http/servlet/configeditor/data/NavTreeDataMaker.java index 19133e1b50..155a465503 100644 --- a/server/src/main/java/password/pwm/http/servlet/configeditor/data/NavTreeDataMaker.java +++ b/server/src/main/java/password/pwm/http/servlet/configeditor/data/NavTreeDataMaker.java @@ -106,7 +106,7 @@ private static List makeDisplayTextNavItems( final NavTreeSettings navTreeSettings ) { - final DomainID domainID = navTreeSettings.getDomainManageMode() == DomainManageMode.domain + final DomainID domainID = navTreeSettings.domainManageMode() == DomainManageMode.domain ? domainId : DomainID.systemId(); @@ -121,8 +121,8 @@ private static List makeDisplayTextNavItemsForDomain( { final ArrayList navigationData = new ArrayList<>(); - final int level = navTreeSettings.getLevel(); - final boolean modifiedSettingsOnly = navTreeSettings.isModifiedSettingsOnly(); + final int level = navTreeSettings.filterState().level(); + final boolean modifiedSettingsOnly = navTreeSettings.filterState().modifiedSettingsOnly(); boolean includeDisplayText = false; if ( level >= 1 ) @@ -225,7 +225,7 @@ private static List navTreeItemsForCategory( final NavTreeSettings navTreeSettings ) { - final Locale locale = navTreeSettings.getLocale(); + final Locale locale = navTreeSettings.locale(); if ( !loopCategory.hasProfiles() ) { @@ -261,13 +261,28 @@ private static List navTreeItemsForCategory( ? NavTreeItem.NavItemType.category : NavTreeItem.NavItemType.navigation; - final NavTreeItem profileInfo = navTreeItemForCategory( loopCategory, locale, profileId ).toBuilder() + + // add profile data + final NavTreeItem profileInfo = new NavTreeItem( + "profile-" + loopCategory.getKey() + "-" + profileId, + profileId == null ? "Default" : profileId.stringValue(), + parentKeyForCategory( loopCategory, profileId ), + loopCategory.getKey(), + profileId == null ? null : profileId.stringValue(), + type, + null, + loopCategory.toMenuLocationDebug( profileId, locale ), + null ); + + + /* + navTreeItemForCategory( loopCategory, locale, profileId ).toBuilder() .name( profileId == null ? "Default" : profileId.stringValue() ) .id( "profile-" + loopCategory.getKey() + "-" + profileId ) .parent( loopCategory.getKey() ) .type( type ) .build(); - +*/ navigationData.add( profileInfo ); } @@ -285,6 +300,14 @@ private static List navTreeItemsForCategory( return Collections.unmodifiableList( navigationData ); } + private static String parentKeyForCategory( final PwmSettingCategory category, final ProfileID profileId ) + { + return category.getParent() != null + ? ( profileId != null ? "profile-" + category.getParent().getKey() + "-" + profileId : category.getParent().getKey() ) + : ROOT_NODE_ID; + + } + private static NavTreeItem navTreeItemForCategory( final PwmSettingCategory category, @@ -292,9 +315,8 @@ private static NavTreeItem navTreeItemForCategory( final ProfileID profileId ) { - final String parent = category.getParent() != null - ? ( profileId != null ? "profile-" + category.getParent().getKey() + "-" + profileId : category.getParent().getKey() ) - : ROOT_NODE_ID; + + final String parent = parentKeyForCategory( category, profileId ); final NavTreeItem.NavItemType type = !category.hasChildren() && !category.isTopLevelProfile() ? NavTreeItem.NavItemType.category @@ -321,7 +343,7 @@ private static boolean categoryMatcher( { if ( category == PwmSettingCategory.HTTPS_SERVER ) { - if ( !navTreeSettings.isMangeHttps() ) + if ( !navTreeSettings.mangeHttps() ) { return false; } @@ -373,23 +395,23 @@ static boolean settingMatcher( } final PwmSettingCategory settingCategory = setting.getCategory(); - if ( navTreeSettings.getDomainManageMode() == DomainManageMode.system + if ( navTreeSettings.domainManageMode() == DomainManageMode.system && settingCategory.getScope() != PwmSettingScope.SYSTEM ) { return false; } - else if ( navTreeSettings.getDomainManageMode() == DomainManageMode.domain + else if ( navTreeSettings.domainManageMode() == DomainManageMode.domain && settingCategory.getScope() != PwmSettingScope.DOMAIN ) { return false; } - if ( navTreeSettings.isModifiedSettingsOnly() && valueIsDefault ) + if ( navTreeSettings.filterState().modifiedSettingsOnly() && valueIsDefault ) { return false; } - final int level = navTreeSettings.getLevel(); + final int level = navTreeSettings.filterState().level(); if ( setting.getLevel() > level ) { return false; @@ -401,14 +423,14 @@ else if ( navTreeSettings.getDomainManageMode() == DomainManageMode.domain return false; } - if ( StringUtil.isEmpty( navTreeSettings.getFilterText() ) ) + if ( StringUtil.isEmpty( navTreeSettings.filterState().text() ) ) { return true; } else { final StoredValue storedValue = storedConfiguration.readStoredValue( storedConfigKey ).orElseThrow(); - for ( final String term : StringUtil.whitespaceSplit( navTreeSettings.getFilterText() ) ) + for ( final String term : StringUtil.whitespaceSplit( navTreeSettings.filterState().text() ) ) { if ( ConfigSearchMachine.matchSetting( storedConfiguration, setting, storedValue, term, PwmConstants.DEFAULT_LOCALE ) ) { @@ -424,7 +446,7 @@ private static void moveNavItemToTopOfList( final String categoryID, final List< { // put templates on top final Optional templateEntry = navigationData.stream() - .filter( entry -> categoryID.equals( entry.getId() ) ) + .filter( entry -> categoryID.equals( entry.id() ) ) .findFirst(); if ( templateEntry.isPresent() ) diff --git a/server/src/main/java/password/pwm/http/servlet/configeditor/data/NavTreeItem.java b/server/src/main/java/password/pwm/http/servlet/configeditor/data/NavTreeItem.java index e4813173ca..fa0a7403ae 100644 --- a/server/src/main/java/password/pwm/http/servlet/configeditor/data/NavTreeItem.java +++ b/server/src/main/java/password/pwm/http/servlet/configeditor/data/NavTreeItem.java @@ -21,23 +21,25 @@ package password.pwm.http.servlet.configeditor.data; import lombok.Builder; -import lombok.Value; import java.util.List; -@Value -@Builder( toBuilder = true ) -public class NavTreeItem +public record NavTreeItem( + String id, + String name, + String parent, + String category, + String profile, + NavItemType type, + String profileSetting, + String menuLocation, + List keys +) { - private final String id; - private final String name; - private final String parent; - private final String category; - private final String profile; - private final NavItemType type; - private final String profileSetting; - private final String menuLocation; - private final List keys; + @Builder( toBuilder = false ) + public NavTreeItem + { + } public enum NavItemType { diff --git a/server/src/main/java/password/pwm/http/servlet/configeditor/data/NavTreeSettings.java b/server/src/main/java/password/pwm/http/servlet/configeditor/data/NavTreeSettings.java index 1498e8d56b..16e6d528eb 100644 --- a/server/src/main/java/password/pwm/http/servlet/configeditor/data/NavTreeSettings.java +++ b/server/src/main/java/password/pwm/http/servlet/configeditor/data/NavTreeSettings.java @@ -20,61 +20,58 @@ package password.pwm.http.servlet.configeditor.data; -import lombok.Builder; -import lombok.Value; import password.pwm.EnvironmentProperty; import password.pwm.PwmConstants; import password.pwm.error.PwmUnrecoverableException; -import password.pwm.http.PwmHttpRequestWrapper; import password.pwm.http.PwmRequest; import password.pwm.http.servlet.configeditor.DomainManageMode; import password.pwm.http.servlet.configeditor.DomainStateReader; import java.io.IOException; import java.util.Locale; -import java.util.Map; -@Value -@Builder -public class NavTreeSettings +public record NavTreeSettings( + EditorFilterState filterState, + Locale locale, + DomainManageMode domainManageMode, + boolean mangeHttps +) { - private final boolean modifiedSettingsOnly; - - @Builder.Default - private final int level = 2; - - private final String filterText; - - @Builder.Default - private final Locale locale = PwmConstants.DEFAULT_LOCALE; - - private final DomainManageMode domainManageMode; + public NavTreeSettings( + final EditorFilterState filterState, + final Locale locale, + final DomainManageMode domainManageMode, final boolean mangeHttps + ) + { + this.filterState = filterState == null ? EditorFilterState.DEFAULT : filterState; + this.locale = locale == null ? PwmConstants.DEFAULT_LOCALE : locale; + this.domainManageMode = domainManageMode == null ? DomainManageMode.system : domainManageMode; + this.mangeHttps = mangeHttps; + } - private final boolean mangeHttps; + private static final NavTreeSettings BASIC = new NavTreeSettings( + EditorFilterState.DEFAULT, + PwmConstants.DEFAULT_LOCALE, + DomainManageMode.system, + false ); public static NavTreeSettings forBasic() { - return NavTreeSettings.builder() - .domainManageMode( DomainManageMode.system ) - .build(); + return BASIC; + } + + public static NavTreeSettings forMode( final DomainManageMode domainManageMode ) + { + return new NavTreeSettings( EditorFilterState.DEFAULT, PwmConstants.DEFAULT_LOCALE, domainManageMode, false ); } - public static NavTreeSettings readFromRequest( final PwmRequest pwmRequest ) throws PwmUnrecoverableException, IOException + public static NavTreeSettings readFromRequest( final PwmRequest pwmRequest ) + throws PwmUnrecoverableException, IOException { - final Map inputParameters = pwmRequest.readBodyAsJsonMap( PwmHttpRequestWrapper.Flag.BypassValidation ); - final boolean modifiedSettingsOnly = ( boolean ) inputParameters.get( "modifiedSettingsOnly" ); - final int level = ( int ) ( ( double ) inputParameters.get( "level" ) ); - final String filterText = ( String ) inputParameters.get( "text" ); + final EditorFilterState filterState = pwmRequest.readBodyAsJsonObject( EditorFilterState.class ); final DomainStateReader domainStateReader = DomainStateReader.forRequest( pwmRequest ); final boolean manageHttps = pwmRequest.getPwmApplication().getPwmEnvironment().readPropertyAsBoolean( EnvironmentProperty.ManageHttps ); - return NavTreeSettings.builder() - .modifiedSettingsOnly( modifiedSettingsOnly ) - .domainManageMode( domainStateReader.getMode() ) - .mangeHttps( manageHttps ) - .level( level ) - .filterText( filterText ) - .locale( pwmRequest.getLocale() ) - .build(); + return new NavTreeSettings( filterState, pwmRequest.getLocale(), domainStateReader.getMode(), manageHttps ); } } diff --git a/server/src/main/java/password/pwm/http/servlet/configeditor/data/SettingData.java b/server/src/main/java/password/pwm/http/servlet/configeditor/data/SettingData.java index 7a1cc7807f..4c6e0721c5 100644 --- a/server/src/main/java/password/pwm/http/servlet/configeditor/data/SettingData.java +++ b/server/src/main/java/password/pwm/http/servlet/configeditor/data/SettingData.java @@ -20,20 +20,17 @@ package password.pwm.http.servlet.configeditor.data; -import lombok.Builder; -import lombok.Value; import password.pwm.config.PwmSettingTemplateSet; import java.util.Map; -@Value -@Builder -public class SettingData +public record SettingData( + Map settings, + Map categories, + Map locales, + Object ldapProfileIds, + PwmSettingTemplateSet currentTemplate, + VarData var +) { - private final Map settings; - private final Map categories; - private final Map locales; - private final Object ldapProfileIds; - private final PwmSettingTemplateSet currentTemplate; - private final VarData var; } diff --git a/server/src/main/java/password/pwm/http/servlet/configeditor/data/SettingDataMaker.java b/server/src/main/java/password/pwm/http/servlet/configeditor/data/SettingDataMaker.java index d93efcc8f1..4b71606d69 100644 --- a/server/src/main/java/password/pwm/http/servlet/configeditor/data/SettingDataMaker.java +++ b/server/src/main/java/password/pwm/http/servlet/configeditor/data/SettingDataMaker.java @@ -32,13 +32,12 @@ import password.pwm.error.PwmUnrecoverableException; import password.pwm.i18n.PwmLocaleBundle; import password.pwm.util.java.CollectionUtil; +import password.pwm.util.java.CollectorUtil; +import password.pwm.util.java.EnumUtil; import password.pwm.util.java.TimeDuration; import password.pwm.util.logging.PwmLogger; import java.time.Instant; -import java.util.Arrays; -import java.util.Collections; -import java.util.LinkedHashMap; import java.util.List; import java.util.Locale; import java.util.Map; @@ -71,48 +70,41 @@ public static SettingData generateSettingData( settingMap = interestedSets.stream() .sorted() - .collect( Collectors.toMap( + .collect( CollectorUtil.toUnmodifiableLinkedMap( PwmSetting::getKey, - pwmSetting -> SettingInfo.forSetting( pwmSetting, templateSet, locale ), - ( u, v ) -> - { - throw new IllegalStateException(); - }, - LinkedHashMap::new ) ); + pwmSetting -> SettingInfo.forSetting( pwmSetting, templateSet, locale ) ) ); } - final Map categoryInfoMap = Collections.unmodifiableMap( Arrays.stream( PwmSettingCategory.values() ) - .collect( Collectors.toMap( + final Map categoryInfoMap = EnumUtil.enumStream( PwmSettingCategory.class ) + .collect( CollectorUtil.toUnmodifiableLinkedMap( PwmSettingCategory::getKey, - pwmSettingCategory -> CategoryInfo.forCategory( pwmSettingCategory, locale ), - ( u, v ) -> v, - LinkedHashMap::new ) ) ); + pwmSettingCategory -> CategoryInfo.forCategory( pwmSettingCategory, locale ) ) ); - final Map labelMap = Collections.unmodifiableMap( Arrays.stream( PwmLocaleBundle.values() ) - .collect( Collectors.toMap( + + final Map labelMap = EnumUtil.enumStream( PwmLocaleBundle.class ) + .collect( CollectorUtil.toUnmodifiableLinkedMap( pwmLocaleBundle -> pwmLocaleBundle.getTheClass().getSimpleName(), - LocaleInfo::forBundle, - ( u, v ) -> v, - LinkedHashMap::new ) ) ); + LocaleInfo::forBundle ) ); final List profileIDList = StoredConfigurationUtil.profilesForSetting( domainID, PwmSetting.LDAP_PROFILE_LIST, storedConfiguration ); - final VarData varMap = VarData.builder() - .ldapProfileIds( CollectionUtil.convertListType( profileIDList, ProfileID::toString ) ) - .domainIds( StoredConfigurationUtil.domainList( storedConfiguration ).stream() - .map( DomainID::stringValue ).sorted().collect( Collectors.toList() ) ) - .currentTemplate( templateSet ) - .build(); + final VarData varMap = new VarData( + CollectionUtil.convertListType( profileIDList, ProfileID::toString ), + StoredConfigurationUtil.domainList( storedConfiguration ).stream() + .map( DomainID::stringValue ).sorted().collect( Collectors.toList() ), + templateSet ); + + final SettingData settingData = new SettingData( + settingMap, + categoryInfoMap, + labelMap, + null, + null, + varMap ); - final SettingData settingData = SettingData.builder() - .settings( settingMap ) - .categories( categoryInfoMap ) - .locales( labelMap ) - .var( varMap ) - .build(); LOGGER.trace( sessionLabel, () -> "generated settingData with " - + settingData.getSettings().size() + " settings and " - + settingData.getCategories().size() + " categories", TimeDuration.fromCurrent( startGenerateTime ) ); + + settingData.settings().size() + " settings and " + + settingData.categories().size() + " categories", TimeDuration.fromCurrent( startGenerateTime ) ); return settingData; } diff --git a/server/src/main/java/password/pwm/http/servlet/configeditor/data/SettingInfo.java b/server/src/main/java/password/pwm/http/servlet/configeditor/data/SettingInfo.java index c12e769d12..d5d34dac26 100644 --- a/server/src/main/java/password/pwm/http/servlet/configeditor/data/SettingInfo.java +++ b/server/src/main/java/password/pwm/http/servlet/configeditor/data/SettingInfo.java @@ -20,8 +20,8 @@ package password.pwm.http.servlet.configeditor.data; +import lombok.AccessLevel; import lombok.Builder; -import lombok.Value; import password.pwm.config.PwmSetting; import password.pwm.config.PwmSettingCategory; import password.pwm.config.PwmSettingFlag; @@ -35,23 +35,26 @@ import java.util.Map; import java.util.Set; -@Value -@Builder -public class SettingInfo +public record SettingInfo( + String key, + String label, + String description, + PwmSettingCategory category, + PwmSettingSyntax syntax, + boolean hidden, + boolean required, + Map options, + Map properties, + String pattern, + String placeholder, int level, + Set flags +) { - private String key; - private String label; - private String description; - private PwmSettingCategory category; - private PwmSettingSyntax syntax; - private boolean hidden; - private boolean required; - private Map options; - private Map properties; - private String pattern; - private String placeholder; - private int level; - private Set flags; + @Builder( access = AccessLevel.PRIVATE ) + public SettingInfo + { + } + static SettingInfo forSetting( final PwmSetting setting, @@ -61,17 +64,17 @@ static SettingInfo forSetting( { return SettingInfo.builder() .key( setting.getKey() ) - .description( setting.getDescription( locale ) ) - .level( setting.getLevel() ) .label( setting.getLabel( locale ) ) - .syntax( setting.getSyntax() ) + .description( setting.getDescription( locale ) ) .category( setting.getCategory() ) - .properties( setting.getProperties() ) - .required( setting.isRequired() ) + .syntax( setting.getSyntax() ) .hidden( setting.isHidden() ) + .required( setting.isRequired() ) .options( setting.getOptions() ) + .properties( setting.getProperties() ) .pattern( setting.getRegExPattern().toString() ) .placeholder( setting.getExample( template ) ) + .level( setting.getLevel() ) .flags( Collections.unmodifiableSet( CollectionUtil.copyToEnumSet( setting.getFlags(), PwmSettingFlag.class ) ) ) .build(); } diff --git a/server/src/main/java/password/pwm/http/servlet/configeditor/data/VarData.java b/server/src/main/java/password/pwm/http/servlet/configeditor/data/VarData.java index 9c5086a3f1..78aa5ac852 100644 --- a/server/src/main/java/password/pwm/http/servlet/configeditor/data/VarData.java +++ b/server/src/main/java/password/pwm/http/servlet/configeditor/data/VarData.java @@ -20,17 +20,25 @@ package password.pwm.http.servlet.configeditor.data; -import lombok.Builder; -import lombok.Value; import password.pwm.config.PwmSettingTemplateSet; +import password.pwm.util.java.CollectionUtil; import java.util.List; -@Value -@Builder -public class VarData +public record VarData( + List ldapProfileIds, + List domainIds, + PwmSettingTemplateSet currentTemplate +) { - private final List ldapProfileIds; - private final List domainIds; - private final PwmSettingTemplateSet currentTemplate; + public VarData( + final List ldapProfileIds, + final List domainIds, + final PwmSettingTemplateSet currentTemplate + ) + { + this.ldapProfileIds = CollectionUtil.stripNulls( ldapProfileIds ); + this.domainIds = CollectionUtil.stripNulls( domainIds ); + this.currentTemplate = currentTemplate; + } } diff --git a/server/src/main/java/password/pwm/http/servlet/configeditor/function/UserMatchViewerFunction.java b/server/src/main/java/password/pwm/http/servlet/configeditor/function/UserMatchViewerFunction.java index af27542242..94394fb0d0 100644 --- a/server/src/main/java/password/pwm/http/servlet/configeditor/function/UserMatchViewerFunction.java +++ b/server/src/main/java/password/pwm/http/servlet/configeditor/function/UserMatchViewerFunction.java @@ -134,16 +134,16 @@ private static void validateUserPermissionLdapValues( { for ( final UserPermission userPermission : permissions ) { - if ( userPermission.getType() == UserPermissionType.ldapQuery ) + if ( userPermission.type() == UserPermissionType.ldapQuery ) { - if ( userPermission.getLdapBase() != null && !userPermission.getLdapBase().isEmpty() ) + if ( userPermission.ldapBase() != null && !userPermission.ldapBase().isEmpty() ) { - testIfLdapDNIsValid( sessionLabel, pwmDomain, userPermission.getLdapBase(), userPermission.getLdapProfileID() ); + testIfLdapDNIsValid( sessionLabel, pwmDomain, userPermission.ldapBase(), userPermission.ldapProfileID() ); } } - else if ( userPermission.getType() == UserPermissionType.ldapGroup ) + else if ( userPermission.type() == UserPermissionType.ldapGroup ) { - testIfLdapDNIsValid( sessionLabel, pwmDomain, userPermission.getLdapBase(), userPermission.getLdapProfileID() ); + testIfLdapDNIsValid( sessionLabel, pwmDomain, userPermission.ldapBase(), userPermission.ldapProfileID() ); } } } diff --git a/server/src/main/java/password/pwm/http/servlet/configguide/ConfigGuideForm.java b/server/src/main/java/password/pwm/http/servlet/configguide/ConfigGuideForm.java index d95489c9bb..e96bcb2f31 100644 --- a/server/src/main/java/password/pwm/http/servlet/configguide/ConfigGuideForm.java +++ b/server/src/main/java/password/pwm/http/servlet/configguide/ConfigGuideForm.java @@ -177,10 +177,12 @@ public static StoredConfiguration generateStoredConfig( { // set admin query final String userDN = formData.get( ConfigGuideFormField.PARAM_LDAP_ADMIN_USER ); - final List userPermissions = Collections.singletonList( UserPermission.builder() - .type( UserPermissionType.ldapUser ) - .ldapBase( userDN ) - .build() ); + final List userPermissions = Collections.singletonList( new UserPermission( + UserPermissionType.ldapUser, + null, + null, + userDN ) ); + modifySetting( modifier, PwmSetting.QUERY_MATCH_PWM_ADMIN, null, new UserPermissionValue( userPermissions ) ); } diff --git a/server/src/main/java/password/pwm/http/servlet/configguide/ConfigGuideServlet.java b/server/src/main/java/password/pwm/http/servlet/configguide/ConfigGuideServlet.java index d7e8ea772f..f492137bf5 100644 --- a/server/src/main/java/password/pwm/http/servlet/configguide/ConfigGuideServlet.java +++ b/server/src/main/java/password/pwm/http/servlet/configguide/ConfigGuideServlet.java @@ -312,7 +312,7 @@ public ProcessStatus restLdapHealth( case LDAP_TESTUSER: { final String testUserValue = configGuideBean.getFormData().get( ConfigGuideFormField.PARAM_LDAP_TEST_USER ); - if ( testUserValue != null && !testUserValue.isEmpty() ) + if ( !StringUtil.isEmpty( testUserValue ) ) { records.addAll( ldapHealthChecker.checkBasicLdapConnectivity( sessionLabel, tempDomain, tempDomain.getConfig(), ldapProfile, false ) ); records.addAll( ldapHealthChecker.doLdapTestUserCheck( sessionLabel, tempDomain.getConfig(), ldapProfile, tempDomain ) ); @@ -330,7 +330,10 @@ public ProcessStatus restLdapHealth( case DATABASE: { - records.addAll( DatabaseStatusChecker.checkNewDatabaseStatus( pwmRequest.getPwmApplication(), tempAppConfig ) ); + records.addAll( DatabaseStatusChecker.checkNewDatabaseStatus( + pwmRequest.getLabel(), + pwmRequest.getPwmApplication().getPwmEnvironment(), + tempAppConfig ) ); } break; @@ -338,12 +341,12 @@ public ProcessStatus restLdapHealth( PwmUtil.unhandledSwitchStatement( configGuideBean.getStep() ); } - final PublicHealthData jsonOutput = PublicHealthData.builder() - .records( PublicHealthRecord.fromHealthRecords( records, pwmRequest.getLocale(), tempDomain.getConfig() ) ) - .timestamp( Instant.now() ) - .overall( HealthUtils.getMostSevereHealthStatus( records ).toString() ) - .build(); - final RestResultBean restResultBean = RestResultBean.withData( jsonOutput, PublicHealthData.class ); + final PublicHealthData jsonOutput = new PublicHealthData( + Instant.now(), + HealthUtils.getMostSevereHealthStatus( records ).toString(), + PublicHealthRecord.fromHealthRecords( records, pwmRequest.getLocale(), tempDomain.getConfig() ) ); + + final RestResultBean restResultBean = RestResultBean.withData( jsonOutput, PublicHealthData.class ); pwmRequest.outputJsonResult( restResultBean ); return ProcessStatus.Halt; } @@ -373,7 +376,8 @@ public ProcessStatus restBrowseLdap( final LdapBrowser ldapBrowser = new LdapBrowser( pwmRequest.getLabel(), pwmRequest.getPwmDomain().getLdapService().getChaiProviderFactory(), - storedConfiguration + storedConfiguration, + pwmRequest.getPwmApplication().getPwmScheduler() ); final LdapBrowser.LdapBrowseResult result = ldapBrowser.doBrowse( domainID, ConfigGuideForm.LDAP_PROFILE_NAME, dn ); ldapBrowser.close(); @@ -437,7 +441,7 @@ public ProcessStatus restExtendSchema( final PwmRequest pwmRequest ) { final SchemaOperationResult schemaOperationResult = ConfigGuideUtils.extendSchema( pwmRequest.getPwmDomain(), configGuideBean, true ); pwmRequest.outputJsonResult( RestResultBean.withData( - schemaOperationResult.getOperationLog(), + schemaOperationResult.operationLog(), String.class ) ); } catch ( final Exception e ) @@ -583,7 +587,7 @@ public ProcessStatus restSettingData( final PwmRequest pwmRequest ) storedConfiguration, pwmRequest.getLabel(), pwmRequest.getLocale(), - NavTreeSettings.builder().domainManageMode( DomainManageMode.single ).build() + NavTreeSettings.forMode( DomainManageMode.single ) ); final RestResultBean restResultBean = RestResultBean.withData( settingData, SettingData.class ); diff --git a/server/src/main/java/password/pwm/http/servlet/forgottenpw/ForgottenPasswordServlet.java b/server/src/main/java/password/pwm/http/servlet/forgottenpw/ForgottenPasswordServlet.java index b700242f71..e2ddf47e81 100644 --- a/server/src/main/java/password/pwm/http/servlet/forgottenpw/ForgottenPasswordServlet.java +++ b/server/src/main/java/password/pwm/http/servlet/forgottenpw/ForgottenPasswordServlet.java @@ -541,7 +541,7 @@ public ProcessStatus processEnterCode( final PwmRequest pwmRequest ) // clean session, user supplied token (clicked email, etc) and this is first request ForgottenPasswordUtil.initForgottenPasswordBean( pwmRequestContext, - tokenPayload.getUserIdentity(), + tokenPayload.userIdentity(), forgottenPasswordBean ); } @@ -550,7 +550,7 @@ public ProcessStatus processEnterCode( final PwmRequest pwmRequest ) if ( pwmRequest.getDomainConfig().readSettingAsBoolean( PwmSetting.DISPLAY_TOKEN_SUCCESS_BUTTON ) ) { - pwmRequest.setAttribute( PwmRequestAttribute.TokenDestItems, tokenPayload.getDestination() ); + pwmRequest.setAttribute( PwmRequestAttribute.TokenDestItems, tokenPayload.destination() ); pwmRequest.forwardToJsp( JspUrl.RECOVER_PASSWORD_TOKEN_SUCCESS ); return ProcessStatus.Halt; } @@ -689,7 +689,7 @@ public ProcessStatus processOAuthReturn( final PwmRequest pwmRequest ) final OAuthForgottenPasswordResults results = pwmRequest.getPwmDomain().getSecureService().decryptObject( encryptedResult, OAuthForgottenPasswordResults.class ); LOGGER.trace( pwmRequest, () -> "received" ); - final String userDNfromOAuth = results.getUsername(); + final String userDNfromOAuth = results.username(); if ( userDNfromOAuth == null || userDNfromOAuth.isEmpty() ) { final String errorMsg = "oauth server coderesolver endpoint did not return a username value"; @@ -1162,7 +1162,7 @@ protected void nextStep( final PwmRequest pwmRequest ) { final PasswordStatus passwordStatus = userInfo.getPasswordStatus(); - if ( !passwordStatus.isExpired() && !passwordStatus.isPreExpired() ) + if ( !passwordStatus.expired() && !passwordStatus.preExpired() ) { if ( userInfo.isPasswordLocked() ) { diff --git a/server/src/main/java/password/pwm/http/servlet/forgottenpw/ForgottenPasswordStageProcessor.java b/server/src/main/java/password/pwm/http/servlet/forgottenpw/ForgottenPasswordStageProcessor.java index 01c8583c9a..d104052829 100644 --- a/server/src/main/java/password/pwm/http/servlet/forgottenpw/ForgottenPasswordStageProcessor.java +++ b/server/src/main/java/password/pwm/http/servlet/forgottenpw/ForgottenPasswordStageProcessor.java @@ -395,7 +395,7 @@ public Optional nextStage( final ForgottenPasswordStateM { final PasswordStatus passwordStatus = userInfo.getPasswordStatus(); - if ( !passwordStatus.isExpired() && !passwordStatus.isPreExpired() ) + if ( !passwordStatus.expired() && !passwordStatus.preExpired() ) { if ( userInfo.isPasswordLocked() ) { diff --git a/server/src/main/java/password/pwm/http/servlet/forgottenpw/ForgottenPasswordUtil.java b/server/src/main/java/password/pwm/http/servlet/forgottenpw/ForgottenPasswordUtil.java index 9bf485bcc4..37a97f48d4 100644 --- a/server/src/main/java/password/pwm/http/servlet/forgottenpw/ForgottenPasswordUtil.java +++ b/server/src/main/java/password/pwm/http/servlet/forgottenpw/ForgottenPasswordUtil.java @@ -239,7 +239,7 @@ static boolean checkAuthRecord( if ( optionalHttpAuthRecord.isPresent() ) { final HttpAuthRecord httpAuthRecord = optionalHttpAuthRecord.get(); - if ( userGuid.get().equals( httpAuthRecord.getGuid() ) ) + if ( userGuid.get().equals( httpAuthRecord.guid() ) ) { LOGGER.debug( pwmRequest, () -> "auth record cookie validated" ); return true; diff --git a/server/src/main/java/password/pwm/http/servlet/helpdesk/HelpdeskCardInfoBean.java b/server/src/main/java/password/pwm/http/servlet/helpdesk/HelpdeskCardInfoBean.java deleted file mode 100644 index 24c3dc208d..0000000000 --- a/server/src/main/java/password/pwm/http/servlet/helpdesk/HelpdeskCardInfoBean.java +++ /dev/null @@ -1,138 +0,0 @@ -/* - * Password Management Servlets (PWM) - * http://www.pwm-project.org - * - * Copyright (c) 2006-2009 Novell, Inc. - * Copyright (c) 2009-2021 The PWM Project - * - * 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. - */ - -package password.pwm.http.servlet.helpdesk; - -import com.novell.ldapchai.ChaiUser; -import com.novell.ldapchai.exception.ChaiUnavailableException; -import lombok.Builder; -import lombok.Value; -import password.pwm.PwmDomain; -import password.pwm.bean.SessionLabel; -import password.pwm.bean.UserIdentity; -import password.pwm.config.PwmSetting; -import password.pwm.config.profile.HelpdeskProfile; -import password.pwm.error.PwmUnrecoverableException; -import password.pwm.http.PwmRequest; -import password.pwm.http.servlet.peoplesearch.PhotoDataReader; -import password.pwm.ldap.UserInfoFactory; -import password.pwm.user.UserInfo; -import password.pwm.util.java.TimeDuration; -import password.pwm.util.json.JsonFactory; -import password.pwm.util.logging.PwmLogger; -import password.pwm.util.macro.MacroRequest; - -import java.time.Instant; -import java.util.ArrayList; -import java.util.List; -import java.util.Locale; -import java.util.Optional; - -@Value -@Builder -public class HelpdeskCardInfoBean -{ - private static final PwmLogger LOGGER = PwmLogger.forClass( HelpdeskCardInfoBean.class ); - - private String userKey; - private List displayNames; - private String photoURL; - - static HelpdeskCardInfoBean makeHelpdeskCardInfo( - final PwmRequest pwmRequest, - final HelpdeskProfile helpdeskProfile, - final UserIdentity userIdentity - ) - throws PwmUnrecoverableException, ChaiUnavailableException - { - final HelpdeskCardInfoBean.HelpdeskCardInfoBeanBuilder builder = HelpdeskCardInfoBean.builder(); - final Instant startTime = Instant.now(); - LOGGER.trace( pwmRequest, () -> "beginning to assemble card data report for user " + userIdentity ); - final Locale actorLocale = pwmRequest.getLocale(); - final ChaiUser theUser = HelpdeskServletUtil.getChaiUser( pwmRequest, helpdeskProfile, userIdentity ); - - if ( !theUser.exists() ) - { - return null; - } - - final UserInfo userInfo = UserInfoFactory.newUserInfo( - pwmRequest.getPwmApplication(), - pwmRequest.getLabel(), - actorLocale, - userIdentity, - theUser.getChaiProvider() - ); - - builder.userKey( HelpdeskServletUtil.obfuscateUserIdentity( pwmRequest, userIdentity ) ); - - final PhotoDataReader photoDataReader = HelpdeskServlet.photoDataReader( pwmRequest, helpdeskProfile, userIdentity ); - final Optional optionalPhotoUrl = photoDataReader.figurePhotoURL(); - optionalPhotoUrl.ifPresent( builder::photoURL ); - - builder.displayNames( figureDisplayNames( pwmRequest.getPwmDomain(), helpdeskProfile, pwmRequest.getLabel(), userInfo ) ); - - final TimeDuration timeDuration = TimeDuration.fromCurrent( startTime ); - final HelpdeskCardInfoBean helpdeskCardInfoBean = builder.build(); - - if ( pwmRequest.getAppConfig().isDevDebugMode() ) - { - LOGGER.trace( pwmRequest, () -> "completed assembly of card data report for user " + userIdentity - + " in " + timeDuration.asCompactString() + ", contents: " + JsonFactory.get().serialize( helpdeskCardInfoBean ) ); - } - - return builder.build(); - } - - static String figureDisplayName( - final HelpdeskProfile helpdeskProfile, - final MacroRequest macroRequest - ) - { - final String configuredDisplayName = helpdeskProfile.readSettingAsString( PwmSetting.HELPDESK_DETAIL_DISPLAY_NAME ); - if ( configuredDisplayName != null && !configuredDisplayName.isEmpty() ) - { - return macroRequest.expandMacros( configuredDisplayName ); - } - return null; - } - - private static List figureDisplayNames( - final PwmDomain pwmDomain, - final HelpdeskProfile helpdeskProfile, - final SessionLabel sessionLabel, - final UserInfo userInfo - ) - throws PwmUnrecoverableException - { - final List displayLabels = new ArrayList<>(); - final List displayStringSettings = helpdeskProfile.readSettingAsStringArray( PwmSetting.HELPDESK_DISPLAY_NAMES_CARD_LABELS ); - if ( displayStringSettings != null ) - { - final MacroRequest macroRequest = MacroRequest.forUser( pwmDomain.getPwmApplication(), sessionLabel, userInfo, null ); - for ( final String displayStringSetting : displayStringSettings ) - { - final String displayLabel = macroRequest.expandMacros( displayStringSetting ); - displayLabels.add( displayLabel ); - } - } - return displayLabels; - } -} diff --git a/server/src/main/java/password/pwm/http/servlet/helpdesk/HelpdeskClientData.java b/server/src/main/java/password/pwm/http/servlet/helpdesk/HelpdeskClientData.java new file mode 100644 index 0000000000..77e1ef135c --- /dev/null +++ b/server/src/main/java/password/pwm/http/servlet/helpdesk/HelpdeskClientData.java @@ -0,0 +1,173 @@ +/* + * Password Management Servlets (PWM) + * http://www.pwm-project.org + * + * Copyright (c) 2006-2009 Novell, Inc. + * Copyright (c) 2009-2021 The PWM Project + * + * 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. + */ + +package password.pwm.http.servlet.helpdesk; + +import lombok.AccessLevel; +import lombok.Builder; +import password.pwm.config.PwmSetting; +import password.pwm.config.option.HelpdeskClearResponseMode; +import password.pwm.config.option.HelpdeskUIMode; +import password.pwm.config.option.IdentityVerificationMethod; +import password.pwm.config.option.MessageSendMethod; +import password.pwm.config.profile.HelpdeskProfile; +import password.pwm.config.value.VerificationMethodValue; +import password.pwm.config.value.data.ActionConfiguration; +import password.pwm.config.value.data.FormConfiguration; +import password.pwm.http.servlet.peoplesearch.bean.SearchAttributeBean; +import password.pwm.util.java.CollectionUtil; +import password.pwm.util.java.CollectorUtil; +import password.pwm.util.java.StringUtil; + +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Set; +import java.util.function.Function; + +public record HelpdeskClientData( + Map searchColumns, + boolean enablePhoto, + boolean maskPasswords, + HelpdeskClearResponseMode clearResponses, + HelpdeskUIMode pwUiMode, + MessageSendMethod tokenSendMethod, + Map actions, + Map> verificationMethods, + List verificationForm, + int maxAdvancedSearchAttributes, + List advancedSearchAttributes, + boolean enableAdvancedSearch +) +{ + @Builder( access = AccessLevel.PRIVATE ) + public HelpdeskClientData + { + } + + public record ActionInformation( + String name, + String description + ) + { + } + + public record FormInformation( + String name, + String label + ) + { + static FormInformation fromFormConfiguration( + final FormConfiguration formConfiguration, + final Locale locale + ) + { + final String name = formConfiguration.getName(); + String label = formConfiguration.getLabel( locale ); + label = !StringUtil.isEmpty( label ) ? label : formConfiguration.getName(); + return new HelpdeskClientData.FormInformation( name, label ); + } + } + + static HelpdeskClientData fromConfig( + final HelpdeskProfile helpdeskProfile, + final Locale locale + ) + { + final HelpdeskClientData.HelpdeskClientDataBuilder builder = HelpdeskClientData.builder(); + + + builder.searchColumns( makeSearchColumns( helpdeskProfile, locale ) ); + builder.actions( makeActions( helpdeskProfile ) ); + builder.verificationMethods( makeVerificationMethods( helpdeskProfile ) ); + builder.verificationForm( makeFormInformation( helpdeskProfile, locale ) ); + + { + final List searchAttributes = SearchAttributeBean.searchAttributesFromForm( + locale, + helpdeskProfile.readSettingAsForm( PwmSetting.HELPDESK_SEARCH_FORM ) ); + + builder.enableAdvancedSearch( helpdeskProfile.readSettingAsBoolean( PwmSetting.HELPDESK_ENABLE_ADVANCED_SEARCH ) ); + builder.maxAdvancedSearchAttributes( 3 ); + builder.advancedSearchAttributes( searchAttributes ); + } + + // detail page + builder.maskPasswords( helpdeskProfile.readSettingAsBoolean( PwmSetting.HELPDESK_PASSWORD_MASKVALUE ) ); + builder.clearResponses( helpdeskProfile.readSettingAsEnum( PwmSetting.HELPDESK_CLEAR_RESPONSES, HelpdeskClearResponseMode.class ) ); + builder.pwUiMode( helpdeskProfile.readSettingAsEnum( PwmSetting.HELPDESK_SET_PASSWORD_MODE, HelpdeskUIMode.class ) ); + builder.tokenSendMethod( helpdeskProfile.readSettingAsEnum( PwmSetting.HELPDESK_TOKEN_SEND_METHOD, MessageSendMethod.class ) ); + builder.enablePhoto( helpdeskProfile.readSettingAsBoolean( PwmSetting.HELPDESK_ENABLE_PHOTOS ) ); + + return builder.build(); + } + + + private static Map> makeVerificationMethods( + final HelpdeskProfile helpdeskProfile + ) + { + return Map.of( + VerificationMethodValue.EnabledState.optional, helpdeskProfile.readOptionalVerificationMethods(), + VerificationMethodValue.EnabledState.required, helpdeskProfile.readRequiredVerificationMethods() ); + + } + + private static Map makeSearchColumns( final HelpdeskProfile helpdeskProfile, final Locale locale ) + { + // search page + final List searchForm = helpdeskProfile.readSettingAsForm( PwmSetting.HELPDESK_SEARCH_RESULT_FORM ); + return searchForm.stream().collect( CollectorUtil.toUnmodifiableLinkedMap( + FormConfiguration::getName, + formConfiguration -> formConfiguration.getLabel( locale ) + ) ); + } + + private static Map makeActions( final HelpdeskProfile helpdeskProfile ) + { + final List actionConfigurations = helpdeskProfile.readSettingAsAction( PwmSetting.HELPDESK_ACTIONS ); + + return actionConfigurations.stream().map( actionConfiguration -> new ActionInformation( + actionConfiguration.getName(), + actionConfiguration.getDescription() ) ) + .collect( CollectorUtil.toUnmodifiableLinkedMap( + ActionInformation::name, + Function.identity() ) ); + + } + + static List makeFormInformation( + final HelpdeskProfile helpdeskProfile, + final Locale locale + ) + { + final List attributeVerificationForm = helpdeskProfile.readSettingAsForm( PwmSetting.HELPDESK_VERIFICATION_FORM ); + + if ( CollectionUtil.isEmpty( attributeVerificationForm ) ) + { + return List.of(); + } + + return attributeVerificationForm.stream() + .map( f -> HelpdeskClientData.FormInformation.fromFormConfiguration( f, locale ) ) + .toList(); + } + +} diff --git a/server/src/main/java/password/pwm/http/servlet/helpdesk/HelpdeskClientDataBean.java b/server/src/main/java/password/pwm/http/servlet/helpdesk/HelpdeskClientDataBean.java deleted file mode 100644 index 93be7621cb..0000000000 --- a/server/src/main/java/password/pwm/http/servlet/helpdesk/HelpdeskClientDataBean.java +++ /dev/null @@ -1,150 +0,0 @@ -/* - * Password Management Servlets (PWM) - * http://www.pwm-project.org - * - * Copyright (c) 2006-2009 Novell, Inc. - * Copyright (c) 2009-2021 The PWM Project - * - * 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. - */ - -package password.pwm.http.servlet.helpdesk; - -import lombok.Builder; -import lombok.Value; -import password.pwm.config.PwmSetting; -import password.pwm.config.option.HelpdeskClearResponseMode; -import password.pwm.config.option.HelpdeskUIMode; -import password.pwm.config.option.IdentityVerificationMethod; -import password.pwm.config.option.MessageSendMethod; -import password.pwm.config.profile.HelpdeskProfile; -import password.pwm.config.value.data.ActionConfiguration; -import password.pwm.config.value.data.FormConfiguration; -import password.pwm.http.servlet.peoplesearch.bean.SearchAttributeBean; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Locale; -import java.util.Map; - -@Value -@Builder -public class HelpdeskClientDataBean -{ - private Map searchColumns; - private boolean enablePhoto; - private boolean maskPasswords; - private HelpdeskClearResponseMode clearResponses; - private HelpdeskUIMode pwUiMode; - private MessageSendMethod tokenSendMethod; - private Map actions; - private Map> verificationMethods; - private List verificationForm; - private int maxAdvancedSearchAttributes; - private List advancedSearchAttributes; - private boolean enableAdvancedSearch; - - - @Value - public static class ActionInformation - { - private String name; - private String description; - } - - @Value - public static class FormInformation - { - private String name; - private String label; - } - - - static HelpdeskClientDataBean fromConfig( - final HelpdeskProfile helpdeskProfile, - final Locale locale - ) - { - final HelpdeskClientDataBean.HelpdeskClientDataBeanBuilder builder = HelpdeskClientDataBean.builder(); - { - // search page - final List searchForm = helpdeskProfile.readSettingAsForm( PwmSetting.HELPDESK_SEARCH_RESULT_FORM ); - final Map searchColumns = new LinkedHashMap<>( searchForm.size() ); - for ( final FormConfiguration formConfiguration : searchForm ) - { - searchColumns.put( formConfiguration.getName(), formConfiguration.getLabel( locale ) ); - } - builder.searchColumns( searchColumns ); - } - { - // detail page - builder.maskPasswords( helpdeskProfile.readSettingAsBoolean( PwmSetting.HELPDESK_PASSWORD_MASKVALUE ) ); - builder.clearResponses( helpdeskProfile.readSettingAsEnum( PwmSetting.HELPDESK_CLEAR_RESPONSES, HelpdeskClearResponseMode.class ) ); - builder.pwUiMode( helpdeskProfile.readSettingAsEnum( PwmSetting.HELPDESK_SET_PASSWORD_MODE, HelpdeskUIMode.class ) ); - builder.tokenSendMethod( helpdeskProfile.readSettingAsEnum( PwmSetting.HELPDESK_TOKEN_SEND_METHOD, MessageSendMethod.class ) ); - builder.enablePhoto( helpdeskProfile.readSettingAsBoolean( PwmSetting.HELPDESK_ENABLE_PHOTOS ) ); - } - { - // actions - final List actionConfigurations = helpdeskProfile.readSettingAsAction( PwmSetting.HELPDESK_ACTIONS ); - final Map actions = new LinkedHashMap<>( actionConfigurations.size() ); - for ( final ActionConfiguration actionConfiguration : actionConfigurations ) - { - final HelpdeskClientDataBean.ActionInformation actionInformation = new HelpdeskClientDataBean.ActionInformation( - actionConfiguration.getName(), - actionConfiguration.getDescription() - ); - actions.put( actionConfiguration.getName(), actionInformation ); - } - - builder.actions( actions ); - } - { - final Map> verificationMethodsMap = new HashMap<>(); - verificationMethodsMap.put( "optional", helpdeskProfile.readOptionalVerificationMethods() ); - verificationMethodsMap.put( "required", helpdeskProfile.readRequiredVerificationMethods() ); - builder.verificationMethods( verificationMethodsMap ); - } - { - final List attributeVerificationForm = helpdeskProfile.readSettingAsForm( PwmSetting.HELPDESK_VERIFICATION_FORM ); - final List formInformations = new ArrayList<>(); - if ( attributeVerificationForm != null ) - { - for ( final FormConfiguration formConfiguration : attributeVerificationForm ) - { - final String name = formConfiguration.getName(); - String label = formConfiguration.getLabel( locale ); - label = ( label != null && !label.isEmpty() ) ? label : formConfiguration.getName(); - final HelpdeskClientDataBean.FormInformation formInformation = new HelpdeskClientDataBean.FormInformation( name, label ); - formInformations.add( formInformation ); - } - } - builder.verificationForm( formInformations ); - } - { - final List searchAttributes = SearchAttributeBean.searchAttributesFromForm( - locale, - helpdeskProfile.readSettingAsForm( PwmSetting.HELPDESK_SEARCH_FORM ) ); - - builder.enableAdvancedSearch( helpdeskProfile.readSettingAsBoolean( PwmSetting.HELPDESK_ENABLE_ADVANCED_SEARCH ) ); - builder.maxAdvancedSearchAttributes( 3 ); - builder.advancedSearchAttributes( searchAttributes ); - } - - - return builder.build(); - } -} diff --git a/server/src/main/java/password/pwm/http/servlet/helpdesk/HelpdeskClientState.java b/server/src/main/java/password/pwm/http/servlet/helpdesk/HelpdeskClientState.java new file mode 100644 index 0000000000..3128d6e98d --- /dev/null +++ b/server/src/main/java/password/pwm/http/servlet/helpdesk/HelpdeskClientState.java @@ -0,0 +1,176 @@ +/* + * Password Management Servlets (PWM) + * http://www.pwm-project.org + * + * Copyright (c) 2006-2009 Novell, Inc. + * Copyright (c) 2009-2021 The PWM Project + * + * 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. + */ + +package password.pwm.http.servlet.helpdesk; + +import password.pwm.AppProperty; +import password.pwm.bean.UserIdentity; +import password.pwm.config.option.IdentityVerificationMethod; +import password.pwm.error.PwmUnrecoverableException; +import password.pwm.http.PwmRequest; +import password.pwm.http.PwmRequestContext; +import password.pwm.util.java.CollectionUtil; +import password.pwm.util.java.StringUtil; +import password.pwm.util.java.TimeDuration; +import password.pwm.util.json.JsonFactory; +import password.pwm.util.logging.PwmLogger; + +import java.time.Instant; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.Optional; + +public record HelpdeskClientState( + UserIdentity actor, + List records +) +{ + private static final PwmLogger LOGGER = PwmLogger.forClass( HelpdeskClientState.class ); + + public HelpdeskClientState( + final UserIdentity actor, + final List records + ) + { + this.actor = Objects.requireNonNull( actor ); + this.records = CollectionUtil.stripNulls( records ).stream() + .sorted() + .toList(); + } + + public HelpdeskClientState addRecord( + final UserIdentity identity, + final IdentityVerificationMethod method ) + { + final List outputList = CollectionUtil.addListItems( + removeRecord( identity, method ), + new HelpdeskVerificationRecord( identity, method, Instant.now() ) ); + + return new HelpdeskClientState( actor, outputList ); + } + + private List removeRecord( + final UserIdentity identity, + final IdentityVerificationMethod method + ) + { + return records().stream() + .filter( record -> !record.matches( identity, method ) ) + .toList(); + } + + public boolean hasRecord( final UserIdentity identity, final IdentityVerificationMethod method ) + { + return getRecord( identity, method ).isPresent(); + } + + private Optional getRecord( + final UserIdentity identity, + final IdentityVerificationMethod method ) + { + return records.stream().filter( record -> record.matches( identity, method ) ).findFirst(); + } + + private HelpdeskClientState purgeOldRecords( final TimeDuration maximumAge ) + { + return new HelpdeskClientState( + this.actor, + this.records.stream() + .filter( record -> TimeDuration.fromCurrent( record.timestamp() ).isShorterThan( maximumAge ) ) + .toList() ); + } + + public List asDisplayableValidationRecords( + final PwmRequestContext pwmRequestContext + ) + throws PwmUnrecoverableException + { + final List returnList = new ArrayList<>( records.size() ); + for ( final HelpdeskVerificationRecord record : records ) + { + returnList.add( record.toViewableRecord( pwmRequestContext ) ); + } + + Collections.sort( returnList ); + + return List.copyOf( returnList ); + } + + private static TimeDuration figureMaxAge( final PwmRequest pwmRequest ) + { + final int maxAgeSeconds = Integer.parseInt( pwmRequest.getDomainConfig() + .readAppProperty( AppProperty.HELPDESK_VERIFICATION_TIMEOUT_SECONDS ) ); + return TimeDuration.of( maxAgeSeconds, TimeDuration.Unit.SECONDS ); + } + + + String toClientString( final PwmRequest pwmRequest ) + throws PwmUnrecoverableException + { + final TimeDuration maxAge = figureMaxAge( pwmRequest ); + final HelpdeskClientState purgedState = this.purgeOldRecords( maxAge ); + return pwmRequest.encryptObjectToString( purgedState ); + } + + public static HelpdeskClientState fromClientString( + final PwmRequest pwmRequest, + final String rawValue + ) + { + final UserIdentity actor = pwmRequest.getUserInfoIfLoggedIn(); + + HelpdeskClientState inputState = null; + if ( !StringUtil.isEmpty( rawValue ) ) + { + try + { + inputState = pwmRequest.decryptObject( rawValue, HelpdeskClientState.class ); + if ( inputState != null && !Objects.equals( inputState.actor(), actor ) ) + { + inputState = null; + } + } + catch ( final Exception e ) + { + LOGGER.trace( pwmRequest, () -> "unable to deserializing client verification state, will ignore; error: " + e.getMessage() ); + } + } + + if ( inputState == null ) + { + return new HelpdeskClientState( actor, List.of() ); + } + + final TimeDuration maxAge = figureMaxAge( pwmRequest ); + + final HelpdeskClientState outputRecord = inputState.purgeOldRecords( maxAge ); + + { + LOGGER.debug( pwmRequest, () -> "read current state: " + JsonFactory.get().serialize( outputRecord ) ); + } + + return outputRecord; + } + +} + + diff --git a/server/src/main/java/password/pwm/http/servlet/helpdesk/HelpdeskDetailActionRequest.java b/server/src/main/java/password/pwm/http/servlet/helpdesk/HelpdeskDetailActionRequest.java new file mode 100644 index 0000000000..9de7abf6e4 --- /dev/null +++ b/server/src/main/java/password/pwm/http/servlet/helpdesk/HelpdeskDetailActionRequest.java @@ -0,0 +1,32 @@ +/* + * Password Management Servlets (PWM) + * http://www.pwm-project.org + * + * Copyright (c) 2006-2009 Novell, Inc. + * Copyright (c) 2009-2021 The PWM Project + * + * 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. + */ + +package password.pwm.http.servlet.helpdesk; + +import password.pwm.http.servlet.helpdesk.data.HelpdeskTargetUserRequest; + +record HelpdeskDetailActionRequest( + String userKey, + String verificationState, + String actionName +) + implements HelpdeskTargetUserRequest +{ +} diff --git a/client/angular/src/models/column.model.ts b/server/src/main/java/password/pwm/http/servlet/helpdesk/HelpdeskDetailButton.java similarity index 77% rename from client/angular/src/models/column.model.ts rename to server/src/main/java/password/pwm/http/servlet/helpdesk/HelpdeskDetailButton.java index d35d3df944..01427da85a 100644 --- a/client/angular/src/models/column.model.ts +++ b/server/src/main/java/password/pwm/http/servlet/helpdesk/HelpdeskDetailButton.java @@ -18,11 +18,15 @@ * limitations under the License. */ +package password.pwm.http.servlet.helpdesk; -export default class Column { - constructor(public label: string, - public valueExpression: string, - public visible?: boolean) { - this.visible = visible !== false; - } +public enum HelpdeskDetailButton +{ + changePassword, + unlock, + clearResponses, + clearOtpSecret, + verification, + deleteUser, + executeAction, } diff --git a/server/src/main/java/password/pwm/http/servlet/helpdesk/HelpdeskDetailServlet.java b/server/src/main/java/password/pwm/http/servlet/helpdesk/HelpdeskDetailServlet.java new file mode 100644 index 0000000000..9939db8066 --- /dev/null +++ b/server/src/main/java/password/pwm/http/servlet/helpdesk/HelpdeskDetailServlet.java @@ -0,0 +1,715 @@ +/* + * Password Management Servlets (PWM) + * http://www.pwm-project.org + * + * Copyright (c) 2006-2009 Novell, Inc. + * Copyright (c) 2009-2021 The PWM Project + * + * 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. + */ + +package password.pwm.http.servlet.helpdesk; + +import com.novell.ldapchai.ChaiUser; +import com.novell.ldapchai.exception.ChaiError; +import com.novell.ldapchai.exception.ChaiOperationException; +import com.novell.ldapchai.exception.ChaiPasswordPolicyException; +import com.novell.ldapchai.exception.ChaiUnavailableException; +import com.novell.ldapchai.provider.ChaiProvider; +import password.pwm.PwmConstants; +import password.pwm.PwmDomain; +import password.pwm.bean.UserIdentity; +import password.pwm.config.PwmSetting; +import password.pwm.config.option.HelpdeskUIMode; +import password.pwm.config.profile.HelpdeskProfile; +import password.pwm.config.profile.PwmPasswordPolicy; +import password.pwm.config.value.data.ActionConfiguration; +import password.pwm.error.ErrorInformation; +import password.pwm.error.PwmError; +import password.pwm.error.PwmException; +import password.pwm.error.PwmOperationalException; +import password.pwm.error.PwmUnrecoverableException; +import password.pwm.http.HttpMethod; +import password.pwm.http.JspUrl; +import password.pwm.http.ProcessStatus; +import password.pwm.http.PwmHttpRequestWrapper; +import password.pwm.http.PwmRequest; +import password.pwm.http.PwmRequestAttribute; +import password.pwm.http.PwmSession; +import password.pwm.http.servlet.AbstractPwmServlet; +import password.pwm.http.servlet.ControlledPwmServlet; +import password.pwm.http.servlet.PwmServletDefinition; +import password.pwm.http.servlet.helpdesk.data.HelpdeskCheckVerificationRequest; +import password.pwm.http.servlet.helpdesk.data.HelpdeskCheckVerificationResponse; +import password.pwm.http.servlet.helpdesk.data.HelpdeskTargetUserRequest; +import password.pwm.i18n.Message; +import password.pwm.ldap.UserInfoFactory; +import password.pwm.svc.cr.CrService; +import password.pwm.svc.event.AuditEvent; +import password.pwm.svc.event.AuditRecordFactory; +import password.pwm.svc.event.AuditServiceClient; +import password.pwm.svc.event.HelpdeskAuditRecord; +import password.pwm.svc.intruder.IntruderServiceClient; +import password.pwm.svc.otp.OtpService; +import password.pwm.svc.stats.Statistic; +import password.pwm.svc.stats.StatisticsClient; +import password.pwm.user.UserInfo; +import password.pwm.util.PasswordData; +import password.pwm.util.java.JavaHelper; +import password.pwm.util.logging.PwmLogger; +import password.pwm.util.macro.MacroRequest; +import password.pwm.util.operations.ActionExecutor; +import password.pwm.util.password.PasswordUtility; +import password.pwm.util.password.RandomGeneratorConfig; +import password.pwm.ws.server.RestResultBean; +import password.pwm.ws.server.rest.RestCheckPasswordServer; +import password.pwm.ws.server.rest.RestRandomPasswordServer; +import password.pwm.ws.server.rest.RestSetPasswordServer; + +import javax.servlet.ServletException; +import javax.servlet.annotation.WebServlet; +import java.io.IOException; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Optional; + + +@WebServlet( + name = "HelpdeskDetailServlet", + urlPatterns = { + PwmConstants.URL_PREFIX_PRIVATE + "/helpdesk/detail", + PwmConstants.URL_PREFIX_PRIVATE + "/helpdesk/detail/", + } +) +public class HelpdeskDetailServlet extends ControlledPwmServlet +{ + private static final PwmLogger LOGGER = PwmLogger.forClass( HelpdeskDetailServlet.class ); + + @Override + protected PwmLogger getLogger() + { + return LOGGER; + } + + public enum HelpdeskDetailAction implements AbstractPwmServlet.ProcessAction + { + checkPassword( HttpMethod.POST ), + setPassword( HttpMethod.POST ), + randomPassword( HttpMethod.POST ), + unlockIntruder( HttpMethod.POST ), + clearOtpSecret( HttpMethod.POST ), + clearResponses( HttpMethod.POST ), + + checkOptionalVerification( HttpMethod.POST ), + validateOtpCode( HttpMethod.POST ), + sendVerificationToken( HttpMethod.POST ), + verifyVerificationToken( HttpMethod.POST ), + validateAttributes( HttpMethod.POST ), + executeAction( HttpMethod.POST ), + deleteUser( HttpMethod.POST ),; + + private final HttpMethod method; + + HelpdeskDetailAction( final HttpMethod method ) + { + this.method = method; + } + + @Override + public Collection permittedMethods( ) + { + return Collections.singletonList( method ); + } + } + + @Override + public Optional> getProcessActionsClass( ) + { + return Optional.of( HelpdeskDetailServlet.HelpdeskDetailAction.class ); + } + + @Override + protected void nextStep( final PwmRequest pwmRequest ) + throws PwmUnrecoverableException, IOException, ChaiUnavailableException, ServletException + { + gotoDetailJSP( pwmRequest ); + } + + @Override + public ProcessStatus preProcessCheck( final PwmRequest pwmRequest ) + throws PwmUnrecoverableException, IOException, ServletException + { + return null; + } + + private static void gotoDetailJSP( final PwmRequest pwmRequest ) + throws PwmUnrecoverableException, IOException + { + try + { + final UserIdentity targetIdentity = HelpdeskServlet.readUserKeyRequestParameter( pwmRequest ); + final String rawVerificationStr = pwmRequest.readParameterAsString( "verificationState", PwmHttpRequestWrapper.Flag.BypassValidation ); + final HelpdeskClientState state = HelpdeskClientState.fromClientString( pwmRequest, rawVerificationStr ); + final HelpdeskClientData helpdeskClientData = HelpdeskClientData.fromConfig( pwmRequest.getHelpdeskProfile(), pwmRequest.getLocale() ); + + HelpdeskServletUtil.verifyIfRequiredVerificationPassed( pwmRequest, targetIdentity, state ); + + final String outputUserKey = HelpdeskServletUtil.obfuscateUserIdentity( pwmRequest, targetIdentity ); + LOGGER.trace( pwmRequest, + () -> "helpdesk detail view request for user details of " + targetIdentity.toString() ); + + final HelpdeskUserDetail helpdeskDetailInfoBean = + HelpdeskUserDetail.makeHelpdeskDetailInfo( pwmRequest, targetIdentity ); + + HelpdeskServletUtil.submitAuditEvent( pwmRequest, targetIdentity, AuditEvent.HELPDESK_VIEW_DETAIL, null ); + + StatisticsClient.incrementStat( pwmRequest, Statistic.HELPDESK_USER_LOOKUP ); + pwmRequest.setAttribute( PwmRequestAttribute.HelpdeskClientData, helpdeskClientData ); + pwmRequest.setAttribute( PwmRequestAttribute.HelpdeskUserKey, outputUserKey ); + pwmRequest.setAttribute( PwmRequestAttribute.HelpdeskDetailInfo, helpdeskDetailInfoBean ); + pwmRequest.forwardToJsp( JspUrl.HELPDESK_DETAIL ); + } + catch ( final Exception e ) + { + LOGGER.debug( pwmRequest, () -> "ignoring detail page view request for target identity " + + " reason: " + e.getMessage() ); + pwmRequest.getPwmResponse().sendRedirect( PwmServletDefinition.Helpdesk ); + } + } + + @ActionHandler( action = "checkPassword" ) + public ProcessStatus processCheckPasswordAction( final PwmRequest pwmRequest ) + throws IOException, PwmUnrecoverableException, ChaiUnavailableException + { + final HelpdeskPasswordOperationRequest passwordOperationRequest = pwmRequest.readBodyAsJsonObject( HelpdeskPasswordOperationRequest.class ); + + final UserIdentity targetUser = HelpdeskServletUtil.readUserIdentity( pwmRequest, passwordOperationRequest.userKey() ); + + final HelpdeskClientState verificationState = HelpdeskClientState.fromClientString( pwmRequest, passwordOperationRequest.verificationState() ); + HelpdeskServletUtil.verifyButtonEnabled( pwmRequest, targetUser, HelpdeskDetailButton.changePassword ); + HelpdeskServletUtil.verifyIfRequiredVerificationPassed( pwmRequest, targetUser, verificationState ); + + final ChaiUser chaiUser = HelpdeskServletUtil.getChaiUser( pwmRequest, targetUser ); + final UserInfo userInfo = UserInfoFactory.newUserInfo( + pwmRequest.getPwmApplication(), + pwmRequest.getLabel(), + pwmRequest.getLocale(), + targetUser, + chaiUser.getChaiProvider() + ); + + { + final HelpdeskProfile helpdeskProfile = HelpdeskServlet.getHelpdeskProfile( pwmRequest ); + final HelpdeskUIMode mode = helpdeskProfile.readSettingAsEnum( PwmSetting.HELPDESK_SET_PASSWORD_MODE, HelpdeskUIMode.class ); + if ( mode == HelpdeskUIMode.none ) + { + throw new PwmUnrecoverableException( new ErrorInformation( PwmError.ERROR_SECURITY_VIOLATION, "setting " + + PwmSetting.HELPDESK_SET_PASSWORD_MODE.toMenuLocationDebug( helpdeskProfile.getId(), pwmRequest.getLocale() ) + + " must not be set to none" ) ); + } + } + + final PasswordUtility.PasswordCheckInfo passwordCheckInfo = PasswordUtility.checkEnteredPassword( + pwmRequest.getPwmRequestContext(), + chaiUser, + userInfo, + null, + PasswordData.forStringValue( passwordOperationRequest.password1() ), + PasswordData.forStringValue( passwordOperationRequest.password2() ) + ); + + final RestCheckPasswordServer.JsonOutput jsonResponse = RestCheckPasswordServer.JsonOutput.fromPasswordCheckInfo( passwordCheckInfo ); + + final RestResultBean restResultBean = RestResultBean.withData( jsonResponse, RestCheckPasswordServer.JsonOutput.class ); + pwmRequest.outputJsonResult( restResultBean ); + + return ProcessStatus.Halt; + } + + @ActionHandler( action = "setPassword" ) + public ProcessStatus processSetPasswordAction( final PwmRequest pwmRequest ) throws IOException, PwmUnrecoverableException, ChaiUnavailableException + { + final HelpdeskProfile helpdeskProfile = pwmRequest.getHelpdeskProfile( ); + + final RestSetPasswordServer.JsonInputData jsonInput = + pwmRequest.readBodyAsJsonObject( RestSetPasswordServer.JsonInputData.class ); + + final UserIdentity userIdentity = HelpdeskServletUtil.readUserIdentity( pwmRequest, jsonInput.username() ); + final ChaiUser chaiUser = HelpdeskServletUtil.getChaiUser( pwmRequest, userIdentity ); + final UserInfo userInfo = UserInfoFactory.newUserInfo( + pwmRequest.getPwmApplication(), + pwmRequest.getLabel(), + pwmRequest.getLocale(), + userIdentity, + chaiUser.getChaiProvider() + ); + + HelpdeskServletUtil.checkIfUserIdentityViewable( pwmRequest, userIdentity ); + final HelpdeskUIMode mode = helpdeskProfile.readSettingAsEnum( PwmSetting.HELPDESK_SET_PASSWORD_MODE, HelpdeskUIMode.class ); + + if ( mode == HelpdeskUIMode.none ) + { + throw new PwmUnrecoverableException( new ErrorInformation( PwmError.ERROR_SECURITY_VIOLATION, "setting " + + PwmSetting.HELPDESK_SET_PASSWORD_MODE.toMenuLocationDebug( helpdeskProfile.getId(), pwmRequest.getLocale() ) + + " must not be set to none" ) ); + } + + + final PasswordData newPassword; + if ( jsonInput.password() == null ) + { + if ( mode != HelpdeskUIMode.random ) + { + throw new PwmUnrecoverableException( new ErrorInformation( PwmError.ERROR_SECURITY_VIOLATION, "setting " + + PwmSetting.HELPDESK_SET_PASSWORD_MODE.toMenuLocationDebug( helpdeskProfile.getId(), pwmRequest.getLocale() ) + + " is set to " + mode + " and no password is included in request" ) ); + } + final PwmPasswordPolicy passwordPolicy = PasswordUtility.readPasswordPolicyForUser( + pwmRequest.getPwmDomain(), + pwmRequest.getLabel(), + userIdentity, + chaiUser ); + newPassword = PasswordUtility.generateRandom( + pwmRequest.getLabel(), + passwordPolicy, + pwmRequest.getPwmDomain() + ); + } + else + { + if ( mode == HelpdeskUIMode.random ) + { + throw new PwmUnrecoverableException( new ErrorInformation( PwmError.ERROR_SECURITY_VIOLATION, "setting " + + PwmSetting.HELPDESK_SET_PASSWORD_MODE.toMenuLocationDebug( helpdeskProfile.getId(), pwmRequest.getLocale() ) + + " is set to autogen yet a password is included in request" ) ); + } + + newPassword = new PasswordData( jsonInput.password() ); + } + + + try + { + PasswordUtility.helpdeskSetUserPassword( + pwmRequest, + chaiUser, + userInfo, + pwmRequest.getPwmDomain(), + newPassword + ); + } + catch ( final PwmException e ) + { + LOGGER.error( () -> "error during set password REST operation: " + e.getMessage() ); + pwmRequest.outputJsonResult( RestResultBean.fromError( e.getErrorInformation(), pwmRequest ) ); + return ProcessStatus.Halt; + } + + pwmRequest.outputJsonResult( RestResultBean.forSuccessMessage( pwmRequest, Message.Success_ChangedHelpdeskPassword, userInfo.getUsername() ) ); + return ProcessStatus.Halt; + } + + private static T helpdeskDetailActionReader( + final PwmRequest pwmRequest, final Class classOfT, + final HelpdeskDetailButton button + ) + throws PwmUnrecoverableException, IOException + { + final T actionRequest = pwmRequest.readBodyAsJsonObject( classOfT ); + final HelpdeskClientState verificationState = actionRequest.readVerificationState( pwmRequest ); + final UserIdentity targetUser = actionRequest.readTargetUser( pwmRequest ); + + HelpdeskServletUtil.verifyButtonEnabled( pwmRequest, targetUser, button ); + HelpdeskServletUtil.checkIfRequiredVerificationPassed( pwmRequest, targetUser, verificationState ); + + return actionRequest; + } + + @ActionHandler( action = "clearOtpSecret" ) + public ProcessStatus restClearOtpSecret( + final PwmRequest pwmRequest + ) + throws ServletException, IOException, PwmUnrecoverableException, ChaiUnavailableException + { + final HelpdeskDetailActionRequest actionRequest = helpdeskDetailActionReader( + pwmRequest, + HelpdeskDetailActionRequest.class, + HelpdeskDetailButton.clearOtpSecret ); + + final UserIdentity targetUser = actionRequest.readTargetUser( pwmRequest ); + + //clear pwm intruder setting. + IntruderServiceClient.clearUserIdentity( pwmRequest, targetUser ); + + try + { + + final OtpService service = pwmRequest.getPwmDomain().getOtpService(); + final ChaiUser chaiUser = HelpdeskServletUtil.getChaiUser( pwmRequest, targetUser ); + service.clearOTPUserConfiguration( pwmRequest, targetUser, chaiUser ); + { + // mark the event log + final HelpdeskAuditRecord auditRecord = AuditRecordFactory.make( pwmRequest ).createHelpdeskAuditRecord( + AuditEvent.HELPDESK_CLEAR_OTP_SECRET, + pwmRequest.getPwmSession().getUserInfo().getUserIdentity(), + null, + targetUser, + pwmRequest.getLabel().getSourceAddress(), + pwmRequest.getLabel().getSourceHostname() + ); + AuditServiceClient.submit( pwmRequest, auditRecord ); + } + } + catch ( final PwmOperationalException e ) + { + final PwmError returnMsg = e.getError(); + final ErrorInformation error = new ErrorInformation( returnMsg, e.getMessage() ); + pwmRequest.respondWithError( error ); + LOGGER.warn( pwmRequest, () -> "error clearing OTP secret for user '" + targetUser + "'' " + error.toDebugStr() + ", " + e.getMessage() ); + return ProcessStatus.Halt; + } + + final RestResultBean restResultBean = RestResultBean.forSuccessMessage( pwmRequest, Message.Success_Unknown ); + pwmRequest.outputJsonResult( restResultBean ); + return ProcessStatus.Halt; + } + + @ActionHandler( action = "clearResponses" ) + public ProcessStatus restClearResponsesHandler( final PwmRequest pwmRequest ) + throws IOException, PwmUnrecoverableException, ServletException, ChaiUnavailableException, PwmOperationalException + { + final HelpdeskDetailActionRequest actionRequest = helpdeskDetailActionReader( + pwmRequest, + HelpdeskDetailActionRequest.class, + HelpdeskDetailButton.clearResponses ); + + final UserIdentity targetUser = actionRequest.readTargetUser( pwmRequest ); + final ChaiUser chaiUser = HelpdeskServletUtil.getChaiUser( pwmRequest, targetUser ); + + final CrService crService = pwmRequest.getPwmDomain().getCrService(); + crService.clearResponses( + pwmRequest.getLabel(), + targetUser, + chaiUser + ); + + HelpdeskServletUtil.submitAuditEvent( pwmRequest, targetUser, AuditEvent.HELPDESK_CLEAR_RESPONSES, null ); + + final RestResultBean restResultBean = RestResultBean.forSuccessMessage( pwmRequest, Message.Success_Unknown ); + pwmRequest.outputJsonResult( restResultBean ); + return ProcessStatus.Halt; + } + + @ActionHandler( action = "unlockIntruder" ) + public ProcessStatus restUnlockIntruder( + final PwmRequest pwmRequest + ) + throws PwmUnrecoverableException, ChaiUnavailableException, IOException, ServletException + { + final HelpdeskDetailActionRequest actionRequest = helpdeskDetailActionReader( + pwmRequest, + HelpdeskDetailActionRequest.class, + HelpdeskDetailButton.unlock ); + + final UserIdentity targetUser = actionRequest.readTargetUser( pwmRequest ); + + //clear pwm intruder setting. + IntruderServiceClient.clearUserIdentity( pwmRequest, targetUser ); + + try + { + final ChaiUser chaiUser = HelpdeskServletUtil.getChaiUser( pwmRequest, targetUser ); + + // send notice email + HelpdeskServletUtil.sendUnlockNoticeEmail( pwmRequest, targetUser, chaiUser ); + + chaiUser.unlockPassword(); + + HelpdeskServletUtil.submitAuditEvent( pwmRequest, targetUser, AuditEvent.HELPDESK_UNLOCK_PASSWORD, null ); + } + catch ( final ChaiPasswordPolicyException e ) + { + final ChaiError passwordError = e.getErrorCode(); + final PwmError pwmError = PwmError.forChaiError( passwordError ).orElse( PwmError.PASSWORD_UNKNOWN_VALIDATION ); + pwmRequest.respondWithError( new ErrorInformation( pwmError ) ); + LOGGER.trace( pwmRequest, () -> "ChaiPasswordPolicyException was thrown while resetting password: " + e.getMessage() ); + return ProcessStatus.Halt; + } + catch ( final ChaiOperationException e ) + { + final PwmError returnMsg = PwmError.forChaiError( e.getErrorCode() ).orElse( PwmError.ERROR_INTERNAL ); + final ErrorInformation error = new ErrorInformation( returnMsg, e.getMessage() ); + pwmRequest.respondWithError( error ); + LOGGER.warn( pwmRequest, () -> "error resetting password for user '" + + targetUser.toDisplayString() + + "'' " + error.toDebugStr() + ", " + e.getMessage() ); + return ProcessStatus.Halt; + } + + final RestResultBean restResultBean = RestResultBean.forSuccessMessage( pwmRequest, Message.Success_Unknown ); + pwmRequest.outputJsonResult( restResultBean ); + return ProcessStatus.Halt; + } + + @ActionHandler( action = "checkOptionalVerification" ) + public ProcessStatus restCheckOptionalVerification( final PwmRequest pwmRequest ) + throws IOException, PwmUnrecoverableException, ServletException + { + final HelpdeskProfile helpdeskProfile = HelpdeskServlet.getHelpdeskProfile( pwmRequest ); + final var checkRequest = pwmRequest.readBodyAsJsonObject( HelpdeskCheckVerificationRequest.class ); + + final UserIdentity userIdentity = HelpdeskServletUtil.readUserIdentity( pwmRequest, checkRequest.userKey() ); + HelpdeskServletUtil.checkIfUserIdentityViewable( pwmRequest, userIdentity ); + + final String rawVerificationStr = checkRequest.verificationState(); + final HelpdeskClientState state = HelpdeskClientState.fromClientString( pwmRequest, rawVerificationStr ); + final boolean passed = HelpdeskServletUtil.checkIfRequiredVerificationPassed( pwmRequest, userIdentity, state ); + if ( !passed ) + { + throw new PwmUnrecoverableException( PwmError.ERROR_SECURITY_VIOLATION, "attempt to request optional verification check when required verifications not passed" ); + } + final HelpdeskVerificationOptions optionsBean = HelpdeskVerificationOptions.fromConfig( pwmRequest, helpdeskProfile, userIdentity ); + final HelpdeskCheckVerificationResponse responseBean = new HelpdeskCheckVerificationResponse( false, + optionsBean ); + final RestResultBean restResultBean = RestResultBean.withData( responseBean, HelpdeskCheckVerificationResponse.class ); + pwmRequest.outputJsonResult( restResultBean ); + return ProcessStatus.Halt; + } + + @ActionHandler( action = "randomPassword" ) + public ProcessStatus processRandomPasswordAction( final PwmRequest pwmRequest ) + throws IOException, PwmUnrecoverableException + { + final RestRandomPasswordServer.JsonInput input = pwmRequest.readBodyAsJsonObject( RestRandomPasswordServer.JsonInput.class ); + final UserIdentity userIdentity = HelpdeskServletUtil.readUserIdentity( pwmRequest, input.username() ); + + HelpdeskServletUtil.checkIfUserIdentityViewable( pwmRequest, userIdentity ); + + final ChaiUser chaiUser = HelpdeskServletUtil.getChaiUser( pwmRequest, userIdentity ); + final UserInfo userInfo = UserInfoFactory.newUserInfo( + pwmRequest.getPwmApplication(), + pwmRequest.getLabel(), + pwmRequest.getLocale(), + userIdentity, + chaiUser.getChaiProvider() + ); + + final RandomGeneratorConfig randomConfig = RandomGeneratorConfig.make( pwmRequest.getPwmDomain(), userInfo.getPasswordPolicy() ); + final PasswordData randomPassword = PasswordUtility.generateRandom( pwmRequest.getLabel(), randomConfig, pwmRequest.getPwmDomain() ); + final RestRandomPasswordServer.JsonOutput jsonOutput = new RestRandomPasswordServer.JsonOutput( randomPassword.getStringValue() ); + + final RestResultBean restResultBean + = RestResultBean.withData( jsonOutput, RestRandomPasswordServer.JsonOutput.class ); + pwmRequest.outputJsonResult( restResultBean ); + return ProcessStatus.Halt; + } + + @ActionHandler( action = "executeAction" ) + public ProcessStatus processExecuteActionRequest( + final PwmRequest pwmRequest + ) + throws PwmUnrecoverableException, IOException + { + final HelpdeskDetailActionRequest actionRequest = helpdeskDetailActionReader( + pwmRequest, + HelpdeskDetailActionRequest.class, + null ); + + final UserIdentity targetUserIdentity = actionRequest.readTargetUser( pwmRequest ); + + final HelpdeskProfile helpdeskProfile = HelpdeskServlet.getHelpdeskProfile( pwmRequest ); + LOGGER.debug( pwmRequest, () -> "received executeAction request for user " + targetUserIdentity.toString() ); + + final List actionConfigurations = helpdeskProfile.readSettingAsAction( PwmSetting.HELPDESK_ACTIONS ); + final String requestedName = JavaHelper.requireNonEmpty( actionRequest.actionName() ); + final ActionConfiguration requestedAction = actionConfigurations.stream() + .filter( action -> action.getName().equals( requestedName ) ) + .findFirst() + .orElseThrow( () -> + { + final String errorMsg = "request to execute unknown action: " + requestedName; + final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_INTERNAL, errorMsg ); + return new PwmUnrecoverableException( errorInformation ); + } ); + + // check if user should be seen by actor + HelpdeskServletUtil.checkIfUserIdentityViewable( pwmRequest, targetUserIdentity ); + + try + { + final ChaiUser chaiUser = HelpdeskServletUtil.getChaiUser( pwmRequest, targetUserIdentity ); + final MacroRequest macroRequest = HelpdeskServletUtil.getTargetUserMacroRequest( pwmRequest, targetUserIdentity ); + final ActionExecutor actionExecutor = new ActionExecutor.ActionExecutorSettings( pwmRequest.getPwmDomain(), chaiUser ) + .setExpandPwmMacros( true ) + .setMacroMachine( macroRequest ) + .createActionExecutor(); + + actionExecutor.executeAction( requestedAction, pwmRequest.getLabel() ); + + // mark the event log + HelpdeskServletUtil.submitAuditEvent( pwmRequest, targetUserIdentity, AuditEvent.HELPDESK_ACTION, requestedAction.getName() ); + final RestResultBean restResultBean = RestResultBean.forSuccessMessage( + pwmRequest.getLocale(), + pwmRequest.getDomainConfig(), + Message.Success_Action, + requestedAction.getName() ); + + pwmRequest.outputJsonResult( restResultBean ); + return ProcessStatus.Halt; + } + catch ( final PwmOperationalException e ) + { + LOGGER.error( pwmRequest, () -> e.getErrorInformation().toDebugStr() ); + final RestResultBean restResultBean = RestResultBean.fromError( e.getErrorInformation(), pwmRequest ); + pwmRequest.outputJsonResult( restResultBean ); + return ProcessStatus.Halt; + } + } + + @ActionHandler( action = "deleteUser" ) + public ProcessStatus restDeleteUserRequest( + final PwmRequest pwmRequest + ) + throws ChaiUnavailableException, PwmUnrecoverableException, IOException + { + final HelpdeskDetailActionRequest actionRequest = helpdeskDetailActionReader( + pwmRequest, + HelpdeskDetailActionRequest.class, + HelpdeskDetailButton.deleteUser ); + + final UserIdentity targetUser = actionRequest.readTargetUser( pwmRequest ); + + final HelpdeskProfile helpdeskProfile = HelpdeskServlet.getHelpdeskProfile( pwmRequest ); + final PwmDomain pwmDomain = pwmRequest.getPwmDomain(); + final PwmSession pwmSession = pwmRequest.getPwmSession(); + + // read the userID for later logging. + String userID = null; + try + { + final UserInfo deletedUserInfo = UserInfoFactory.newUserInfoUsingProxy( + pwmRequest.getPwmApplication(), + pwmRequest.getLabel(), + targetUser, + pwmRequest.getLocale() ); + userID = deletedUserInfo.getUsername(); + } + catch ( final PwmUnrecoverableException e ) + { + LOGGER.warn( pwmRequest, () -> "unable to read username of deleted user while creating audit record" ); + } + + // execute user delete operation + final ChaiProvider provider = helpdeskProfile.readSettingAsBoolean( PwmSetting.HELPDESK_USE_PROXY ) + ? pwmDomain.getProxyChaiProvider( pwmRequest.getLabel(), targetUser.getLdapProfileID() ) + : pwmRequest.getClientConnectionHolder().getActor().getChaiProvider(); + + try + { + provider.deleteEntry( targetUser.getUserDN() ); + } + catch ( final ChaiOperationException e ) + { + final String errorMsg = "error while attempting to delete user " + targetUser + ", error: " + e.getMessage(); + final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_INTERNAL, errorMsg ); + LOGGER.debug( pwmRequest, () -> errorMsg ); + pwmRequest.outputJsonResult( RestResultBean.fromError( errorInformation, pwmRequest ) ); + return ProcessStatus.Halt; + } + + // mark the event log + { + //normally the audit record builder reads the userID while constructing the record, but because the target user is already deleted, + //it will be included here explicitly. + final AuditRecordFactory.AuditUserDefinition auditUserDefinition = new AuditRecordFactory.AuditUserDefinition( + userID, + targetUser.getUserDN(), + targetUser.getLdapProfileID() + ); + final HelpdeskAuditRecord auditRecord = AuditRecordFactory.make( pwmRequest ).createHelpdeskAuditRecord( + AuditEvent.HELPDESK_DELETE_USER, + pwmSession.getUserInfo().getUserIdentity(), + null, + auditUserDefinition, + pwmSession.getSessionStateBean().getSrcAddress(), + pwmSession.getSessionStateBean().getSrcHostname() + ); + + AuditServiceClient.submit( pwmRequest, auditRecord ); + } + + LOGGER.info( pwmRequest, () -> "user " + targetUser + " has been deleted" ); + + final RestResultBean restResultBean = RestResultBean.forSuccessMessage( pwmRequest, Message.Success_Unknown ); + pwmRequest.outputJsonResult( restResultBean ); + return ProcessStatus.Halt; + } + + @ActionHandler( action = "validateOtpCode" ) + public ProcessStatus restValidateOtpCodeRequest( + final PwmRequest pwmRequest + ) + throws IOException, PwmUnrecoverableException, ServletException + { + final HelpdeskVerificationRequest actionRequest = helpdeskDetailActionReader( + pwmRequest, + HelpdeskVerificationRequest.class, + HelpdeskDetailButton.verification ); + + + return HelpdeskServletUtil.validateOtpCodeImpl( pwmRequest, actionRequest ); + } + + @ActionHandler( action = "sendVerificationToken" ) + public ProcessStatus restSendVerificationTokenRequest( + final PwmRequest pwmRequest + ) + throws IOException, PwmUnrecoverableException + { + final HelpdeskSendVerificationTokenRequest helpdeskVerificationRequest = helpdeskDetailActionReader( + pwmRequest, + HelpdeskSendVerificationTokenRequest.class, + HelpdeskDetailButton.verification ); + + HelpdeskServletUtil.sendVerificationTokenRequestImpl( pwmRequest, helpdeskVerificationRequest ); + + return ProcessStatus.Halt; + } + + @ActionHandler( action = "verifyVerificationToken" ) + public ProcessStatus restVerifyVerificationTokenRequest( + final PwmRequest pwmRequest + ) + throws IOException, PwmUnrecoverableException + { + final HelpdeskVerificationRequest helpdeskVerificationRequest = helpdeskDetailActionReader( + pwmRequest, + HelpdeskVerificationRequest.class, + HelpdeskDetailButton.verification ); + + return HelpdeskServletUtil.verifyVerificationTokenRequestImpl( pwmRequest, helpdeskVerificationRequest ); + } + + @ActionHandler( action = "validateAttributes" ) + public ProcessStatus restValidateAttributes( final PwmRequest pwmRequest ) + throws IOException, PwmUnrecoverableException + { + final HelpdeskVerificationRequest actionRequest = helpdeskDetailActionReader( + pwmRequest, + HelpdeskVerificationRequest.class, + HelpdeskDetailButton.verification ); + + return HelpdeskServletUtil.validateAttributesImpl( pwmRequest, actionRequest ); + } +} diff --git a/server/src/main/java/password/pwm/http/servlet/helpdesk/HelpdeskVerificationResponseBean.java b/server/src/main/java/password/pwm/http/servlet/helpdesk/HelpdeskDetailServletUtil.java similarity index 84% rename from server/src/main/java/password/pwm/http/servlet/helpdesk/HelpdeskVerificationResponseBean.java rename to server/src/main/java/password/pwm/http/servlet/helpdesk/HelpdeskDetailServletUtil.java index bd4d316591..f88824d88d 100644 --- a/server/src/main/java/password/pwm/http/servlet/helpdesk/HelpdeskVerificationResponseBean.java +++ b/server/src/main/java/password/pwm/http/servlet/helpdesk/HelpdeskDetailServletUtil.java @@ -20,11 +20,9 @@ package password.pwm.http.servlet.helpdesk; -import lombok.Value; - -@Value -public class HelpdeskVerificationResponseBean +public final class HelpdeskDetailServletUtil { - private boolean passed; - private String verificationState; + private HelpdeskDetailServletUtil() + { + } } diff --git a/client/angular/src/models/search-result.model.ts b/server/src/main/java/password/pwm/http/servlet/helpdesk/HelpdeskPasswordOperationRequest.java similarity index 69% rename from client/angular/src/models/search-result.model.ts rename to server/src/main/java/password/pwm/http/servlet/helpdesk/HelpdeskPasswordOperationRequest.java index 3d5a4b9abd..90dd780988 100644 --- a/client/angular/src/models/search-result.model.ts +++ b/server/src/main/java/password/pwm/http/servlet/helpdesk/HelpdeskPasswordOperationRequest.java @@ -18,15 +18,17 @@ * limitations under the License. */ +package password.pwm.http.servlet.helpdesk; -import { IPerson } from './person.model'; +import password.pwm.http.servlet.helpdesk.data.HelpdeskTargetUserRequest; -export default class SearchResult { - sizeExceeded: boolean; - people: IPerson[]; +record HelpdeskPasswordOperationRequest( + String userKey, + String verificationState, + String password1, + String password2 +) + implements HelpdeskTargetUserRequest - constructor(options: any) { - this.sizeExceeded = options.sizeExceeded; - this.people = options.searchResults.map((person: any) => (person)); - } +{ } diff --git a/server/src/main/java/password/pwm/http/servlet/helpdesk/HelpdeskSendVerificationTokenRequest.java b/server/src/main/java/password/pwm/http/servlet/helpdesk/HelpdeskSendVerificationTokenRequest.java new file mode 100644 index 0000000000..4cd89413cc --- /dev/null +++ b/server/src/main/java/password/pwm/http/servlet/helpdesk/HelpdeskSendVerificationTokenRequest.java @@ -0,0 +1,32 @@ +/* + * Password Management Servlets (PWM) + * http://www.pwm-project.org + * + * Copyright (c) 2006-2009 Novell, Inc. + * Copyright (c) 2009-2021 The PWM Project + * + * 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. + */ + +package password.pwm.http.servlet.helpdesk; + +import password.pwm.http.servlet.helpdesk.data.HelpdeskTargetUserRequest; + +record HelpdeskSendVerificationTokenRequest( + String userKey, + String verificationState, + String id +) + implements HelpdeskTargetUserRequest +{ +} diff --git a/server/src/main/java/password/pwm/http/servlet/helpdesk/HelpdeskServlet.java b/server/src/main/java/password/pwm/http/servlet/helpdesk/HelpdeskServlet.java index e74c728d7c..77c20986af 100644 --- a/server/src/main/java/password/pwm/http/servlet/helpdesk/HelpdeskServlet.java +++ b/server/src/main/java/password/pwm/http/servlet/helpdesk/HelpdeskServlet.java @@ -20,34 +20,17 @@ package password.pwm.http.servlet.helpdesk; -import com.novell.ldapchai.ChaiUser; -import com.novell.ldapchai.exception.ChaiError; -import com.novell.ldapchai.exception.ChaiException; import com.novell.ldapchai.exception.ChaiOperationException; -import com.novell.ldapchai.exception.ChaiPasswordPolicyException; import com.novell.ldapchai.exception.ChaiUnavailableException; -import com.novell.ldapchai.provider.ChaiProvider; -import password.pwm.AppProperty; import password.pwm.PwmConstants; import password.pwm.PwmDomain; -import password.pwm.bean.EmailItemBean; import password.pwm.bean.PhotoDataBean; -import password.pwm.bean.TokenDestinationItem; import password.pwm.bean.UserIdentity; -import password.pwm.config.DomainConfig; import password.pwm.config.PwmSetting; -import password.pwm.config.option.HelpdeskClearResponseMode; -import password.pwm.config.option.HelpdeskUIMode; -import password.pwm.config.option.IdentityVerificationMethod; -import password.pwm.config.option.MessageSendMethod; import password.pwm.config.profile.HelpdeskProfile; -import password.pwm.config.profile.PwmPasswordPolicy; -import password.pwm.config.value.data.ActionConfiguration; import password.pwm.config.value.data.FormConfiguration; import password.pwm.error.ErrorInformation; import password.pwm.error.PwmError; -import password.pwm.error.PwmException; -import password.pwm.error.PwmOperationalException; import password.pwm.error.PwmUnrecoverableException; import password.pwm.http.HttpMethod; import password.pwm.http.JspUrl; @@ -55,68 +38,41 @@ import password.pwm.http.PwmHttpRequestWrapper; import password.pwm.http.PwmRequest; import password.pwm.http.PwmRequestAttribute; -import password.pwm.http.PwmSession; import password.pwm.http.servlet.AbstractPwmServlet; import password.pwm.http.servlet.ControlledPwmServlet; +import password.pwm.http.servlet.helpdesk.data.HelpdeskCheckVerificationRequest; +import password.pwm.http.servlet.helpdesk.data.HelpdeskCheckVerificationResponse; +import password.pwm.http.servlet.helpdesk.data.HelpdeskSearchRequest; +import password.pwm.http.servlet.helpdesk.data.HelpdeskSearchResponse; import password.pwm.http.servlet.peoplesearch.PhotoDataReader; import password.pwm.http.servlet.peoplesearch.SearchRequestBean; -import password.pwm.i18n.Message; -import password.pwm.ldap.UserInfoFactory; import password.pwm.ldap.search.SearchConfiguration; import password.pwm.ldap.search.UserSearchResults; import password.pwm.ldap.search.UserSearchService; -import password.pwm.svc.cr.CrService; -import password.pwm.svc.event.AuditEvent; -import password.pwm.svc.event.AuditRecordFactory; -import password.pwm.svc.event.AuditServiceClient; -import password.pwm.svc.event.HelpdeskAuditRecord; -import password.pwm.svc.intruder.IntruderServiceClient; -import password.pwm.svc.otp.OTPUserRecord; -import password.pwm.svc.otp.OtpService; -import password.pwm.svc.secure.DomainSecureService; -import password.pwm.svc.stats.Statistic; -import password.pwm.svc.stats.StatisticsClient; -import password.pwm.svc.token.TokenService; -import password.pwm.svc.token.TokenUtil; -import password.pwm.user.UserInfo; -import password.pwm.util.PasswordData; +import password.pwm.svc.cache.CacheKey; +import password.pwm.svc.cache.CachePolicy; +import password.pwm.svc.cache.CacheService; import password.pwm.util.java.CollectionUtil; -import password.pwm.util.java.JavaHelper; -import password.pwm.util.java.PwmTimeUtil; +import password.pwm.util.java.EnumUtil; import password.pwm.util.java.PwmUtil; +import password.pwm.util.java.StatisticCounterBundle; import password.pwm.util.java.StringUtil; import password.pwm.util.java.TimeDuration; import password.pwm.util.json.JsonFactory; import password.pwm.util.logging.PwmLogger; -import password.pwm.util.macro.MacroRequest; -import password.pwm.util.operations.ActionExecutor; -import password.pwm.util.password.PasswordUtility; -import password.pwm.util.password.RandomGeneratorConfig; import password.pwm.ws.server.RestResultBean; -import password.pwm.ws.server.rest.RestCheckPasswordServer; -import password.pwm.ws.server.rest.RestRandomPasswordServer; -import password.pwm.ws.server.rest.RestSetPasswordServer; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import java.io.IOException; -import java.time.Instant; import java.util.Collection; import java.util.Collections; -import java.util.HashMap; -import java.util.LinkedHashMap; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Optional; import java.util.concurrent.Callable; -/** - * Admin interaction servlet for reset user passwords. - * - * @author BoAnSen - */ - @WebServlet( name = "HelpdeskServlet", urlPatterns = { @@ -128,6 +84,8 @@ public class HelpdeskServlet extends ControlledPwmServlet { private static final PwmLogger LOGGER = PwmLogger.forClass( HelpdeskServlet.class ); + private static final StatisticCounterBundle ACTION_STATS = new StatisticCounterBundle<>( HelpdeskAction.class ); + @Override protected PwmLogger getLogger() { @@ -136,23 +94,14 @@ protected PwmLogger getLogger() public enum HelpdeskAction implements AbstractPwmServlet.ProcessAction { - unlockIntruder( HttpMethod.POST ), - clearOtpSecret( HttpMethod.POST ), + helpdeskClientData( HttpMethod.GET ), search( HttpMethod.POST ), - detail( HttpMethod.POST ), - executeAction( HttpMethod.POST ), - deleteUser( HttpMethod.POST ), validateOtpCode( HttpMethod.POST ), sendVerificationToken( HttpMethod.POST ), verifyVerificationToken( HttpMethod.POST ), - clientData( HttpMethod.GET ), checkVerification( HttpMethod.POST ), showVerifications( HttpMethod.POST ), validateAttributes( HttpMethod.POST ), - clearResponses( HttpMethod.POST ), - checkPassword( HttpMethod.POST ), - setPassword( HttpMethod.POST ), - randomPassword( HttpMethod.POST ), photo( HttpMethod.GET ), card( HttpMethod.POST ),; @@ -177,7 +126,7 @@ public Optional> getProcessActionsClass( ) } - private HelpdeskProfile getHelpdeskProfile( final PwmRequest pwmRequest ) throws PwmUnrecoverableException + static HelpdeskProfile getHelpdeskProfile( final PwmRequest pwmRequest ) throws PwmUnrecoverableException { return pwmRequest.getHelpdeskProfile( ); } @@ -185,10 +134,17 @@ private HelpdeskProfile getHelpdeskProfile( final PwmRequest pwmRequest ) throws @Override protected void nextStep( final PwmRequest pwmRequest ) throws PwmUnrecoverableException, IOException, ChaiUnavailableException, ServletException + { + forwardToSearchJsp( pwmRequest ); + } + + private static ProcessStatus forwardToSearchJsp( final PwmRequest pwmRequest ) + throws PwmUnrecoverableException, ServletException, IOException { final HelpdeskProfile helpdeskProfile = getHelpdeskProfile( pwmRequest ); pwmRequest.setAttribute( PwmRequestAttribute.HelpdeskVerificationEnabled, !helpdeskProfile.readRequiredVerificationMethods().isEmpty() ); pwmRequest.forwardToJsp( JspUrl.HELPDESK_SEARCH ); + return ProcessStatus.Halt; } @Override @@ -221,209 +177,25 @@ public ProcessStatus preProcessCheck( final PwmRequest pwmRequest ) throws PwmUn // verify the chaiProvider is available - ie, password is supplied, proxy available etc. // we do this now so redirects can handle properly instead of during a later rest request. final UserIdentity loggedInUser = pwmRequest.getPwmSession().getUserInfo().getUserIdentity(); - HelpdeskServletUtil.getChaiUser( pwmRequest, helpdeskProfile, loggedInUser ).getChaiProvider(); - - return ProcessStatus.Continue; - } - - @ActionHandler( action = "clientData" ) - public ProcessStatus restClientData( final PwmRequest pwmRequest ) - throws IOException, PwmUnrecoverableException - { - final HelpdeskProfile helpdeskProfile = getHelpdeskProfile( pwmRequest ); - final HelpdeskClientDataBean returnValues = HelpdeskClientDataBean.fromConfig( helpdeskProfile, pwmRequest.getLocale() ); - - final RestResultBean restResultBean = RestResultBean.withData( returnValues, HelpdeskClientDataBean.class ); - LOGGER.trace( pwmRequest, () -> "returning clientData: " + JsonFactory.get().serialize( restResultBean ) ); - pwmRequest.outputJsonResult( restResultBean ); - return ProcessStatus.Halt; - } - - @ActionHandler( action = "executeAction" ) - public ProcessStatus processExecuteActionRequest( - final PwmRequest pwmRequest - ) - throws PwmUnrecoverableException, IOException, ServletException - { - final HelpdeskProfile helpdeskProfile = getHelpdeskProfile( pwmRequest ); - final String userKey = pwmRequest.readBodyAsJsonStringMap( PwmHttpRequestWrapper.Flag.BypassValidation ).get( PwmConstants.PARAM_USERKEY ); - if ( userKey == null || userKey.length() < 1 ) - { - final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_MISSING_PARAMETER, PwmConstants.PARAM_USERKEY + " parameter is missing" ); - setLastError( pwmRequest, errorInformation ); - pwmRequest.respondWithError( errorInformation, false ); - return ProcessStatus.Halt; - } - final UserIdentity targetUserIdentity = HelpdeskServletUtil.clarifyUserIdentity( pwmRequest, userKey ); - LOGGER.debug( pwmRequest, () -> "received executeAction request for user " + targetUserIdentity.toString() ); + HelpdeskServletUtil.getChaiUser( pwmRequest, loggedInUser ).getChaiProvider(); - final List actionConfigurations = helpdeskProfile.readSettingAsAction( PwmSetting.HELPDESK_ACTIONS ); - final String requestedName = pwmRequest.readParameterAsString( "name" ); - ActionConfiguration action = null; - for ( final ActionConfiguration loopAction : actionConfigurations ) - { - if ( requestedName != null && requestedName.equals( loopAction.getName() ) ) - { - action = loopAction; - break; - } - } - if ( action == null ) - { - final String errorMsg = "request to execute unknown action: " + requestedName; - final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_INTERNAL, errorMsg ); - LOGGER.debug( pwmRequest, errorInformation ); - final RestResultBean restResultBean = RestResultBean.fromError( errorInformation, pwmRequest ); - pwmRequest.outputJsonResult( restResultBean ); - return ProcessStatus.Halt; - } + EnumUtil.readEnumFromString( HelpdeskAction.class, pwmRequest.readParameterAsString( PwmConstants.PARAM_ACTION_REQUEST ) ) + .ifPresent( ACTION_STATS::increment ); - // check if user should be seen by actor - HelpdeskServletUtil.checkIfUserIdentityViewable( pwmRequest, helpdeskProfile, targetUserIdentity ); + System.out.println( ACTION_STATS.debugStats( PwmConstants.DEFAULT_LOCALE ) ); - try - { - final PwmSession pwmSession = pwmRequest.getPwmSession(); - - final ChaiUser chaiUser = HelpdeskServletUtil.getChaiUser( pwmRequest, helpdeskProfile, targetUserIdentity ); - final MacroRequest macroRequest = HelpdeskServletUtil.getTargetUserMacroRequest( pwmRequest, helpdeskProfile, targetUserIdentity ); - final ActionExecutor actionExecutor = new ActionExecutor.ActionExecutorSettings( pwmRequest.getPwmDomain(), chaiUser ) - .setExpandPwmMacros( true ) - .setMacroMachine( macroRequest ) - .createActionExecutor(); - - actionExecutor.executeAction( action, pwmRequest.getLabel() ); - - // mark the event log - { - final HelpdeskAuditRecord auditRecord = AuditRecordFactory.make( pwmRequest ).createHelpdeskAuditRecord( - AuditEvent.HELPDESK_ACTION, - pwmSession.getUserInfo().getUserIdentity(), - action.getName(), - targetUserIdentity, - pwmSession.getSessionStateBean().getSrcAddress(), - pwmSession.getSessionStateBean().getSrcHostname() - ); - - AuditServiceClient.submit( pwmRequest, auditRecord ); - } - final RestResultBean restResultBean = RestResultBean.forSuccessMessage( - pwmRequest.getLocale(), - pwmRequest.getDomainConfig(), - Message.Success_Action, - action.getName() ); - - pwmRequest.outputJsonResult( restResultBean ); - return ProcessStatus.Halt; - } - catch ( final PwmOperationalException e ) - { - LOGGER.error( pwmRequest, () -> e.getErrorInformation().toDebugStr() ); - final RestResultBean restResultBean = RestResultBean.fromError( e.getErrorInformation(), pwmRequest ); - pwmRequest.outputJsonResult( restResultBean ); - return ProcessStatus.Halt; - } - } - @ActionHandler( action = "deleteUser" ) - public ProcessStatus restDeleteUserRequest( - final PwmRequest pwmRequest - ) - throws ChaiUnavailableException, PwmUnrecoverableException, IOException, ServletException - { - final HelpdeskProfile helpdeskProfile = getHelpdeskProfile( pwmRequest ); - final PwmDomain pwmDomain = pwmRequest.getPwmDomain(); - final PwmSession pwmSession = pwmRequest.getPwmSession(); - - final String userKey = pwmRequest.readParameterAsString( PwmConstants.PARAM_USERKEY, PwmHttpRequestWrapper.Flag.BypassValidation ); - if ( userKey.length() < 1 ) - { - final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_MISSING_PARAMETER, PwmConstants.PARAM_USERKEY + " parameter is missing" ); - setLastError( pwmRequest, errorInformation ); - pwmRequest.respondWithError( errorInformation, false ); - return ProcessStatus.Halt; - } - - final UserIdentity userIdentity = HelpdeskServletUtil.clarifyUserIdentity( pwmRequest, userKey ); - LOGGER.info( pwmRequest, () -> "received deleteUser request by " + pwmSession.getUserInfo().getUserIdentity().toString() + " for user " + userIdentity.toString() ); - - // check if user should be seen by actor - HelpdeskServletUtil.checkIfUserIdentityViewable( pwmRequest, helpdeskProfile, userIdentity ); - - // read the userID for later logging. - String userID = null; - try - { - final UserInfo deletedUserInfo = UserInfoFactory.newUserInfoUsingProxy( - pwmRequest.getPwmApplication(), - pwmRequest.getLabel(), - userIdentity, - pwmRequest.getLocale() ); - userID = deletedUserInfo.getUsername(); - } - catch ( final PwmUnrecoverableException e ) - { - LOGGER.warn( pwmRequest, () -> "unable to read username of deleted user while creating audit record" ); - } - - // execute user delete operation - final ChaiProvider provider = helpdeskProfile.readSettingAsBoolean( PwmSetting.HELPDESK_USE_PROXY ) - ? pwmDomain.getProxyChaiProvider( pwmRequest.getLabel(), userIdentity.getLdapProfileID() ) - : pwmRequest.getClientConnectionHolder().getActor().getChaiProvider(); - - - try - { - provider.deleteEntry( userIdentity.getUserDN() ); - } - catch ( final ChaiOperationException e ) - { - final String errorMsg = "error while attempting to delete user " + userIdentity + ", error: " + e.getMessage(); - final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_INTERNAL, errorMsg ); - LOGGER.debug( pwmRequest, () -> errorMsg ); - pwmRequest.outputJsonResult( RestResultBean.fromError( errorInformation, pwmRequest ) ); - return ProcessStatus.Halt; - } - - // mark the event log - { - //normally the audit record builder reads the userID while constructing the record, but because the target user is already deleted, - //it will be included here explicitly. - final AuditRecordFactory.AuditUserDefinition auditUserDefinition = new AuditRecordFactory.AuditUserDefinition( - userID, - userIdentity.getUserDN(), - userIdentity.getLdapProfileID() - ); - final HelpdeskAuditRecord auditRecord = AuditRecordFactory.make( pwmRequest ).createHelpdeskAuditRecord( - AuditEvent.HELPDESK_DELETE_USER, - pwmSession.getUserInfo().getUserIdentity(), - null, - auditUserDefinition, - pwmSession.getSessionStateBean().getSrcAddress(), - pwmSession.getSessionStateBean().getSrcHostname() - ); - - AuditServiceClient.submit( pwmRequest, auditRecord ); - } - - LOGGER.info( pwmRequest, () -> "user " + userIdentity + " has been deleted" ); - - final RestResultBean restResultBean = RestResultBean.forSuccessMessage( pwmRequest, Message.Success_Unknown ); - pwmRequest.outputJsonResult( restResultBean ); - return ProcessStatus.Halt; + return ProcessStatus.Continue; } - @ActionHandler( action = "detail" ) - public ProcessStatus processDetailRequest( - final PwmRequest pwmRequest - ) - throws ChaiUnavailableException, PwmUnrecoverableException, IOException, ServletException + @ActionHandler( action = "helpdeskClientData" ) + public ProcessStatus restHelpdeskClientData( final PwmRequest pwmRequest ) + throws IOException, PwmUnrecoverableException { final HelpdeskProfile helpdeskProfile = getHelpdeskProfile( pwmRequest ); - final UserIdentity userIdentity = readUserKeyRequestParameter( pwmRequest ); - final HelpdeskDetailInfoBean helpdeskDetailInfoBean = HelpdeskServletUtil.processDetailRequestImpl( pwmRequest, helpdeskProfile, userIdentity ); + final HelpdeskClientData returnValues = HelpdeskClientData.fromConfig( helpdeskProfile, pwmRequest.getLocale() ); - final RestResultBean restResultBean = RestResultBean.withData( helpdeskDetailInfoBean, HelpdeskDetailInfoBean.class ); + final RestResultBean restResultBean = RestResultBean.withData( returnValues, HelpdeskClientData.class ); pwmRequest.outputJsonResult( restResultBean ); return ProcessStatus.Halt; } @@ -432,16 +204,16 @@ public ProcessStatus processDetailRequest( public ProcessStatus processCardRequest( final PwmRequest pwmRequest ) - throws ChaiUnavailableException, PwmUnrecoverableException, IOException, ServletException + throws PwmUnrecoverableException, IOException { final HelpdeskProfile helpdeskProfile = getHelpdeskProfile( pwmRequest ); final UserIdentity userIdentity = readUserKeyRequestParameter( pwmRequest ); - HelpdeskServletUtil.checkIfUserIdentityViewable( pwmRequest, helpdeskProfile, userIdentity ); + HelpdeskServletUtil.checkIfUserIdentityViewable( pwmRequest, userIdentity ); - final HelpdeskCardInfoBean helpdeskCardInfoBean = HelpdeskCardInfoBean.makeHelpdeskCardInfo( pwmRequest, helpdeskProfile, userIdentity ); + final HelpdeskUserCard helpdeskCardInfoBean = HelpdeskServletUtil.makeHelpdeskCardInfo( pwmRequest, helpdeskProfile, userIdentity ); - final RestResultBean restResultBean = RestResultBean.withData( helpdeskCardInfoBean, HelpdeskCardInfoBean.class ); + final RestResultBean restResultBean = RestResultBean.withData( helpdeskCardInfoBean, HelpdeskUserCard.class ); pwmRequest.outputJsonResult( restResultBean ); return ProcessStatus.Halt; } @@ -453,395 +225,110 @@ public ProcessStatus restSearchRequest( throws PwmUnrecoverableException, IOException { final HelpdeskProfile helpdeskProfile = getHelpdeskProfile( pwmRequest ); - final HelpdeskSearchRequestBean searchRequest = pwmRequest.readBodyAsJsonObject( HelpdeskSearchRequestBean.class ); - final HelpdeskSearchResultsBean searchResultsBean; + final HelpdeskSearchRequest searchRequest = pwmRequest.readBodyAsJsonObject( HelpdeskSearchRequest.class ); - try - { - searchResultsBean = searchImpl( pwmRequest, helpdeskProfile, searchRequest ); - } - catch ( final PwmOperationalException e ) - { - throw new PwmUnrecoverableException( e.getErrorInformation() ); - } + final CacheKey cacheKey = CacheKey.newKey( + HelpdeskServletUtil.class, + pwmRequest.getUserInfoIfLoggedIn(), + HelpdeskServlet.HelpdeskAction.search + JsonFactory.get().serialize( searchRequest ) ); + + final CachePolicy policy = CachePolicy.makePolicyWithExpiration( TimeDuration.ZERO ); + + final CacheService cacheService = pwmRequest.getPwmDomain().getCacheService(); + + final HelpdeskSearchResponse searchResultsBean = cacheService.get( + cacheKey, + policy, + HelpdeskSearchResponse.class, + () -> searchImpl( pwmRequest, helpdeskProfile, searchRequest ) ); - final RestResultBean restResultBean = RestResultBean.withData( searchResultsBean, HelpdeskSearchResultsBean.class ); + final RestResultBean restResultBean = RestResultBean.withData( searchResultsBean, HelpdeskSearchResponse.class ); pwmRequest.outputJsonResult( restResultBean ); return ProcessStatus.Halt; - } - private static HelpdeskSearchResultsBean searchImpl( + private static HelpdeskSearchResponse searchImpl( final PwmRequest pwmRequest, final HelpdeskProfile helpdeskProfile, - final HelpdeskSearchRequestBean searchRequest + final HelpdeskSearchRequest searchRequest ) - throws PwmUnrecoverableException, PwmOperationalException + throws PwmUnrecoverableException { - final boolean useProxy = helpdeskProfile.readSettingAsBoolean( PwmSetting.HELPDESK_USE_PROXY ); final List searchForm = helpdeskProfile.readSettingAsForm( PwmSetting.HELPDESK_SEARCH_RESULT_FORM ); final int maxResults = ( int ) helpdeskProfile.readSettingAsLong( PwmSetting.HELPDESK_RESULT_LIMIT ); - final SearchRequestBean.SearchMode searchMode = searchRequest.getMode() == null + final SearchRequestBean.SearchMode searchMode = searchRequest.mode() == null ? SearchRequestBean.SearchMode.simple - : searchRequest.getMode(); - - + : searchRequest.mode(); switch ( searchMode ) { - case simple: - { - if ( StringUtil.isEmpty( searchRequest.getUsername() ) ) - { - return HelpdeskSearchResultsBean.emptyResult(); - } - } - break; - - case advanced: - { - if ( CollectionUtil.isEmpty( searchRequest.nonEmptySearchValues() ) ) - { - return HelpdeskSearchResultsBean.emptyResult(); - } - } - break; - - - default: - PwmUtil.unhandledSwitchStatement( searchMode ); - } - - final UserSearchService userSearchService = pwmRequest.getPwmDomain().getUserSearchEngine(); - - - final SearchConfiguration searchConfiguration; - { - final SearchConfiguration.SearchConfigurationBuilder builder = SearchConfiguration.builder(); - builder.contexts( helpdeskProfile.readSettingAsStringArray( PwmSetting.HELPDESK_SEARCH_BASE ) ); - builder.enableContextValidation( false ); - builder.enableValueEscaping( true ); - builder.enableSplitWhitespace( true ); - - if ( !useProxy ) - { - final UserIdentity loggedInUser = pwmRequest.getPwmSession().getUserInfo().getUserIdentity(); - builder.ldapProfile( loggedInUser.getLdapProfileID() ); - builder.chaiProvider( HelpdeskServletUtil.getChaiUser( pwmRequest, helpdeskProfile, loggedInUser ).getChaiProvider() ); - } - - switch ( searchMode ) - { - case simple: - { - builder.username( searchRequest.getUsername() ); - builder.filter( HelpdeskServletUtil.makeAdvancedSearchFilter( pwmRequest.getDomainConfig(), helpdeskProfile ) ); - } - break; - - case advanced: - { - final Map formValues = new LinkedHashMap<>(); - final Map requestSearchValues = SearchRequestBean.searchValueToMap( searchRequest.getSearchValues() ); - for ( final FormConfiguration formConfiguration : helpdeskProfile.readSettingAsForm( PwmSetting.HELPDESK_SEARCH_FORM ) ) + case simple -> { - final String attribute = formConfiguration.getName(); - final String value = requestSearchValues.get( attribute ); - if ( StringUtil.notEmpty( value ) ) + if ( StringUtil.isEmpty( searchRequest.username() ) ) { - formValues.put( formConfiguration, value ); + return HelpdeskSearchResponse.emptyResult(); } } + case advanced -> + { + if ( CollectionUtil.isEmpty( searchRequest.searchValues() ) ) + { + return HelpdeskSearchResponse.emptyResult(); + } + } + default -> PwmUtil.unhandledSwitchStatement( searchMode ); + } - builder.formValues( formValues ); - builder.filter( HelpdeskServletUtil.makeAdvancedSearchFilter( pwmRequest.getDomainConfig(), helpdeskProfile, requestSearchValues ) ); - - } - break; - + final UserSearchService userSearchService = pwmRequest.getPwmDomain().getUserSearchEngine(); - default: - PwmUtil.unhandledSwitchStatement( searchMode ); - } + final SearchConfiguration searchConfiguration = HelpdeskServletUtil.makeSearchConfiguration( + pwmRequest, + searchRequest, + searchMode ); - searchConfiguration = builder.build(); - } - - final UserSearchResults results; - final boolean sizeExceeded; - { - final Locale locale = pwmRequest.getLocale(); - results = userSearchService.performMultiUserSearchFromForm( locale, searchConfiguration, maxResults, searchForm, pwmRequest.getLabel() ); - sizeExceeded = results.isSizeExceeded(); - } + final Locale locale = pwmRequest.getLocale(); + final UserSearchResults results = userSearchService.performMultiUserSearchFromForm( locale, searchConfiguration, maxResults, searchForm, pwmRequest.getLabel() ); + final boolean sizeExceeded = results.isSizeExceeded(); final List> jsonResults = results.resultsAsJsonOutput( userIdentity -> HelpdeskServletUtil.obfuscateUserIdentity( pwmRequest, userIdentity ), pwmRequest.getUserInfoIfLoggedIn() ); - return HelpdeskSearchResultsBean.builder() - .searchResults( jsonResults ) - .sizeExceeded( sizeExceeded ) - .build(); - } - - @ActionHandler( action = "unlockIntruder" ) - public ProcessStatus restUnlockIntruder( - final PwmRequest pwmRequest - ) - throws PwmUnrecoverableException, ChaiUnavailableException, IOException, ServletException - { - final HelpdeskProfile helpdeskProfile = getHelpdeskProfile( pwmRequest ); - final String userKey = pwmRequest.readParameterAsString( PwmConstants.PARAM_USERKEY, PwmHttpRequestWrapper.Flag.BypassValidation ); - if ( userKey.length() < 1 ) - { - final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_MISSING_PARAMETER, "userKey parameter is missing" ); - pwmRequest.respondWithError( errorInformation, false ); - return ProcessStatus.Halt; - } - final UserIdentity userIdentity = HelpdeskServletUtil.clarifyUserIdentity( pwmRequest, userKey ); - - if ( !helpdeskProfile.readSettingAsBoolean( PwmSetting.HELPDESK_ENABLE_UNLOCK ) ) - { - final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_UNAUTHORIZED, "password unlock request, but helpdesk unlock is not enabled" ); - LOGGER.error( pwmRequest, errorInformation ); - pwmRequest.respondWithError( errorInformation ); - return ProcessStatus.Halt; - } - - //clear pwm intruder setting. - IntruderServiceClient.clearUserIdentity( pwmRequest, userIdentity ); - - try - { - final ChaiUser chaiUser = HelpdeskServletUtil.getChaiUser( pwmRequest, helpdeskProfile, userIdentity ); - - // send notice email - HelpdeskServletUtil.sendUnlockNoticeEmail( pwmRequest, helpdeskProfile, userIdentity, chaiUser ); - - chaiUser.unlockPassword(); - { - // mark the event log - final HelpdeskAuditRecord auditRecord = AuditRecordFactory.make( pwmRequest ).createHelpdeskAuditRecord( - AuditEvent.HELPDESK_UNLOCK_PASSWORD, - pwmRequest.getPwmSession().getUserInfo().getUserIdentity(), - null, - userIdentity, - pwmRequest.getLabel().getSourceAddress(), - pwmRequest.getLabel().getSourceHostname() - ); - - AuditServiceClient.submit( pwmRequest, auditRecord ); - } - } - catch ( final ChaiPasswordPolicyException e ) - { - final ChaiError passwordError = e.getErrorCode(); - final PwmError pwmError = PwmError.forChaiError( passwordError ).orElse( PwmError.PASSWORD_UNKNOWN_VALIDATION ); - pwmRequest.respondWithError( new ErrorInformation( pwmError ) ); - LOGGER.trace( pwmRequest, () -> "ChaiPasswordPolicyException was thrown while resetting password: " + e.getMessage() ); - return ProcessStatus.Halt; - } - catch ( final ChaiOperationException e ) - { - final PwmError returnMsg = PwmError.forChaiError( e.getErrorCode() ).orElse( PwmError.ERROR_INTERNAL ); - final ErrorInformation error = new ErrorInformation( returnMsg, e.getMessage() ); - pwmRequest.respondWithError( error ); - LOGGER.warn( pwmRequest, () -> "error resetting password for user '" + userIdentity.toDisplayString() + "'' " + error.toDebugStr() + ", " + e.getMessage() ); - return ProcessStatus.Halt; - } - - final RestResultBean restResultBean = RestResultBean.forSuccessMessage( pwmRequest, Message.Success_Unknown ); - pwmRequest.outputJsonResult( restResultBean ); - return ProcessStatus.Halt; + return new HelpdeskSearchResponse( jsonResults, sizeExceeded ); } @ActionHandler( action = "validateOtpCode" ) public ProcessStatus restValidateOtpCodeRequest( final PwmRequest pwmRequest ) - throws IOException, PwmUnrecoverableException, ServletException, ChaiUnavailableException + throws IOException, PwmUnrecoverableException, ServletException { - final HelpdeskProfile helpdeskProfile = getHelpdeskProfile( pwmRequest ); - - final HelpdeskVerificationRequestBean helpdeskVerificationRequestBean = JsonFactory.get().deserialize( + final HelpdeskVerificationRequest helpdeskVerificationRequest = JsonFactory.get().deserialize( pwmRequest.readRequestBodyAsString(), - HelpdeskVerificationRequestBean.class + HelpdeskVerificationRequest.class ); - final String userKey = helpdeskVerificationRequestBean.getUserKey(); - if ( userKey == null || userKey.isEmpty() ) - { - final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_MISSING_PARAMETER, "userKey parameter is missing" ); - pwmRequest.respondWithError( errorInformation, false ); - return ProcessStatus.Halt; - } - final UserIdentity userIdentity = HelpdeskServletUtil.clarifyUserIdentity( pwmRequest, userKey ); - if ( !helpdeskProfile.readOptionalVerificationMethods().contains( IdentityVerificationMethod.OTP ) ) - { - final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_UNAUTHORIZED, "password otp verification request, but otp verify is not enabled" ); - LOGGER.error( pwmRequest, errorInformation ); - pwmRequest.respondWithError( errorInformation ); - return ProcessStatus.Halt; - } - - final String code = helpdeskVerificationRequestBean.getCode(); - final OTPUserRecord otpUserRecord = pwmRequest.getPwmDomain().getOtpService().readOTPUserConfiguration( pwmRequest.getLabel(), userIdentity ); - try - { - final boolean passed = pwmRequest.getPwmDomain().getOtpService().validateToken( - pwmRequest.getLabel(), - userIdentity, - otpUserRecord, - code, - false - ); - - final HelpdeskVerificationStateBean verificationStateBean = HelpdeskVerificationStateBean.fromClientString( - pwmRequest, - helpdeskVerificationRequestBean.getVerificationState() - ); - - if ( passed ) - { - final PwmSession pwmSession = pwmRequest.getPwmSession(); - final HelpdeskAuditRecord auditRecord = AuditRecordFactory.make( pwmRequest ).createHelpdeskAuditRecord( - AuditEvent.HELPDESK_VERIFY_OTP, - pwmSession.getUserInfo().getUserIdentity(), - null, - userIdentity, - pwmSession.getSessionStateBean().getSrcAddress(), - pwmSession.getSessionStateBean().getSrcHostname() - ); - AuditServiceClient.submit( pwmRequest, auditRecord ); - - StatisticsClient.incrementStat( pwmRequest, Statistic.HELPDESK_VERIFY_OTP ); - verificationStateBean.addRecord( userIdentity, IdentityVerificationMethod.OTP ); - } - else - { - final PwmSession pwmSession = pwmRequest.getPwmSession(); - final HelpdeskAuditRecord auditRecord = AuditRecordFactory.make( pwmRequest ).createHelpdeskAuditRecord( - AuditEvent.HELPDESK_VERIFY_OTP_INCORRECT, - pwmSession.getUserInfo().getUserIdentity(), - null, - userIdentity, - pwmSession.getSessionStateBean().getSrcAddress(), - pwmSession.getSessionStateBean().getSrcHostname() - ); - AuditServiceClient.submit( pwmRequest, auditRecord ); - } - - return outputVerificationResponseBean( pwmRequest, passed, verificationStateBean ); - } - catch ( final PwmOperationalException e ) - { - pwmRequest.outputJsonResult( RestResultBean.fromError( e.getErrorInformation(), pwmRequest ) ); - } - return ProcessStatus.Halt; + return HelpdeskServletUtil.validateOtpCodeImpl( pwmRequest, helpdeskVerificationRequest ); } @ActionHandler( action = "sendVerificationToken" ) public ProcessStatus restSendVerificationTokenRequest( final PwmRequest pwmRequest ) - throws IOException, PwmUnrecoverableException, ChaiUnavailableException + throws IOException, PwmUnrecoverableException { - final HelpdeskProfile helpdeskProfile = getHelpdeskProfile( pwmRequest ); - - final Instant startTime = Instant.now(); - final DomainConfig config = pwmRequest.getDomainConfig(); - final Map bodyParams = pwmRequest.readBodyAsJsonStringMap(); - - final UserIdentity targetUserIdentity = HelpdeskServletUtil.clarifyUserIdentity( pwmRequest, bodyParams.get( PwmConstants.PARAM_USERKEY ) ); - final UserInfo targetUserInfo = HelpdeskServletUtil.getTargetUserInfo( pwmRequest, helpdeskProfile, targetUserIdentity ); - - final String requestedTokenID = bodyParams.get( "id" ); - - final TokenDestinationItem tokenDestinationItem; - { - final List items = TokenUtil.figureAvailableTokenDestinations( - pwmRequest.getPwmDomain(), - pwmRequest.getLabel(), - pwmRequest.getLocale(), - targetUserInfo, - MessageSendMethod.CHOICE_SMS_EMAIL ); - - final Optional selectedTokenDest = TokenDestinationItem.tokenDestinationItemForID( items, requestedTokenID ); - - if ( selectedTokenDest.isPresent() ) - { - tokenDestinationItem = selectedTokenDest.get(); - } - else - { - throw PwmUnrecoverableException.newException( PwmError.ERROR_INTERNAL, "unknown token id '" + requestedTokenID + "' in request" ); - } - } - - final HelpdeskDetailInfoBean helpdeskDetailInfoBean = HelpdeskDetailInfoBean.makeHelpdeskDetailInfo( pwmRequest, helpdeskProfile, targetUserIdentity ); - if ( helpdeskDetailInfoBean == null ) - { - final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_MISSING_PARAMETER, "unable to read helpdesk detail data for specified user" ); - LOGGER.error( pwmRequest, errorInformation ); - pwmRequest.outputJsonResult( RestResultBean.fromError( errorInformation, pwmRequest ) ); - return ProcessStatus.Halt; - } - final MacroRequest macroRequest = HelpdeskServletUtil.getTargetUserMacroRequest( pwmRequest, helpdeskProfile, targetUserIdentity ); - final String configuredTokenString = config.readAppProperty( AppProperty.HELPDESK_TOKEN_VALUE ); - final String tokenKey = macroRequest.expandMacros( configuredTokenString ); - final EmailItemBean emailItemBean = config.readSettingAsEmail( PwmSetting.EMAIL_HELPDESK_TOKEN, pwmRequest.getLocale() ); - - LOGGER.debug( pwmRequest, () -> "generated token code for " + targetUserIdentity.toDelimitedKey() ); - - final String smsMessage = config.readSettingAsLocalizedString( PwmSetting.SMS_HELPDESK_TOKEN_TEXT, pwmRequest.getLocale() ); - - try - { - TokenService.TokenSender.sendToken( - TokenService.TokenSendInfo.builder() - .pwmDomain( pwmRequest.getPwmDomain() ) - .userInfo( targetUserInfo ) - .macroRequest( macroRequest ) - .configuredEmailSetting( emailItemBean ) - .tokenDestinationItem( tokenDestinationItem ) - .smsMessage( smsMessage ) - .tokenKey( tokenKey ) - .sessionLabel( pwmRequest.getLabel() ) - .build() - ); - } - catch ( final PwmException e ) - { - LOGGER.error( pwmRequest, e.getErrorInformation() ); - pwmRequest.outputJsonResult( RestResultBean.fromError( e.getErrorInformation(), pwmRequest ) ); - return ProcessStatus.Halt; - } - - StatisticsClient.incrementStat( pwmRequest, Statistic.HELPDESK_TOKENS_SENT ); - final HelpdeskVerificationRequestBean helpdeskVerificationRequestBean = new HelpdeskVerificationRequestBean(); - helpdeskVerificationRequestBean.setDestination( tokenDestinationItem.getDisplay() ); - helpdeskVerificationRequestBean.setUserKey( bodyParams.get( PwmConstants.PARAM_USERKEY ) ); + final HelpdeskSendVerificationTokenRequest helpdeskVerificationRequest = JsonFactory.get().deserialize( + pwmRequest.readRequestBodyAsString(), + HelpdeskSendVerificationTokenRequest.class + ); - final HelpdeskVerificationRequestBean.TokenData tokenData = new HelpdeskVerificationRequestBean.TokenData(); - tokenData.setToken( tokenKey ); - tokenData.setIssueDate( Instant.now() ); + HelpdeskServletUtil.checkIfUserIdentityViewable( pwmRequest, helpdeskVerificationRequest.readTargetUser( pwmRequest ) ); - final DomainSecureService domainSecureService = pwmRequest.getPwmDomain().getSecureService(); - helpdeskVerificationRequestBean.setTokenData( domainSecureService.encryptObjectToString( tokenData ) ); + HelpdeskServletUtil.sendVerificationTokenRequestImpl( pwmRequest, helpdeskVerificationRequest ); - final RestResultBean restResultBean = RestResultBean.withData( helpdeskVerificationRequestBean, HelpdeskVerificationRequestBean.class ); - pwmRequest.outputJsonResult( restResultBean ); - LOGGER.debug( pwmRequest, () -> "helpdesk operator " - + pwmRequest.getUserInfoIfLoggedIn().toDisplayString() - + " issued token for verification against user " - + targetUserIdentity.toDisplayString() - + " sent to destination(s) " - + tokenDestinationItem.getDisplay() - + " (" + TimeDuration.fromCurrent( startTime ).asCompactString() + ")" ); return ProcessStatus.Halt; } @@ -849,499 +336,67 @@ public ProcessStatus restSendVerificationTokenRequest( public ProcessStatus restVerifyVerificationTokenRequest( final PwmRequest pwmRequest ) - throws IOException, PwmUnrecoverableException, ServletException - { - final HelpdeskVerificationRequestBean helpdeskVerificationRequestBean = - pwmRequest.readBodyAsJsonObject( HelpdeskVerificationRequestBean.class ); - final String token = helpdeskVerificationRequestBean.getCode(); - - final DomainSecureService domainSecureService = pwmRequest.getPwmDomain().getSecureService(); - final HelpdeskVerificationRequestBean.TokenData tokenData = domainSecureService.decryptObject( - helpdeskVerificationRequestBean.getTokenData(), - HelpdeskVerificationRequestBean.TokenData.class - ); - - final UserIdentity userIdentity = HelpdeskServletUtil.clarifyUserIdentity( - pwmRequest, helpdeskVerificationRequestBean.getUserKey() ); - - if ( tokenData == null || tokenData.getIssueDate() == null || tokenData.getToken() == null || tokenData.getToken().isEmpty() ) - { - final String errorMsg = "token data is corrupted"; - throw new PwmUnrecoverableException( new ErrorInformation( PwmError.ERROR_TOKEN_INCORRECT, errorMsg ) ); - } - - final TimeDuration maxTokenAge = TimeDuration.of( - Long.parseLong( pwmRequest.getDomainConfig().readAppProperty( AppProperty.HELPDESK_TOKEN_MAX_AGE ) ), - TimeDuration.Unit.SECONDS - ); - final Instant maxTokenAgeTimestamp = Instant.ofEpochMilli( System.currentTimeMillis() - maxTokenAge.asMillis() ); - if ( tokenData.getIssueDate().isBefore( maxTokenAgeTimestamp ) ) - { - final String errorMsg = "token is older than maximum issue time (" + maxTokenAge.asCompactString() + ")"; - throw new PwmUnrecoverableException( new ErrorInformation( PwmError.ERROR_TOKEN_EXPIRED, errorMsg ) ); - } - - final boolean passed = tokenData.getToken().equals( token ); - - final HelpdeskVerificationStateBean verificationStateBean = HelpdeskVerificationStateBean.fromClientString( - pwmRequest, - helpdeskVerificationRequestBean.getVerificationState() - ); - - if ( passed ) - { - final PwmSession pwmSession = pwmRequest.getPwmSession(); - final HelpdeskAuditRecord auditRecord = AuditRecordFactory.make( pwmRequest ).createHelpdeskAuditRecord( - AuditEvent.HELPDESK_VERIFY_TOKEN, - pwmSession.getUserInfo().getUserIdentity(), - null, - userIdentity, - pwmSession.getSessionStateBean().getSrcAddress(), - pwmSession.getSessionStateBean().getSrcHostname() - ); - AuditServiceClient.submit( pwmRequest, auditRecord ); - verificationStateBean.addRecord( userIdentity, IdentityVerificationMethod.TOKEN ); - } - else - { - final PwmSession pwmSession = pwmRequest.getPwmSession(); - final HelpdeskAuditRecord auditRecord = AuditRecordFactory.make( pwmRequest ).createHelpdeskAuditRecord( - AuditEvent.HELPDESK_VERIFY_TOKEN_INCORRECT, - pwmSession.getUserInfo().getUserIdentity(), - null, - userIdentity, - pwmSession.getSessionStateBean().getSrcAddress(), - pwmSession.getSessionStateBean().getSrcHostname() - ); - AuditServiceClient.submit( pwmRequest, auditRecord ); - } - - return outputVerificationResponseBean( pwmRequest, passed, verificationStateBean ); - } - - @ActionHandler( action = "clearOtpSecret" ) - public ProcessStatus restClearOtpSecret( - final PwmRequest pwmRequest - ) - throws ServletException, IOException, PwmUnrecoverableException, ChaiUnavailableException + throws IOException, PwmUnrecoverableException { - final HelpdeskProfile helpdeskProfile = getHelpdeskProfile( pwmRequest ); - - final Map bodyMap = pwmRequest.readBodyAsJsonStringMap( PwmHttpRequestWrapper.Flag.BypassValidation ); - final UserIdentity userIdentity = HelpdeskServletUtil.userIdentityFromMap( pwmRequest, bodyMap ); - - if ( !helpdeskProfile.readSettingAsBoolean( PwmSetting.HELPDESK_CLEAR_OTP_BUTTON ) ) - { - final String errorMsg = "clear otp request, but helpdesk clear otp button is not enabled"; - final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_SERVICE_NOT_AVAILABLE, errorMsg ); - LOGGER.error( pwmRequest, () -> errorMsg ); - pwmRequest.respondWithError( errorInformation ); - return ProcessStatus.Halt; - } - - //clear pwm intruder setting. - IntruderServiceClient.clearUserIdentity( pwmRequest, userIdentity ); + final HelpdeskVerificationRequest helpdeskVerificationRequest = + pwmRequest.readBodyAsJsonObject( HelpdeskVerificationRequest.class ); - try - { + HelpdeskServletUtil.checkIfUserIdentityViewable( pwmRequest, helpdeskVerificationRequest.readTargetUser( pwmRequest ) ); - final OtpService service = pwmRequest.getPwmDomain().getOtpService(); - final ChaiUser chaiUser = HelpdeskServletUtil.getChaiUser( pwmRequest, helpdeskProfile, userIdentity ); - service.clearOTPUserConfiguration( pwmRequest, userIdentity, chaiUser ); - { - // mark the event log - final HelpdeskAuditRecord auditRecord = AuditRecordFactory.make( pwmRequest ).createHelpdeskAuditRecord( - AuditEvent.HELPDESK_CLEAR_OTP_SECRET, - pwmRequest.getPwmSession().getUserInfo().getUserIdentity(), - null, - userIdentity, - pwmRequest.getLabel().getSourceAddress(), - pwmRequest.getLabel().getSourceHostname() - ); - AuditServiceClient.submit( pwmRequest, auditRecord ); - } - } - catch ( final PwmOperationalException e ) - { - final PwmError returnMsg = e.getError(); - final ErrorInformation error = new ErrorInformation( returnMsg, e.getMessage() ); - pwmRequest.respondWithError( error ); - LOGGER.warn( pwmRequest, () -> "error clearing OTP secret for user '" + userIdentity + "'' " + error.toDebugStr() + ", " + e.getMessage() ); - return ProcessStatus.Halt; - } - - final RestResultBean restResultBean = RestResultBean.forSuccessMessage( pwmRequest, Message.Success_Unknown ); - pwmRequest.outputJsonResult( restResultBean ); - return ProcessStatus.Halt; + return HelpdeskServletUtil.verifyVerificationTokenRequestImpl( pwmRequest, helpdeskVerificationRequest ); } @ActionHandler( action = "checkVerification" ) public ProcessStatus restCheckVerification( final PwmRequest pwmRequest ) throws IOException, PwmUnrecoverableException, ServletException { - final HelpdeskProfile helpdeskProfile = getHelpdeskProfile( pwmRequest ); - final Map bodyMap = pwmRequest.readBodyAsJsonStringMap( PwmHttpRequestWrapper.Flag.BypassValidation ); - - final UserIdentity userIdentity = HelpdeskServletUtil.userIdentityFromMap( pwmRequest, bodyMap ); - HelpdeskServletUtil.checkIfUserIdentityViewable( pwmRequest, helpdeskProfile, userIdentity ); - - final String rawVerificationStr = bodyMap.get( HelpdeskVerificationStateBean.PARAMETER_VERIFICATION_STATE_KEY ); - final HelpdeskVerificationStateBean state = HelpdeskVerificationStateBean.fromClientString( pwmRequest, rawVerificationStr ); - final boolean passed = HelpdeskServletUtil.checkIfRequiredVerificationPassed( userIdentity, state, helpdeskProfile ); - final HelpdeskVerificationOptionsBean optionsBean = HelpdeskVerificationOptionsBean.makeBean( pwmRequest, helpdeskProfile, userIdentity ); - final HashMap results = new HashMap<>(); - results.put( "passed", passed ); - results.put( "verificationOptions", optionsBean ); - final RestResultBean restResultBean = RestResultBean.withData( results, Map.class ); - pwmRequest.outputJsonResult( restResultBean ); - return ProcessStatus.Halt; - } - - - @ActionHandler( action = "showVerifications" ) - public ProcessStatus restShowVerifications( final PwmRequest pwmRequest ) - throws IOException, PwmUnrecoverableException - { - final Map bodyMap = pwmRequest.readBodyAsJsonStringMap( PwmHttpRequestWrapper.Flag.BypassValidation ); - final String rawVerificationStr = bodyMap.get( HelpdeskVerificationStateBean.PARAMETER_VERIFICATION_STATE_KEY ); - final HelpdeskVerificationStateBean state = HelpdeskVerificationStateBean.fromClientString( pwmRequest, rawVerificationStr ); - final HashMap results = new HashMap<>(); - try - { - results.put( "records", state.asViewableValidationRecords( pwmRequest.getPwmRequestContext() ) ); - } - catch ( final ChaiOperationException e ) - { - throw PwmUnrecoverableException.fromChaiException( e ); - } - final RestResultBean restResultBean = RestResultBean.withData( results, Map.class ); - pwmRequest.outputJsonResult( restResultBean ); - return ProcessStatus.Halt; - } - - @ActionHandler( action = "validateAttributes" ) - public ProcessStatus restValidateAttributes( final PwmRequest pwmRequest ) - throws IOException, PwmUnrecoverableException, ServletException - { - final HelpdeskProfile helpdeskProfile = getHelpdeskProfile( pwmRequest ); - final String bodyString = pwmRequest.readRequestBodyAsString(); - final HelpdeskVerificationRequestBean helpdeskVerificationRequestBean = JsonFactory.get().deserialize( - bodyString, - HelpdeskVerificationRequestBean.class - ); - - final UserIdentity userIdentity = HelpdeskServletUtil.clarifyUserIdentity( pwmRequest, - helpdeskVerificationRequestBean.getUserKey() ); - - boolean passed = false; - { - final List verificationForms = helpdeskProfile.readSettingAsForm( PwmSetting.HELPDESK_VERIFICATION_FORM ); - if ( verificationForms == null || verificationForms.isEmpty() ) - { - final ErrorInformation errorInformation = new ErrorInformation( - PwmError.ERROR_INVALID_CONFIG, - "attempt to verify ldap attributes with no ldap verification attributes configured" - ); - throw new PwmUnrecoverableException( errorInformation ); - } - - final Map bodyMap = JsonFactory.get().deserializeStringMap( bodyString ); - final ChaiUser chaiUser = HelpdeskServletUtil.getChaiUser( pwmRequest, helpdeskProfile, userIdentity ); - - int successCount = 0; - for ( final FormConfiguration formConfiguration : verificationForms ) - { - final String name = formConfiguration.getName(); - final String suppliedValue = bodyMap.get( name ); - try - { - if ( chaiUser.compareStringAttribute( name, suppliedValue ) ) - { - successCount++; - } - } - catch ( final ChaiException e ) - { - LOGGER.error( pwmRequest, () -> "error comparing ldap attribute during verification " + e.getMessage() ); - } - } - if ( successCount == verificationForms.size() ) - { - passed = true; - } - } - - final HelpdeskVerificationStateBean verificationStateBean = HelpdeskVerificationStateBean.fromClientString( - pwmRequest, - helpdeskVerificationRequestBean.getVerificationState() - ); + final var checkRequest = pwmRequest.readBodyAsJsonObject( HelpdeskCheckVerificationRequest.class ); - if ( passed ) - { - final PwmSession pwmSession = pwmRequest.getPwmSession(); - final HelpdeskAuditRecord auditRecord = AuditRecordFactory.make( pwmRequest ).createHelpdeskAuditRecord( - AuditEvent.HELPDESK_VERIFY_ATTRIBUTES, - pwmSession.getUserInfo().getUserIdentity(), - null, - userIdentity, - pwmSession.getSessionStateBean().getSrcAddress(), - pwmSession.getSessionStateBean().getSrcHostname() - ); - AuditServiceClient.submit( pwmRequest, auditRecord ); - verificationStateBean.addRecord( userIdentity, IdentityVerificationMethod.ATTRIBUTES ); - } - else - { - final PwmSession pwmSession = pwmRequest.getPwmSession(); - final HelpdeskAuditRecord auditRecord = AuditRecordFactory.make( pwmRequest ).createHelpdeskAuditRecord( - AuditEvent.HELPDESK_VERIFY_ATTRIBUTES_INCORRECT, - pwmSession.getUserInfo().getUserIdentity(), - null, - userIdentity, - pwmSession.getSessionStateBean().getSrcAddress(), - pwmSession.getSessionStateBean().getSrcHostname() - ); - AuditServiceClient.submit( pwmRequest, auditRecord ); - } - - return outputVerificationResponseBean( pwmRequest, passed, verificationStateBean ); - } - - private ProcessStatus outputVerificationResponseBean( - final PwmRequest pwmRequest, - final boolean passed, - final HelpdeskVerificationStateBean verificationStateBean - ) - throws IOException, PwmUnrecoverableException - { - // add a delay to prevent continuous checks - final long delayMs = JavaHelper.silentParseLong( pwmRequest.getDomainConfig().readAppProperty( AppProperty.HELPDESK_VERIFICATION_INVALID_DELAY_MS ), 500 ); - PwmTimeUtil.jitterPause( TimeDuration.of( delayMs, TimeDuration.Unit.MILLISECONDS ), pwmRequest.getPwmDomain().getSecureService(), 0.3f ); + final UserIdentity userIdentity = checkRequest.readTargetUser( pwmRequest ); + HelpdeskServletUtil.checkIfUserIdentityViewable( pwmRequest, userIdentity ); - final HelpdeskVerificationResponseBean responseBean = new HelpdeskVerificationResponseBean( + final HelpdeskClientState state = checkRequest.readVerificationState( pwmRequest ); + final boolean passed = HelpdeskServletUtil.checkIfRequiredVerificationPassed( pwmRequest, userIdentity, state ); + final HelpdeskVerificationOptions optionsBean = HelpdeskVerificationOptions.fromConfig( pwmRequest, helpdeskProfile, userIdentity ); + final HelpdeskCheckVerificationResponse responseBean = new HelpdeskCheckVerificationResponse( passed, - verificationStateBean.toClientString( pwmRequest.getPwmDomain() ) - ); - final RestResultBean restResultBean = RestResultBean.withData( responseBean, HelpdeskVerificationResponseBean.class ); + optionsBean ); + final RestResultBean restResultBean = RestResultBean.withData( responseBean, HelpdeskCheckVerificationResponse.class ); pwmRequest.outputJsonResult( restResultBean ); return ProcessStatus.Halt; } - @ActionHandler( action = "clearResponses" ) - public ProcessStatus restClearResponsesHandler( final PwmRequest pwmRequest ) - throws IOException, PwmUnrecoverableException, ServletException, ChaiUnavailableException, PwmOperationalException - { - final UserIdentity userIdentity; - try - { - userIdentity = readUserKeyRequestParameter( pwmRequest ); - } - catch ( final PwmUnrecoverableException e ) - { - pwmRequest.outputJsonResult( RestResultBean.fromError( e.getErrorInformation() ) ); - return ProcessStatus.Halt; - } - final HelpdeskProfile helpdeskProfile = pwmRequest.getHelpdeskProfile( ); - HelpdeskServletUtil.checkIfUserIdentityViewable( pwmRequest, helpdeskProfile, userIdentity ); - - { - final boolean buttonEnabled = helpdeskProfile.readSettingAsBoolean( PwmSetting.HELPDESK_CLEAR_RESPONSES_BUTTON ); - final HelpdeskClearResponseMode mode = helpdeskProfile.readSettingAsEnum( PwmSetting.HELPDESK_CLEAR_RESPONSES, HelpdeskClearResponseMode.class ); - if ( !buttonEnabled && ( mode == HelpdeskClearResponseMode.no ) ) - { - throw new PwmUnrecoverableException( new ErrorInformation( PwmError.ERROR_SECURITY_VIOLATION, "setting " - + PwmSetting.HELPDESK_CLEAR_RESPONSES_BUTTON.toMenuLocationDebug( helpdeskProfile.getId(), pwmRequest.getLocale() ) - + " must be enabled or setting " - + PwmSetting.HELPDESK_CLEAR_RESPONSES.toMenuLocationDebug( helpdeskProfile.getId(), pwmRequest.getLocale() ) - + "must be set to yes or ask" ) ); - } - } - - final ChaiUser chaiUser = HelpdeskServletUtil.getChaiUser( pwmRequest, helpdeskProfile, userIdentity ); - - final CrService crService = pwmRequest.getPwmDomain().getCrService(); - crService.clearResponses( - pwmRequest.getLabel(), - userIdentity, - chaiUser - ); - - // mark the event log - { - final HelpdeskAuditRecord auditRecord = AuditRecordFactory.make( pwmRequest ).createHelpdeskAuditRecord( - AuditEvent.HELPDESK_CLEAR_RESPONSES, - pwmRequest.getPwmSession().getUserInfo().getUserIdentity(), - null, - userIdentity, - pwmRequest.getPwmSession().getSessionStateBean().getSrcAddress(), - pwmRequest.getPwmSession().getSessionStateBean().getSrcHostname() - ); - AuditServiceClient.submit( pwmRequest, auditRecord ); - } - - final RestResultBean restResultBean = RestResultBean.forSuccessMessage( pwmRequest, Message.Success_Unknown ); - pwmRequest.outputJsonResult( restResultBean ); - return ProcessStatus.Halt; - } - - @ActionHandler( action = "checkPassword" ) - public ProcessStatus processCheckPasswordAction( final PwmRequest pwmRequest ) throws IOException, PwmUnrecoverableException, ChaiUnavailableException + @ActionHandler( action = "showVerifications" ) + public ProcessStatus restShowVerifications( final PwmRequest pwmRequest ) + throws IOException, PwmUnrecoverableException, ChaiOperationException { - final RestCheckPasswordServer.JsonInput jsonInput = pwmRequest.readBodyAsJsonObject( RestCheckPasswordServer.JsonInput.class ); - - final UserIdentity userIdentity = HelpdeskServletUtil.clarifyUserIdentity( pwmRequest, jsonInput.getUsername() ); - final HelpdeskProfile helpdeskProfile = getHelpdeskProfile( pwmRequest ); - - HelpdeskServletUtil.checkIfUserIdentityViewable( pwmRequest, helpdeskProfile, userIdentity ); - - final ChaiUser chaiUser = HelpdeskServletUtil.getChaiUser( pwmRequest, getHelpdeskProfile( pwmRequest ), userIdentity ); - final UserInfo userInfo = UserInfoFactory.newUserInfo( - pwmRequest.getPwmApplication(), - pwmRequest.getLabel(), - pwmRequest.getLocale(), - userIdentity, - chaiUser.getChaiProvider() - ); + final var checkRequest = pwmRequest.readBodyAsJsonObject( HelpdeskCheckVerificationRequest.class ); + final HelpdeskClientState state = checkRequest.readVerificationState( pwmRequest ); + record ShowVerificationResponse( + List records + ) { - final HelpdeskUIMode mode = helpdeskProfile.readSettingAsEnum( PwmSetting.HELPDESK_SET_PASSWORD_MODE, HelpdeskUIMode.class ); - if ( mode == HelpdeskUIMode.none ) - { - throw new PwmUnrecoverableException( new ErrorInformation( PwmError.ERROR_SECURITY_VIOLATION, "setting " - + PwmSetting.HELPDESK_SET_PASSWORD_MODE.toMenuLocationDebug( helpdeskProfile.getId(), pwmRequest.getLocale() ) - + " must not be set to none" ) ); - } } - final PasswordUtility.PasswordCheckInfo passwordCheckInfo = PasswordUtility.checkEnteredPassword( - pwmRequest.getPwmRequestContext(), - chaiUser, - userInfo, - null, - PasswordData.forStringValue( jsonInput.getPassword1() ), - PasswordData.forStringValue( jsonInput.getPassword2() ) - ); - - final RestCheckPasswordServer.JsonOutput jsonResponse = RestCheckPasswordServer.JsonOutput.fromPasswordCheckInfo( passwordCheckInfo ); - - final RestResultBean restResultBean = RestResultBean.withData( jsonResponse, RestCheckPasswordServer.JsonOutput.class ); + final ShowVerificationResponse response = new ShowVerificationResponse( state.asDisplayableValidationRecords( pwmRequest.getPwmRequestContext() ) ); + final RestResultBean restResultBean = RestResultBean.withData( response, ShowVerificationResponse.class ); pwmRequest.outputJsonResult( restResultBean ); - return ProcessStatus.Halt; } - @ActionHandler( action = "setPassword" ) - public ProcessStatus processSetPasswordAction( final PwmRequest pwmRequest ) throws IOException, PwmUnrecoverableException, ChaiUnavailableException - { - final HelpdeskProfile helpdeskProfile = pwmRequest.getHelpdeskProfile( ); - - final RestSetPasswordServer.JsonInputData jsonInput = - pwmRequest.readBodyAsJsonObject( RestSetPasswordServer.JsonInputData.class ); - - final UserIdentity userIdentity = HelpdeskServletUtil.clarifyUserIdentity( pwmRequest, jsonInput.getUsername() ); - final ChaiUser chaiUser = HelpdeskServletUtil.getChaiUser( pwmRequest, helpdeskProfile, userIdentity ); - final UserInfo userInfo = UserInfoFactory.newUserInfo( - pwmRequest.getPwmApplication(), - pwmRequest.getLabel(), - pwmRequest.getLocale(), - userIdentity, - chaiUser.getChaiProvider() - ); - - HelpdeskServletUtil.checkIfUserIdentityViewable( pwmRequest, helpdeskProfile, userIdentity ); - final HelpdeskUIMode mode = helpdeskProfile.readSettingAsEnum( PwmSetting.HELPDESK_SET_PASSWORD_MODE, HelpdeskUIMode.class ); - - if ( mode == HelpdeskUIMode.none ) - { - throw new PwmUnrecoverableException( new ErrorInformation( PwmError.ERROR_SECURITY_VIOLATION, "setting " - + PwmSetting.HELPDESK_SET_PASSWORD_MODE.toMenuLocationDebug( helpdeskProfile.getId(), pwmRequest.getLocale() ) - + " must not be set to none" ) ); - } - - - final PasswordData newPassword; - if ( jsonInput.getPassword() == null ) - { - if ( mode != HelpdeskUIMode.random ) - { - throw new PwmUnrecoverableException( new ErrorInformation( PwmError.ERROR_SECURITY_VIOLATION, "setting " - + PwmSetting.HELPDESK_SET_PASSWORD_MODE.toMenuLocationDebug( helpdeskProfile.getId(), pwmRequest.getLocale() ) - + " is set to " + mode + " and no password is included in request" ) ); - } - final PwmPasswordPolicy passwordPolicy = PasswordUtility.readPasswordPolicyForUser( - pwmRequest.getPwmDomain(), - pwmRequest.getLabel(), - userIdentity, - chaiUser ); - newPassword = PasswordUtility.generateRandom( - pwmRequest.getLabel(), - passwordPolicy, - pwmRequest.getPwmDomain() - ); - } - else - { - if ( mode == HelpdeskUIMode.random ) - { - throw new PwmUnrecoverableException( new ErrorInformation( PwmError.ERROR_SECURITY_VIOLATION, "setting " - + PwmSetting.HELPDESK_SET_PASSWORD_MODE.toMenuLocationDebug( helpdeskProfile.getId(), pwmRequest.getLocale() ) - + " is set to autogen yet a password is included in request" ) ); - } - - newPassword = new PasswordData( jsonInput.getPassword() ); - } - - - try - { - PasswordUtility.helpdeskSetUserPassword( - pwmRequest, - chaiUser, - userInfo, - pwmRequest.getPwmDomain(), - newPassword - ); - } - catch ( final PwmException e ) - { - LOGGER.error( () -> "error during set password REST operation: " + e.getMessage() ); - pwmRequest.outputJsonResult( RestResultBean.fromError( e.getErrorInformation(), pwmRequest ) ); - return ProcessStatus.Halt; - } - - pwmRequest.outputJsonResult( RestResultBean.forSuccessMessage( pwmRequest, Message.Success_ChangedHelpdeskPassword, userInfo.getUsername() ) ); - return ProcessStatus.Halt; - } - - @ActionHandler( action = "randomPassword" ) - public ProcessStatus processRandomPasswordAction( final PwmRequest pwmRequest ) throws IOException, PwmUnrecoverableException, ChaiUnavailableException + @ActionHandler( action = "validateAttributes" ) + public ProcessStatus restValidateAttributes( final PwmRequest pwmRequest ) + throws IOException, PwmUnrecoverableException { - final RestRandomPasswordServer.JsonInput input = pwmRequest.readBodyAsJsonObject( RestRandomPasswordServer.JsonInput.class ); - final UserIdentity userIdentity = HelpdeskServletUtil.clarifyUserIdentity( pwmRequest, input.getUsername() ); - - final HelpdeskProfile helpdeskProfile = getHelpdeskProfile( pwmRequest ); - - HelpdeskServletUtil.checkIfUserIdentityViewable( pwmRequest, helpdeskProfile, userIdentity ); - - final ChaiUser chaiUser = HelpdeskServletUtil.getChaiUser( pwmRequest, helpdeskProfile, userIdentity ); - final UserInfo userInfo = UserInfoFactory.newUserInfo( - pwmRequest.getPwmApplication(), - pwmRequest.getLabel(), - pwmRequest.getLocale(), - userIdentity, - chaiUser.getChaiProvider() + final HelpdeskVerificationRequest helpdeskVerificationRequest = JsonFactory.get().deserialize( + pwmRequest.readRequestBodyAsString(), + HelpdeskVerificationRequest.class ); - final RandomGeneratorConfig randomConfig = RandomGeneratorConfig.make( pwmRequest.getPwmDomain(), userInfo.getPasswordPolicy() ); - final PasswordData randomPassword = PasswordUtility.generateRandom( pwmRequest.getLabel(), randomConfig, pwmRequest.getPwmDomain() ); - final RestRandomPasswordServer.JsonOutput jsonOutput = new RestRandomPasswordServer.JsonOutput(); - jsonOutput.setPassword( randomPassword.getStringValue() ); - - final RestResultBean restResultBean = RestResultBean.withData( jsonOutput, RestRandomPasswordServer.JsonOutput.class ); - pwmRequest.outputJsonResult( restResultBean ); - return ProcessStatus.Halt; + return HelpdeskServletUtil.validateAttributesImpl( pwmRequest, helpdeskVerificationRequest ); } @ActionHandler( action = "photo" ) @@ -1350,7 +405,7 @@ public ProcessStatus processUserPhotoImageRequest( final PwmRequest pwmRequest ) { final UserIdentity userIdentity = readUserKeyRequestParameter( pwmRequest ); final HelpdeskProfile helpdeskProfile = getHelpdeskProfile( pwmRequest ); - HelpdeskServletUtil.checkIfUserIdentityViewable( pwmRequest, helpdeskProfile, userIdentity ); + HelpdeskServletUtil.checkIfUserIdentityViewable( pwmRequest, userIdentity ); final PhotoDataReader photoDataReader = photoDataReader( pwmRequest, helpdeskProfile, userIdentity ); LOGGER.debug( pwmRequest, () -> "received user photo request to view user " + userIdentity.toString() ); @@ -1360,17 +415,17 @@ public ProcessStatus processUserPhotoImageRequest( final PwmRequest pwmRequest ) return ProcessStatus.Halt; } - private UserIdentity readUserKeyRequestParameter( final PwmRequest pwmRequest ) + static UserIdentity readUserKeyRequestParameter( final PwmRequest pwmRequest ) throws PwmUnrecoverableException { final String userKey = pwmRequest.readParameterAsString( PwmConstants.PARAM_USERKEY, PwmHttpRequestWrapper.Flag.BypassValidation ); - if ( userKey.length() < 1 ) + if ( StringUtil.isEmpty( userKey ) ) { final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_MISSING_PARAMETER, "userKey parameter is missing" ); throw new PwmUnrecoverableException( errorInformation ); } - return HelpdeskServletUtil.clarifyUserIdentity( pwmRequest, userKey ); + return HelpdeskServletUtil.readUserIdentity( pwmRequest, userKey ); } static PhotoDataReader photoDataReader( final PwmRequest pwmRequest, final HelpdeskProfile helpdeskProfile, final UserIdentity userIdentity ) @@ -1381,7 +436,7 @@ static PhotoDataReader photoDataReader( final PwmRequest pwmRequest, final Helpd final PhotoDataReader.Settings settings = PhotoDataReader.Settings.builder() .enabled( enabled ) .photoPermissions( null ) - .chaiProvider( HelpdeskServletUtil.getChaiUser( pwmRequest, helpdeskProfile, userIdentity ).getChaiProvider() ) + .chaiProvider( HelpdeskServletUtil.getChaiUser( pwmRequest, userIdentity ).getChaiProvider() ) .build(); return new PhotoDataReader( pwmRequest, settings, userIdentity ); diff --git a/server/src/main/java/password/pwm/http/servlet/helpdesk/HelpdeskServletUtil.java b/server/src/main/java/password/pwm/http/servlet/helpdesk/HelpdeskServletUtil.java index ba11f36df2..ecfffc8c00 100644 --- a/server/src/main/java/password/pwm/http/servlet/helpdesk/HelpdeskServletUtil.java +++ b/server/src/main/java/password/pwm/http/servlet/helpdesk/HelpdeskServletUtil.java @@ -21,47 +21,79 @@ package password.pwm.http.servlet.helpdesk; import com.novell.ldapchai.ChaiUser; -import com.novell.ldapchai.exception.ChaiUnavailableException; +import com.novell.ldapchai.exception.ChaiException; +import password.pwm.AppProperty; import password.pwm.PwmConstants; import password.pwm.PwmDomain; import password.pwm.bean.EmailItemBean; +import password.pwm.bean.SessionLabel; +import password.pwm.bean.TokenDestinationItem; import password.pwm.bean.UserIdentity; import password.pwm.config.DomainConfig; import password.pwm.config.PwmSetting; import password.pwm.config.option.IdentityVerificationMethod; +import password.pwm.config.option.MessageSendMethod; import password.pwm.config.profile.HelpdeskProfile; import password.pwm.config.value.data.FormConfiguration; import password.pwm.config.value.data.UserPermission; import password.pwm.error.ErrorInformation; import password.pwm.error.PwmError; +import password.pwm.error.PwmException; +import password.pwm.error.PwmOperationalException; import password.pwm.error.PwmUnrecoverableException; -import password.pwm.http.PwmHttpRequestWrapper; +import password.pwm.http.ProcessStatus; import password.pwm.http.PwmRequest; +import password.pwm.http.PwmSession; +import password.pwm.http.servlet.helpdesk.data.HelpdeskSearchRequest; +import password.pwm.http.servlet.peoplesearch.PhotoDataReader; +import password.pwm.http.servlet.peoplesearch.SearchRequestBean; +import password.pwm.i18n.Error; +import password.pwm.i18n.Message; import password.pwm.ldap.UserInfoFactory; import password.pwm.ldap.permission.UserPermissionType; import password.pwm.ldap.permission.UserPermissionUtility; +import password.pwm.ldap.search.SearchConfiguration; import password.pwm.svc.event.AuditEvent; import password.pwm.svc.event.AuditRecordFactory; import password.pwm.svc.event.AuditServiceClient; import password.pwm.svc.event.HelpdeskAuditRecord; +import password.pwm.svc.otp.OTPUserRecord; +import password.pwm.svc.secure.DomainSecureService; import password.pwm.svc.stats.Statistic; import password.pwm.svc.stats.StatisticsClient; +import password.pwm.svc.token.TokenService; +import password.pwm.svc.token.TokenUtil; import password.pwm.user.UserInfo; +import password.pwm.util.i18n.LocaleHelper; import password.pwm.util.java.CollectionUtil; +import password.pwm.util.java.JavaHelper; +import password.pwm.util.java.PwmTimeUtil; +import password.pwm.util.java.PwmUtil; import password.pwm.util.java.StringUtil; +import password.pwm.util.java.TimeDuration; import password.pwm.util.logging.PwmLogger; import password.pwm.util.macro.MacroRequest; +import password.pwm.ws.server.RestResultBean; +import javax.servlet.ServletException; +import java.io.IOException; +import java.time.Instant; +import java.util.ArrayList; import java.util.Collection; +import java.util.LinkedHashMap; import java.util.List; import java.util.Locale; import java.util.Map; -import java.util.Objects; +import java.util.Optional; -public class HelpdeskServletUtil +public final class HelpdeskServletUtil { private static final PwmLogger LOGGER = PwmLogger.forClass( HelpdeskServletUtil.class ); + private HelpdeskServletUtil() + { + } + static String makeAdvancedSearchFilter( final DomainConfig domainConfig, final HelpdeskProfile helpdeskProfile ) { final String configuredFilter = helpdeskProfile.readSettingAsString( PwmSetting.HELPDESK_SEARCH_FILTER ); @@ -70,8 +102,10 @@ static String makeAdvancedSearchFilter( final DomainConfig domainConfig, final H return configuredFilter; } - final List defaultObjectClasses = domainConfig.readSettingAsStringArray( PwmSetting.DEFAULT_OBJECT_CLASSES ); - final List searchAttributes = helpdeskProfile.readSettingAsForm( PwmSetting.HELPDESK_SEARCH_FORM ); + final List defaultObjectClasses = + domainConfig.readSettingAsStringArray( PwmSetting.DEFAULT_OBJECT_CLASSES ); + final List searchAttributes = + helpdeskProfile.readSettingAsForm( PwmSetting.HELPDESK_SEARCH_FORM ); final StringBuilder filter = new StringBuilder(); //open AND clause for objectclasses and attributes @@ -112,8 +146,10 @@ static String makeAdvancedSearchFilter( final Map attributesInSearchRequest ) { - final List defaultObjectClasses = domainConfig.readSettingAsStringArray( PwmSetting.DEFAULT_OBJECT_CLASSES ); - final List searchAttributes = helpdeskProfile.readSettingAsForm( PwmSetting.HELPDESK_SEARCH_FORM ); + final List defaultObjectClasses = + domainConfig.readSettingAsStringArray( PwmSetting.DEFAULT_OBJECT_CLASSES ); + final List searchAttributes = + helpdeskProfile.readSettingAsForm( PwmSetting.HELPDESK_SEARCH_FORM ); return makeAdvancedSearchFilter( defaultObjectClasses, searchAttributes, attributesInSearchRequest ); } @@ -152,7 +188,6 @@ public static String makeAdvancedSearchFilter( filter.append( '%' ).append( searchAttribute ).append( "%)" ); } else - { filter.append( "*%" ).append( searchAttribute ).append( "%*)" ); } @@ -168,26 +203,24 @@ public static String makeAdvancedSearchFilter( return filter.toString(); } - static void checkIfUserIdentityViewable( final PwmRequest pwmRequest, - final HelpdeskProfile helpdeskProfile, final UserIdentity userIdentity ) throws PwmUnrecoverableException { - final String filterSetting = makeAdvancedSearchFilter( pwmRequest.getDomainConfig(), helpdeskProfile ); + final String filterSetting = makeAdvancedSearchFilter( pwmRequest.getDomainConfig(), pwmRequest.getHelpdeskProfile() ); String filterString = filterSetting.replace( PwmConstants.VALUE_REPLACEMENT_USERNAME, "*" ); while ( filterString.contains( "**" ) ) { filterString = filterString.replace( "**", "*" ); } - final UserPermission userPermission = UserPermission.builder() - .type( UserPermissionType.ldapQuery ) - .ldapQuery( filterString ) - .ldapProfileID( userIdentity.getLdapProfileID() ) - .build(); + final UserPermission userPermission = new UserPermission( + UserPermissionType.ldapQuery, + userIdentity.getLdapProfileID(), + filterString, + null ); final boolean match = UserPermissionUtility.testUserPermission( pwmRequest.getPwmRequestContext(), @@ -197,78 +230,41 @@ static void checkIfUserIdentityViewable( if ( !match ) { - throw new PwmUnrecoverableException( new ErrorInformation( + throw new PwmUnrecoverableException( PwmError.ERROR_SERVICE_NOT_AVAILABLE, - "requested userDN is not available within configured search filter" ) - ); + "requested user is not available within configured search filter" ); } } - static HelpdeskDetailInfoBean processDetailRequestImpl( + static void verifyIfRequiredVerificationPassed( final PwmRequest pwmRequest, - final HelpdeskProfile helpdeskProfile, - final UserIdentity userIdentity + final UserIdentity targetUser, + final HelpdeskClientState verificationStateBean ) - throws ChaiUnavailableException, PwmUnrecoverableException + throws PwmUnrecoverableException { - final UserIdentity actorUserIdentity = pwmRequest.getUserInfoIfLoggedIn().canonicalized( pwmRequest.getLabel(), pwmRequest.getPwmApplication() ); - - if ( actorUserIdentity.canonicalEquals( pwmRequest.getLabel(), userIdentity, pwmRequest.getPwmApplication() ) ) - { - final String errorMsg = "cannot select self"; - final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_UNAUTHORIZED, errorMsg ); - throw new PwmUnrecoverableException( errorInformation ); - } - LOGGER.trace( pwmRequest, () -> "helpdesk detail view request for user details of " + userIdentity.toString() + " by actor " + actorUserIdentity ); - - final HelpdeskVerificationStateBean verificationStateBean = HelpdeskVerificationStateBean.fromClientString( - pwmRequest, - pwmRequest.readParameterAsString( HelpdeskVerificationStateBean.PARAMETER_VERIFICATION_STATE_KEY, PwmHttpRequestWrapper.Flag.BypassValidation ) - ); - - if ( !HelpdeskServletUtil.checkIfRequiredVerificationPassed( userIdentity, verificationStateBean, helpdeskProfile ) ) + if ( !HelpdeskServletUtil.checkIfRequiredVerificationPassed( pwmRequest, targetUser, verificationStateBean ) ) { final String errorMsg = "selected user has not been verified"; final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_UNAUTHORIZED, errorMsg ); throw new PwmUnrecoverableException( errorInformation ); } - - final HelpdeskDetailInfoBean helpdeskDetailInfoBean = HelpdeskDetailInfoBean.makeHelpdeskDetailInfo( pwmRequest, helpdeskProfile, userIdentity ); - final HelpdeskAuditRecord auditRecord = AuditRecordFactory.make( pwmRequest ).createHelpdeskAuditRecord( - AuditEvent.HELPDESK_VIEW_DETAIL, - pwmRequest.getPwmSession().getUserInfo().getUserIdentity(), - null, - userIdentity, - pwmRequest.getLabel().getSourceAddress(), - pwmRequest.getLabel().getSourceHostname() - ); - AuditServiceClient.submit( pwmRequest, auditRecord ); - - StatisticsClient.incrementStat( pwmRequest, Statistic.HELPDESK_USER_LOOKUP ); - return helpdeskDetailInfoBean; } - static UserIdentity userIdentityFromMap( final PwmRequest pwmRequest, final Map bodyMap ) + static boolean checkIfRequiredVerificationPassed( + final PwmRequest pwmRequest, + final UserIdentity targetUser, + final HelpdeskClientState verificationStateBean + ) throws PwmUnrecoverableException { - final String userKey = bodyMap.get( "userKey" ); - if ( StringUtil.isEmpty( userKey ) ) - { - final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_MISSING_PARAMETER, "userKey parameter is missing" ); - throw new PwmUnrecoverableException( errorInformation ); - } + final HelpdeskProfile helpdeskProfile = pwmRequest.getHelpdeskProfile(); + final Collection requiredMethods = + helpdeskProfile.readRequiredVerificationMethods(); - return HelpdeskServletUtil.clarifyUserIdentity( pwmRequest, userKey ); - } + verifyTargetNotSelf( pwmRequest, targetUser ); - static boolean checkIfRequiredVerificationPassed( - final UserIdentity userIdentity, - final HelpdeskVerificationStateBean verificationStateBean, - final HelpdeskProfile helpdeskProfile - ) - { - final Collection requiredMethods = helpdeskProfile.readRequiredVerificationMethods(); if ( CollectionUtil.isEmpty( requiredMethods ) ) { return true; @@ -276,7 +272,7 @@ static boolean checkIfRequiredVerificationPassed( for ( final IdentityVerificationMethod method : requiredMethods ) { - if ( verificationStateBean.hasRecord( userIdentity, method ) ) + if ( verificationStateBean.hasRecord( targetUser, method ) ) { return true; } @@ -285,9 +281,26 @@ static boolean checkIfRequiredVerificationPassed( return false; } + private static void verifyTargetNotSelf( + final PwmRequest pwmRequest, + final UserIdentity targetUser + ) + throws PwmUnrecoverableException + { + final UserIdentity actorUserIdentity = + pwmRequest.getUserInfoIfLoggedIn().canonicalized( pwmRequest.getLabel(), + pwmRequest.getPwmApplication() ); + + if ( actorUserIdentity.canonicalEquals( pwmRequest.getLabel(), targetUser, pwmRequest.getPwmApplication() ) ) + { + final String errorMsg = "cannot select self"; + final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_UNAUTHORIZED, errorMsg ); + throw new PwmUnrecoverableException( errorInformation ); + } + } + static void sendUnlockNoticeEmail( final PwmRequest pwmRequest, - final HelpdeskProfile helpdeskProfile, final UserIdentity userIdentity, final ChaiUser chaiUser @@ -297,11 +310,13 @@ static void sendUnlockNoticeEmail( final PwmDomain pwmDomain = pwmRequest.getPwmDomain(); final DomainConfig config = pwmRequest.getDomainConfig(); final Locale locale = pwmRequest.getLocale(); - final EmailItemBean configuredEmailSetting = config.readSettingAsEmail( PwmSetting.EMAIL_HELPDESK_UNLOCK, locale ); + final EmailItemBean configuredEmailSetting = config.readSettingAsEmail( PwmSetting.EMAIL_HELPDESK_UNLOCK, + locale ); if ( configuredEmailSetting == null ) { - LOGGER.debug( pwmRequest, () -> "skipping send helpdesk unlock notice email for '" + userIdentity + "' no email configured" ); + LOGGER.debug( pwmRequest, () -> "skipping send helpdesk unlock notice email for '" + userIdentity + + "' no" + " email configured" ); return; } @@ -313,7 +328,7 @@ static void sendUnlockNoticeEmail( chaiUser.getChaiProvider() ); - final MacroRequest macroRequest = getTargetUserMacroRequest( pwmRequest, helpdeskProfile, userIdentity ); + final MacroRequest macroRequest = getTargetUserMacroRequest( pwmRequest, userIdentity ); pwmDomain.getPwmApplication().getEmailQueue().submitEmail( configuredEmailSetting, @@ -324,11 +339,11 @@ static void sendUnlockNoticeEmail( static ChaiUser getChaiUser( final PwmRequest pwmRequest, - final HelpdeskProfile helpdeskProfile, final UserIdentity userIdentity ) throws PwmUnrecoverableException { + final HelpdeskProfile helpdeskProfile = pwmRequest.getHelpdeskProfile(); final boolean useProxy = helpdeskProfile.readSettingAsBoolean( PwmSetting.HELPDESK_USE_PROXY ); return useProxy ? pwmRequest.getPwmDomain().getProxiedChaiUser( pwmRequest.getLabel(), userIdentity ) @@ -337,7 +352,6 @@ static ChaiUser getChaiUser( static UserInfo getTargetUserInfo( final PwmRequest pwmRequest, - final HelpdeskProfile helpdeskProfile, final UserIdentity targetUserIdentity ) throws PwmUnrecoverableException @@ -347,7 +361,7 @@ static UserInfo getTargetUserInfo( pwmRequest.getLabel(), pwmRequest.getLocale(), targetUserIdentity, - getChaiUser( pwmRequest, helpdeskProfile, targetUserIdentity ).getChaiProvider() + getChaiUser( pwmRequest, targetUserIdentity ).getChaiProvider() ); } @@ -363,35 +377,540 @@ static String obfuscateUserIdentity( final PwmRequest pwmRequest, final UserIden } } - static UserIdentity clarifyUserIdentity( final PwmRequest pwmRequest, final String input ) + + public static UserIdentity readUserIdentity( final PwmRequest pwmRequest, final String input ) throws PwmUnrecoverableException { - Objects.requireNonNull( input ); + try + { + final UserIdentity userIdentity = pwmRequest.decryptObject( input, UserIdentity.class ); + HelpdeskServletUtil.checkIfUserIdentityViewable( pwmRequest, userIdentity ); + return userIdentity; + } + catch ( final Exception e ) + { + LOGGER.debug( pwmRequest, () -> "error reading userKey from request: " + e.getMessage() ); + throw new PwmUnrecoverableException( PwmError.ERROR_MISSING_PARAMETER, + "unreadable userKey parameter: " + e.getMessage() ); + } - return pwmRequest.decryptObject( input, UserIdentity.class ); } - static MacroRequest getTargetUserMacroRequest( final PwmRequest pwmRequest, - final HelpdeskProfile helpdeskProfile, final UserIdentity targetUserIdentity ) throws PwmUnrecoverableException { - final MacroRequest macroRequest = MacroRequest.forUser( + if ( targetUserIdentity != null ) + { + final UserInfo targetUserInfo = getTargetUserInfo( pwmRequest, targetUserIdentity ); + return MacroRequest.forTargetUser( + pwmRequest.getPwmApplication(), + pwmRequest.getLabel(), + pwmRequest.getPwmSession().getUserInfo(), + pwmRequest.getPwmSession().getLoginInfoBean(), + targetUserInfo ); + } + + return MacroRequest.forUser( pwmRequest.getPwmApplication(), pwmRequest.getLabel(), - getTargetUserInfo( pwmRequest, helpdeskProfile, targetUserIdentity ), - pwmRequest.getPwmSession().getLoginInfoBean() + pwmRequest.getPwmSession().getUserInfo(), + pwmRequest.getPwmSession().getLoginInfoBean() ); + } + + static SearchConfiguration makeSearchConfiguration( + final PwmRequest pwmRequest, + final HelpdeskSearchRequest searchRequest, + final SearchRequestBean.SearchMode searchMode + ) + throws PwmUnrecoverableException + { + final HelpdeskProfile helpdeskProfile = HelpdeskServlet.getHelpdeskProfile( pwmRequest ); + final boolean useProxy = helpdeskProfile.readSettingAsBoolean( PwmSetting.HELPDESK_USE_PROXY ); + + final SearchConfiguration searchConfiguration; + { + final SearchConfiguration.SearchConfigurationBuilder builder = SearchConfiguration.builder(); + builder.contexts( helpdeskProfile.readSettingAsStringArray( PwmSetting.HELPDESK_SEARCH_BASE ) ); + builder.enableContextValidation( false ); + builder.enableValueEscaping( true ); + builder.enableSplitWhitespace( true ); + + if ( !useProxy ) + { + final UserIdentity loggedInUser = pwmRequest.getPwmSession().getUserInfo().getUserIdentity(); + builder.ldapProfile( loggedInUser.getLdapProfileID() ); + builder.chaiProvider( HelpdeskServletUtil.getChaiUser( pwmRequest, loggedInUser ).getChaiProvider() ); + } + + switch ( searchMode ) + { + case simple -> + { + builder.username( searchRequest.username() ); + builder.filter( HelpdeskServletUtil.makeAdvancedSearchFilter( pwmRequest.getDomainConfig(), + helpdeskProfile ) ); + } + case advanced -> + { + final Map formValues = new LinkedHashMap<>(); + final Map requestSearchValues = SearchRequestBean.searchValueToMap( searchRequest.searchValues() ); + + for ( final FormConfiguration formConfiguration : helpdeskProfile.readSettingAsForm( PwmSetting.HELPDESK_SEARCH_FORM ) ) + { + final String attribute = formConfiguration.getName(); + final String value = requestSearchValues.get( attribute ); + if ( StringUtil.notEmpty( value ) ) + { + formValues.put( formConfiguration, value ); + } + } + + builder.formValues( formValues ); + builder.filter( HelpdeskServletUtil.makeAdvancedSearchFilter( pwmRequest.getDomainConfig(), + helpdeskProfile, requestSearchValues ) ); + + } + default -> PwmUtil.unhandledSwitchStatement( searchMode ); + } + + searchConfiguration = builder.build(); + } + return searchConfiguration; + } + + static ProcessStatus validateOtpCodeImpl( + final PwmRequest pwmRequest, + final HelpdeskVerificationRequest helpdeskVerificationRequest + ) + throws IOException, PwmUnrecoverableException, ServletException + { + final HelpdeskProfile helpdeskProfile = HelpdeskServlet.getHelpdeskProfile( pwmRequest ); + + final String userKey = helpdeskVerificationRequest.userKey(); + if ( StringUtil.isEmpty( userKey ) ) + { + final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_MISSING_PARAMETER, "userKey parameter is missing" ); + pwmRequest.respondWithError( errorInformation, false ); + return ProcessStatus.Halt; + } + final UserIdentity userIdentity = HelpdeskServletUtil.readUserIdentity( pwmRequest, userKey ); + + if ( !helpdeskProfile.readOptionalVerificationMethods().contains( IdentityVerificationMethod.OTP ) ) + { + final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_UNAUTHORIZED, "password otp verification request, but otp verify is not enabled" ); + LOGGER.error( pwmRequest, errorInformation ); + pwmRequest.respondWithError( errorInformation ); + return ProcessStatus.Halt; + } + + final String code = helpdeskVerificationRequest.code(); + final OTPUserRecord otpUserRecord = pwmRequest.getPwmDomain().getOtpService().readOTPUserConfiguration( pwmRequest.getLabel(), userIdentity ); + try + { + final boolean passed = pwmRequest.getPwmDomain().getOtpService().validateToken( + pwmRequest.getLabel(), + userIdentity, + otpUserRecord, + code, + false + ); + + final HelpdeskClientState inputState = HelpdeskClientState.fromClientString( + pwmRequest, + helpdeskVerificationRequest.verificationState() + ); + + final HelpdeskClientState outputState; + if ( passed ) + { + submitAuditEvent( pwmRequest, userIdentity, AuditEvent.HELPDESK_VERIFY_OTP, null ); + StatisticsClient.incrementStat( pwmRequest, Statistic.HELPDESK_VERIFY_OTP ); + outputState = inputState.addRecord( userIdentity, IdentityVerificationMethod.OTP ); + } + else + { + submitAuditEvent( pwmRequest, userIdentity, AuditEvent.HELPDESK_VERIFY_OTP_INCORRECT, null ); + outputState = inputState; + } + + final String userMessage = passed + ? LocaleHelper.getLocalizedMessage( Message.Success_Unknown, pwmRequest ) + : LocaleHelper.getLocalizedMessage( Error.Error_WrongOtpToken, pwmRequest ); + + return HelpdeskServletUtil.outputVerificationResponseBean( pwmRequest, passed, userMessage, outputState ); + } + catch ( final PwmOperationalException e ) + { + pwmRequest.outputJsonResult( RestResultBean.fromError( e.getErrorInformation(), pwmRequest ) ); + } + return ProcessStatus.Halt; + } + + static ProcessStatus validateAttributesImpl( + final PwmRequest pwmRequest, + final HelpdeskVerificationRequest helpdeskVerificationRequest ) + throws IOException, PwmUnrecoverableException + { + final HelpdeskProfile helpdeskProfile = HelpdeskServlet.getHelpdeskProfile( pwmRequest ); + + final UserIdentity userIdentity = HelpdeskServletUtil.readUserIdentity( pwmRequest, + helpdeskVerificationRequest.userKey() ); + + String userMessage = LocaleHelper.getLocalizedMessage( Error.Error_TokenIncorrect, pwmRequest ); + final List verifiedAttributes = new ArrayList<>(); + boolean passed = false; + { + final List verificationForms = helpdeskProfile.readSettingAsForm( PwmSetting.HELPDESK_VERIFICATION_FORM ); + if ( verificationForms == null || verificationForms.isEmpty() ) + { + final ErrorInformation errorInformation = new ErrorInformation( + PwmError.ERROR_INVALID_CONFIG, + "attempt to verify ldap attributes with no ldap verification attributes configured" + ); + throw new PwmUnrecoverableException( errorInformation ); + } + + final ChaiUser chaiUser = HelpdeskServletUtil.getChaiUser( pwmRequest, userIdentity ); + + int successCount = 0; + for ( final FormConfiguration formConfiguration : verificationForms ) + { + final String name = formConfiguration.getName(); + final String suppliedValue = helpdeskVerificationRequest.attributeData().get( name ); + if ( suppliedValue != null ) + { + try + { + if ( chaiUser.compareStringAttribute( name, suppliedValue ) ) + { + verifiedAttributes.add( name ); + successCount++; + } + else + { + userMessage = LocaleHelper.getLocalizedMessage( Error.Error_FieldBadConfirm, pwmRequest, name ); + } + } + catch ( final ChaiException e ) + { + LOGGER.error( pwmRequest, () -> "error comparing ldap attribute during verification " + e.getMessage() ); + } + } + } + if ( successCount == verificationForms.size() ) + { + passed = true; + userMessage = LocaleHelper.getLocalizedMessage( Message.Success_Unknown, pwmRequest ); + } + } + + final HelpdeskClientState inputState = HelpdeskClientState.fromClientString( + pwmRequest, + helpdeskVerificationRequest.verificationState() ); - if ( targetUserIdentity != null ) + + final HelpdeskClientState outputState; + if ( passed ) + { + final String message = "verified attributes: " + StringUtil.collectionToString( verifiedAttributes ); + submitAuditEvent( pwmRequest, userIdentity, AuditEvent.HELPDESK_VERIFY_ATTRIBUTES, message ); + outputState = inputState.addRecord( userIdentity, IdentityVerificationMethod.ATTRIBUTES ); + } + else + { + submitAuditEvent( pwmRequest, userIdentity, AuditEvent.HELPDESK_VERIFY_ATTRIBUTES_INCORRECT, null ); + outputState = inputState; + } + + return outputVerificationResponseBean( pwmRequest, passed, userMessage, outputState ); + } + + + static ProcessStatus outputVerificationResponseBean( + final PwmRequest pwmRequest, + final boolean passed, + final String userMessage, + final HelpdeskClientState verificationStateBean + ) + throws IOException, PwmUnrecoverableException + { + // add a delay to prevent continuous checks + final long delayMs = JavaHelper.silentParseLong( pwmRequest.getDomainConfig().readAppProperty( AppProperty.HELPDESK_VERIFICATION_INVALID_DELAY_MS ), 500 ); + PwmTimeUtil.jitterPause( TimeDuration.of( delayMs, TimeDuration.Unit.MILLISECONDS ), pwmRequest.getPwmDomain().getSecureService(), 0.3f ); + + final HelpdeskVerificationResponse responseBean = new HelpdeskVerificationResponse( + passed, + userMessage, + verificationStateBean.toClientString( pwmRequest ) + ); + final RestResultBean restResultBean = RestResultBean.withData( responseBean, HelpdeskVerificationResponse.class ); + pwmRequest.outputJsonResult( restResultBean ); + return ProcessStatus.Halt; + } + + static void verifyButtonEnabled( + final PwmRequest pwmRequest, + final UserIdentity targetUser, + final HelpdeskDetailButton button + ) + throws PwmUnrecoverableException + { + final HelpdeskUserDetail helpdeskUserDetail = + HelpdeskUserDetail.makeHelpdeskDetailInfo( pwmRequest, targetUser ); + + if ( button == null ) + { + return; + } + + if ( !helpdeskUserDetail.visibleButtons().contains( button ) ) + { + final String errorMsg = "request for action '" + button + "' but button is not visible"; + final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_SERVICE_NOT_AVAILABLE, errorMsg ); + throw new PwmUnrecoverableException( errorInformation ); + } + if ( !helpdeskUserDetail.enabledButtons().contains( button ) ) + { + final String errorMsg = "request for action '" + button + "' but button is not enabled"; + final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_SERVICE_NOT_AVAILABLE, errorMsg ); + throw new PwmUnrecoverableException( errorInformation ); + } + } + + static void submitAuditEvent( + final PwmRequest pwmRequest, + final UserIdentity targetUser, + final AuditEvent auditEvent, + final String message + ) + throws PwmUnrecoverableException + { + final PwmSession pwmSession = pwmRequest.getPwmSession(); + + final HelpdeskAuditRecord helpdeskAuditRecord = AuditRecordFactory.make( pwmRequest ).createHelpdeskAuditRecord( + auditEvent, + pwmSession.getUserInfo().getUserIdentity(), + message, + targetUser, + pwmSession.getSessionStateBean().getSrcAddress(), + pwmSession.getSessionStateBean().getSrcHostname() + ); + + AuditServiceClient.submit( pwmRequest, helpdeskAuditRecord ); + } + + + + static ProcessStatus verifyVerificationTokenRequestImpl( + final PwmRequest pwmRequest, + final HelpdeskVerificationRequest helpdeskVerificationRequest + ) + throws IOException, PwmUnrecoverableException + { + + final String token = helpdeskVerificationRequest.code(); + + final DomainSecureService domainSecureService = pwmRequest.getPwmDomain().getSecureService(); + final HelpdeskVerificationRequest.TokenData tokenData = domainSecureService.decryptObject( + helpdeskVerificationRequest.tokenData(), + HelpdeskVerificationRequest.TokenData.class + ); + + final UserIdentity userIdentity = helpdeskVerificationRequest.readTargetUser( pwmRequest ); + + final TimeDuration maxTokenAge = TimeDuration.of( + Long.parseLong( pwmRequest.getDomainConfig().readAppProperty( AppProperty.HELPDESK_TOKEN_MAX_AGE ) ), + TimeDuration.Unit.SECONDS + ); + final Instant maxTokenAgeTimestamp = Instant.ofEpochMilli( System.currentTimeMillis() - maxTokenAge.asMillis() ); + if ( tokenData.issueDate().isBefore( maxTokenAgeTimestamp ) ) + { + final String errorMsg = "token is older than maximum issue time (" + maxTokenAge.asCompactString() + ")"; + throw new PwmUnrecoverableException( new ErrorInformation( PwmError.ERROR_TOKEN_EXPIRED, errorMsg ) ); + } + + final boolean passed = tokenData.token().equals( token ); + + final HelpdeskClientState inputState = helpdeskVerificationRequest.readVerificationState( pwmRequest ); + + final HelpdeskClientState outputState; + if ( passed ) { - final UserInfo targetUserInfo = getTargetUserInfo( pwmRequest, helpdeskProfile, targetUserIdentity ); - return macroRequest.toBuilder().targetUserInfo( targetUserInfo ).build(); + final PwmSession pwmSession = pwmRequest.getPwmSession(); + final HelpdeskAuditRecord auditRecord = AuditRecordFactory.make( pwmRequest ).createHelpdeskAuditRecord( + AuditEvent.HELPDESK_VERIFY_TOKEN, + pwmSession.getUserInfo().getUserIdentity(), + null, + userIdentity, + pwmSession.getSessionStateBean().getSrcAddress(), + pwmSession.getSessionStateBean().getSrcHostname() + ); + AuditServiceClient.submit( pwmRequest, auditRecord ); + outputState = inputState.addRecord( userIdentity, IdentityVerificationMethod.TOKEN ); + } + else + { + final PwmSession pwmSession = pwmRequest.getPwmSession(); + final HelpdeskAuditRecord auditRecord = AuditRecordFactory.make( pwmRequest ).createHelpdeskAuditRecord( + AuditEvent.HELPDESK_VERIFY_TOKEN_INCORRECT, + pwmSession.getUserInfo().getUserIdentity(), + null, + userIdentity, + pwmSession.getSessionStateBean().getSrcAddress(), + pwmSession.getSessionStateBean().getSrcHostname() + ); + AuditServiceClient.submit( pwmRequest, auditRecord ); + outputState = inputState; + } + + final String userMessage = passed + ? LocaleHelper.getLocalizedMessage( Message.Success_Unknown, pwmRequest ) + : LocaleHelper.getLocalizedMessage( Error.Error_TokenIncorrect, pwmRequest ); + + return HelpdeskServletUtil.outputVerificationResponseBean( pwmRequest, passed, userMessage, outputState ); + } + + static void sendVerificationTokenRequestImpl( + final PwmRequest pwmRequest, + final HelpdeskSendVerificationTokenRequest helpdeskVerificationRequest + ) + throws IOException, PwmUnrecoverableException + { + final Instant startTime = Instant.now(); + final DomainConfig config = pwmRequest.getDomainConfig(); + + final UserIdentity targetUserIdentity = helpdeskVerificationRequest.readTargetUser( pwmRequest ); + final UserInfo targetUserInfo = HelpdeskServletUtil.getTargetUserInfo( pwmRequest, targetUserIdentity ); + + final String requestedTokenID = helpdeskVerificationRequest.id(); + + final TokenDestinationItem tokenDestinationItem; + { + final List items = TokenUtil.figureAvailableTokenDestinations( + pwmRequest.getPwmDomain(), + pwmRequest.getLabel(), + pwmRequest.getLocale(), + targetUserInfo, + MessageSendMethod.CHOICE_SMS_EMAIL ); + + final Optional selectedTokenDest = TokenDestinationItem.tokenDestinationItemForID( items, requestedTokenID ); + + if ( selectedTokenDest.isPresent() ) + { + tokenDestinationItem = selectedTokenDest.get(); + } + else + { + throw PwmUnrecoverableException.newException( PwmError.ERROR_INTERNAL, "unknown token id '" + requestedTokenID + "' in request" ); + } + } + + final MacroRequest macroRequest = HelpdeskServletUtil.getTargetUserMacroRequest( pwmRequest, targetUserIdentity ); + final String configuredTokenString = config.readAppProperty( AppProperty.HELPDESK_TOKEN_VALUE ); + final String tokenKey = macroRequest.expandMacros( configuredTokenString ); + final EmailItemBean emailItemBean = config.readSettingAsEmail( PwmSetting.EMAIL_HELPDESK_TOKEN, pwmRequest.getLocale() ); + + LOGGER.debug( pwmRequest, () -> "generated token code for " + targetUserIdentity.toDelimitedKey() ); + + final String smsMessage = config.readSettingAsLocalizedString( PwmSetting.SMS_HELPDESK_TOKEN_TEXT, pwmRequest.getLocale() ); + + try + { + TokenService.TokenSender.sendToken( + TokenService.TokenSendInfo.builder() + .pwmDomain( pwmRequest.getPwmDomain() ) + .userInfo( targetUserInfo ) + .macroRequest( macroRequest ) + .configuredEmailSetting( emailItemBean ) + .tokenDestinationItem( tokenDestinationItem ) + .smsMessage( smsMessage ) + .tokenKey( tokenKey ) + .sessionLabel( pwmRequest.getLabel() ) + .build() + ); + } + catch ( final PwmException e ) + { + LOGGER.error( pwmRequest, e.getErrorInformation() ); + pwmRequest.outputJsonResult( RestResultBean.fromError( e.getErrorInformation(), pwmRequest ) ); + return; } - return macroRequest; + final DomainSecureService domainSecureService = pwmRequest.getPwmDomain().getSecureService(); + final String newTokenData = domainSecureService.encryptObjectToString( new HelpdeskVerificationRequest.TokenData( + tokenKey, + Instant.now() ) ); + + StatisticsClient.incrementStat( pwmRequest, Statistic.HELPDESK_TOKENS_SENT ); + + final HelpdeskVerificationRequest helpdeskVerificationRequestBean = new HelpdeskVerificationRequest( + tokenDestinationItem.getDisplay(), + helpdeskVerificationRequest.userKey(), + Map.of(), + null, + newTokenData, + null ); + + + final RestResultBean restResultBean = RestResultBean.withData( helpdeskVerificationRequestBean, HelpdeskVerificationRequest.class ); + pwmRequest.outputJsonResult( restResultBean ); + LOGGER.debug( pwmRequest, () -> "helpdesk operator " + + pwmRequest.getUserInfoIfLoggedIn().toDisplayString() + + " issued token for verification against user " + + targetUserIdentity.toDisplayString() + + " sent to destination(s) " + + tokenDestinationItem.getDisplay() + + " (" + TimeDuration.fromCurrent( startTime ).asCompactString() + ")" ); + + } + + static HelpdeskUserCard makeHelpdeskCardInfo( + final PwmRequest pwmRequest, + final HelpdeskProfile helpdeskProfile, + final UserIdentity userIdentity + ) + throws PwmUnrecoverableException + { + final Instant startTime = Instant.now(); + LOGGER.trace( pwmRequest, () -> "beginning to assemble card data report for user " + userIdentity ); + final UserInfo userInfo = HelpdeskServletUtil.getTargetUserInfo( pwmRequest, userIdentity ); + + final String userKey = HelpdeskServletUtil.obfuscateUserIdentity( pwmRequest, userIdentity ); + + final PhotoDataReader photoDataReader = HelpdeskServlet.photoDataReader( pwmRequest, helpdeskProfile, userIdentity ); + final String optionalPhotoUrl = photoDataReader.figurePhotoURL().orElse( null ); + + final List displayLines = figureCardDisplayLines( pwmRequest.getPwmDomain(), helpdeskProfile, pwmRequest.getLabel(), userInfo ); + + LOGGER.trace( pwmRequest, () -> "completed assembly of card data report for user " + userIdentity, TimeDuration.fromCurrent( startTime ) ); + + return new HelpdeskUserCard( userKey, displayLines, optionalPhotoUrl ); + } + + + private static List figureCardDisplayLines( + final PwmDomain pwmDomain, + final HelpdeskProfile helpdeskProfile, + final SessionLabel sessionLabel, + final UserInfo userInfo + ) + { + final List displayLabels = new ArrayList<>(); + final List displayStringSettings = helpdeskProfile.readSettingAsStringArray( PwmSetting.HELPDESK_DISPLAY_NAMES_CARD_LABELS ); + if ( displayStringSettings != null ) + { + final MacroRequest macroRequest = MacroRequest.forUser( pwmDomain.getPwmApplication(), sessionLabel, userInfo, null ); + for ( final String displayStringSetting : displayStringSettings ) + { + final String displayLabel = macroRequest.expandMacros( displayStringSetting ); + displayLabels.add( displayLabel ); + } + } + return List.copyOf( displayLabels ); } + } diff --git a/client/angular/src/modules/changepassword/password-suggestions.scss b/server/src/main/java/password/pwm/http/servlet/helpdesk/HelpdeskUserCard.java similarity index 78% rename from client/angular/src/modules/changepassword/password-suggestions.scss rename to server/src/main/java/password/pwm/http/servlet/helpdesk/HelpdeskUserCard.java index 256e21a67c..0d8d3259c5 100644 --- a/client/angular/src/modules/changepassword/password-suggestions.scss +++ b/server/src/main/java/password/pwm/http/servlet/helpdesk/HelpdeskUserCard.java @@ -1,4 +1,4 @@ -/*! +/* * Password Management Servlets (PWM) * http://www.pwm-project.org * @@ -18,6 +18,14 @@ * limitations under the License. */ -.randomPasswordDialog { - height: 395px; width: 350px; max-width: 350px; margin-top: auto; margin-bottom: auto; -} \ No newline at end of file +package password.pwm.http.servlet.helpdesk; + +import java.util.List; + +public record HelpdeskUserCard( + String userKey, + List cardDisplayLines, + String photoURL +) +{ +} diff --git a/server/src/main/java/password/pwm/http/servlet/helpdesk/HelpdeskDetailInfoBean.java b/server/src/main/java/password/pwm/http/servlet/helpdesk/HelpdeskUserDetail.java similarity index 58% rename from server/src/main/java/password/pwm/http/servlet/helpdesk/HelpdeskDetailInfoBean.java rename to server/src/main/java/password/pwm/http/servlet/helpdesk/HelpdeskUserDetail.java index 51367f003c..8ce43de478 100644 --- a/server/src/main/java/password/pwm/http/servlet/helpdesk/HelpdeskDetailInfoBean.java +++ b/server/src/main/java/password/pwm/http/servlet/helpdesk/HelpdeskUserDetail.java @@ -21,11 +21,9 @@ package password.pwm.http.servlet.helpdesk; import com.novell.ldapchai.ChaiPasswordRule; -import com.novell.ldapchai.ChaiUser; import com.novell.ldapchai.cr.Challenge; -import com.novell.ldapchai.exception.ChaiUnavailableException; +import lombok.AccessLevel; import lombok.Builder; -import lombok.Value; import password.pwm.bean.ResponseInfoBean; import password.pwm.bean.SessionLabel; import password.pwm.bean.UserIdentity; @@ -41,111 +39,69 @@ import password.pwm.http.PwmRequest; import password.pwm.http.bean.DisplayElement; import password.pwm.http.servlet.accountinfo.AccountInformationBean; -import password.pwm.util.password.PasswordRequirementViewableRuleGenerator; import password.pwm.i18n.Display; -import password.pwm.ldap.UserInfoFactory; import password.pwm.ldap.ViewableUserInfoDisplayReader; import password.pwm.user.UserInfo; import password.pwm.util.form.FormUtility; import password.pwm.util.i18n.LocaleHelper; import password.pwm.util.java.CollectionUtil; +import password.pwm.util.java.EnumUtil; +import password.pwm.util.java.StringUtil; import password.pwm.util.java.TimeDuration; import password.pwm.util.json.JsonFactory; import password.pwm.util.logging.PwmLogger; import password.pwm.util.macro.MacroRequest; +import password.pwm.util.password.PasswordRequirementViewableRuleGenerator; import java.time.Instant; import java.util.ArrayList; import java.util.Collections; -import java.util.LinkedHashMap; -import java.util.LinkedHashSet; +import java.util.EnumSet; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Set; - -@Value -@Builder -public class HelpdeskDetailInfoBean +import java.util.concurrent.atomic.AtomicInteger; + +public record HelpdeskUserDetail( + String userKey, + List userHistory, + List passwordPolicyRules, + List passwordRequirements, + String passwordPolicyDN, + String passwordPolicyID, + List statusData, + List profileData, + List helpdeskResponses, + Set visibleButtons, + Set enabledButtons, + HelpdeskVerificationOptions verificationOptions +) { - private static final PwmLogger LOGGER = PwmLogger.forClass( HelpdeskDetailInfoBean.class ); - - private String userKey; - - private String userDisplayName; - - private List userHistory; + private static final PwmLogger LOGGER = PwmLogger.forClass( HelpdeskUserDetail.class ); - private Map passwordPolicyRules; - private List passwordRequirements; - private String passwordPolicyDN; - private String passwordPolicyID; - - private List statusData; - private List profileData; - private List helpdeskResponses; - - private Set visibleButtons; - private Set enabledButtons; - - private HelpdeskVerificationOptionsBean verificationOptions; - - public enum StandardButton + @Builder( access = AccessLevel.PRIVATE ) + public HelpdeskUserDetail { - back, - refresh, - changePassword, - unlock, - clearResponses, - clearOtpSecret, - verification, - deleteUser, } - static HelpdeskDetailInfoBean makeHelpdeskDetailInfo( + static HelpdeskUserDetail makeHelpdeskDetailInfo( final PwmRequest pwmRequest, - final HelpdeskProfile helpdeskProfile, final UserIdentity userIdentity ) - throws PwmUnrecoverableException, ChaiUnavailableException + throws PwmUnrecoverableException { - final HelpdeskDetailInfoBeanBuilder builder = HelpdeskDetailInfoBean.builder(); final Instant startTime = Instant.now(); - LOGGER.trace( pwmRequest, () -> "beginning to assemble detail data report for user " + userIdentity ); - final Locale actorLocale = pwmRequest.getLocale(); - final ChaiUser theUser = HelpdeskServletUtil.getChaiUser( pwmRequest, helpdeskProfile, userIdentity ); + final HelpdeskProfile helpdeskProfile = pwmRequest.getHelpdeskProfile(); + LOGGER.trace( pwmRequest, () -> "beginning to assemble detail data info for user " + userIdentity ); - if ( !theUser.exists() ) - { - return null; - } - - final UserInfo userInfo = UserInfoFactory.newUserInfo( - pwmRequest.getPwmApplication(), - pwmRequest.getLabel(), - actorLocale, - userIdentity, - theUser.getChaiProvider() - ); - final MacroRequest macroRequest = MacroRequest.forUser( pwmRequest.getPwmApplication(), pwmRequest.getLabel(), userInfo, null ); + final HelpdeskUserDetailBuilder builder = HelpdeskUserDetail.builder(); - try - { + final UserInfo userInfo = HelpdeskServletUtil.getTargetUserInfo( pwmRequest, userIdentity ); - final AccountInformationProfile accountInformationProfile = pwmRequest.getAccountInfoProfile(); + final MacroRequest macroRequest = MacroRequest.forUser( pwmRequest.getPwmApplication(), pwmRequest.getLabel(), userInfo, null ); - final List userHistory = AccountInformationBean.makeAuditInfo( - pwmRequest.getPwmDomain(), - accountInformationProfile, - pwmRequest.getLabel(), - userInfo, - pwmRequest.getLocale() ); - builder.userHistory( userHistory ); - } - catch ( final Exception e ) - { - LOGGER.error( pwmRequest, () -> "unexpected error reading userHistory for user '" + userIdentity + "', " + e.getMessage() ); - } + builder.userHistory( makeUserHistory( pwmRequest, userInfo ) ); builder.userKey( HelpdeskServletUtil.obfuscateUserIdentity( pwmRequest, userIdentity ) ); @@ -185,33 +141,7 @@ static HelpdeskDetailInfoBean makeHelpdeskDetailInfo( builder.passwordPolicyID( LocaleHelper.getLocalizedMessage( Display.Value_NotApplicable, pwmRequest ) ); } - { - final ResponseInfoBean responseInfoBean = userInfo.getResponseInfoBean(); - if ( responseInfoBean != null ) - { - final Map helpdeskCrMap = responseInfoBean.getHelpdeskCrMap(); - if ( helpdeskCrMap != null ) - { - final List responseDisplay = new ArrayList<>( helpdeskCrMap.size() ); - int counter = 0; - for ( final Map.Entry entry : helpdeskCrMap.entrySet() ) - { - counter++; - responseDisplay.add( new DisplayElement( - "item_" + counter, - DisplayElement.Type.string, - entry.getKey().getChallengeText(), - entry.getValue() - ) ); - } - builder.helpdeskResponses = responseDisplay; - } - } - } - - builder.userDisplayName( HelpdeskCardInfoBean.figureDisplayName( helpdeskProfile, macroRequest ) ); - - final TimeDuration timeDuration = TimeDuration.fromCurrent( startTime ); + builder.helpdeskResponses = makeHelpdeskResponses( userInfo ); { final Set viewStatusFields = helpdeskProfile.readSettingAsOptionList( PwmSetting.HELPDESK_VIEW_STATUS_VALUES, ViewStatusFields.class ); @@ -225,102 +155,175 @@ static HelpdeskDetailInfoBean makeHelpdeskDetailInfo( } { - final Set visibleButtons = determineVisibleButtons( helpdeskProfile ); + final Set visibleButtons = determineVisibleButtons( helpdeskProfile ); builder.visibleButtons( visibleButtons ); builder.enabledButtons( determineEnabledButtons( visibleButtons, userInfo ) ); } - builder.verificationOptions( HelpdeskVerificationOptionsBean.makeBean( pwmRequest, helpdeskProfile, userIdentity ) ); + builder.verificationOptions( HelpdeskVerificationOptions.fromConfig( pwmRequest, helpdeskProfile, userIdentity ) ); - final HelpdeskDetailInfoBean helpdeskDetailInfoBean = builder.build(); + final HelpdeskUserDetail helpdeskDetailInfoBean = builder.build(); if ( pwmRequest.getAppConfig().isDevDebugMode() ) { - LOGGER.trace( pwmRequest, () -> "completed assembly of detail data report for user " + userIdentity - + " in " + timeDuration.asCompactString() + ", contents: " + JsonFactory.get().serialize( helpdeskDetailInfoBean ) ); + LOGGER.trace( pwmRequest, () -> "completed assembly of detail data info for user " + userIdentity + + ", contents: " + JsonFactory.get().serialize( helpdeskDetailInfoBean ), + TimeDuration.fromCurrent( startTime ) ); } return builder.build(); } - private static Set determineVisibleButtons( - final HelpdeskProfile helpdeskProfile + private static List makeHelpdeskResponses( + final UserInfo userInfo ) + throws PwmUnrecoverableException { - final Set buttons = new LinkedHashSet<>(); + final ResponseInfoBean responseInfoBean = userInfo.getResponseInfoBean(); + if ( responseInfoBean == null ) + { + return List.of(); + } - buttons.add( StandardButton.refresh ); - buttons.add( StandardButton.back ); + final Map helpdeskCrMap = responseInfoBean.getHelpdeskCrMap(); + if ( helpdeskCrMap == null ) + { + return List.of(); + } + + final List responseDisplay = new ArrayList<>( helpdeskCrMap.size() ); + int counter = 0; + + for ( final Map.Entry entry : helpdeskCrMap.entrySet() ) + { + counter++; + responseDisplay.add( DisplayElement.create( + "item_" + counter, + DisplayElement.Type.string, + entry.getKey().getChallengeText(), + entry.getValue() + ) ); + } + + return List.copyOf( responseDisplay ); + } + + private static List makeUserHistory( + final PwmRequest pwmRequest, + final UserInfo userInfo + ) + { + try + { + final AccountInformationProfile accountInformationProfile = pwmRequest.getAccountInfoProfile(); + + final List userHistory = AccountInformationBean.makeAuditInfo( + pwmRequest.getPwmDomain(), + accountInformationProfile, + pwmRequest.getLabel(), + userInfo, + pwmRequest.getLocale() ); + + final AtomicInteger counter = new AtomicInteger(); + + return userHistory.stream() + .map( record -> DisplayElement.create( + String.valueOf( counter.incrementAndGet() ), + DisplayElement.Type.timestamp, + record.getLabel(), + record.getTimestamp().toString() ) ) + .toList(); + + } + catch ( final Exception e ) + { + LOGGER.error( pwmRequest, () -> "unexpected error reading userHistory for user '" + + userInfo.getUserIdentity() + "', " + e.getMessage() ); + } + + return List.of(); + } + + private static Set determineVisibleButtons( + final HelpdeskProfile helpdeskProfile + ) + { + final Set buttons = EnumSet.noneOf( HelpdeskDetailButton.class ); { final HelpdeskUIMode uiMode = helpdeskProfile.readSettingAsEnum( PwmSetting.HELPDESK_SET_PASSWORD_MODE, HelpdeskUIMode.class ); if ( uiMode != HelpdeskUIMode.none ) { - buttons.add( StandardButton.changePassword ); + buttons.add( HelpdeskDetailButton.changePassword ); } } if ( helpdeskProfile.readSettingAsBoolean( PwmSetting.HELPDESK_ENABLE_UNLOCK ) ) { - buttons.add( StandardButton.unlock ); + buttons.add( HelpdeskDetailButton.unlock ); } if ( helpdeskProfile.readSettingAsBoolean( PwmSetting.HELPDESK_CLEAR_RESPONSES_BUTTON ) ) { - buttons.add( StandardButton.clearResponses ); + buttons.add( HelpdeskDetailButton.clearResponses ); } if ( helpdeskProfile.readSettingAsBoolean( PwmSetting.HELPDESK_CLEAR_OTP_BUTTON ) ) { - buttons.add( StandardButton.clearOtpSecret ); + buttons.add( HelpdeskDetailButton.clearOtpSecret ); } if ( !helpdeskProfile.readOptionalVerificationMethods().isEmpty() ) { - buttons.add( StandardButton.verification ); + buttons.add( HelpdeskDetailButton.verification ); } if ( helpdeskProfile.readSettingAsBoolean( PwmSetting.HELPDESK_DELETE_USER_BUTTON ) ) { - buttons.add( StandardButton.deleteUser ); + buttons.add( HelpdeskDetailButton.deleteUser ); + } + + if ( helpdeskProfile.readSettingAsAction( PwmSetting.HELPDESK_ACTIONS ).isEmpty() ) + { + buttons.add( HelpdeskDetailButton.executeAction ); } return Collections.unmodifiableSet( buttons ); } - private static Set determineEnabledButtons( - final Set visibleButtons, + private static Set determineEnabledButtons( + final Set visibleButtons, final UserInfo userInfo ) throws PwmUnrecoverableException { - final Set buttons = new LinkedHashSet<>( visibleButtons ); + final Set buttons = EnumUtil.copyToEnumSet( visibleButtons, HelpdeskDetailButton.class ); - if ( buttons.contains( StandardButton.unlock ) ) + if ( buttons.contains( HelpdeskDetailButton.unlock ) ) { final boolean enabled = userInfo.isPasswordLocked(); if ( !enabled ) { - buttons.remove( StandardButton.unlock ); + buttons.remove( HelpdeskDetailButton.unlock ); } } - if ( buttons.contains( StandardButton.clearResponses ) ) + if ( buttons.contains( HelpdeskDetailButton.clearResponses ) ) { final boolean enabled = userInfo.getResponseInfoBean() != null; if ( !enabled ) { - buttons.remove( StandardButton.clearResponses ); + buttons.remove( HelpdeskDetailButton.clearResponses ); } } - if ( buttons.contains( StandardButton.clearOtpSecret ) ) + if ( buttons.contains( HelpdeskDetailButton.clearOtpSecret ) ) { final boolean enabled = userInfo.getOtpUserRecord() != null; if ( !enabled ) { - buttons.remove( StandardButton.clearOtpSecret ); + buttons.remove( HelpdeskDetailButton.clearOtpSecret ); } } @@ -344,37 +347,56 @@ private static List getProfileData( final FormConfiguration formConfiguration = entry.getKey(); if ( formConfiguration.isMultivalue() ) { - profileData.add( new DisplayElement( + profileData.add( DisplayElement.createMultiValue( formConfiguration.getName(), DisplayElement.Type.multiString, formConfiguration.getLabel( actorLocale ), - entry.getValue() + normalizeMultiValues( entry.getValue() ) ) ); } else { - final String value = CollectionUtil.isEmpty( entry.getValue() ) - ? "" - : entry.getValue().iterator().next(); - profileData.add( new DisplayElement( + profileData.add( DisplayElement.create( formConfiguration.getName(), DisplayElement.Type.string, formConfiguration.getLabel( actorLocale ), - value + normalizeStringValue( entry.getValue() ) ) ); } } return profileData; } - private static Map makePasswordPolicyRules( + private static List normalizeMultiValues( final List values ) + { + final List multiValues = CollectionUtil.stripNulls( values ) + .stream().filter( s -> !StringUtil.isEmpty( s ) ) + .toList(); + + return CollectionUtil.isEmpty( multiValues ) + ? List.of( "" ) + : multiValues; + } + + private static String normalizeStringValue( final List values ) + { + final List multiValues = CollectionUtil.stripNulls( values ) + .stream().filter( s -> !StringUtil.isEmpty( s ) ) + .toList(); + + return CollectionUtil.isEmpty( multiValues ) + ? "" + : values.iterator().next(); + } + + private static List makePasswordPolicyRules( final UserInfo userInfo, final Locale locale, final DomainConfig domainConfig ) throws PwmUnrecoverableException { - final Map passwordRules = new LinkedHashMap<>(); + final List passwordRules = new ArrayList<>(); if ( userInfo.getPasswordPolicy() != null ) { for ( final PwmPasswordRule rule : PwmPasswordRule.values() ) @@ -385,17 +407,28 @@ private static Map makePasswordPolicyRules( { final boolean value = Boolean.parseBoolean( userInfo.getPasswordPolicy().getValue( rule ) ); final String sValue = LocaleHelper.booleanString( value, locale, domainConfig ); - passwordRules.put( rule.getLabel( locale, domainConfig ), sValue ); + passwordRules.add( DisplayElement.create( + rule.getKey(), + DisplayElement.Type.string, + rule.getLabel( locale, domainConfig ), + sValue ) ); } else { - passwordRules.put( rule.getLabel( locale, domainConfig ), - userInfo.getPasswordPolicy().getValue( rule ) ); + final String sValue = userInfo.getPasswordPolicy().getValue( rule ); + if ( !StringUtil.isEmpty( sValue ) ) + { + passwordRules.add( DisplayElement.create( + rule.getKey(), + DisplayElement.Type.string, + rule.getLabel( locale, domainConfig ), + sValue ) ); + } } } } } - return Collections.unmodifiableMap( passwordRules ); + return List.copyOf( passwordRules ); } } diff --git a/server/src/main/java/password/pwm/http/servlet/helpdesk/HelpdeskVerificationDisplayRecord.java b/server/src/main/java/password/pwm/http/servlet/helpdesk/HelpdeskVerificationDisplayRecord.java new file mode 100644 index 0000000000..8fa1568935 --- /dev/null +++ b/server/src/main/java/password/pwm/http/servlet/helpdesk/HelpdeskVerificationDisplayRecord.java @@ -0,0 +1,62 @@ +/* + * Password Management Servlets (PWM) + * http://www.pwm-project.org + * + * Copyright (c) 2006-2009 Novell, Inc. + * Copyright (c) 2009-2021 The PWM Project + * + * 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. + */ + +package password.pwm.http.servlet.helpdesk; + +import org.jetbrains.annotations.NotNull; + +import java.time.Instant; +import java.util.Comparator; +import java.util.Objects; + +record HelpdeskVerificationDisplayRecord( + Instant timestamp, + String profile, + String username, + String method +) + implements Comparable + +{ + private static final Comparator COMPARATOR = Comparator.comparing( + HelpdeskVerificationDisplayRecord::username ) + .thenComparing( HelpdeskVerificationDisplayRecord::profile ) + .thenComparing( HelpdeskVerificationDisplayRecord::method ) + .thenComparing( HelpdeskVerificationDisplayRecord::timestamp ); + + HelpdeskVerificationDisplayRecord( + final Instant timestamp, + final String profile, + final String username, + final String method + ) + { + this.timestamp = Objects.requireNonNull( timestamp ); + this.profile = Objects.requireNonNull( profile ); + this.username = Objects.requireNonNull( username ); + this.method = Objects.requireNonNull( method ); + } + + @Override + public int compareTo( @NotNull final HelpdeskVerificationDisplayRecord o ) + { + return COMPARATOR.compare( this, o ); + } +} diff --git a/server/src/main/java/password/pwm/http/servlet/helpdesk/HelpdeskVerificationOptions.java b/server/src/main/java/password/pwm/http/servlet/helpdesk/HelpdeskVerificationOptions.java new file mode 100644 index 0000000000..6d605cac0a --- /dev/null +++ b/server/src/main/java/password/pwm/http/servlet/helpdesk/HelpdeskVerificationOptions.java @@ -0,0 +1,213 @@ +/* + * Password Management Servlets (PWM) + * http://www.pwm-project.org + * + * Copyright (c) 2006-2009 Novell, Inc. + * Copyright (c) 2009-2021 The PWM Project + * + * 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. + */ + +package password.pwm.http.servlet.helpdesk; + + +import com.novell.ldapchai.ChaiUser; +import password.pwm.bean.TokenDestinationItem; +import password.pwm.bean.UserIdentity; +import password.pwm.config.PwmSetting; +import password.pwm.config.option.IdentityVerificationMethod; +import password.pwm.config.option.MessageSendMethod; +import password.pwm.config.profile.HelpdeskProfile; +import password.pwm.config.value.VerificationMethodValue; +import password.pwm.error.ErrorInformation; +import password.pwm.error.PwmError; +import password.pwm.error.PwmUnrecoverableException; +import password.pwm.http.PwmRequest; +import password.pwm.ldap.UserInfoFactory; +import password.pwm.svc.token.TokenUtil; +import password.pwm.user.UserInfo; +import password.pwm.util.java.CollectionUtil; +import password.pwm.util.logging.PwmLogger; + +import java.util.ArrayList; +import java.util.EnumMap; +import java.util.EnumSet; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Set; + +public record HelpdeskVerificationOptions( + Map> verificationMethods, + List verificationForm, + List tokenDestinations +) +{ + private static final PwmLogger LOGGER = PwmLogger.forClass( HelpdeskVerificationOptions.class ); + + public HelpdeskVerificationOptions( + final Map> verificationMethods, + final List verificationForm, + final List tokenDestinations + ) + { + this.verificationMethods = CollectionUtil.stripNulls( verificationMethods ); + this.verificationForm = CollectionUtil.stripNulls( verificationForm ); + this.tokenDestinations = CollectionUtil.stripNulls( tokenDestinations ); + } + + static HelpdeskVerificationOptions fromConfig( + final PwmRequest pwmRequest, + final HelpdeskProfile helpdeskProfile, + final UserIdentity targetUser + ) + throws PwmUnrecoverableException + { + final ChaiUser theUser = HelpdeskServletUtil.getChaiUser( pwmRequest, targetUser ); + final UserInfo userInfo = UserInfoFactory.newUserInfo( + pwmRequest.getPwmApplication(), + pwmRequest.getLabel(), + pwmRequest.getLocale(), + targetUser, + theUser.getChaiProvider() ); + + final Locale locale = pwmRequest.getLocale(); + + final List formInformation = HelpdeskClientData.makeFormInformation( + helpdeskProfile, + locale ); + + final List tokenDestinations = makeTokenDestinationItems( pwmRequest, helpdeskProfile, userInfo ); + + final Set unavailableMethods = makeIdentityVerificationMethods( helpdeskProfile, + userInfo, formInformation, tokenDestinations ); + + final Map> verificationMethodsMap = + makeEnabledStateSetMap( helpdeskProfile, unavailableMethods ); + + if ( + CollectionUtil.isEmpty( verificationMethodsMap.get( VerificationMethodValue.EnabledState.required ) ) + && !CollectionUtil.isEmpty( helpdeskProfile.readRequiredVerificationMethods() ) + ) + { + final String msg = "configuration requires verification, but target user has no eligible required verification methods available."; + final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_TOKEN_MISSING_CONTACT, msg ); + throw new PwmUnrecoverableException( errorInformation ); + } + + return new HelpdeskVerificationOptions( + verificationMethodsMap, + formInformation, + tokenDestinations ); + } + + private static Map> makeEnabledStateSetMap( + final HelpdeskProfile helpdeskProfile, + final Set unavailableMethods + ) + { + final Map> returnMap + = new EnumMap<>( VerificationMethodValue.EnabledState.class ); + + { + final Set optionalMethods = CollectionUtil.copyToEnumSet( + helpdeskProfile.readOptionalVerificationMethods(), + IdentityVerificationMethod.class ); + optionalMethods.removeAll( unavailableMethods ); + returnMap.put( VerificationMethodValue.EnabledState.optional, optionalMethods ); + } + + { + final Set requiredMethods = CollectionUtil.copyToEnumSet( + helpdeskProfile.readRequiredVerificationMethods(), + IdentityVerificationMethod.class ); + requiredMethods.removeAll( unavailableMethods ); + returnMap.put( VerificationMethodValue.EnabledState.required, requiredMethods ); + } + + return Map.copyOf( returnMap ); + } + + private static Set makeIdentityVerificationMethods( + final HelpdeskProfile helpdeskProfile, + final UserInfo userInfo, + final List formInformation, + final List tokenDestinations + ) + throws PwmUnrecoverableException + { + final Set returnSet = EnumSet.noneOf( IdentityVerificationMethod.class ); + final Set workSet = EnumSet.noneOf( IdentityVerificationMethod.class ); + workSet.addAll( helpdeskProfile.readOptionalVerificationMethods() ); + workSet.addAll( helpdeskProfile.readRequiredVerificationMethods() ); + + if ( workSet.contains( IdentityVerificationMethod.ATTRIBUTES ) ) + { + if ( CollectionUtil.isEmpty( formInformation ) ) + { + returnSet.add( IdentityVerificationMethod.ATTRIBUTES ); + } + } + + if ( workSet.contains( IdentityVerificationMethod.OTP ) ) + { + if ( userInfo.getOtpUserRecord() == null ) + { + returnSet.add( IdentityVerificationMethod.OTP ); + } + } + + if ( workSet.contains( IdentityVerificationMethod.TOKEN ) ) + { + if ( CollectionUtil.isEmpty( tokenDestinations ) ) + { + returnSet.add( IdentityVerificationMethod.TOKEN ); + } + } + + return Set.copyOf( returnSet ); + } + + private static List makeTokenDestinationItems( + final PwmRequest pwmRequest, + final HelpdeskProfile helpdeskProfile, + final UserInfo userInfo + ) + { + final MessageSendMethod testSetting = helpdeskProfile.readSettingAsEnum( + PwmSetting.HELPDESK_TOKEN_SEND_METHOD, MessageSendMethod.class ); + + final List returnList = new ArrayList<>( ); + + if ( testSetting != null && testSetting != MessageSendMethod.NONE ) + { + try + { + returnList.addAll( TokenUtil.figureAvailableTokenDestinations( + pwmRequest.getPwmDomain(), + pwmRequest.getLabel(), + pwmRequest.getLocale(), + userInfo, + testSetting + ) ); + } + catch ( final PwmUnrecoverableException e ) + { + LOGGER.trace( pwmRequest, () -> "error while calculating available token methods: " + e.getMessage() ); + } + } + return List.copyOf( TokenDestinationItem.stripValues( returnList ) ); + + } + +} diff --git a/server/src/main/java/password/pwm/http/servlet/helpdesk/HelpdeskVerificationOptionsBean.java b/server/src/main/java/password/pwm/http/servlet/helpdesk/HelpdeskVerificationOptionsBean.java deleted file mode 100644 index f2c3032dbd..0000000000 --- a/server/src/main/java/password/pwm/http/servlet/helpdesk/HelpdeskVerificationOptionsBean.java +++ /dev/null @@ -1,210 +0,0 @@ -/* - * Password Management Servlets (PWM) - * http://www.pwm-project.org - * - * Copyright (c) 2006-2009 Novell, Inc. - * Copyright (c) 2009-2021 The PWM Project - * - * 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. - */ - -package password.pwm.http.servlet.helpdesk; - - -import com.novell.ldapchai.ChaiUser; -import lombok.Builder; -import lombok.Value; -import password.pwm.bean.TokenDestinationItem; -import password.pwm.bean.UserIdentity; -import password.pwm.config.PwmSetting; -import password.pwm.config.option.IdentityVerificationMethod; -import password.pwm.config.option.MessageSendMethod; -import password.pwm.config.profile.HelpdeskProfile; -import password.pwm.config.value.VerificationMethodValue; -import password.pwm.config.value.data.FormConfiguration; -import password.pwm.error.ErrorInformation; -import password.pwm.error.PwmError; -import password.pwm.error.PwmUnrecoverableException; -import password.pwm.http.PwmRequest; -import password.pwm.ldap.UserInfoFactory; -import password.pwm.svc.token.TokenUtil; -import password.pwm.user.UserInfo; -import password.pwm.util.java.CollectionUtil; -import password.pwm.util.logging.PwmLogger; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.EnumMap; -import java.util.EnumSet; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.Set; - -@Value -@Builder -public class HelpdeskVerificationOptionsBean -{ - private static final PwmLogger LOGGER = PwmLogger.forClass( HelpdeskVerificationOptionsBean.class ); - - private Map> verificationMethods; - private List verificationForm; - private List tokenDestinations; - - static HelpdeskVerificationOptionsBean makeBean( - final PwmRequest pwmRequest, - final HelpdeskProfile helpdeskProfile, - final UserIdentity targetUser - - ) - throws PwmUnrecoverableException - { - final ChaiUser theUser = HelpdeskServletUtil.getChaiUser( pwmRequest, helpdeskProfile, targetUser ); - final UserInfo userInfo = UserInfoFactory.newUserInfo( - pwmRequest.getPwmApplication(), - pwmRequest.getLabel(), - pwmRequest.getLocale(), - targetUser, - theUser.getChaiProvider() ); - - final Locale locale = pwmRequest.getLocale(); - - final List formInformations; - { - final List returnList = new ArrayList<>(); - final List attributeVerificationForm = helpdeskProfile.readSettingAsForm( PwmSetting.HELPDESK_VERIFICATION_FORM ); - if ( attributeVerificationForm != null ) - { - for ( final FormConfiguration formConfiguration : attributeVerificationForm ) - { - final String name = formConfiguration.getName(); - String label = formConfiguration.getLabel( locale ); - label = ( label != null && !label.isEmpty() ) ? label : formConfiguration.getName(); - final HelpdeskClientDataBean.FormInformation formInformation = new HelpdeskClientDataBean.FormInformation( name, label ); - returnList.add( formInformation ); - } - } - formInformations = Collections.unmodifiableList( returnList ); - } - - final List tokenDestinations; - { - final MessageSendMethod testSetting = helpdeskProfile.readSettingAsEnum( PwmSetting.HELPDESK_TOKEN_SEND_METHOD, MessageSendMethod.class ); - final List returnList = new ArrayList<>( ); - - if ( testSetting != null && testSetting != MessageSendMethod.NONE ) - { - try - { - returnList.addAll( TokenUtil.figureAvailableTokenDestinations( - pwmRequest.getPwmDomain(), - pwmRequest.getLabel(), - pwmRequest.getLocale(), - userInfo, - testSetting - ) ); - } - catch ( final PwmUnrecoverableException e ) - { - LOGGER.trace( pwmRequest, () -> "error while calculating available token methods: " + e.getMessage() ); - } - } - tokenDestinations = Collections.unmodifiableList( TokenDestinationItem.stripValues( returnList ) ); - } - - final Set unavailableMethods; - { - final Set returnSet = EnumSet.noneOf( IdentityVerificationMethod.class ); - final Set workSet = EnumSet.noneOf( IdentityVerificationMethod.class ); - workSet.addAll( helpdeskProfile.readOptionalVerificationMethods() ); - workSet.addAll( helpdeskProfile.readRequiredVerificationMethods() ); - - for ( final IdentityVerificationMethod method : workSet ) - { - switch ( method ) - { - case ATTRIBUTES: - { - if ( CollectionUtil.isEmpty( formInformations ) ) - { - returnSet.add( IdentityVerificationMethod.ATTRIBUTES ); - } - } - break; - - case OTP: - { - if ( userInfo.getOtpUserRecord() == null ) - { - returnSet.add( IdentityVerificationMethod.OTP ); - } - - } - break; - - case TOKEN: - { - if ( CollectionUtil.isEmpty( tokenDestinations ) ) - { - returnSet.add( IdentityVerificationMethod.TOKEN ); - } - } - break; - - default: - break; - } - } - - unavailableMethods = Collections.unmodifiableSet( returnSet ); - } - - final Map> verificationMethodsMap; - { - final Map> returnMap - = new EnumMap<>( VerificationMethodValue.EnabledState.class ); - { - final Set optionalMethods = CollectionUtil.copyToEnumSet( - helpdeskProfile.readOptionalVerificationMethods(), - IdentityVerificationMethod.class ); - optionalMethods.removeAll( unavailableMethods ); - returnMap.put( VerificationMethodValue.EnabledState.optional, optionalMethods ); - } - { - final Set requiredMethods = CollectionUtil.copyToEnumSet( - helpdeskProfile.readRequiredVerificationMethods(), - IdentityVerificationMethod.class ); - requiredMethods.removeAll( unavailableMethods ); - returnMap.put( VerificationMethodValue.EnabledState.required, requiredMethods ); - } - verificationMethodsMap = Collections.unmodifiableMap( returnMap ); - } - - if ( - CollectionUtil.isEmpty( verificationMethodsMap.get( VerificationMethodValue.EnabledState.required ) ) - && !CollectionUtil.isEmpty( helpdeskProfile.readRequiredVerificationMethods() ) - ) - { - final String msg = "configuration requires verification, but target user has no eligible required verification methods available."; - final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_TOKEN_MISSING_CONTACT, msg ); - throw new PwmUnrecoverableException( errorInformation ); - } - - return HelpdeskVerificationOptionsBean.builder() - .tokenDestinations( tokenDestinations ) - .verificationForm( formInformations ) - .verificationMethods( verificationMethodsMap ) - .build(); - } -} diff --git a/server/src/main/java/password/pwm/http/servlet/helpdesk/HelpdeskVerificationRecord.java b/server/src/main/java/password/pwm/http/servlet/helpdesk/HelpdeskVerificationRecord.java new file mode 100644 index 0000000000..bd65ac17f2 --- /dev/null +++ b/server/src/main/java/password/pwm/http/servlet/helpdesk/HelpdeskVerificationRecord.java @@ -0,0 +1,89 @@ +/* + * Password Management Servlets (PWM) + * http://www.pwm-project.org + * + * Copyright (c) 2006-2009 Novell, Inc. + * Copyright (c) 2009-2021 The PWM Project + * + * 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. + */ + +package password.pwm.http.servlet.helpdesk; + +import org.jetbrains.annotations.NotNull; +import password.pwm.PwmConstants; +import password.pwm.bean.UserIdentity; +import password.pwm.config.option.IdentityVerificationMethod; +import password.pwm.error.PwmUnrecoverableException; +import password.pwm.http.PwmRequestContext; +import password.pwm.ldap.UserInfoFactory; +import password.pwm.user.UserInfo; + +import java.time.Instant; +import java.util.Comparator; +import java.util.Objects; + +record HelpdeskVerificationRecord( + UserIdentity identity, + IdentityVerificationMethod method, + Instant timestamp +) + implements Comparable +{ + HelpdeskVerificationRecord( + final UserIdentity identity, + final IdentityVerificationMethod method, + final Instant timestamp + ) + { + this.timestamp = Objects.requireNonNull( timestamp ); + this.identity = Objects.requireNonNull( identity ); + this.method = Objects.requireNonNull( method ); + } + + private static final Comparator COMPARATOR = Comparator.comparing( + HelpdeskVerificationRecord::identity ) + .thenComparing( HelpdeskVerificationRecord::method ) + .thenComparing( HelpdeskVerificationRecord::timestamp ); + + boolean matches( final UserIdentity identity, final IdentityVerificationMethod method ) + { + return this.method == method && Objects.equals( this.identity, identity ); + } + + + @Override + public int compareTo( @NotNull final HelpdeskVerificationRecord o ) + { + return COMPARATOR.compare( this, o ); + } + + public HelpdeskVerificationDisplayRecord toViewableRecord( final PwmRequestContext pwmRequestContext ) + throws PwmUnrecoverableException + { + final UserInfo userInfo = UserInfoFactory.newUserInfoUsingProxy( + pwmRequestContext.getPwmApplication(), + pwmRequestContext.getSessionLabel(), + this.identity(), + PwmConstants.DEFAULT_LOCALE ); + final String username = userInfo.getUsername(); + final String profile = + pwmRequestContext.getPwmDomain().getConfig().getLdapProfiles().get( this.identity().getLdapProfileID() ) + .getDisplayName( pwmRequestContext.getLocale() ); + final String method = this.method().getLabel( pwmRequestContext.getPwmDomain().getConfig(), + pwmRequestContext.getLocale() ); + + return new HelpdeskVerificationDisplayRecord( this.timestamp(), profile, username, + method ); + } +} diff --git a/server/src/main/java/password/pwm/http/servlet/helpdesk/HelpdeskVerificationRequest.java b/server/src/main/java/password/pwm/http/servlet/helpdesk/HelpdeskVerificationRequest.java new file mode 100644 index 0000000000..8a4aa822c1 --- /dev/null +++ b/server/src/main/java/password/pwm/http/servlet/helpdesk/HelpdeskVerificationRequest.java @@ -0,0 +1,76 @@ +/* + * Password Management Servlets (PWM) + * http://www.pwm-project.org + * + * Copyright (c) 2006-2009 Novell, Inc. + * Copyright (c) 2009-2021 The PWM Project + * + * 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. + */ + +package password.pwm.http.servlet.helpdesk; + +import password.pwm.http.servlet.helpdesk.data.HelpdeskTargetUserRequest; +import password.pwm.util.java.CollectionUtil; +import password.pwm.util.java.JavaHelper; + +import java.time.Instant; +import java.util.Map; +import java.util.Objects; + + +/** + * @param tokenData Contains encrypted {@link TokenData} during transport. + */ +public record HelpdeskVerificationRequest( + String destination, + String userKey, + Map attributeData, + String code, + String tokenData, + String verificationState +) + implements HelpdeskTargetUserRequest +{ + public HelpdeskVerificationRequest( + final String destination, + final String userKey, + final Map attributeData, + final String code, + final String tokenData, + final String verificationState + ) + { + this.destination = destination; + this.userKey = userKey; + this.attributeData = CollectionUtil.stripNulls( attributeData ); + this.code = code; + this.tokenData = tokenData; + this.verificationState = verificationState; + } + + public record TokenData( + String token, + Instant issueDate + ) + { + public TokenData( + final String token, + final Instant issueDate + ) + { + this.token = JavaHelper.requireNonEmpty( token ); + this.issueDate = Objects.requireNonNull( issueDate ); + } + } +} diff --git a/client/angular/src/models/orgchart-data.model.ts b/server/src/main/java/password/pwm/http/servlet/helpdesk/HelpdeskVerificationResponse.java similarity index 80% rename from client/angular/src/models/orgchart-data.model.ts rename to server/src/main/java/password/pwm/http/servlet/helpdesk/HelpdeskVerificationResponse.java index fc01c211bb..7ba72642c8 100644 --- a/client/angular/src/models/orgchart-data.model.ts +++ b/server/src/main/java/password/pwm/http/servlet/helpdesk/HelpdeskVerificationResponse.java @@ -18,11 +18,12 @@ * limitations under the License. */ +package password.pwm.http.servlet.helpdesk; -import { IPerson } from './person.model'; -export default class IOrgChartData { - manager?: IPerson; - children?: IPerson[]; - self: IPerson; - assistant?: IPerson; +public record HelpdeskVerificationResponse( + boolean passed, + String message, + String verificationState +) +{ } diff --git a/server/src/main/java/password/pwm/http/servlet/helpdesk/HelpdeskVerificationStateBean.java b/server/src/main/java/password/pwm/http/servlet/helpdesk/HelpdeskVerificationStateBean.java deleted file mode 100644 index b08ac85f24..0000000000 --- a/server/src/main/java/password/pwm/http/servlet/helpdesk/HelpdeskVerificationStateBean.java +++ /dev/null @@ -1,189 +0,0 @@ -/* - * Password Management Servlets (PWM) - * http://www.pwm-project.org - * - * Copyright (c) 2006-2009 Novell, Inc. - * Copyright (c) 2009-2021 The PWM Project - * - * 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. - */ - -package password.pwm.http.servlet.helpdesk; - -import com.novell.ldapchai.exception.ChaiOperationException; -import lombok.Value; -import password.pwm.AppProperty; -import password.pwm.PwmConstants; -import password.pwm.PwmDomain; -import password.pwm.bean.UserIdentity; -import password.pwm.config.option.IdentityVerificationMethod; -import password.pwm.error.PwmUnrecoverableException; -import password.pwm.http.PwmRequest; -import password.pwm.http.PwmRequestContext; -import password.pwm.ldap.UserInfoFactory; -import password.pwm.user.UserInfo; -import password.pwm.util.java.TimeDuration; -import password.pwm.util.json.JsonFactory; -import password.pwm.util.logging.PwmLogger; - -import java.time.Instant; -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.TreeMap; - -class HelpdeskVerificationStateBean -{ - private static final PwmLogger LOGGER = PwmLogger.forClass( HelpdeskVerificationStateBean.class ); - public static final String PARAMETER_VERIFICATION_STATE_KEY = "verificationState"; - - private final UserIdentity actor; - private final List records = new ArrayList<>(); - - private transient TimeDuration maximumAge; - - private HelpdeskVerificationStateBean( final UserIdentity actor ) - { - this.actor = actor; - } - - public UserIdentity getActor( ) - { - return actor; - } - - public List getRecords( ) - { - return records; - } - - public void addRecord( final UserIdentity identity, final IdentityVerificationMethod method ) - { - purgeOldRecords(); - - final Optional optionalRecord = getRecord( identity, method ); - optionalRecord.ifPresent( records::remove ); - records.add( new HelpdeskValidationRecord( Instant.now(), identity, method ) ); - } - - public boolean hasRecord( final UserIdentity identity, final IdentityVerificationMethod method ) - { - purgeOldRecords(); - return getRecord( identity, method ).isPresent(); - } - - private Optional getRecord( final UserIdentity identity, final IdentityVerificationMethod method ) - { - for ( final HelpdeskValidationRecord record : records ) - { - if ( record.getIdentity().equals( identity ) && ( method == null || record.getMethod() == method ) ) - { - return Optional.of( record ); - } - } - return Optional.empty(); - } - - - void purgeOldRecords( ) - { - for ( final Iterator iterator = records.iterator(); iterator.hasNext(); ) - { - final HelpdeskValidationRecord record = iterator.next(); - final Instant timestamp = record.getTimestamp(); - final TimeDuration age = TimeDuration.fromCurrent( timestamp ); - if ( age.isLongerThan( maximumAge ) ) - { - iterator.remove(); - } - } - } - - List asViewableValidationRecords( final PwmRequestContext pwmRequestContext ) - throws ChaiOperationException, PwmUnrecoverableException - { - final Map returnRecords = new TreeMap<>(); - for ( final HelpdeskValidationRecord record : records ) - { - final UserInfo userInfo = UserInfoFactory.newUserInfoUsingProxy( - pwmRequestContext.getPwmApplication(), - pwmRequestContext.getSessionLabel(), - record.getIdentity(), - PwmConstants.DEFAULT_LOCALE ); - final String username = userInfo.getUsername(); - final String profile = pwmRequestContext.getPwmDomain().getConfig().getLdapProfiles().get( record.getIdentity().getLdapProfileID() ) - .getDisplayName( pwmRequestContext.getLocale() ); - final String method = record.getMethod().getLabel( pwmRequestContext.getPwmDomain().getConfig(), pwmRequestContext.getLocale() ); - returnRecords.put( record.getTimestamp(), new ViewableValidationRecord( record.getTimestamp(), profile, username, method ) ); - } - return List.copyOf( returnRecords.values() ); - } - - @Value - static class ViewableValidationRecord - { - private Instant timestamp; - private String profile; - private String username; - private String method; - } - - @Value - static class HelpdeskValidationRecord - { - private Instant timestamp; - private UserIdentity identity; - private IdentityVerificationMethod method; - } - - String toClientString( final PwmDomain pwmDomain ) throws PwmUnrecoverableException - { - return pwmDomain.getSecureService().encryptObjectToString( this ); - } - - static HelpdeskVerificationStateBean fromClientString( - final PwmRequest pwmRequest, - final String rawValue - ) - throws PwmUnrecoverableException - { - final int maxAgeSeconds = Integer.parseInt( pwmRequest.getDomainConfig().readAppProperty( AppProperty.HELPDESK_VERIFICATION_TIMEOUT_SECONDS ) ); - final TimeDuration maxAge = TimeDuration.of( maxAgeSeconds, TimeDuration.Unit.SECONDS ); - final UserIdentity actor = pwmRequest.getUserInfoIfLoggedIn(); - - HelpdeskVerificationStateBean state = null; - if ( rawValue != null && !rawValue.isEmpty() ) - { - state = pwmRequest.getPwmDomain().getSecureService().decryptObject( rawValue, HelpdeskVerificationStateBean.class ); - if ( !state.getActor().equals( actor ) ) - { - state = null; - } - } - - state = state != null ? state : new HelpdeskVerificationStateBean( actor ); - state.maximumAge = maxAge; - state.purgeOldRecords(); - - { - final HelpdeskVerificationStateBean finalState = state; - LOGGER.debug( pwmRequest, () -> "read current state: " + JsonFactory.get().serialize( finalState ) ); - } - - return state; - } -} - - diff --git a/server/src/main/java/password/pwm/http/servlet/helpdesk/data/HelpdeskCheckVerificationRequest.java b/server/src/main/java/password/pwm/http/servlet/helpdesk/data/HelpdeskCheckVerificationRequest.java new file mode 100644 index 0000000000..bf58a463ef --- /dev/null +++ b/server/src/main/java/password/pwm/http/servlet/helpdesk/data/HelpdeskCheckVerificationRequest.java @@ -0,0 +1,29 @@ +/* + * Password Management Servlets (PWM) + * http://www.pwm-project.org + * + * Copyright (c) 2006-2009 Novell, Inc. + * Copyright (c) 2009-2021 The PWM Project + * + * 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. + */ + +package password.pwm.http.servlet.helpdesk.data; + +public record HelpdeskCheckVerificationRequest( + String userKey, + String verificationState +) + implements HelpdeskTargetUserRequest +{ +} diff --git a/client/angular/src/modules/peoplesearch/person.filters.ts b/server/src/main/java/password/pwm/http/servlet/helpdesk/data/HelpdeskCheckVerificationResponse.java similarity index 74% rename from client/angular/src/modules/peoplesearch/person.filters.ts rename to server/src/main/java/password/pwm/http/servlet/helpdesk/data/HelpdeskCheckVerificationResponse.java index c34bb76fb3..2ba431f969 100644 --- a/client/angular/src/modules/peoplesearch/person.filters.ts +++ b/server/src/main/java/password/pwm/http/servlet/helpdesk/data/HelpdeskCheckVerificationResponse.java @@ -18,11 +18,13 @@ * limitations under the License. */ +package password.pwm.http.servlet.helpdesk.data; -import { IPerson } from '../../models/person.model'; +import password.pwm.http.servlet.helpdesk.HelpdeskVerificationOptions; -export function FullNameFilter(): (person: IPerson) => string { - return (person: IPerson): string => { - return `${person.givenName} ${person.sn}`; - }; +public record HelpdeskCheckVerificationResponse( + boolean passed, + HelpdeskVerificationOptions verificationOptions +) +{ } diff --git a/server/src/main/java/password/pwm/http/servlet/helpdesk/HelpdeskSearchRequestBean.java b/server/src/main/java/password/pwm/http/servlet/helpdesk/data/HelpdeskSearchRequest.java similarity index 56% rename from server/src/main/java/password/pwm/http/servlet/helpdesk/HelpdeskSearchRequestBean.java rename to server/src/main/java/password/pwm/http/servlet/helpdesk/data/HelpdeskSearchRequest.java index 9013866a4a..8f9f946f78 100644 --- a/server/src/main/java/password/pwm/http/servlet/helpdesk/HelpdeskSearchRequestBean.java +++ b/server/src/main/java/password/pwm/http/servlet/helpdesk/data/HelpdeskSearchRequest.java @@ -18,27 +18,28 @@ * limitations under the License. */ -package password.pwm.http.servlet.helpdesk; +package password.pwm.http.servlet.helpdesk.data; -import lombok.Builder; -import lombok.Value; import password.pwm.http.servlet.peoplesearch.SearchRequestBean; +import password.pwm.util.java.CollectionUtil; import java.util.List; -@Value -@Builder -public class HelpdeskSearchRequestBean -{ - @Builder.Default - private SearchRequestBean.SearchMode mode = SearchRequestBean.SearchMode.simple; - - private String username; - private List searchValues; +public record HelpdeskSearchRequest( + SearchRequestBean.SearchMode mode, + String username, + List searchValues - public List nonEmptySearchValues() +) +{ + public HelpdeskSearchRequest( + final SearchRequestBean.SearchMode mode, + final String username, + final List searchValues + ) { - return SearchRequestBean.filterNonEmptySearchValues( getSearchValues() ); + this.mode = mode == null ? SearchRequestBean.SearchMode.simple : mode; + this.username = username; + this.searchValues = CollectionUtil.stripNulls( searchValues ); } - } diff --git a/client/angular/src/modules/helpdesk/verifications-dialog.component.scss b/server/src/main/java/password/pwm/http/servlet/helpdesk/data/HelpdeskSearchResponse.java similarity index 65% rename from client/angular/src/modules/helpdesk/verifications-dialog.component.scss rename to server/src/main/java/password/pwm/http/servlet/helpdesk/data/HelpdeskSearchResponse.java index a5d20001a4..b1512cfb96 100644 --- a/client/angular/src/modules/helpdesk/verifications-dialog.component.scss +++ b/server/src/main/java/password/pwm/http/servlet/helpdesk/data/HelpdeskSearchResponse.java @@ -1,4 +1,4 @@ -/*! +/* * Password Management Servlets (PWM) * http://www.pwm-project.org * @@ -18,18 +18,19 @@ * limitations under the License. */ -#verification-token-destination { - margin: 0; - width: 210px; -} +package password.pwm.http.servlet.helpdesk.data; -.token-destination-submitter { - display: flex; - flex-wrap: wrap; - align-items: center; +import java.util.Collections; +import java.util.List; +import java.util.Map; - > * { - margin-right: 10px !important; - margin-top: 4px !important; +public record HelpdeskSearchResponse( + List> searchResults, + boolean sizeExceeded +) +{ + public static HelpdeskSearchResponse emptyResult() + { + return new HelpdeskSearchResponse( Collections.emptyList(), true ); } } diff --git a/server/src/main/java/password/pwm/http/servlet/helpdesk/data/HelpdeskTargetUserRequest.java b/server/src/main/java/password/pwm/http/servlet/helpdesk/data/HelpdeskTargetUserRequest.java new file mode 100644 index 0000000000..3466646929 --- /dev/null +++ b/server/src/main/java/password/pwm/http/servlet/helpdesk/data/HelpdeskTargetUserRequest.java @@ -0,0 +1,45 @@ +/* + * Password Management Servlets (PWM) + * http://www.pwm-project.org + * + * Copyright (c) 2006-2009 Novell, Inc. + * Copyright (c) 2009-2021 The PWM Project + * + * 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. + */ + +package password.pwm.http.servlet.helpdesk.data; + +import password.pwm.bean.UserIdentity; +import password.pwm.error.PwmUnrecoverableException; +import password.pwm.http.PwmRequest; +import password.pwm.http.servlet.helpdesk.HelpdeskClientState; +import password.pwm.http.servlet.helpdesk.HelpdeskServletUtil; + +public interface HelpdeskTargetUserRequest +{ + String userKey(); + + String verificationState(); + + default UserIdentity readTargetUser( final PwmRequest pwmRequest ) + throws PwmUnrecoverableException + { + return HelpdeskServletUtil.readUserIdentity( pwmRequest, this.userKey() ); + } + + default HelpdeskClientState readVerificationState( final PwmRequest pwmRequest ) + { + return HelpdeskClientState.fromClientString( pwmRequest, this.verificationState() ); + } +} diff --git a/server/src/main/java/password/pwm/http/servlet/newuser/NewUserFormUtils.java b/server/src/main/java/password/pwm/http/servlet/newuser/NewUserFormUtils.java index 69ed97e43e..f7c98f450e 100644 --- a/server/src/main/java/password/pwm/http/servlet/newuser/NewUserFormUtils.java +++ b/server/src/main/java/password/pwm/http/servlet/newuser/NewUserFormUtils.java @@ -109,7 +109,7 @@ static NewUserTokenData fromTokenPayload( { final DomainSecureService domainSecureService = pwmRequest.getPwmDomain().getSecureService(); - final Map payloadMap = tokenPayload.getData(); + final Map payloadMap = tokenPayload.data(); if ( !payloadMap.containsKey( NewUserServlet.TOKEN_PAYLOAD_ATTR ) ) { diff --git a/server/src/main/java/password/pwm/http/servlet/newuser/NewUserServlet.java b/server/src/main/java/password/pwm/http/servlet/newuser/NewUserServlet.java index c65c814f64..c231063567 100644 --- a/server/src/main/java/password/pwm/http/servlet/newuser/NewUserServlet.java +++ b/server/src/main/java/password/pwm/http/servlet/newuser/NewUserServlet.java @@ -523,7 +523,7 @@ public ProcessStatus handleEnterCodeRequest( final PwmRequest pwmRequest ) newUserBean.setProfileID( newUserTokenData.getProfileID() ); final NewUserForm newUserFormFromToken = newUserTokenData.getFormData(); - final TokenDestinationItem.Type tokenType = tokenPayload.getDestination().getType(); + final TokenDestinationItem.Type tokenType = tokenPayload.destination().getType(); if ( tokenType == TokenDestinationItem.Type.email ) { @@ -573,7 +573,7 @@ else if ( tokenType == TokenDestinationItem.Type.sms ) if ( pwmRequest.getDomainConfig().readSettingAsBoolean( PwmSetting.DISPLAY_TOKEN_SUCCESS_BUTTON ) ) { - pwmRequest.setAttribute( PwmRequestAttribute.TokenDestItems, tokenPayload.getDestination() ); + pwmRequest.setAttribute( PwmRequestAttribute.TokenDestItems, tokenPayload.destination() ); pwmRequest.forwardToJsp( JspUrl.NEW_USER_TOKEN_SUCCESS ); return ProcessStatus.Halt; } diff --git a/server/src/main/java/password/pwm/http/servlet/newuser/NewUserUtils.java b/server/src/main/java/password/pwm/http/servlet/newuser/NewUserUtils.java index 359f501792..b198de9e0f 100644 --- a/server/src/main/java/password/pwm/http/servlet/newuser/NewUserUtils.java +++ b/server/src/main/java/password/pwm/http/servlet/newuser/NewUserUtils.java @@ -213,7 +213,7 @@ static void createUser( final PasswordData temporaryPassword; { final RandomGeneratorConfig randomGeneratorConfig = RandomGeneratorConfig.make( pwmRequest.getPwmDomain(), - newUserProfile.getNewUserPasswordPolicy( pwmRequest.getPwmRequestContext() ) ); + newUserProfile.getNewUserPasswordPolicy( pwmRequest.getPwmRequestContext() ) ); temporaryPassword = PasswordUtility.generateRandom( pwmRequest.getLabel(), randomGeneratorConfig, pwmDomain ); } @@ -435,31 +435,22 @@ private static boolean testIfEntryNameExists( final PwmRequest pwmRequest, final String rdnValue ) - throws PwmUnrecoverableException, ChaiUnavailableException + throws PwmUnrecoverableException { final UserSearchService userSearchService = pwmRequest.getPwmDomain().getUserSearchEngine(); final SearchConfiguration searchConfiguration = SearchConfiguration.builder() .username( rdnValue ) .build(); - try - { - final Map> results = userSearchService.performMultiUserSearch( - searchConfiguration, 2, Collections.emptyList(), pwmRequest.getLabel() ); - return results != null && !results.isEmpty(); - } - catch ( final PwmOperationalException e ) - { - final String msg = "ldap error while searching for duplicate entry names: " + e.getMessage(); - NewUserUtils.LOGGER.error( pwmRequest, () -> msg ); - throw new PwmUnrecoverableException( new ErrorInformation( PwmError.ERROR_NEW_USER_FAILURE, msg ) ); - } + final Map> results = userSearchService.performMultiUserSearch( + searchConfiguration, 2, Collections.emptyList(), pwmRequest.getLabel() ); + return results != null && !results.isEmpty(); } private static void sendNewUserEmailConfirmation( final PwmRequest pwmRequest ) - throws PwmUnrecoverableException, ChaiUnavailableException + throws PwmUnrecoverableException { final PwmSession pwmSession = pwmRequest.getPwmSession(); final UserInfo userInfo = pwmSession.getUserInfo(); @@ -591,28 +582,31 @@ private static void remoteSendFormData( final NewUserBean newUserBean = NewUserServlet.getNewUserBean( pwmRequest ); final NewUserProfile newUserProfile = NewUserServlet.getNewUserProfile( pwmRequest ); - final FormDataRequestBean.FormInfo formInfo = FormDataRequestBean.FormInfo.builder() - .mode( mode ) - .moduleProfileID( newUserBean.getProfileID() ) - .sessionID( pwmRequest.getPwmSession().getLoginInfoBean().getGuid() ) - .module( FormDataRequestBean.FormType.NewUser ) - .build(); + final FormDataRequestBean.FormInfo formInfo = new FormDataRequestBean.FormInfo( + FormDataRequestBean.FormType.NewUser, + newUserBean.getProfileID(), + mode, + pwmRequest.getPwmSession().getLoginInfoBean().getGuid() ); - final FormDataRequestBean formDataRequestBean = FormDataRequestBean.builder() - .formInfo( formInfo ) - .formConfigurations( newUserProfile.readSettingAsForm( PwmSetting.NEWUSER_FORM ) ) - .formValues( newUserForm.getFormData() ) - .build(); + final FormDataRequestBean formDataRequestBean = new FormDataRequestBean( + newUserForm.getFormData(), + newUserProfile.readSettingAsForm( PwmSetting.NEWUSER_FORM ), + formInfo, + null, + null ); + + final FormDataResponseBean formDataResponseBean = restFormDataClient.invoke( + formDataRequestBean, + pwmRequest.getLocale() ); - final FormDataResponseBean formDataResponseBean = restFormDataClient.invoke( formDataRequestBean, pwmRequest.getLocale() ); - if ( formDataResponseBean.isError() ) + if ( formDataResponseBean.error() ) { final ErrorInformation error = new ErrorInformation( PwmError.ERROR_REMOTE_ERROR_VALUE, - formDataResponseBean.getErrorDetail(), + formDataResponseBean.errorDetail(), new String[] { - formDataResponseBean.getErrorMessage(), + formDataResponseBean.errorMessage(), } ); throw new PwmDataValidationException( error ); @@ -775,7 +769,7 @@ static ProcessStatus checkForTokenVerificationProgress( TokenUtil.initializeAndSendToken( pwmRequest.getPwmRequestContext(), TokenUtil.TokenInitAndSendRequest.builder() - .userInfo( macroRequest.getUserInfo() ) + .userInfo( macroRequest.userInfo() ) .tokenDestinationItem( tokenDestinationItem ) .emailToSend( PwmSetting.EMAIL_NEWUSER_VERIFICATION ) .tokenType( TokenType.NEWUSER ) diff --git a/server/src/main/java/password/pwm/http/servlet/oauth/OAuthConsumerServlet.java b/server/src/main/java/password/pwm/http/servlet/oauth/OAuthConsumerServlet.java index 1fe59fa2ce..db8b954feb 100644 --- a/server/src/main/java/password/pwm/http/servlet/oauth/OAuthConsumerServlet.java +++ b/server/src/main/java/password/pwm/http/servlet/oauth/OAuthConsumerServlet.java @@ -49,6 +49,7 @@ import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import java.io.IOException; +import java.time.Instant; import java.util.HashMap; import java.util.Map; import java.util.Optional; @@ -214,7 +215,7 @@ protected void processAction( final PwmRequest pwmRequest ) return; } - if ( resolveResults == null || resolveResults.getAccessToken() == null || resolveResults.getAccessToken().isEmpty() ) + if ( resolveResults == null || resolveResults.accessToken() == null || resolveResults.accessToken().isEmpty() ) { final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_OAUTH_ERROR, "browser redirect from oauth server did not include an access token" ); @@ -237,7 +238,7 @@ protected void processAction( final PwmRequest pwmRequest ) } */ - final String oauthSuppliedUsername = oAuthMachine.makeOAuthGetUserInfoRequest( pwmRequest, resolveResults.getAccessToken() ); + final String oauthSuppliedUsername = oAuthMachine.makeOAuthGetUserInfoRequest( pwmRequest, resolveResults.accessToken() ); if ( oAuthUseCaseCase == OAuthUseCase.ForgottenPassword ) { @@ -338,7 +339,7 @@ private static OAuthSettings makeOAuthSettings( final PwmRequest pwmRequest, fin private void redirectToForgottenPasswordServlet( final PwmRequest pwmRequest, final String oauthSuppliedUsername ) throws IOException, PwmUnrecoverableException { - final OAuthForgottenPasswordResults results = new OAuthForgottenPasswordResults( true, oauthSuppliedUsername ); + final OAuthForgottenPasswordResults results = new OAuthForgottenPasswordResults( true, oauthSuppliedUsername, Instant.now() ); final String encryptedResults = pwmRequest.getPwmDomain().getSecureService().encryptObjectToString( results ); final Map httpParams = new HashMap<>(); diff --git a/server/src/main/java/password/pwm/http/servlet/oauth/OAuthForgottenPasswordResults.java b/server/src/main/java/password/pwm/http/servlet/oauth/OAuthForgottenPasswordResults.java index 247ea522a9..6c614ee2cb 100644 --- a/server/src/main/java/password/pwm/http/servlet/oauth/OAuthForgottenPasswordResults.java +++ b/server/src/main/java/password/pwm/http/servlet/oauth/OAuthForgottenPasswordResults.java @@ -23,33 +23,12 @@ import java.time.Instant; /** - * This Json object gets sent as a redirect from the oauth consumer servlet to the ForgttenPasswordServlet. + * This Json object gets sent as a redirect from the oauth consumer servlet to the {@link password.pwm.http.servlet.forgottenpw.ForgottenPasswordServlet}. */ -public class OAuthForgottenPasswordResults +public record OAuthForgottenPasswordResults( + boolean authenticated, + String username, + Instant timestamp +) { - private final boolean authenticated; - private final String username; - private final Instant timestamp; - - public OAuthForgottenPasswordResults( final boolean authenticated, final String username ) - { - this.authenticated = authenticated; - this.username = username; - this.timestamp = Instant.now(); - } - - public boolean isAuthenticated( ) - { - return authenticated; - } - - public String getUsername( ) - { - return username; - } - - public Instant getTimestamp( ) - { - return timestamp; - } } diff --git a/server/src/main/java/password/pwm/http/servlet/oauth/OAuthMachine.java b/server/src/main/java/password/pwm/http/servlet/oauth/OAuthMachine.java index 702140143c..1a70863ddb 100644 --- a/server/src/main/java/password/pwm/http/servlet/oauth/OAuthMachine.java +++ b/server/src/main/java/password/pwm/http/servlet/oauth/OAuthMachine.java @@ -119,14 +119,14 @@ public void redirectUserToOAuthServer( final String code = config.readDomainProperty( DomainProperty.OAUTH_ID_REQUEST_TYPE ); final Map urlParams = new LinkedHashMap<>(); - urlParams.put( config.readDomainProperty( DomainProperty.HTTP_PARAM_OAUTH_CLIENT_ID ), settings.getClientID() ); + urlParams.put( config.readDomainProperty( DomainProperty.HTTP_PARAM_OAUTH_CLIENT_ID ), settings.clientID() ); urlParams.put( config.readDomainProperty( DomainProperty.HTTP_PARAM_OAUTH_RESPONSE_TYPE ), code ); urlParams.put( config.readDomainProperty( DomainProperty.HTTP_PARAM_OAUTH_STATE ), state ); urlParams.put( config.readDomainProperty( DomainProperty.HTTP_PARAM_OAUTH_REDIRECT_URI ), redirectUri ); - if ( StringUtil.notEmpty( settings.getScope() ) ) + if ( StringUtil.notEmpty( settings.scope() ) ) { - urlParams.put( config.readDomainProperty( DomainProperty.HTTP_PARAM_OAUTH_SCOPE ), settings.getScope() ); + urlParams.put( config.readDomainProperty( DomainProperty.HTTP_PARAM_OAUTH_SCOPE ), settings.scope() ); } if ( userIdentity != null ) @@ -135,7 +135,7 @@ public void redirectUserToOAuthServer( parametersValue.ifPresent( s -> urlParams.put( "parameters", s ) ); } - final String redirectUrl = PwmURL.appendAndEncodeUrlParameters( settings.getLoginURL(), urlParams ); + final String redirectUrl = PwmURL.appendAndEncodeUrlParameters( settings.loginURL(), urlParams ); pwmRequest.getPwmResponse().sendRedirect( redirectUrl ); pwmRequest.getPwmSession().getSessionStateBean().setOauthInProgress( true ); @@ -149,17 +149,17 @@ OAuthResolveResults makeOAuthResolveRequest( throws PwmUnrecoverableException { final DomainConfig config = pwmRequest.getDomainConfig(); - final String requestUrl = settings.getCodeResolveUrl(); + final String requestUrl = settings.codeResolveUrl(); final String grantType = config.readDomainProperty( DomainProperty.OAUTH_ID_ACCESS_GRANT_TYPE ); final String redirectUri = figureOauthSelfEndPointUrl( pwmRequest ); - final String clientID = settings.getClientID(); + final String clientID = settings.clientID(); final Map requestParams = new HashMap<>(); requestParams.put( config.readDomainProperty( DomainProperty.HTTP_PARAM_OAUTH_CODE ), requestCode ); requestParams.put( config.readDomainProperty( DomainProperty.HTTP_PARAM_OAUTH_GRANT_TYPE ), grantType ); requestParams.put( config.readDomainProperty( DomainProperty.HTTP_PARAM_OAUTH_REDIRECT_URI ), redirectUri ); requestParams.put( config.readDomainProperty( DomainProperty.HTTP_PARAM_OAUTH_CLIENT_ID ), clientID ); - requestParams.put( config.readDomainProperty( DomainProperty.HTTP_PARAM_OAUTH_CLIENT_SECRET ), settings.getSecret().getStringValue() ); + requestParams.put( config.readDomainProperty( DomainProperty.HTTP_PARAM_OAUTH_CLIENT_SECRET ), settings.secret().getStringValue() ); final PwmHttpClientResponse restResults = makeHttpRequest( pwmRequest, "oauth code resolver", settings, requestUrl, requestParams, null ); @@ -184,11 +184,11 @@ private OAuthResolveResults resolveResultsFromResponseBody( final String accessToken = readAttributeFromBodyMap( resolveResponseBodyStr, oauthAccessTokenParam ); final String refreshToken = readAttributeFromBodyMap( resolveResponseBodyStr, refreshTokenParam ); - return OAuthResolveResults.builder() - .accessToken( accessToken ) - .refreshToken( refreshToken ) - .expiresSeconds( expireSeconds ) - .build(); + return new OAuthResolveResults( + accessToken, + expireSeconds, + refreshToken ); + } private OAuthResolveResults makeOAuthRefreshRequest( @@ -198,7 +198,7 @@ private OAuthResolveResults makeOAuthRefreshRequest( throws PwmUnrecoverableException { final DomainConfig config = pwmRequest.getDomainConfig(); - final String requestUrl = settings.getCodeResolveUrl(); + final String requestUrl = settings.codeResolveUrl(); final String grantType = config.readDomainProperty( DomainProperty.OAUTH_ID_REFRESH_GRANT_TYPE ); final Map requestParams = new HashMap<>(); @@ -219,10 +219,10 @@ String makeOAuthGetUserInfoRequest( final PwmHttpClientResponse restResults; { final DomainConfig config = pwmRequest.getDomainConfig(); - final String requestUrl = settings.getAttributesUrl(); + final String requestUrl = settings.attributesUrl(); final Map requestParams = new HashMap<>(); requestParams.put( config.readDomainProperty( DomainProperty.HTTP_PARAM_OAUTH_ACCESS_TOKEN ), accessToken ); - requestParams.put( config.readDomainProperty( DomainProperty.HTTP_PARAM_OAUTH_ATTRIBUTES ), settings.getDnAttributeName() ); + requestParams.put( config.readDomainProperty( DomainProperty.HTTP_PARAM_OAUTH_ATTRIBUTES ), settings.dnAttributeName() ); restResults = makeHttpRequest( pwmRequest, "OAuth userinfo", settings, requestUrl, requestParams, accessToken ); } @@ -230,11 +230,11 @@ String makeOAuthGetUserInfoRequest( LOGGER.trace( sessionLabel, () -> "received attribute values from OAuth IdP for attributes: " ); - final String oauthSuppliedUsername = readAttributeFromBodyMap( resultBody, settings.getDnAttributeName() ); + final String oauthSuppliedUsername = readAttributeFromBodyMap( resultBody, settings.dnAttributeName() ); if ( StringUtil.isEmpty( oauthSuppliedUsername ) ) { - final String msg = "OAuth server did not respond with an username value for configured attribute '" + settings.getDnAttributeName() + "'"; + final String msg = "OAuth server did not respond with an username value for configured attribute '" + settings.dnAttributeName() + "'"; final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_OAUTH_ERROR, msg ); LOGGER.error( sessionLabel, errorInformation ); throw new PwmUnrecoverableException( errorInformation ); @@ -256,7 +256,7 @@ private static PwmHttpClientResponse makeHttpRequest( throws PwmUnrecoverableException { final String requestBody = PwmURL.encodeParametersToFormBody( requestParams ); - final List certs = settings.getCertificates(); + final List certs = settings.certificates(); final PwmHttpClientRequest pwmHttpClientRequest; { @@ -264,7 +264,7 @@ private static PwmHttpClientResponse makeHttpRequest( if ( StringUtil.isEmpty( accessToken ) ) { headers.put( HttpHeader.Authorization.getHttpName(), - new BasicAuthInfo( settings.getClientID(), settings.getSecret() ).toAuthHeader() ); + new BasicAuthInfo( settings.clientID(), settings.secret() ).toAuthHeader() ); } else { @@ -380,12 +380,14 @@ public boolean checkOAuthExpiration( loginInfoBean.getOauthRefToken() ); if ( resolveResults != null ) { - if ( resolveResults.getExpiresSeconds() > 0 ) + if ( resolveResults.expiresSeconds() > 0 ) { - final Instant accessTokenExpirationDate = Instant.ofEpochMilli( System.currentTimeMillis() + 1000 * resolveResults.getExpiresSeconds() ); - LOGGER.trace( sessionLabel, () -> "noted oauth access token expiration at timestamp " + StringUtil.toIsoDate( accessTokenExpirationDate ) ); + final Instant accessTokenExpirationDate = Instant.ofEpochMilli( System.currentTimeMillis() + + 1000 * resolveResults.expiresSeconds() ); + LOGGER.trace( sessionLabel, () -> "noted oauth access token expiration at timestamp " + + StringUtil.toIsoDate( accessTokenExpirationDate ) ); loginInfoBean.setOauthExp( accessTokenExpirationDate ); - loginInfoBean.setOauthRefToken( resolveResults.getRefreshToken() ); + loginInfoBean.setOauthRefToken( resolveResults.refreshToken() ); return false; } } @@ -407,7 +409,7 @@ private String makeStateStringForRequest( ) throws PwmUnrecoverableException { - final OAuthUseCase oAuthUseCase = settings.getUse(); + final OAuthUseCase oAuthUseCase = settings.use(); final String sessionId = pwmRequest.getPwmSession().getSessionStateBean().getSessionVerificationKey(); @@ -444,7 +446,7 @@ private Optional figureUsernameGrantParam( return Optional.empty(); } - final String macroText = settings.getUsernameSendValue(); + final String macroText = settings.usernameSendValue(); if ( StringUtil.isEmpty( macroText ) ) { return Optional.empty(); @@ -454,7 +456,7 @@ private Optional figureUsernameGrantParam( final String username = macroRequest.expandMacros( macroText ); LOGGER.debug( sessionLabel, () -> "calculated username value for user as: " + username ); - final String grantUrl = settings.getLoginURL(); + final String grantUrl = settings.loginURL(); final String signUrl = grantUrl.replace( "/grant", "/sign" ); final Map requestPayload; diff --git a/server/src/main/java/password/pwm/http/servlet/oauth/OAuthResolveResults.java b/server/src/main/java/password/pwm/http/servlet/oauth/OAuthResolveResults.java index 81e5d6eb5a..1836a186c1 100644 --- a/server/src/main/java/password/pwm/http/servlet/oauth/OAuthResolveResults.java +++ b/server/src/main/java/password/pwm/http/servlet/oauth/OAuthResolveResults.java @@ -20,14 +20,10 @@ package password.pwm.http.servlet.oauth; -import lombok.Builder; -import lombok.Value; - -@Value -@Builder( toBuilder = true ) -class OAuthResolveResults +record OAuthResolveResults( + String accessToken, + long expiresSeconds, + String refreshToken +) { - private String accessToken; - private long expiresSeconds; - private String refreshToken; } diff --git a/server/src/main/java/password/pwm/http/servlet/oauth/OAuthSettings.java b/server/src/main/java/password/pwm/http/servlet/oauth/OAuthSettings.java index 28589cba08..c651610e27 100644 --- a/server/src/main/java/password/pwm/http/servlet/oauth/OAuthSettings.java +++ b/server/src/main/java/password/pwm/http/servlet/oauth/OAuthSettings.java @@ -20,68 +20,83 @@ package password.pwm.http.servlet.oauth; -import lombok.Builder; -import lombok.Value; import password.pwm.config.DomainConfig; import password.pwm.config.PwmSetting; import password.pwm.config.profile.ForgottenPasswordProfile; import password.pwm.util.PasswordData; +import password.pwm.util.java.StringUtil; import java.security.cert.X509Certificate; import java.util.List; -@Value -@Builder -public class OAuthSettings +public record OAuthSettings( + String loginURL, + String codeResolveUrl, + String attributesUrl, + String scope, + String clientID, + PasswordData secret, + String dnAttributeName, + OAuthUseCase use, + List certificates, + String usernameSendValue +) { - private String loginURL; - private String codeResolveUrl; - private String attributesUrl; - private String scope; - private String clientID; - private PasswordData secret; - private String dnAttributeName; - private OAuthUseCase use; - private List certificates; - private String usernameSendValue; + private static final OAuthSettings EMPTY = new OAuthSettings( + null, + null, + null, + null, + null, + null, + null, + null, + List.of(), + null ); + + public static OAuthSettings empty() + { + return EMPTY; + } public boolean oAuthIsConfigured() { - return ( loginURL != null && !loginURL.isEmpty() ) - && ( codeResolveUrl != null && !codeResolveUrl.isEmpty() ) - && ( attributesUrl != null && !attributesUrl.isEmpty() ) - && ( clientID != null && !clientID.isEmpty() ) + return !StringUtil.isEmpty( loginURL ) + && !StringUtil.isEmpty( codeResolveUrl ) + && !StringUtil.isEmpty( attributesUrl ) + && !StringUtil.isEmpty( clientID ) && ( secret != null ) - && ( dnAttributeName != null && !dnAttributeName.isEmpty() ); + && !StringUtil.isEmpty( dnAttributeName ); } public static OAuthSettings forSSOAuthentication( final DomainConfig config ) { - return OAuthSettings.builder() - .loginURL( config.readSettingAsString( PwmSetting.OAUTH_ID_LOGIN_URL ) ) - .codeResolveUrl( config.readSettingAsString( PwmSetting.OAUTH_ID_CODERESOLVE_URL ) ) - .attributesUrl( config.readSettingAsString( PwmSetting.OAUTH_ID_ATTRIBUTES_URL ) ) - .clientID( config.readSettingAsString( PwmSetting.OAUTH_ID_CLIENTNAME ) ) - .secret( config.readSettingAsPassword( PwmSetting.OAUTH_ID_SECRET ) ) - .dnAttributeName( config.readSettingAsString( PwmSetting.OAUTH_ID_DN_ATTRIBUTE_NAME ) ) - .certificates( config.readSettingAsCertificate( PwmSetting.OAUTH_ID_CERTIFICATE ) ) - .scope( config.readSettingAsString( PwmSetting.OAUTH_ID_SCOPE ) ) - .use( OAuthUseCase.Authentication ) - .build(); + return new OAuthSettings( + config.readSettingAsString( PwmSetting.OAUTH_ID_LOGIN_URL ), + config.readSettingAsString( PwmSetting.OAUTH_ID_CODERESOLVE_URL ), + config.readSettingAsString( PwmSetting.OAUTH_ID_ATTRIBUTES_URL ), + config.readSettingAsString( PwmSetting.OAUTH_ID_SCOPE ), + config.readSettingAsString( PwmSetting.OAUTH_ID_CLIENTNAME ), + config.readSettingAsPassword( PwmSetting.OAUTH_ID_SECRET ), + config.readSettingAsString( PwmSetting.OAUTH_ID_DN_ATTRIBUTE_NAME ), + OAuthUseCase.Authentication, + config.readSettingAsCertificate( PwmSetting.OAUTH_ID_CERTIFICATE ), + null ); + } public static OAuthSettings forForgottenPassword( final ForgottenPasswordProfile config ) { - return OAuthSettings.builder() - .loginURL( config.readSettingAsString( PwmSetting.RECOVERY_OAUTH_ID_LOGIN_URL ) ) - .codeResolveUrl( config.readSettingAsString( PwmSetting.RECOVERY_OAUTH_ID_CODERESOLVE_URL ) ) - .attributesUrl( config.readSettingAsString( PwmSetting.RECOVERY_OAUTH_ID_ATTRIBUTES_URL ) ) - .clientID( config.readSettingAsString( PwmSetting.RECOVERY_OAUTH_ID_CLIENTNAME ) ) - .secret( config.readSettingAsPassword( PwmSetting.RECOVERY_OAUTH_ID_SECRET ) ) - .dnAttributeName( config.readSettingAsString( PwmSetting.RECOVERY_OAUTH_ID_DN_ATTRIBUTE_NAME ) ) - .certificates( config.readSettingAsCertificate( PwmSetting.RECOVERY_OAUTH_ID_CERTIFICATE ) ) - .use( OAuthUseCase.ForgottenPassword ) - .usernameSendValue( config.readSettingAsString( PwmSetting.RECOVERY_OAUTH_ID_USERNAME_SEND_VALUE ) ) - .build(); + return new OAuthSettings( + config.readSettingAsString( PwmSetting.RECOVERY_OAUTH_ID_LOGIN_URL ), + config.readSettingAsString( PwmSetting.RECOVERY_OAUTH_ID_CODERESOLVE_URL ), + config.readSettingAsString( PwmSetting.RECOVERY_OAUTH_ID_ATTRIBUTES_URL ), + null, + config.readSettingAsString( PwmSetting.RECOVERY_OAUTH_ID_CLIENTNAME ), + config.readSettingAsPassword( PwmSetting.RECOVERY_OAUTH_ID_SECRET ), + config.readSettingAsString( PwmSetting.RECOVERY_OAUTH_ID_DN_ATTRIBUTE_NAME ), + OAuthUseCase.ForgottenPassword, + config.readSettingAsCertificate( PwmSetting.RECOVERY_OAUTH_ID_CERTIFICATE ), + config.readSettingAsString( PwmSetting.RECOVERY_OAUTH_ID_USERNAME_SEND_VALUE ) ); } } diff --git a/server/src/main/java/password/pwm/http/servlet/peoplesearch/PeopleSearchDataReader.java b/server/src/main/java/password/pwm/http/servlet/peoplesearch/PeopleSearchDataReader.java index ff402089dd..e324674c1b 100644 --- a/server/src/main/java/password/pwm/http/servlet/peoplesearch/PeopleSearchDataReader.java +++ b/server/src/main/java/password/pwm/http/servlet/peoplesearch/PeopleSearchDataReader.java @@ -122,7 +122,11 @@ SearchResultBean makeSearchResultBean( final SearchResultBean cachedResult = pwmRequest.getPwmDomain().getCacheService().get( cacheKey, SearchResultBean.class ); if ( cachedResult != null ) { - final SearchResultBean copyWithCacheSet = cachedResult.toBuilder().fromCache( true ).build(); + final SearchResultBean copyWithCacheSet = new SearchResultBean( + cachedResult.searchResults(), + cachedResult.sizeExceeded(), + cachedResult.aboutResultMessage(), + true ); StatisticsClient.incrementStat( pwmRequest, Statistic.PEOPLESEARCH_CACHE_HITS ); return copyWithCacheSet; } @@ -133,12 +137,11 @@ SearchResultBean makeSearchResultBean( } // if not in cache, build results from ldap - final SearchResultBean searchResultBean = makeSearchResultsImpl( searchRequestBean ) - .toBuilder().fromCache( false ).build(); + final SearchResultBean searchResultBean = makeSearchResultsImpl( searchRequestBean ); StatisticsClient.incrementStat( pwmRequest, Statistic.PEOPLESEARCH_SEARCHES ); storeDataInCache( cacheKey, searchResultBean ); - LOGGER.trace( pwmRequest, () -> "returning " + searchResultBean.getSearchResults().size() + LOGGER.trace( pwmRequest, () -> "returning " + searchResultBean.searchResults().size() + " results for search request " + JsonFactory.get().serialize( searchRequestBean ) ); return searchResultBean; @@ -173,22 +176,23 @@ OrgChartDataBean makeOrgChartData( } } - final OrgChartDataBean orgChartData = new OrgChartDataBean(); // make self reference - orgChartData.setSelf( makeOrgChartReferenceForIdentity( userIdentity ) ); + final OrgChartReferenceBean self = makeOrgChartReferenceForIdentity( userIdentity ); + OrgChartReferenceBean parent = null; { // make parent reference final List parentIdentities = readUserDNAttributeValues( userIdentity, peopleSearchConfiguration.getOrgChartParentAttr( userIdentity ) ); - if ( parentIdentities != null && !parentIdentities.isEmpty() ) + if ( !CollectionUtil.isEmpty( parentIdentities ) ) { final UserIdentity parentIdentity = parentIdentities.get( 0 ); - orgChartData.setParent( makeOrgChartReferenceForIdentity( parentIdentity ) ); + parent = makeOrgChartReferenceForIdentity( parentIdentity ); } } int childCount = 0; + List children = List.of(); if ( !noChildren ) { // make children reference @@ -199,9 +203,9 @@ OrgChartDataBean makeOrgChartData( final OrgChartReferenceBean childReference = makeOrgChartReferenceForIdentity( childIdentity ); if ( childReference != null ) { - if ( childReference.getDisplayNames() != null && !childReference.getDisplayNames().isEmpty() ) + if ( !CollectionUtil.isEmpty( childReference.displayNames() ) ) { - final String firstDisplayName = childReference.getDisplayNames().iterator().next(); + final String firstDisplayName = childReference.displayNames().iterator().next(); sortedChildren.put( firstDisplayName, childReference ); } else @@ -211,24 +215,25 @@ OrgChartDataBean makeOrgChartData( childCount++; } } - orgChartData.setChildren( List.copyOf( sortedChildren.values() ) ); + children = List.copyOf( sortedChildren.values() ); } + OrgChartReferenceBean assistant = null; if ( StringUtil.notEmpty( peopleSearchConfiguration.getOrgChartAssistantAttr( userIdentity ) ) ) { final List assistantIdentities = readUserDNAttributeValues( userIdentity, peopleSearchConfiguration.getOrgChartAssistantAttr( userIdentity ) ); - if ( assistantIdentities != null && !assistantIdentities.isEmpty() ) + if ( !CollectionUtil.isEmpty( assistantIdentities ) ) { final UserIdentity assistantIdentity = assistantIdentities.iterator().next(); - final OrgChartReferenceBean assistantReference = makeOrgChartReferenceForIdentity( assistantIdentity ); - if ( assistantReference != null ) - { - orgChartData.setAssistant( assistantReference ); - } + assistant = makeOrgChartReferenceForIdentity( assistantIdentity ); } } final TimeDuration totalTime = TimeDuration.fromCurrent( startTime ); + + + + final OrgChartDataBean orgChartData = new OrgChartDataBean( parent, self, assistant, children ); storeDataInCache( cacheKey, orgChartData ); { final int finalChildCount = childCount; @@ -263,28 +268,22 @@ UserDetailBean makeUserDetailRequest( final UserSearchResults detailResults = doDetailLookup( userIdentity ); final Map searchResults = detailResults.getResults().get( userIdentity ); - - final UserDetailBean userDetailBean = new UserDetailBean(); - userDetailBean.setUserKey( PeopleSearchServlet.obfuscateUserIdentity( pwmRequest, userIdentity ) ); final List detailFormConfig = this.peopleSearchConfiguration.getSearchDetailForm(); final Map attributeBeans = convertResultMapToBeans( userIdentity, detailFormConfig, searchResults ); - - userDetailBean.setDetail( attributeBeans ); - final PhotoDataReader photoDataReader = photoDataReader( userIdentity ); - final Optional photoURL = photoDataReader.figurePhotoURL( ); - photoURL.ifPresent( userDetailBean::setPhotoURL ); - - final List displayName = figureDisplaynames( userIdentity ); - if ( displayName != null ) - { - userDetailBean.setDisplayNames( displayName ); - } + final String photoURL = photoDataReader.figurePhotoURL( ).orElse( null ); + final List displayNames = figureDisplaynames( userIdentity ); - userDetailBean.setLinks( makeUserDetailLinks( userIdentity ) ); + final UserDetailBean userDetailBean = new UserDetailBean( + displayNames, + PeopleSearchServlet.obfuscateUserIdentity( pwmRequest, userIdentity ), + attributeBeans, + photoURL, + makeUserDetailLinks( userIdentity ) ); LOGGER.trace( pwmRequest, () -> "finished building userDetail result of " + userIdentity + " in " + TimeDuration.fromCurrent( startTime ).asCompactString() ); + storeDataInCache( cacheKey, userDetailBean ); return userDetailBean; } @@ -310,13 +309,10 @@ private List makeUserDetailLinks( final UserIdentity actorIde final MacroRequest macroRequest = getMacroMachine( actorIdentity ); for ( final Map.Entry entry : linkMap.entrySet() ) { - final String key = entry.getKey(); + final String name = entry.getKey(); final String value = entry.getValue(); - final String parsedValue = macroRequest.expandMacros( value ); - final LinkReferenceBean linkReference = new LinkReferenceBean(); - linkReference.setName( key ); - linkReference.setLink( parsedValue ); - returnList.add( linkReference ); + final String link = macroRequest.expandMacros( value ); + returnList.add( new LinkReferenceBean( name, link ) ); } return returnList; } @@ -387,15 +383,13 @@ private OrgChartReferenceBean makeOrgChartReferenceForIdentity( ) throws PwmUnrecoverableException { - final OrgChartReferenceBean orgChartReferenceBean = new OrgChartReferenceBean(); - orgChartReferenceBean.setUserKey( PeopleSearchServlet.obfuscateUserIdentity( pwmRequest, userIdentity ) ); final PhotoDataReader photoDataReader = photoDataReader( userIdentity ); - photoDataReader.figurePhotoURL( ).ifPresent( orgChartReferenceBean::setPhotoURL ); + final String userKey = PeopleSearchServlet.obfuscateUserIdentity( pwmRequest, userIdentity ); + final String photoUrl = photoDataReader.figurePhotoURL( ).orElse( null ); final List displayLabels = figureDisplaynames( userIdentity ); - orgChartReferenceBean.setDisplayNames( displayLabels ); - return orgChartReferenceBean; + return new OrgChartReferenceBean( userKey, displayLabels, photoUrl ); } private List readUserDNAttributeValues( @@ -507,7 +501,7 @@ private List figureDisplaynames( displayLabels.add( displayLabel ); } } - return displayLabels; + return List.copyOf( displayLabels ); } Optional readPhotoData( final UserIdentity userIdentity ) @@ -530,53 +524,61 @@ private Map convertResultMapToBeans( { if ( formConfiguration.isRequired() || searchResults.containsKey( formConfiguration.getName() ) ) { - final AttributeDetailBean bean = new AttributeDetailBean(); - bean.setName( formConfiguration.getName() ); - bean.setLabel( formConfiguration.getLabel( pwmRequest.getLocale() ) ); - bean.setType( formConfiguration.getType() ); + + boolean searchable = false; if ( searchAttributes.contains( formConfiguration.getName() ) ) { if ( formConfiguration.getType() != FormConfiguration.Type.userDN ) { - bean.setSearchable( true ); + searchable = true; } } + + + List userReferences = List.of(); + List values = List.of(); if ( formConfiguration.getType() == FormConfiguration.Type.userDN ) { if ( searchResults.containsKey( formConfiguration.getName() ) ) { final List identityValues = readUserDNAttributeValues( userIdentity, formConfiguration.getName() ); - final TreeMap userReferences = new TreeMap<>(); + final List references = new ArrayList<>(); for ( final UserIdentity loopIdentity : identityValues ) { final String displayValue = figureDisplaynameValue( pwmRequest, loopIdentity ); - final UserReferenceBean userReference = new UserReferenceBean(); - userReference.setUserKey( PeopleSearchServlet.obfuscateUserIdentity( pwmRequest, loopIdentity ) ); - userReference.setDisplayName( displayValue ); - userReferences.put( displayValue, userReference ); + final UserReferenceBean userReference = new UserReferenceBean( + PeopleSearchServlet.obfuscateUserIdentity( pwmRequest, loopIdentity ), + displayValue ); + references.add( userReference ); } - bean.setUserReferences( userReferences.values() ); + Collections.sort( references ); + userReferences = List.copyOf( references ); } } else { if ( formConfiguration.isMultivalue() ) { - bean.setValues( readUserMultiAttributeValues( pwmRequest, userIdentity, formConfiguration.getName() ) ); + values = readUserMultiAttributeValues( pwmRequest, userIdentity, formConfiguration.getName() ); } else { if ( searchResults.containsKey( formConfiguration.getName() ) ) { - bean.setValues( Collections.singletonList( searchResults.get( formConfiguration.getName() ) ) ); + values = List.of( searchResults.get( formConfiguration.getName() ) ); } else { - bean.setValues( Collections.emptyList() ); + values = List.of(); } } } - returnObj.put( formConfiguration.getName(), bean ); + returnObj.put( formConfiguration.getName(), new AttributeDetailBean( + formConfiguration.getName(), + formConfiguration.getLabel( pwmRequest.getLocale() ), + formConfiguration.getType(), + + values, userReferences, searchable ) ); } } return returnObj; @@ -616,11 +618,12 @@ void checkIfUserIdentityViewable( filterString = filterString.replace( "**", "*" ); } - final UserPermission userPermission = UserPermission.builder() - .type( UserPermissionType.ldapQuery ) - .ldapQuery( filterString ) - .ldapProfileID( userIdentity.getLdapProfileID() ) - .build(); + final UserPermission userPermission = new UserPermission( + UserPermissionType.ldapQuery, + userIdentity.getLdapProfileID(), + null, + filterString ); + return UserPermissionUtility.testUserPermission( pwmRequest.getPwmRequestContext(), userIdentity, userPermission ); }; @@ -630,14 +633,16 @@ void checkIfUserIdentityViewable( { if ( !result ) { - final String msg = "attempt to read data of out-of-scope userDN '" + userIdentity.toDisplayString() + "' by user " + userIdentity.toDisplayString(); + final String msg = "attempt to read data of out-of-scope userDN '" + userIdentity.toDisplayString() + + "' by user " + userIdentity.toDisplayString(); LOGGER.warn( pwmRequest, () -> msg ); throw PwmUnrecoverableException.newException( PwmError.ERROR_SERVICE_NOT_AVAILABLE, msg ); } } finally { - LOGGER.trace( pwmRequest, () -> "completed checkIfUserViewable for " + userIdentity.toDisplayString() + " in ", TimeDuration.fromCurrent( startTime ) ); + LOGGER.trace( pwmRequest, () -> "completed checkIfUserViewable for " + userIdentity.toDisplayString() + + " in ", TimeDuration.fromCurrent( startTime ) ); } } @@ -763,34 +768,23 @@ private SearchResultBean makeSearchResultsImpl( } else { - return SearchResultBean.builder().searchResults( Collections.emptyList() ).build(); + return SearchResultBean.empty(); } } final UserSearchService userSearchService = pwmRequest.getPwmDomain().getUserSearchEngine(); - final UserSearchResults results; - final boolean sizeExceeded; - try - { - final List searchForm = peopleSearchConfiguration.getResultForm(); - final int maxResults = peopleSearchConfiguration.getResultLimit(); - final Locale locale = pwmRequest.getLocale(); - results = userSearchService.performMultiUserSearchFromForm( locale, searchConfiguration, maxResults, searchForm, pwmRequest.getLabel() ); - sizeExceeded = results.isSizeExceeded(); - } - catch ( final PwmOperationalException e ) - { - final ErrorInformation errorInformation = e.getErrorInformation(); - LOGGER.error( pwmRequest.getLabel(), errorInformation::toDebugStr ); - throw new PwmUnrecoverableException( errorInformation ); - } + final List searchForm = peopleSearchConfiguration.getResultForm(); + final int maxResults = peopleSearchConfiguration.getResultLimit(); + final Locale locale = pwmRequest.getLocale(); + final UserSearchResults results = userSearchService.performMultiUserSearchFromForm( locale, searchConfiguration, maxResults, searchForm, pwmRequest.getLabel() ); + final boolean sizeExceeded = results.isSizeExceeded(); final List> resultOutput = new ArrayList<>( results.resultsAsJsonOutput( userIdentity -> PeopleSearchServlet.obfuscateUserIdentity( pwmRequest, userIdentity ), null ) ); - if ( searchRequest.isIncludeDisplayName() ) + if ( searchRequest.includeDisplayName() ) { for ( final Map map : resultOutput ) { @@ -823,11 +817,7 @@ private SearchResultBean makeSearchResultsImpl( } ); - return SearchResultBean.builder() - .sizeExceeded( sizeExceeded ) - .searchResults( resultOutput ) - .aboutResultMessage( aboutMessage ) - .build(); + return new SearchResultBean( resultOutput, sizeExceeded, aboutMessage, false ); } private Optional makeSearchConfiguration( @@ -847,50 +837,44 @@ private Optional makeSearchConfiguration( builder.chaiProvider( pwmRequest.getClientConnectionHolder().getActorChaiProvider() ); } - final SearchRequestBean.SearchMode searchMode = searchRequest.getMode() == null - ? SearchRequestBean.SearchMode.simple - : searchRequest.getMode(); + final SearchRequestBean.SearchMode searchMode = searchRequest.mode(); switch ( searchMode ) { - case simple: - { - if ( StringUtil.isEmpty( searchRequest.getUsername() ) ) - { - return Optional.empty(); - } - - builder.filter( makeSimpleSearchFilter() ); - builder.username( searchRequest.getUsername() ); - } - break; - - case advanced: - { - if ( CollectionUtil.isEmpty( searchRequest.nonEmptySearchValues() ) ) - { - return Optional.empty(); - } - - final Map formValues = new LinkedHashMap<>(); - final Map requestSearchValues = SearchRequestBean.searchValueToMap( searchRequest.getSearchValues() ); - for ( final FormConfiguration formConfiguration : peopleSearchConfiguration.getSearchForm() ) - { - final String attribute = formConfiguration.getName(); - final String value = requestSearchValues.get( attribute ); - if ( StringUtil.notEmpty( value ) ) + case simple -> { - formValues.put( formConfiguration, value ); + if ( StringUtil.isEmpty( searchRequest.username() ) ) + { + return Optional.empty(); + } + + builder.filter( makeSimpleSearchFilter() ); + builder.username( searchRequest.username() ); } - } + case advanced -> + { + if ( CollectionUtil.isEmpty( searchRequest.searchValues() ) ) + { + return Optional.empty(); + } - builder.filter( makeAdvancedFilter( requestSearchValues ) ); - builder.formValues( formValues ); - } - break; + final Map formValues = new LinkedHashMap<>(); + final Map requestSearchValues = + SearchRequestBean.searchValueToMap( searchRequest.searchValues() ); + for ( final FormConfiguration formConfiguration : peopleSearchConfiguration.getSearchForm() ) + { + final String attribute = formConfiguration.getName(); + final String value = requestSearchValues.get( attribute ); + if ( StringUtil.notEmpty( value ) ) + { + formValues.put( formConfiguration, value ); + } + } - default: - PwmUtil.unhandledSwitchStatement( searchMode ); + builder.filter( makeAdvancedFilter( requestSearchValues ) ); + builder.formValues( formValues ); + } + default -> PwmUtil.unhandledSwitchStatement( searchMode ); } return Optional.of( builder.build() ); @@ -939,9 +923,9 @@ public List getMailToLink( if ( depth > 0 ) { final OrgChartDataBean orgChartDataBean = this.makeOrgChartData( userIdentity, false ); - for ( final OrgChartReferenceBean orgChartReferenceBean : orgChartDataBean.getChildren() ) + for ( final OrgChartReferenceBean orgChartReferenceBean : orgChartDataBean.children() ) { - final String userKey = orgChartReferenceBean.getUserKey(); + final String userKey = orgChartReferenceBean.userKey(); final UserIdentity childIdentity = PeopleSearchServlet.readUserIdentityFromKey( pwmRequest, userKey ); returnValues.addAll( getMailToLink( childIdentity, depth - 1 ) ); } @@ -1042,7 +1026,7 @@ private void doJob() // export display card if ( orgChartExportState.getIncludeData().contains( OrgChartExportState.IncludeData.displayCard ) ) { - outputRowValues.addAll( orgChartDataBean.getSelf().displayNames ); + outputRowValues.addAll( orgChartDataBean.self().displayNames() ); } @@ -1050,9 +1034,9 @@ private void doJob() if ( orgChartExportState.getIncludeData().contains( OrgChartExportState.IncludeData.displayForm ) ) { final UserDetailBean userDetailBean = makeUserDetailRequest( userIdentity ); - for ( final Map.Entry entry : userDetailBean.getDetail().entrySet() ) + for ( final Map.Entry entry : userDetailBean.detail().entrySet() ) { - final List values = entry.getValue().getValues(); + final List values = entry.getValue().values(); if ( CollectionUtil.isEmpty( values ) ) { outputRowValues.add( " " ); @@ -1074,12 +1058,12 @@ else if ( values.size() == 1 ) if ( depth > 0 && orgChartExportState.getRowCounter().get() < peopleSearchConfiguration.getExportCsvMaxItems() ) { - final List children = orgChartDataBean.getChildren(); + final List children = orgChartDataBean.children(); if ( !CollectionUtil.isEmpty( children ) ) { for ( final OrgChartReferenceBean child : children ) { - final String childKey = child.getUserKey(); + final String childKey = child.userKey(); final UserIdentity childIdentity = PeopleSearchServlet.readUserIdentityFromKey( pwmRequest, childKey ); final OrgChartCsvRowOutputJob job = new OrgChartCsvRowOutputJob( orgChartExportState, childIdentity, depth - 1, workforceID ); orgChartExportState.getExecutor().execute( job ); diff --git a/server/src/main/java/password/pwm/http/servlet/peoplesearch/PeopleSearchServlet.java b/server/src/main/java/password/pwm/http/servlet/peoplesearch/PeopleSearchServlet.java index 19333f29c9..7499501126 100644 --- a/server/src/main/java/password/pwm/http/servlet/peoplesearch/PeopleSearchServlet.java +++ b/server/src/main/java/password/pwm/http/servlet/peoplesearch/PeopleSearchServlet.java @@ -157,7 +157,7 @@ public ProcessStatus restSearchRequest( addExpiresHeadersToResponse( pwmRequest ); pwmRequest.outputJsonResult( restResultBean ); - LOGGER.trace( pwmRequest, () -> "returning " + searchResultBean.getSearchResults().size() + " results for search request " + LOGGER.trace( pwmRequest, () -> "returning " + searchResultBean.searchResults().size() + " results for search request " + JsonFactory.get().serialize( searchRequest ) ); return ProcessStatus.Halt; } diff --git a/server/src/main/java/password/pwm/http/servlet/peoplesearch/PhotoDataReader.java b/server/src/main/java/password/pwm/http/servlet/peoplesearch/PhotoDataReader.java index a719b1b6c9..faf595b625 100644 --- a/server/src/main/java/password/pwm/http/servlet/peoplesearch/PhotoDataReader.java +++ b/server/src/main/java/password/pwm/http/servlet/peoplesearch/PhotoDataReader.java @@ -294,11 +294,11 @@ public static void servletRespondWithPhoto( { final PhotoDataBean photoDataBean = optionalPhotoDataBean.get(); final HttpServletResponse resp = pwmRequest.getPwmResponse().getHttpServletResponse(); - resp.setContentType( photoDataBean.getMimeType() ); + resp.setContentType( photoDataBean.mimeType() ); - if ( photoDataBean.getContents() != null && !photoDataBean.getContents().isEmpty() ) + if ( photoDataBean.contents() != null && !photoDataBean.contents().isEmpty() ) { - JavaHelper.copy( photoDataBean.getContents().newByteArrayInputStream(), outputStream ); + JavaHelper.copy( photoDataBean.contents().newByteArrayInputStream(), outputStream ); } } } diff --git a/server/src/main/java/password/pwm/http/servlet/peoplesearch/SearchRequestBean.java b/server/src/main/java/password/pwm/http/servlet/peoplesearch/SearchRequestBean.java index 44d0d9a1bf..d3b0068a98 100644 --- a/server/src/main/java/password/pwm/http/servlet/peoplesearch/SearchRequestBean.java +++ b/server/src/main/java/password/pwm/http/servlet/peoplesearch/SearchRequestBean.java @@ -20,26 +20,32 @@ package password.pwm.http.servlet.peoplesearch; -import lombok.Builder; -import lombok.Value; import password.pwm.util.java.CollectionUtil; -import password.pwm.util.java.StringUtil; +import password.pwm.util.java.CollectorUtil; -import java.util.Collections; import java.util.List; import java.util.Map; -import java.util.stream.Collectors; -@Value -@Builder -public class SearchRequestBean -{ - @Builder.Default - private SearchMode mode = SearchMode.simple; +public record SearchRequestBean( + SearchMode mode, - private String username; - private List searchValues; - private boolean includeDisplayName; + String username, + List searchValues, + boolean includeDisplayName +) +{ + public SearchRequestBean( + final SearchMode mode, + final String username, + final List searchValues, + final boolean includeDisplayName + ) + { + this.mode = mode == null ? SearchMode.simple : mode; + this.username = username == null ? "" : username; + this.searchValues = CollectionUtil.stripNulls( searchValues ); + this.includeDisplayName = includeDisplayName; + } public enum SearchMode { @@ -47,37 +53,19 @@ public enum SearchMode advanced, } - @Value - public static class SearchValue + public record SearchValue( + String key, + String value + ) { - private String key; - private String value; } public static Map searchValueToMap( final List input ) { return input.stream() - .collect( Collectors.toUnmodifiableMap( - SearchValue::getKey, - SearchValue::getValue + .collect( CollectorUtil.toUnmodifiableLinkedMap( + SearchValue::key, + SearchValue::value ) ); } - - public List nonEmptySearchValues() - { - return filterNonEmptySearchValues( getSearchValues() ); - } - - public static List filterNonEmptySearchValues( final List input ) - { - if ( CollectionUtil.isEmpty( input ) ) - { - return Collections.emptyList(); - } - - return input.stream() - .filter( searchValue -> !StringUtil.isEmpty( searchValue.getKey() ) ) - .filter( searchValue -> !StringUtil.isEmpty( searchValue.getValue() ) ) - .collect( Collectors.toUnmodifiableList() ); - } } diff --git a/server/src/main/java/password/pwm/http/servlet/peoplesearch/bean/AttributeDetailBean.java b/server/src/main/java/password/pwm/http/servlet/peoplesearch/bean/AttributeDetailBean.java index 48dec27e44..8642789cfb 100644 --- a/server/src/main/java/password/pwm/http/servlet/peoplesearch/bean/AttributeDetailBean.java +++ b/server/src/main/java/password/pwm/http/servlet/peoplesearch/bean/AttributeDetailBean.java @@ -20,19 +20,19 @@ package password.pwm.http.servlet.peoplesearch.bean; -import lombok.Data; import password.pwm.config.value.data.FormConfiguration; import java.util.Collection; import java.util.List; -@Data -public class AttributeDetailBean +public record AttributeDetailBean( + String name, + String label, + FormConfiguration.Type type, + List values, + Collection userReferences, + boolean searchable +) { - private String name; - private String label; - private FormConfiguration.Type type; - private List values; - private Collection userReferences; - private boolean searchable; } + diff --git a/server/src/main/java/password/pwm/http/servlet/peoplesearch/bean/LinkReferenceBean.java b/server/src/main/java/password/pwm/http/servlet/peoplesearch/bean/LinkReferenceBean.java index da2d99513a..df38ebe6ae 100644 --- a/server/src/main/java/password/pwm/http/servlet/peoplesearch/bean/LinkReferenceBean.java +++ b/server/src/main/java/password/pwm/http/servlet/peoplesearch/bean/LinkReferenceBean.java @@ -20,11 +20,9 @@ package password.pwm.http.servlet.peoplesearch.bean; -import lombok.Data; - -@Data -public class LinkReferenceBean +public record LinkReferenceBean( + String name, + String link +) { - private String name; - private String link; } diff --git a/server/src/main/java/password/pwm/http/servlet/peoplesearch/bean/OrgChartDataBean.java b/server/src/main/java/password/pwm/http/servlet/peoplesearch/bean/OrgChartDataBean.java index 3bcea73566..98f30884b8 100644 --- a/server/src/main/java/password/pwm/http/servlet/peoplesearch/bean/OrgChartDataBean.java +++ b/server/src/main/java/password/pwm/http/servlet/peoplesearch/bean/OrgChartDataBean.java @@ -20,16 +20,27 @@ package password.pwm.http.servlet.peoplesearch.bean; -import lombok.Data; +import password.pwm.util.java.CollectionUtil; -import java.util.Collections; import java.util.List; -@Data -public class OrgChartDataBean +public record OrgChartDataBean( + OrgChartReferenceBean parent, + OrgChartReferenceBean self, + OrgChartReferenceBean assistant, + List children +) { - private OrgChartReferenceBean parent; - private OrgChartReferenceBean self; - private OrgChartReferenceBean assistant; - private List children = Collections.emptyList(); + public OrgChartDataBean( + final OrgChartReferenceBean parent, + final OrgChartReferenceBean self, + final OrgChartReferenceBean assistant, + final List children + ) + { + this.parent = parent; + this.self = self; + this.assistant = assistant; + this.children = CollectionUtil.stripNulls( children ); + } } diff --git a/server/src/main/java/password/pwm/http/servlet/peoplesearch/bean/OrgChartReferenceBean.java b/server/src/main/java/password/pwm/http/servlet/peoplesearch/bean/OrgChartReferenceBean.java index be0edb0f3a..685701a9d4 100644 --- a/server/src/main/java/password/pwm/http/servlet/peoplesearch/bean/OrgChartReferenceBean.java +++ b/server/src/main/java/password/pwm/http/servlet/peoplesearch/bean/OrgChartReferenceBean.java @@ -20,15 +20,24 @@ package password.pwm.http.servlet.peoplesearch.bean; -import lombok.Data; +import password.pwm.util.java.CollectionUtil; -import java.util.ArrayList; import java.util.List; -@Data -public class OrgChartReferenceBean +public record OrgChartReferenceBean( + String userKey, + List displayNames, + String photoURL +) { - public String userKey; - public List displayNames = new ArrayList<>(); - public String photoURL; + public OrgChartReferenceBean( + final String userKey, + final List displayNames, + final String photoURL + ) + { + this.userKey = userKey; + this.displayNames = CollectionUtil.stripNulls( displayNames ); + this.photoURL = photoURL; + } } diff --git a/server/src/main/java/password/pwm/http/servlet/peoplesearch/bean/SearchAttributeBean.java b/server/src/main/java/password/pwm/http/servlet/peoplesearch/bean/SearchAttributeBean.java index 8a60621953..33cf331d22 100644 --- a/server/src/main/java/password/pwm/http/servlet/peoplesearch/bean/SearchAttributeBean.java +++ b/server/src/main/java/password/pwm/http/servlet/peoplesearch/bean/SearchAttributeBean.java @@ -20,25 +20,20 @@ package password.pwm.http.servlet.peoplesearch.bean; -import lombok.Builder; -import lombok.Value; import password.pwm.config.value.data.FormConfiguration; import java.util.ArrayList; -import java.util.Collections; import java.util.List; import java.util.Locale; import java.util.Map; -@Value -@Builder -public class SearchAttributeBean +public record SearchAttributeBean( + String attribute, + String label, + FormConfiguration.Type type, + Map options +) { - private String attribute; - private String label; - private FormConfiguration.Type type; - private Map options; - public static List searchAttributesFromForm( final Locale locale, final List formConfigurations @@ -50,16 +45,15 @@ public static List searchAttributesFromForm( final String attribute = formConfiguration.getName(); final String label = formConfiguration.getLabel( locale ); - final SearchAttributeBean searchAttribute = SearchAttributeBean.builder() - .attribute( attribute ) - .type( formConfiguration.getType() ) - .label( label ) - .options( formConfiguration.getSelectOptions() ) - .build(); + final SearchAttributeBean searchAttribute = new SearchAttributeBean( + attribute, + label, + formConfiguration.getType(), + formConfiguration.getSelectOptions() ); returnList.add( searchAttribute ); } - return Collections.unmodifiableList( returnList ); + return List.copyOf( returnList ); } } diff --git a/server/src/main/java/password/pwm/http/servlet/peoplesearch/bean/SearchResultBean.java b/server/src/main/java/password/pwm/http/servlet/peoplesearch/bean/SearchResultBean.java index 97889f9e85..3250b02205 100644 --- a/server/src/main/java/password/pwm/http/servlet/peoplesearch/bean/SearchResultBean.java +++ b/server/src/main/java/password/pwm/http/servlet/peoplesearch/bean/SearchResultBean.java @@ -20,20 +20,41 @@ package password.pwm.http.servlet.peoplesearch.bean; -import lombok.Builder; -import lombok.Data; -import lombok.Value; +import password.pwm.util.java.CollectionUtil; import java.util.List; import java.util.Map; -@Value -@Builder( toBuilder = true ) -@Data -public class SearchResultBean +public record SearchResultBean( + List> searchResults, + boolean sizeExceeded, + String aboutResultMessage, + boolean fromCache +) { - private List> searchResults; - private boolean sizeExceeded; - private String aboutResultMessage; - private boolean fromCache; + private static final SearchResultBean EMPTY = new SearchResultBean( null, false, null, false ); + + public SearchResultBean( + final List> searchResults, + final boolean sizeExceeded, + final String aboutResultMessage, + final boolean fromCache + ) + { + this.searchResults = searchResults == null + ? List.of() + : List.copyOf( searchResults.stream() + .map( CollectionUtil::stripNulls ) + .filter( m -> !m.isEmpty() ) + .toList() ); + + this.sizeExceeded = sizeExceeded; + this.aboutResultMessage = aboutResultMessage; + this.fromCache = fromCache; + } + + public static SearchResultBean empty() + { + return EMPTY; + } } diff --git a/server/src/main/java/password/pwm/http/servlet/peoplesearch/bean/UserDetailBean.java b/server/src/main/java/password/pwm/http/servlet/peoplesearch/bean/UserDetailBean.java index c8f0776c15..1b05618098 100644 --- a/server/src/main/java/password/pwm/http/servlet/peoplesearch/bean/UserDetailBean.java +++ b/server/src/main/java/password/pwm/http/servlet/peoplesearch/bean/UserDetailBean.java @@ -20,17 +20,15 @@ package password.pwm.http.servlet.peoplesearch.bean; -import lombok.Data; - import java.util.List; import java.util.Map; -@Data -public class UserDetailBean +public record UserDetailBean( + List displayNames, + String userKey, + Map detail, + String photoURL, + List links +) { - private List displayNames; - private String userKey; - private Map detail; - private String photoURL; - private List links; } diff --git a/server/src/main/java/password/pwm/http/servlet/peoplesearch/bean/UserReferenceBean.java b/server/src/main/java/password/pwm/http/servlet/peoplesearch/bean/UserReferenceBean.java index 49986ff4d0..2053b6b687 100644 --- a/server/src/main/java/password/pwm/http/servlet/peoplesearch/bean/UserReferenceBean.java +++ b/server/src/main/java/password/pwm/http/servlet/peoplesearch/bean/UserReferenceBean.java @@ -20,11 +20,21 @@ package password.pwm.http.servlet.peoplesearch.bean; -import lombok.Data; +import java.util.Comparator; -@Data -public class UserReferenceBean +public record UserReferenceBean( + String userKey, + String displayName +) + implements Comparable { - private String userKey; - private String displayName; + private static final Comparator STRING_COMPARATOR = Comparator.comparing( + UserReferenceBean::displayName, + Comparator.nullsLast( Comparator.naturalOrder() ) ); + + @Override + public int compareTo( final UserReferenceBean o ) + { + return STRING_COMPARATOR.compare( this, o ); + } } diff --git a/server/src/main/java/password/pwm/http/servlet/resource/CacheEntry.java b/server/src/main/java/password/pwm/http/servlet/resource/CacheEntry.java index 89a9acf90f..ffca446cbd 100644 --- a/server/src/main/java/password/pwm/http/servlet/resource/CacheEntry.java +++ b/server/src/main/java/password/pwm/http/servlet/resource/CacheEntry.java @@ -20,22 +20,20 @@ package password.pwm.http.servlet.resource; -import lombok.Value; import password.pwm.data.ImmutableByteArray; +import password.pwm.util.java.CollectionUtil; -import java.util.Collections; import java.util.Map; import java.util.Objects; -@Value -final class CacheEntry +record CacheEntry( + ImmutableByteArray entity, + Map headerStrings +) { - private final ImmutableByteArray entity; - private final Map headerStrings; - CacheEntry( final ImmutableByteArray entity, final Map headerStrings ) { this.entity = Objects.requireNonNull( entity ); - this.headerStrings = headerStrings == null ? Collections.emptyMap() : Map.copyOf( headerStrings ); + this.headerStrings = CollectionUtil.stripNulls( headerStrings ); } } diff --git a/server/src/main/java/password/pwm/http/servlet/resource/CacheKey.java b/server/src/main/java/password/pwm/http/servlet/resource/CacheKey.java index b9f500012a..3ea26bb59a 100644 --- a/server/src/main/java/password/pwm/http/servlet/resource/CacheKey.java +++ b/server/src/main/java/password/pwm/http/servlet/resource/CacheKey.java @@ -20,19 +20,17 @@ package password.pwm.http.servlet.resource; -import lombok.Value; - import java.io.IOException; import java.time.Instant; import java.util.Objects; -@Value -final class CacheKey -{ - private final String fileName; - private final boolean acceptsGzip; - private final Instant fileModificationTimestamp; +record CacheKey( + String fileName, + boolean acceptsGzip, + Instant fileModificationTimestamp +) +{ static CacheKey createCacheKey( final FileResource file, final boolean acceptsGzip ) throws IOException { diff --git a/server/src/main/java/password/pwm/http/servlet/resource/ResourceFileServlet.java b/server/src/main/java/password/pwm/http/servlet/resource/ResourceFileServlet.java index 1a7e913bb7..e69de29bb2 100644 --- a/server/src/main/java/password/pwm/http/servlet/resource/ResourceFileServlet.java +++ b/server/src/main/java/password/pwm/http/servlet/resource/ResourceFileServlet.java @@ -1,389 +0,0 @@ -/* - * Password Management Servlets (PWM) - * http://www.pwm-project.org - * - * Copyright (c) 2006-2009 Novell, Inc. - * Copyright (c) 2009-2021 The PWM Project - * - * 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. - */ - -package password.pwm.http.servlet.resource; - -import com.github.benmanes.caffeine.cache.Cache; -import password.pwm.PwmConstants; -import password.pwm.PwmDomain; -import password.pwm.error.ErrorInformation; -import password.pwm.error.PwmError; -import password.pwm.error.PwmUnrecoverableException; -import password.pwm.http.HttpHeader; -import password.pwm.http.HttpMethod; -import password.pwm.http.PwmRequest; -import password.pwm.data.ImmutableByteArray; -import password.pwm.http.servlet.PwmServlet; -import password.pwm.svc.stats.Statistic; -import password.pwm.svc.stats.StatisticsClient; -import password.pwm.util.java.JavaHelper; -import password.pwm.util.java.TimeDuration; -import password.pwm.util.logging.PwmLogger; - -import javax.servlet.annotation.WebServlet; -import javax.servlet.http.HttpServlet; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import java.io.BufferedInputStream; -import java.io.BufferedOutputStream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.time.Instant; -import java.util.HashMap; -import java.util.Map; -import java.util.Optional; -import java.util.function.Supplier; -import java.util.zip.GZIPOutputStream; - -@WebServlet( - name = "ResourceFileServlet", - urlPatterns = { - PwmConstants.URL_PREFIX_PUBLIC + "/resources/*" - } -) -public class ResourceFileServlet extends HttpServlet implements PwmServlet -{ - private static final PwmLogger LOGGER = PwmLogger.forClass( ResourceFileServlet.class ); - - public static final String RESOURCE_PATH = "/public/resources"; - static final String WEBJAR_BASE_URL_PATH = RESOURCE_PATH + "/webjars/"; - static final String WEBJAR_BASE_FILE_PATH = "META-INF/resources/webjars"; - - public static final String THEME_CSS_PATH = "/themes/%THEME%/style.css"; - public static final String THEME_CSS_MOBILE_PATH = "/themes/%THEME%/mobileStyle.css"; - public static final String THEME_CSS_CONFIG_PATH = "/themes/%THEME%/configStyle.css"; - - public static final String TOKEN_THEME = "%THEME%"; - public static final String EMBED_THEME = "embed"; - - @Override - protected void doGet( final HttpServletRequest req, final HttpServletResponse resp ) - throws IOException - { - try - { - final PwmRequest pwmRequest = PwmRequest.forRequest( req, resp ); - processAction( pwmRequest ); - return; - } - catch ( final PwmUnrecoverableException e ) - { - LOGGER.error( () -> "unable to satisfy request using standard mechanism, reverting to raw resource server" ); - } - - resp.sendError( 500, "unable to initialize request for resource url" ); - } - - protected void processAction( final PwmRequest pwmRequest ) - throws IOException, PwmUnrecoverableException - { - final Instant startTime = Instant.now(); - - if ( pwmRequest.getMethod() != HttpMethod.GET ) - { - throw new PwmUnrecoverableException( new ErrorInformation( - PwmError.ERROR_SERVICE_NOT_AVAILABLE, - "unable to process resource request for request method " + pwmRequest.getMethod() ) ); - } - - final PwmDomain pwmDomain = pwmRequest.getPwmDomain(); - final ResourceServletService resourceService = pwmDomain.getResourceServletService(); - final ResourceServletConfiguration resourceConfiguration = resourceService.getResourceServletConfiguration(); - - final ResourceFileRequest resourceFileRequest = new ResourceFileRequest( pwmDomain.getConfig(), resourceConfiguration, pwmRequest.getHttpServletRequest() ); - final String requestURI = resourceFileRequest.getRequestURI(); - - final FileResource file; - { - final Optional resolvedFile = doResolve( resourceService, resourceFileRequest, pwmRequest ); - - if ( resolvedFile.isEmpty() ) - { - return; - } - - file = resolvedFile.get(); - } - - // Get content type by file name and set default GZIP support and content disposition. - final String contentType = resourceFileRequest.getReturnContentType(); - final boolean acceptsGzip = resourceFileRequest.allowsCompression(); - - final HttpServletResponse response = pwmRequest.getPwmResponse().getHttpServletResponse(); - - if ( respondWithNotModified( pwmRequest, resourceConfiguration ) ) - { - return; - } - - // Initialize response. - addExpirationHeaders( resourceConfiguration, response ); - response.setHeader( HttpHeader.ETag.getHttpName(), resourceConfiguration.getNonceValue() ); - response.setContentType( contentType ); - - try - { - boolean fromCache = false; - String debugText; - try - { - fromCache = handleCacheableResponse( resourceFileRequest, response, resourceService.getCacheMap() ); - debugText = makeDebugText( fromCache, acceptsGzip, false ); - } - catch ( final UncacheableResourceException e ) - { - handleUncachedResponse( response, file, acceptsGzip ); - debugText = makeDebugText( fromCache, acceptsGzip, true ); - } - - pwmRequest.debugHttpRequestToLog( debugText, TimeDuration.fromCurrent( pwmRequest.getRequestStartTime() ) ); - - StatisticsClient.incrementStat( pwmDomain, Statistic.HTTP_RESOURCE_REQUESTS ); - resourceService.getAverageStats().update( ResourceServletService.AverageStat.cacheHitRatio, fromCache ? 1 : 0 ); - resourceService.getAverageStats().update( ResourceServletService.AverageStat.avgResponseTimeMS, TimeDuration.fromCurrent( startTime ).asDuration() ); - resourceService.getCountingStats().increment( ResourceServletService.CountingStat.requestsServed ); - resourceService.getCountingStats().increment( ResourceServletService.CountingStat.bytesServed, file.length() ); - } - catch ( final Exception e ) - { - final Supplier msg = () -> "error fulfilling response for url '" + requestURI + "', error: " + e.getMessage(); - if ( e instanceof IOException ) - { - LOGGER.trace( pwmRequest, msg ); - } - else - { - LOGGER.error( pwmRequest, msg ); - } - - } - } - - private Optional doResolve( - final ResourceServletService resourceService, - final ResourceFileRequest resourceFileRequest, - final PwmRequest pwmRequest - - ) - throws IOException - { - try - { - final Optional resolvedFile = resourceFileRequest.getRequestedFileResource(); - - if ( resolvedFile.isEmpty() ) - { - pwmRequest.getPwmResponse().getHttpServletResponse().sendError( HttpServletResponse.SC_NOT_FOUND ); - resourceService.getCountingStats().increment( ResourceServletService.CountingStat.requestsNotFound ); - - try - { - pwmRequest.debugHttpRequestToLog( "returning HTTP 404 status", null ); - } - catch ( final PwmUnrecoverableException e ) - { - /* noop */ - } - return Optional.empty(); - } - - return resolvedFile; - } - catch ( final PwmUnrecoverableException e ) - { - pwmRequest.getPwmResponse().getHttpServletResponse().sendError( HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage() ); - try - { - pwmRequest.debugHttpRequestToLog( "returning HTTP 500 status", null ); - } - catch ( final PwmUnrecoverableException e2 ) - { - /* noop */ - } - } - - return Optional.empty(); - } - - private String makeDebugText( final boolean fromCache, final boolean acceptsGzip, final boolean uncacheable ) - { - if ( uncacheable ) - { - final StringBuilder debugText = new StringBuilder(); - debugText.append( "(uncacheable" ); - if ( acceptsGzip ) - { - debugText.append( ", gzip" ); - } - debugText.append( ')' ); - return debugText.toString(); - } - - if ( fromCache || acceptsGzip ) - { - final StringBuilder debugText = new StringBuilder(); - debugText.append( '(' ); - if ( fromCache ) - { - debugText.append( "cached" ); - } - if ( fromCache && acceptsGzip ) - { - debugText.append( ", " ); - } - if ( acceptsGzip ) - { - debugText.append( "gzip" ); - } - debugText.append( ')' ); - return debugText.toString(); - } - else - { - return "(not cached)"; - } - } - - private boolean handleCacheableResponse( - final ResourceFileRequest resourceFileRequest, - final HttpServletResponse response, - final Cache responseCache - ) - throws UncacheableResourceException, IOException, PwmUnrecoverableException - { - final FileResource file = resourceFileRequest.getRequestedFileResource().orElseThrow(); - - if ( file.length() > resourceFileRequest.getResourceServletConfiguration().getMaxCacheBytes() ) - { - throw new UncacheableResourceException( "file to large to cache" ); - } - - boolean fromCache = false; - final CacheKey cacheKey = CacheKey.createCacheKey( file, resourceFileRequest.allowsCompression() ); - CacheEntry cacheEntry = responseCache.getIfPresent( cacheKey ); - if ( cacheEntry == null ) - { - final Map headers = new HashMap<>(); - final ByteArrayOutputStream tempOutputStream = new ByteArrayOutputStream(); - - try ( InputStream input = file.getInputStream() ) - { - if ( resourceFileRequest.allowsCompression() ) - { - try ( GZIPOutputStream gzipOutputStream = new GZIPOutputStream( tempOutputStream ) ) - { - headers.put( HttpHeader.ContentEncoding.getHttpName(), "gzip" ); - JavaHelper.copy( input, gzipOutputStream ); - } - } - else - { - JavaHelper.copy( input, tempOutputStream ); - } - } - - final ImmutableByteArray entity = ImmutableByteArray.of( tempOutputStream.toByteArray() ); - headers.put( HttpHeader.ContentLength.getHttpName(), String.valueOf( entity.size() ) ); - cacheEntry = new CacheEntry( entity, headers ); - } - else - { - fromCache = true; - } - - responseCache.put( cacheKey, cacheEntry ); - for ( final String key : cacheEntry.getHeaderStrings().keySet() ) - { - response.setHeader( key, cacheEntry.getHeaderStrings().get( key ) ); - } - - try ( OutputStream responseOutputStream = response.getOutputStream() ) - { - JavaHelper.copy( cacheEntry.getEntity().newByteArrayInputStream(), responseOutputStream ); - } - - return fromCache; - } - - private static void handleUncachedResponse( - final HttpServletResponse response, - final FileResource file, - final boolean acceptsGzip - ) - throws IOException - { - try ( - OutputStream output = new BufferedOutputStream( response.getOutputStream() ); - InputStream input = new BufferedInputStream( file.getInputStream() ) - ) - { - if ( acceptsGzip ) - { - response.setHeader( HttpHeader.ContentEncoding.getHttpName(), "gzip" ); - JavaHelper.copy( input, new GZIPOutputStream( output ) ); - } - else - { - // Content length is not directly predictable in case of GZIP. - // So only add it if there is no means of GZIP, else browser will hang. - if ( file.length() > 0 ) - { - response.setHeader( HttpHeader.ContentLength.getHttpName(), String.valueOf( file.length() ) ); - } - JavaHelper.copy( input, output ); - } - } - - } - - private void addExpirationHeaders( final ResourceServletConfiguration resourceServletConfiguration, final HttpServletResponse httpResponse ) - { - httpResponse.setDateHeader( "Expires", System.currentTimeMillis() + ( resourceServletConfiguration.getCacheExpireSeconds() * 1000 ) ); - httpResponse.setHeader( "Cache-Control", "public, max-age=" + resourceServletConfiguration.getCacheExpireSeconds() ); - httpResponse.setHeader( "Vary", "Accept-Encoding" ); - } - - private boolean respondWithNotModified( final PwmRequest pwmRequest, final ResourceServletConfiguration resourceConfiguration ) - { - final String eTagValue = resourceConfiguration.getNonceValue(); - final HttpServletResponse response = pwmRequest.getPwmResponse().getHttpServletResponse(); - - final String ifNoneMatchValue = pwmRequest.readHeaderValueAsString( HttpHeader.If_None_Match ); - if ( ifNoneMatchValue != null && ifNoneMatchValue.equals( eTagValue ) ) - { - // reply back with etag. - response.reset(); - response.setStatus( HttpServletResponse.SC_NOT_MODIFIED ); - try - { - pwmRequest.debugHttpRequestToLog( "returning HTTP 304 status", null ); - } - catch ( final PwmUnrecoverableException e2 ) - { - /* noop */ - } - return true; - } - - return false; - } -} diff --git a/server/src/main/java/password/pwm/http/servlet/resource/ResourceServletConfiguration.java b/server/src/main/java/password/pwm/http/servlet/resource/ResourceServletConfiguration.java index e843b30aa6..e905f7881f 100644 --- a/server/src/main/java/password/pwm/http/servlet/resource/ResourceServletConfiguration.java +++ b/server/src/main/java/password/pwm/http/servlet/resource/ResourceServletConfiguration.java @@ -113,7 +113,8 @@ private static Map makeCustomFileBundle( final Map.Entry entry = files.entrySet().iterator().next(); final FileValue.FileInformation fileInformation = entry.getKey(); final FileValue.FileContent fileContent = entry.getValue(); - LOGGER.debug( sessionLabel, () -> "examining configured zip file resource for items name=" + fileInformation.getFilename() + LOGGER.debug( sessionLabel, () -> "examining configured zip file resource for items name=" + + fileInformation.filename() + ", size=" + fileContent.size() ); try diff --git a/server/src/main/java/password/pwm/http/servlet/resource/ResourceServletService.java b/server/src/main/java/password/pwm/http/servlet/resource/ResourceServletService.java index c549ecf97c..e63b250414 100644 --- a/server/src/main/java/password/pwm/http/servlet/resource/ResourceServletService.java +++ b/server/src/main/java/password/pwm/http/servlet/resource/ResourceServletService.java @@ -54,6 +54,7 @@ import java.util.Collections; import java.util.Enumeration; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Objects; @@ -67,6 +68,10 @@ public class ResourceServletService extends AbstractPwmService implements PwmSer { private static final PwmLogger LOGGER = PwmLogger.forClass( ResourceServletService.class ); + private static final List TEST_URLS = List.of( + ResourceFileServlet.THEME_CSS_PATH, + ResourceFileServlet.THEME_CSS_MOBILE_PATH ); + private ResourceServletConfiguration resourceServletConfiguration; private Cache cache; private String resourceNonce = ""; @@ -111,16 +116,13 @@ StatisticCounterBundle getCountingStats() public long bytesInCache( ) { - final Map cacheCopy = new HashMap<>( cache.asMap() ); - long cacheByteCount = 0; - for ( final CacheEntry cacheEntry : cacheCopy.values() ) - { - if ( cacheEntry != null && cacheEntry.getEntity() != null ) - { - cacheByteCount += cacheEntry.getEntity().size(); - } - } - return cacheByteCount; + final Set cacheCopy = new HashSet<>( cache.asMap().values() ); + + return cacheCopy.stream() + .filter( Objects::nonNull ) + .filter( entry -> entry.entity() != null ) + .mapToLong( entry -> entry.entity().size() ) + .sum(); } public int itemsInCache( ) @@ -175,6 +177,9 @@ public STATUS postAbstractInit( final PwmApplication pwmApplication, final Domai return STATUS.CLOSED; } + // prevents null cache entry for requests that happened before service init + cache.invalidateAll(); + return STATUS.OPEN; } @@ -244,13 +249,9 @@ public boolean checkIfThemeExists( final PwmRequest pwmRequest, final String the final ServletContext servletContext = pwmRequest.getHttpServletRequest().getServletContext(); - final String[] testUrls = new String[] - { - ResourceFileServlet.THEME_CSS_PATH, - ResourceFileServlet.THEME_CSS_MOBILE_PATH, - }; - for ( final String testUrl : testUrls ) + + for ( final String testUrl : TEST_URLS ) { final String themePathUrl = ResourceFileServlet.RESOURCE_PATH + testUrl.replace( ResourceFileServlet.TOKEN_THEME, themeName ); final Optional resolvedFile = ResourceFileRequest.resolveRequestedResource( @@ -327,7 +328,7 @@ private static void checksumResourceFilePath( final PwmDomain pwmDomain, final D if ( Files.exists( resourcePath ) ) { FileSystemUtility.readFileInformation( Collections.singletonList( resourcePath ) ) - .forEach( consumer::accept ); + .forEach( consumer ); } } } ); diff --git a/server/src/main/java/password/pwm/http/servlet/setupresponses/SetupResponsesUtil.java b/server/src/main/java/password/pwm/http/servlet/setupresponses/SetupResponsesUtil.java index 7a6f4ee4eb..10ee3e29f5 100644 --- a/server/src/main/java/password/pwm/http/servlet/setupresponses/SetupResponsesUtil.java +++ b/server/src/main/java/password/pwm/http/servlet/setupresponses/SetupResponsesUtil.java @@ -225,33 +225,21 @@ private static ErrorInformation convertChaiValidationException( e.getFieldName(), }; - switch ( e.getErrorCode() ) - { - case CR_TOO_FEW_CHALLENGES: - return new ErrorInformation( PwmError.ERROR_MISSING_REQUIRED_RESPONSE, null, fieldNames ); - - case CR_TOO_FEW_RANDOM_RESPONSES: - return new ErrorInformation( PwmError.ERROR_MISSING_RANDOM_RESPONSE, null, fieldNames ); - - case CR_MISSING_REQUIRED_CHALLENGE_TEXT: - return new ErrorInformation( PwmError.ERROR_MISSING_CHALLENGE_TEXT, null, fieldNames ); - - case CR_RESPONSE_TOO_LONG: - return new ErrorInformation( PwmError.ERROR_RESPONSE_TOO_LONG, null, fieldNames ); - - case CR_RESPONSE_TOO_SHORT: - case CR_MISSING_REQUIRED_RESPONSE_TEXT: - return new ErrorInformation( PwmError.ERROR_RESPONSE_TOO_SHORT, null, fieldNames ); - - case CR_DUPLICATE_RESPONSES: - return new ErrorInformation( PwmError.ERROR_RESPONSE_DUPLICATE, null, fieldNames ); - - case CR_TOO_MANY_QUESTION_CHARS: - return new ErrorInformation( PwmError.ERROR_CHALLENGE_IN_RESPONSE, null, fieldNames ); - - default: - return new ErrorInformation( PwmError.ERROR_INTERNAL ); - } + return switch ( e.getErrorCode() ) + { + case CR_TOO_FEW_CHALLENGES -> new ErrorInformation( PwmError.ERROR_MISSING_REQUIRED_RESPONSE, + null, fieldNames ); + case CR_TOO_FEW_RANDOM_RESPONSES -> new ErrorInformation( PwmError.ERROR_MISSING_RANDOM_RESPONSE, + null, fieldNames ); + case CR_MISSING_REQUIRED_CHALLENGE_TEXT -> new ErrorInformation( PwmError.ERROR_MISSING_CHALLENGE_TEXT, null, fieldNames ); + case CR_RESPONSE_TOO_LONG -> new ErrorInformation( PwmError.ERROR_RESPONSE_TOO_LONG, null, + fieldNames ); + case CR_RESPONSE_TOO_SHORT, CR_MISSING_REQUIRED_RESPONSE_TEXT -> new ErrorInformation( PwmError.ERROR_RESPONSE_TOO_SHORT, null, fieldNames ); + case CR_DUPLICATE_RESPONSES -> new ErrorInformation( PwmError.ERROR_RESPONSE_DUPLICATE, null, + fieldNames ); + case CR_TOO_MANY_QUESTION_CHARS -> new ErrorInformation( PwmError.ERROR_CHALLENGE_IN_RESPONSE, null, fieldNames ); + default -> new ErrorInformation( PwmError.ERROR_INTERNAL ); + }; } static SetupResponsesBean.SetupData populateSetupData( diff --git a/server/src/main/java/password/pwm/http/servlet/helpdesk/HelpdeskSearchResultsBean.java b/server/src/main/java/password/pwm/http/tag/PwmDomainTag.java similarity index 56% rename from server/src/main/java/password/pwm/http/servlet/helpdesk/HelpdeskSearchResultsBean.java rename to server/src/main/java/password/pwm/http/tag/PwmDomainTag.java index 152cb1b2d6..6ae0ac6369 100644 --- a/server/src/main/java/password/pwm/http/servlet/helpdesk/HelpdeskSearchResultsBean.java +++ b/server/src/main/java/password/pwm/http/tag/PwmDomainTag.java @@ -18,27 +18,26 @@ * limitations under the License. */ -package password.pwm.http.servlet.helpdesk; +package password.pwm.http.tag; -import lombok.Builder; -import lombok.Value; +import password.pwm.error.PwmUnrecoverableException; +import password.pwm.http.PwmRequest; +import password.pwm.util.logging.PwmLogger; -import java.util.Collections; -import java.util.List; -import java.util.Map; - -@Value -@Builder -public class HelpdeskSearchResultsBean +public class PwmDomainTag extends PwmAbstractTag { - private List> searchResults; - private boolean sizeExceeded; + private static final PwmLogger LOGGER = PwmLogger.forClass( PwmDomainTag.class ); + + @Override + protected PwmLogger getLogger() + { + return LOGGER; + } - static HelpdeskSearchResultsBean emptyResult() + @Override + protected String generateTagBodyContents( final PwmRequest pwmRequest ) + throws PwmUnrecoverableException { - return HelpdeskSearchResultsBean.builder() - .searchResults( Collections.emptyList() ) - .sizeExceeded( false ) - .build(); + return pwmRequest.getDomainID().stringValue(); } } diff --git a/server/src/main/java/password/pwm/http/tag/conditional/PwmIfOptions.java b/server/src/main/java/password/pwm/http/tag/conditional/PwmIfOptions.java index 06caec5742..78829fc1b1 100644 --- a/server/src/main/java/password/pwm/http/tag/conditional/PwmIfOptions.java +++ b/server/src/main/java/password/pwm/http/tag/conditional/PwmIfOptions.java @@ -20,19 +20,17 @@ package password.pwm.http.tag.conditional; -import lombok.Value; import password.pwm.Permission; import password.pwm.config.PwmSetting; import password.pwm.http.PwmRequestFlag; import password.pwm.http.servlet.resource.TextFileResource; -@Value -class PwmIfOptions +public record PwmIfOptions( + boolean negate, + Permission permission, + PwmSetting pwmSetting, + PwmRequestFlag requestFlag, + TextFileResource textFileResource +) { - private final boolean negate; - private final Permission permission; - private final PwmSetting pwmSetting; - private final PwmRequestFlag requestFlag; - private final TextFileResource textFileResource; - } diff --git a/server/src/main/java/password/pwm/http/tag/conditional/PwmIfTest.java b/server/src/main/java/password/pwm/http/tag/conditional/PwmIfTest.java index 197b5b0145..c07bcdf512 100644 --- a/server/src/main/java/password/pwm/http/tag/conditional/PwmIfTest.java +++ b/server/src/main/java/password/pwm/http/tag/conditional/PwmIfTest.java @@ -182,8 +182,8 @@ public boolean test( final PwmIfOptions options ) { - final PwmSetting setting = options != null && options.getPwmSetting() != null - ? options.getPwmSetting() + final PwmSetting setting = options != null && options.pwmSetting() != null + ? options.pwmSetting() : this.pwmSetting; if ( setting == null ) @@ -244,7 +244,7 @@ public boolean test( ) throws ChaiUnavailableException, PwmUnrecoverableException { - final Permission permission = constructorPermission != null ? constructorPermission : options.getPermission(); + final Permission permission = constructorPermission != null ? constructorPermission : options.permission(); if ( permission == null ) { @@ -443,11 +443,11 @@ private static class RequestFlagTest implements Test @Override public boolean test( final PwmRequest pwmRequest, final PwmIfOptions options ) throws ChaiUnavailableException, PwmUnrecoverableException { - if ( options.getRequestFlag() == null ) + if ( options.requestFlag() == null ) { return false; } - return pwmRequest.isFlag( options.getRequestFlag() ); + return pwmRequest.isFlag( options.requestFlag() ); } } @@ -507,7 +507,7 @@ public boolean test( final PwmRequest pwmRequest, final PwmIfOptions options ) t final UserInfo userInfoBean = pwmRequest.getPwmSession().getUserInfo(); final PasswordStatus passwordStatus = userInfoBean.getPasswordStatus(); - return passwordStatus.isExpired() || passwordStatus.isPreExpired() || passwordStatus.isViolatesPolicy(); + return passwordStatus.expired() || passwordStatus.preExpired() || passwordStatus.violatesPolicy(); } } @@ -597,7 +597,7 @@ private static class TextFileExists implements Test public boolean test( final PwmRequest pwmRequest, final PwmIfOptions options ) throws PwmUnrecoverableException { - final TextFileResource textFileResource = options.getTextFileResource(); + final TextFileResource textFileResource = options.textFileResource(); return TextFileResource.readTextFileResource( pwmRequest, textFileResource ).isPresent(); } } diff --git a/server/src/main/java/password/pwm/i18n/Display.java b/server/src/main/java/password/pwm/i18n/Display.java index b4a0b1bd9d..32ba9e5b23 100644 --- a/server/src/main/java/password/pwm/i18n/Display.java +++ b/server/src/main/java/password/pwm/i18n/Display.java @@ -30,7 +30,6 @@ */ public enum Display implements PwmDisplayBundle { - Button_Activate, Button_Agree, Button_Cancel, diff --git a/server/src/main/java/password/pwm/i18n/Error.java b/server/src/main/java/password/pwm/i18n/Error.java index 28f4da7db5..9d9ad64e75 100644 --- a/server/src/main/java/password/pwm/i18n/Error.java +++ b/server/src/main/java/password/pwm/i18n/Error.java @@ -20,14 +20,165 @@ package password.pwm.i18n; -public abstract class Error implements PwmDisplayBundle +public enum Error implements PwmDisplayBundle { + Password_MissingConfirm, + Password_DoesNotMatch, + Password_Missing, + Password_PreviouslyUsed, + Password_BadPassword, + Password_TooLong, + Password_TooShort, + Password_NotEnoughNum, + Password_NotEnoughAlpha, + Password_NotEnoughUpper, + Password_NotEnoughLower, + Password_NotEnoughSpecial, + Password_NotEnoughUnique, + Password_TooManyRepeat, + Password_TooManySpecial, + Password_TooManyAlpha, + Password_TooManyNumeric, + Password_TooManyLower, + Password_TooManyUpper, + Password_TooManyOldChars, + Password_TooManyNonAlphaSpecial, + Password_InvalidChar, + Password_RequiredMissing, + Password_InWordlist, + Password_SameAsOld, + Password_SameAsAttr, + Password_FirstIsNumeric, + Password_LastIsNumeric, + Password_FirstIsSpecial, + Password_LastIsSpecial, + Password_HistoryFull, + Password_MeetsRules, + Password_TooSoon, + Password_UsingDisallowedValue, + Password_TooWeak, + Password_TooManyNonAlpha, + Password_NotEnoughNonAlpha, + Password_UnknownValidation, + Password_NewPasswordRequired, + Password_Expired, + Password_CustomError, + Password_BadOldPassword, + Password_NotEnoughGroups, + Password_TooManyConsecutive, + Error_WrongResponse, + Error_WrongPassword, + Error_Response_NoResponse, + Error_Response_TooShort, + Error_Response_Wordlist, + Error_Response_TooLong, + Error_Response_Duplicate, + Error_Challenge_Duplicate, + Error_Missing_Challenge_Text, + Error_UserAuthenticated, + Error_MissingParameter, + Error_DirectoryUnavailable, + Error_Internal, + Error_ActivationValidationFailed, + Error_CantMatchUser, + Error_ServiceNotAvailable, + Error_UserMisMatch, + Error_ActivateUserNoQueryMatch, + Error_AuthenticationRequired, + Error_NoChallenges, + Error_UserIntruder, + Error_AddressIntruder, + Error_SessionIntruder, + Error_BadSessionPassword, + Error_Unauthorized, + Error_BadSession, + Error_MissingRequiredResponse, + Error_MissingRandomResponse, + Error_BadCaptchaResponse, + Error_CaptchaAPIError, + Error_InvalidConfig, + Error_InvalidFormID, + Error_TokenMissingContact, + Error_TokenIncorrect, + Error_BadCurrentPassword, + Error_Missing_GUID, + Error_TokenExpired, + Error_SecureRequestRequired, + Error_Writing_Responses, + Error_Writing_Otp_Secret, + Error_Unlock_Failure, + Error_Update_Attrs_Failure, + Error_Activation_Failure, + Error_NewUser_Failure, + Error_Activation, + Error_DB_Unavailable, + Error_LocalDB_Unavailable, + Error_App_Unavailable, + Error_IncorrectRequestSequence, + Error_UnreachableCloudService, + Error_InvalidSecurityKey, + Error_Clearing_Responses, + Error_ServiceUnreachable, + Error_ChallengeInResponse, + Error_Multi_Username, + Error_CertificateError, + Error_SyslogWriteError, + Error_TooManyThreads, + Error_SecurityViolation, + Error_NoOtpConfiguration, + Error_TrialViolation, + Error_AccountDisabled, + Error_AccountExpired, + Error_WrongOtpToken, + Error_AttrIntruder, + Error_AuditWrite, + Error_LdapIntruder, + Error_NoLdapConnection, + Error_OAuthError, + Error_FieldRequired, + Error_FieldNotANumber, + Error_FieldInvalidEmail, + Error_FieldTooShort, + Error_FieldTooLong, + Error_FieldDuplicate, + Error_FieldBadConfirm, + Error_FieldRegexNoMatch, + Error_Orig_Admin_Only, + Error_PasswordRequired, + Error_ReportingError, + Error_TokenDestIntruder, + Error_OtpRecoveryUsed, + Error_RedirectIllegal, + Error_CryptError, + Error_SmsSendError, + Error_LdapDataError, + Error_MacroParseError, + Error_NoProfileAssigned, + Error_StartupError, + Error_EnvironmentError, + Error_ApplicationNotRunning, + Error_EmailSendFailure, + Error_PasswordOnlyBad, + Error_RecoverySequenceIncomplete, + Error_FileTypeIncorrect, + Error_FileTooLarge, + Error_NodeServiceError, + Error_RemoteErrorValue, + Error_WordlistImportError, + Error_PwNotifyServiceError, + Error_HttpError, + Error_Timeout, + Error_ConfigUploadSuccess, + Error_ConfigUploadFailure, + Error_ConfigSaveSuccess, + Error_ConfigFormatError, + Error_ConfigLdapFailure, + Error_ConfigLdapSuccess, + Error_HTTP_404,; @Override public String getKey( ) { return this.toString(); - } - } diff --git a/server/src/main/java/password/pwm/ldap/LdapBrowser.java b/server/src/main/java/password/pwm/ldap/LdapBrowser.java index 6a3c44e4ac..2e59ac1a98 100644 --- a/server/src/main/java/password/pwm/ldap/LdapBrowser.java +++ b/server/src/main/java/password/pwm/ldap/LdapBrowser.java @@ -31,7 +31,6 @@ import com.novell.ldapchai.util.ChaiUtility; import com.novell.ldapchai.util.SearchHelper; import lombok.Builder; -import lombok.Value; import password.pwm.DomainProperty; import password.pwm.bean.DomainID; import password.pwm.bean.ProfileID; @@ -43,7 +42,9 @@ import password.pwm.error.ErrorInformation; import password.pwm.error.PwmError; import password.pwm.error.PwmUnrecoverableException; +import password.pwm.util.PwmScheduler; import password.pwm.util.java.CollectorUtil; +import password.pwm.util.java.JavaHelper; import password.pwm.util.java.StringUtil; import password.pwm.util.logging.PwmLogger; @@ -51,26 +52,31 @@ import java.util.Collections; import java.util.HashMap; import java.util.HashSet; -import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeMap; +import java.util.concurrent.Callable; +import java.util.concurrent.ConcurrentHashMap; import java.util.function.Function; -public class LdapBrowser +public class LdapBrowser implements AutoCloseable { public static final String PARAM_DN = "dn"; public static final String PARAM_PROFILE = "profile"; - private static final String ATTR_SUBORDINATE_COUNT = "subordinateCount"; + private static final int MAX_THREADS = 10; private static final PwmLogger LOGGER = PwmLogger.forClass( LdapBrowser.class ); private final StoredConfiguration storedConfiguration; private final SessionLabel sessionLabel; private final ChaiProviderFactory chaiProviderFactory; - private final Map providerCache = new HashMap<>(); + + private final ThreadLocal> providerCache = new ThreadLocal<>(); + private final Set issuedProviders = new HashSet<>(); + + private final PwmScheduler pwmScheduler; private enum DnType { @@ -81,12 +87,14 @@ private enum DnType public LdapBrowser( final SessionLabel sessionLabel, final ChaiProviderFactory chaiProviderFactory, - final StoredConfiguration storedConfiguration + final StoredConfiguration storedConfiguration, + final PwmScheduler pwmScheduler ) { this.sessionLabel = sessionLabel; this.chaiProviderFactory = chaiProviderFactory; this.storedConfiguration = storedConfiguration; + this.pwmScheduler = pwmScheduler; } public LdapBrowseResult doBrowse( @@ -112,11 +120,10 @@ public LdapBrowseResult doBrowse( public void close( ) { - for ( final ChaiProvider chaiProvider : providerCache.values() ) + for ( final ChaiProvider chaiProvider : issuedProviders ) { chaiProvider.close(); } - providerCache.clear(); } private LdapBrowseResult doBrowseImpl( @@ -194,14 +201,23 @@ private void updateBrowseResultChildren( private ChaiProvider getChaiProvider( final DomainID domainID, final ProfileID profile ) throws PwmUnrecoverableException { - if ( !providerCache.containsKey( profile ) ) + if ( providerCache.get() == null ) + { + providerCache.set( new HashMap<>() ); + } + + final Map threadLocalCache = providerCache.get(); + + if ( !threadLocalCache.containsKey( profile ) ) { final DomainConfig domainConfig = AppConfig.forStoredConfig( storedConfiguration ).getDomainConfigs().get( domainID ); final LdapProfile ldapProfile = domainConfig.getLdapProfiles().get( profile ); final ChaiProvider chaiProvider = LdapOperationsHelper.openProxyChaiProvider( chaiProviderFactory, sessionLabel, ldapProfile, domainConfig, null ); - providerCache.put( profile, chaiProvider ); + threadLocalCache.put( profile, chaiProvider ); + issuedProviders.add( chaiProvider ); } - return providerCache.get( profile ); + + return threadLocalCache.get( profile ); } private static int getMaxSizeLimit( @@ -233,13 +249,25 @@ private Map getChildEntries( final Set results = doLdapSearch( domainID, dn, chaiProvider ); - final HashMap returnMap = new LinkedHashMap<>( results.size() ); + final List> callables = new ArrayList<>( results.size() ); + final Map returnMap = new ConcurrentHashMap<>( results.size() ); + for ( final String resultDN : results ) { - final DnType dnType = dnHasSubordinates( resultDN, chaiProvider ); - returnMap.put( resultDN, dnType ); + callables.add( () -> + { + final ChaiProvider threadProvider = getChaiProvider( domainID, profile ); + LOGGER.trace( sessionLabel, () -> "dnCheck on thread " + Thread.currentThread().getName() ); + final DnType dnType = dnHasSubordinates( resultDN, threadProvider ); + returnMap.put( resultDN, dnType ); + return ""; + } ); } + final DomainConfig domainConfig = AppConfig.forStoredConfig( storedConfiguration ).getDomainConfigs().get( domainID ); + final int maxThreads = JavaHelper.silentParseInt( domainConfig.readDomainProperty( DomainProperty.LDAP_BROWSER_MAX_THREADS ), 10 ); + pwmScheduler.executeImmediateThreadPerJobAndAwaitCompletion( maxThreads, callables, sessionLabel, String.class ); + return Collections.unmodifiableMap( returnMap ); } @@ -295,24 +323,26 @@ private static String rdnNameFromDN( final String dn ) return dn.substring( 0, end ); } - @Value - @Builder - public static class LdapBrowseResult + public record LdapBrowseResult( + String dn, + ProfileID profileID, + String parentDN, + List profileList, + boolean maxResults, + List navigableDNlist, + List selectableDNlist + ) { - private String dn; - private ProfileID profileID; - private String parentDN; - private List profileList; - private boolean maxResults; - - private List navigableDNlist; - private List selectableDNlist; + @Builder + public LdapBrowseResult + { + } } - @Value - public static class DNInformation + public record DNInformation( + String entryName, + String dn + ) { - private final String entryName; - private final String dn; } } diff --git a/server/src/main/java/password/pwm/ldap/LdapPermissionCalculator.java b/server/src/main/java/password/pwm/ldap/LdapPermissionCalculator.java index de36feae4d..8aa7dfd89e 100644 --- a/server/src/main/java/password/pwm/ldap/LdapPermissionCalculator.java +++ b/server/src/main/java/password/pwm/ldap/LdapPermissionCalculator.java @@ -21,7 +21,6 @@ package password.pwm.ldap; import com.novell.ldapchai.ChaiConstant; -import lombok.Value; import password.pwm.bean.ProfileID; import password.pwm.config.DomainConfig; import password.pwm.config.LDAPPermissionInfo; @@ -81,14 +80,14 @@ public Map>> getPe final Map>> returnObj = new TreeMap<>(); for ( final PermissionRecord permissionRecord : getPermissionRecords() ) { - if ( permissionRecord.getActor() == actor ) + if ( permissionRecord.actor() == actor ) { final Map> innerMap = returnObj.computeIfAbsent( - permissionRecord.getAttribute(), + permissionRecord.attribute(), ( key ) -> new EnumMap<>( LDAPPermissionInfo.Access.class ) ); final List innerList = innerMap.computeIfAbsent( - permissionRecord.getAccess(), + permissionRecord.access(), ( key ) -> new ArrayList<>() ); innerList.add( permissionRecord ); @@ -212,7 +211,7 @@ private Collection figureRecord( final PwmSetting pwmSetting, { for ( final UserPermission userPermission : userPermissions ) { - if ( userPermission.getType() == UserPermissionType.ldapGroup ) + if ( userPermission.type() == UserPermissionType.ldapGroup ) { permissionRecords.add( new PermissionRecord( groupAttribute, pwmSetting, profile, permissionInfo.getAccess(), permissionInfo.getActor() ) ); } @@ -390,7 +389,7 @@ private Collection permissionsForUserPassword( ) // proxy user set password if ( domainConfig.readSettingAsBoolean( PwmSetting.FORGOTTEN_PASSWORD_ENABLE ) ) { - final Collection templates = domainConfig.getTemplate().getTemplates(); + final Collection templates = domainConfig.getTemplate().templates(); if ( templates.contains( PwmSettingTemplate.NOVL ) || templates.contains( PwmSettingTemplate.NOVL_IDM ) ) { records.add( new PermissionRecord( @@ -461,7 +460,7 @@ private Collection figureStaticRecords( ) { // edir specific attributes - if ( !Collections.disjoint( templateSet.getTemplates(), edirInterestedTemplates ) ) + if ( !Collections.disjoint( templateSet.templates(), edirInterestedTemplates ) ) { final Map ldapAttributes = new LinkedHashMap<>(); ldapAttributes.put( ChaiConstant.ATTR_LDAP_LOCKED_BY_INTRUDER, LDAPPermissionInfo.Access.write ); @@ -506,13 +505,13 @@ private Collection figureStaticRecords( ) return permissionRecords; } - @Value - public static class PermissionRecord + public record PermissionRecord( + String attribute, + PwmSetting pwmSetting, + ProfileID profile, + LDAPPermissionInfo.Access access, + LDAPPermissionInfo.Actor actor + ) { - private final String attribute; - private final PwmSetting pwmSetting; - private final ProfileID profile; - private final LDAPPermissionInfo.Access access; - private final LDAPPermissionInfo.Actor actor; } } diff --git a/server/src/main/java/password/pwm/ldap/LdapUserInfoReader.java b/server/src/main/java/password/pwm/ldap/LdapUserInfoReader.java index a78cd28da9..dcc60fdc3a 100644 --- a/server/src/main/java/password/pwm/ldap/LdapUserInfoReader.java +++ b/server/src/main/java/password/pwm/ldap/LdapUserInfoReader.java @@ -220,13 +220,13 @@ public String getUsername( ) throws PwmUnrecoverableException @Override public PasswordStatus getPasswordStatus( ) throws PwmUnrecoverableException { - final PasswordStatus.PasswordStatusBuilder passwordStatusBuilder = PasswordStatus.builder(); final String userDN = chaiUser.getEntryDN(); final PwmPasswordPolicy passwordPolicy = selfCachedReference.getPasswordPolicy(); final long startTime = System.currentTimeMillis(); LOGGER.trace( sessionLabel, () -> "beginning password status check process for " + userDN ); + boolean violatesPolicy = false; // check if password meets existing policy. if ( passwordPolicy.ruleHelper().readBooleanValue( PwmPasswordRule.EnforceAtLogin ) ) { @@ -241,7 +241,7 @@ public PasswordStatus getPasswordStatus( ) throws PwmUnrecoverableException { LOGGER.debug( sessionLabel, () -> "user " + userDN + " password does not conform to current password policy (" + e.getMessage() + "), marking as requiring change." ); - passwordStatusBuilder.violatesPolicy( true ); + violatesPolicy = true; } catch ( final ChaiUnavailableException e ) { @@ -251,31 +251,34 @@ public PasswordStatus getPasswordStatus( ) throws PwmUnrecoverableException } boolean ldapPasswordExpired = false; - try { - ldapPasswordExpired = chaiUser.isPasswordExpired(); + try + { + ldapPasswordExpired = chaiUser.isPasswordExpired(); - if ( ldapPasswordExpired ) + if ( ldapPasswordExpired ) + { + LOGGER.trace( sessionLabel, () -> "password for " + userDN + " appears to be expired" ); + } + else + { + LOGGER.trace( sessionLabel, () -> "password for " + userDN + " does not appear to be expired" ); + } + } + catch ( final ChaiOperationException e ) { - LOGGER.trace( sessionLabel, () -> "password for " + userDN + " appears to be expired" ); + LOGGER.info( sessionLabel, () -> "error reading LDAP attributes for " + userDN + " while reading isPasswordExpired(): " + e.getMessage() ); } - else + catch ( final ChaiUnavailableException e ) { - LOGGER.trace( sessionLabel, () -> "password for " + userDN + " does not appear to be expired" ); + throw PwmUnrecoverableException.fromChaiException( e ); } } - catch ( final ChaiOperationException e ) - { - LOGGER.info( sessionLabel, () -> "error reading LDAP attributes for " + userDN + " while reading isPasswordExpired(): " + e.getMessage() ); - } - catch ( final ChaiUnavailableException e ) - { - throw PwmUnrecoverableException.fromChaiException( e ); - } final Instant ldapPasswordExpirationTime = selfCachedReference.getPasswordExpirationTime(); boolean preExpired = false; + boolean warnPeriod = false; if ( ldapPasswordExpirationTime != null ) { final TimeDuration expirationInterval = TimeDuration.fromCurrent( ldapPasswordExpirationTime ); @@ -317,23 +320,24 @@ else if ( ldapPasswordExpired ) () -> "user " + userDN + " password will expire within " + diff.asCompactString() + ", marking as within warn period" ); - passwordStatusBuilder.warnPeriod( true ); + warnPeriod = true; } } } } - - passwordStatusBuilder.preExpired( preExpired ); } - LOGGER.debug( sessionLabel, () -> "completed user password status check for " + userDN + " " + passwordStatusBuilder + final PasswordStatus passwordStatus = new PasswordStatus( ldapPasswordExpired, preExpired, violatesPolicy, warnPeriod ); + + LOGGER.debug( sessionLabel, () -> "completed user password status check for " + userDN + " " + passwordStatus + " (" + TimeDuration.fromCurrent( startTime ).asCompactString() + ")" ); - passwordStatusBuilder.expired( ldapPasswordExpired ); - return passwordStatusBuilder.build(); + + return passwordStatus; } @Override - public boolean isRequiresNewPassword( ) throws PwmUnrecoverableException + public boolean isRequiresNewPassword( ) + throws PwmUnrecoverableException { final Optional changePasswordProfile = readChangePasswordProfile(); if ( !changePasswordProfile.isPresent() ) @@ -343,25 +347,25 @@ public boolean isRequiresNewPassword( ) throws PwmUnrecoverableException } final PasswordStatus passwordStatus = selfCachedReference.getPasswordStatus(); - if ( passwordStatus.isExpired() ) + if ( passwordStatus.expired() ) { LOGGER.debug( sessionLabel, () -> "checkPassword: password is expired, marking new password as required" ); return true; } - if ( passwordStatus.isPreExpired() ) + if ( passwordStatus.preExpired() ) { LOGGER.debug( sessionLabel, () -> "checkPassword: password is pre-expired, marking new password as required" ); return true; } - if ( passwordStatus.isWarnPeriod() ) + if ( passwordStatus.warnPeriod() ) { LOGGER.debug( sessionLabel, () -> "checkPassword: password is within warn period, marking new password as required" ); return true; } - if ( passwordStatus.isViolatesPolicy() ) + if ( passwordStatus.violatesPolicy() ) { LOGGER.debug( sessionLabel, () -> "checkPassword: current password violates password policy, marking new password as required" ); return true; @@ -620,14 +624,7 @@ public OTPUserRecord getOtpUserRecord( ) throws PwmUnrecoverableException final OtpService otpService = pwmDomain.getOtpService(); if ( otpService != null && otpService.status() == PwmService.STATUS.OPEN ) { - try - { - return otpService.readOTPUserConfiguration( sessionLabel, userIdentity ); - } - catch ( final ChaiUnavailableException e ) - { - throw PwmUnrecoverableException.fromChaiException( e ); - } + return otpService.readOTPUserConfiguration( sessionLabel, userIdentity ); } return null; } @@ -849,7 +846,7 @@ public boolean isRequiresInteraction( ) throws PwmUnrecoverableException || selfCachedReference.isRequiresResponseConfig() || selfCachedReference.isRequiresUpdateProfile() || selfCachedReference.isRequiresOtpConfig() - || selfCachedReference.getPasswordStatus().isWarnPeriod(); + || selfCachedReference.getPasswordStatus().warnPeriod(); } @Override diff --git a/server/src/main/java/password/pwm/ldap/ViewableUserInfoDisplayReader.java b/server/src/main/java/password/pwm/ldap/ViewableUserInfoDisplayReader.java index 7a71693a25..69d8a16799 100644 --- a/server/src/main/java/password/pwm/ldap/ViewableUserInfoDisplayReader.java +++ b/server/src/main/java/password/pwm/ldap/ViewableUserInfoDisplayReader.java @@ -141,25 +141,25 @@ public static List makeDisplayData( maker.add( ViewStatusFields.PasswordExpired, Display.Field_PasswordExpired, - userInfo.getPasswordStatus().isExpired() + userInfo.getPasswordStatus().expired() ); maker.add( ViewStatusFields.PasswordPreExpired, Display.Field_PasswordPreExpired, - userInfo.getPasswordStatus().isPreExpired() + userInfo.getPasswordStatus().preExpired() ); maker.add( ViewStatusFields.PasswordWarnPeriod, Display.Field_PasswordWithinWarningPeriod, - userInfo.getPasswordStatus().isWarnPeriod() + userInfo.getPasswordStatus().warnPeriod() ); maker.add( ViewStatusFields.PasswordViolatesPolicy, Display.Field_PasswordViolatesPolicy, - userInfo.getPasswordStatus().isViolatesPolicy() + userInfo.getPasswordStatus().violatesPolicy() ); maker.add( @@ -303,7 +303,7 @@ void add( final ViewStatusFields viewStatusField, final Display display, final I ? LocaleHelper.getLocalizedMessage( locale, Display.Value_NotApplicable, config ) : StringUtil.toIsoDate( instant ); - list.add( new DisplayElement( + list.add( DisplayElement.create( display.name(), DisplayElement.Type.timestamp, LocaleHelper.getLocalizedMessage( locale, display, config ), @@ -332,7 +332,7 @@ void add( final ViewStatusFields viewStatusField, final Display display, final S ? LocaleHelper.getLocalizedMessage( locale, Display.Value_NotApplicable, config ) : value; - list.add( new DisplayElement( + list.add( DisplayElement.create( display.name(), DisplayElement.Type.string, LocaleHelper.getLocalizedMessage( locale, display, config ), diff --git a/server/src/main/java/password/pwm/ldap/auth/SessionAuthenticator.java b/server/src/main/java/password/pwm/ldap/auth/SessionAuthenticator.java index 44886cdc1e..5b1d6c932e 100644 --- a/server/src/main/java/password/pwm/ldap/auth/SessionAuthenticator.java +++ b/server/src/main/java/password/pwm/ldap/auth/SessionAuthenticator.java @@ -417,15 +417,15 @@ private void postAuthenticationSequence( IntruderServiceClient.clearUserIdentity( pwmRequest, userIdentity ); IntruderServiceClient.clearAddressAndSession( pwmDomain, pwmSession ); - if ( pwmSession.getUserInfo().getPasswordStatus().isWarnPeriod() ) + if ( pwmSession.getUserInfo().getPasswordStatus().warnPeriod() ) { StatisticsClient.incrementStat( pwmRequest, Statistic.AUTHENTICATION_EXPIRED_WARNING ); } - else if ( pwmSession.getUserInfo().getPasswordStatus().isPreExpired() ) + else if ( pwmSession.getUserInfo().getPasswordStatus().preExpired() ) { StatisticsClient.incrementStat( pwmRequest, Statistic.AUTHENTICATION_PRE_EXPIRED ); } - else if ( pwmSession.getUserInfo().getPasswordStatus().isExpired() ) + else if ( pwmSession.getUserInfo().getPasswordStatus().expired() ) { StatisticsClient.incrementStat( pwmRequest, Statistic.AUTHENTICATION_EXPIRED ); } diff --git a/server/src/main/java/password/pwm/ldap/permission/AllPermissionTypeHelper.java b/server/src/main/java/password/pwm/ldap/permission/AllPermissionTypeHelper.java index 13bca2f26e..a857cfc4e8 100644 --- a/server/src/main/java/password/pwm/ldap/permission/AllPermissionTypeHelper.java +++ b/server/src/main/java/password/pwm/ldap/permission/AllPermissionTypeHelper.java @@ -46,9 +46,9 @@ public boolean testMatch( public SearchConfiguration searchConfigurationFromPermission( final UserPermission userPermission ) throws PwmUnrecoverableException { - final ProfileID profileID = UserPermissionUtility.isAllProfiles( userPermission.getLdapProfileID() ) + final ProfileID profileID = UserPermissionUtility.isAllProfiles( userPermission.ldapProfileID() ) ? null - : userPermission.getLdapProfileID(); + : userPermission.ldapProfileID(); return SearchConfiguration.builder() .username( "*" ) diff --git a/server/src/main/java/password/pwm/ldap/permission/LdapGroupTypeHelper.java b/server/src/main/java/password/pwm/ldap/permission/LdapGroupTypeHelper.java index e18502f24b..5fc1d7ff48 100644 --- a/server/src/main/java/password/pwm/ldap/permission/LdapGroupTypeHelper.java +++ b/server/src/main/java/password/pwm/ldap/permission/LdapGroupTypeHelper.java @@ -49,7 +49,7 @@ public boolean testMatch( throws PwmUnrecoverableException { final Instant startTime = Instant.now(); - final String groupDN = userPermission.getLdapBase(); + final String groupDN = userPermission.ldapBase(); if ( userIdentity == null ) { @@ -88,7 +88,7 @@ public SearchConfiguration searchConfigurationFromPermission( final UserPermissi throws PwmUnrecoverableException { return SearchConfiguration.builder() - .groupDN( userPermission.getLdapBase() ) + .groupDN( userPermission.ldapBase() ) .ldapProfile( UserPermissionUtility.profileIdForPermission( userPermission ).orElse( null ) ) .build(); } @@ -96,7 +96,7 @@ public SearchConfiguration searchConfigurationFromPermission( final UserPermissi @Override public void validatePermission( final UserPermission userPermission ) throws PwmUnrecoverableException { - if ( StringUtil.isEmpty( userPermission.getLdapBase() ) ) + if ( StringUtil.isEmpty( userPermission.ldapBase() ) ) { throw PwmUnrecoverableException.newException( PwmError.CONFIG_FORMAT_ERROR, diff --git a/server/src/main/java/password/pwm/ldap/permission/LdapQueryHelper.java b/server/src/main/java/password/pwm/ldap/permission/LdapQueryHelper.java index 0f6db18920..1e480cd771 100644 --- a/server/src/main/java/password/pwm/ldap/permission/LdapQueryHelper.java +++ b/server/src/main/java/password/pwm/ldap/permission/LdapQueryHelper.java @@ -51,10 +51,10 @@ public boolean testMatch( ) throws PwmUnrecoverableException { - if ( userPermission.getLdapBase() != null && !userPermission.getLdapBase().trim().isEmpty() ) + if ( userPermission.ldapBase() != null && !userPermission.ldapBase().trim().isEmpty() ) { final String canonicalBaseDN = pwmDomain.getConfig().getLdapProfiles().get( userIdentity.getLdapProfileID() ) - .readCanonicalDN( sessionLabel, pwmDomain, userPermission.getLdapBase() ); + .readCanonicalDN( sessionLabel, pwmDomain, userPermission.ldapBase() ); if ( !UserPermissionUtility.testBaseDnMatch( sessionLabel, pwmDomain, canonicalBaseDN, userIdentity ) ) { @@ -67,7 +67,7 @@ public boolean testMatch( return false; } - final String filterString = userPermission.getLdapQuery(); + final String filterString = userPermission.ldapQuery(); LOGGER.trace( sessionLabel, () -> "begin check for ldapQuery match for " + userIdentity + " using queryMatch: " + filterString ); if ( StringUtil.isEmpty( filterString ) ) @@ -124,7 +124,7 @@ public SearchConfiguration searchConfigurationFromPermission( final UserPermissi throws PwmUnrecoverableException { return SearchConfiguration.builder() - .filter( userPermission.getLdapQuery() ) + .filter( userPermission.ldapQuery() ) .ldapProfile( UserPermissionUtility.profileIdForPermission( userPermission ).orElse( null ) ) .build(); } @@ -132,13 +132,13 @@ public SearchConfiguration searchConfigurationFromPermission( final UserPermissi @Override public void validatePermission( final UserPermission userPermission ) throws PwmUnrecoverableException { - if ( StringUtil.isEmpty( userPermission.getLdapQuery() ) ) + if ( StringUtil.isEmpty( userPermission.ldapQuery() ) ) { throw PwmUnrecoverableException.newException( PwmError.CONFIG_FORMAT_ERROR, "userPermission of type " + UserPermissionType.ldapQuery + " must have a ldapQuery value" ); } - Validator.validateLdapSearchFilter( userPermission.getLdapQuery() ); + Validator.validateLdapSearchFilter( userPermission.ldapQuery() ); } } diff --git a/server/src/main/java/password/pwm/ldap/permission/LdapUserDNTypeHelper.java b/server/src/main/java/password/pwm/ldap/permission/LdapUserDNTypeHelper.java index 955535b8fa..5001540636 100644 --- a/server/src/main/java/password/pwm/ldap/permission/LdapUserDNTypeHelper.java +++ b/server/src/main/java/password/pwm/ldap/permission/LdapUserDNTypeHelper.java @@ -47,7 +47,7 @@ public boolean testMatch( ) throws PwmUnrecoverableException { - final String groupDN = userPermission.getLdapQuery(); + final String groupDN = userPermission.ldapQuery(); if ( userIdentity == null ) { @@ -62,7 +62,7 @@ public boolean testMatch( final LdapProfile ldapProfile = userIdentity.getLdapProfile( pwmDomain.getPwmApplication().getConfig() ); final String userCanonicalDN = ldapProfile.readCanonicalDN( sessionLabel, pwmDomain, userIdentity.getUserDN() ); - final String configuredCanonicalDN = ldapProfile.readCanonicalDN( sessionLabel, pwmDomain, userPermission.getLdapBase() ); + final String configuredCanonicalDN = ldapProfile.readCanonicalDN( sessionLabel, pwmDomain, userPermission.ldapBase() ); return Objects.equals( userCanonicalDN, configuredCanonicalDN ); } @@ -74,7 +74,7 @@ public SearchConfiguration searchConfigurationFromPermission( final UserPermissi .enableContextValidation( false ) .enableValueEscaping( false ) .ldapProfile( UserPermissionUtility.profileIdForPermission( userPermission ).orElse( null ) ) - .contexts( Collections.singletonList( userPermission.getLdapBase() ) ) + .contexts( Collections.singletonList( userPermission.ldapBase() ) ) .searchScope( SearchConfiguration.SearchScope.base ) .build(); } @@ -82,7 +82,7 @@ public SearchConfiguration searchConfigurationFromPermission( final UserPermissi @Override public void validatePermission( final UserPermission userPermission ) throws PwmUnrecoverableException { - if ( StringUtil.isEmpty( userPermission.getLdapBase() ) ) + if ( StringUtil.isEmpty( userPermission.ldapBase() ) ) { throw PwmUnrecoverableException.newException( PwmError.CONFIG_FORMAT_ERROR, diff --git a/server/src/main/java/password/pwm/ldap/permission/UserPermissionUtility.java b/server/src/main/java/password/pwm/ldap/permission/UserPermissionUtility.java index b507cd3127..bc052f72ce 100644 --- a/server/src/main/java/password/pwm/ldap/permission/UserPermissionUtility.java +++ b/server/src/main/java/password/pwm/ldap/permission/UserPermissionUtility.java @@ -95,9 +95,9 @@ private static boolean checkIfProfileAppliesToUser( final UserPermission userPermission ) { - return userPermission.getLdapProfileID() == null - || ProfileID.PROFILE_ID_ALL.equals( userPermission.getLdapProfileID() ) - || userIdentity.getLdapProfileID().equals( userPermission.getLdapProfileID() ); + return userPermission.ldapProfileID() == null + || ProfileID.PROFILE_ID_ALL.equals( userPermission.ldapProfileID() ) + || userIdentity.getLdapProfileID().equals( userPermission.ldapProfileID() ); } private static boolean testUserPermission( @@ -118,7 +118,7 @@ private static boolean testUserPermission( return false; } - final PermissionTypeHelper permissionTypeHelper = userPermission.getType().getPermissionTypeTester(); + final PermissionTypeHelper permissionTypeHelper = userPermission.type().getPermissionTypeTester(); final Instant startTime = Instant.now(); final boolean match = permissionTypeHelper.testMatch( pwmDomain, sessionLabel, userIdentity, userPermission ); LOGGER.debug( sessionLabel, () -> "user " + userIdentity.toDisplayString() + " is " @@ -152,7 +152,7 @@ public static List discoverMatchingUsers( { if ( ( maxResultSize ) - resultSet.size() > 0 ) { - final PermissionTypeHelper permissionTypeHelper = userPermission.getType().getPermissionTypeTester(); + final PermissionTypeHelper permissionTypeHelper = userPermission.type().getPermissionTypeTester(); final SearchConfiguration searchConfiguration = permissionTypeHelper.searchConfigurationFromPermission( userPermission ) .toBuilder() .searchTimeout( maxSearchTime ) @@ -186,10 +186,10 @@ public static List discoverMatchingUsers( static Optional profileIdForPermission( final UserPermission userPermission ) { - if ( userPermission.getLdapProfileID() != null - && !ProfileID.PROFILE_ID_ALL.equals( userPermission.getLdapProfileID() ) ) + if ( userPermission.ldapProfileID() != null + && !ProfileID.PROFILE_ID_ALL.equals( userPermission.ldapProfileID() ) ) { - return Optional.of( userPermission.getLdapProfileID() ); + return Optional.of( userPermission.ldapProfileID() ); } return Optional.empty(); @@ -200,12 +200,12 @@ public static void validatePermissionSyntax( final UserPermission userPermission { Objects.requireNonNull( userPermission ); - if ( userPermission.getType() == null ) + if ( userPermission.type() == null ) { throw PwmUnrecoverableException.newException( PwmError.CONFIG_FORMAT_ERROR, "userPermission must have a type value" ); } - final PermissionTypeHelper permissionTypeHelper = userPermission.getType().getPermissionTypeTester(); + final PermissionTypeHelper permissionTypeHelper = userPermission.type().getPermissionTypeTester(); permissionTypeHelper.validatePermission( userPermission ); } diff --git a/server/src/main/java/password/pwm/ldap/schema/SchemaOperationResult.java b/server/src/main/java/password/pwm/ldap/schema/SchemaOperationResult.java index 2f4f7d981f..476bc072e3 100644 --- a/server/src/main/java/password/pwm/ldap/schema/SchemaOperationResult.java +++ b/server/src/main/java/password/pwm/ldap/schema/SchemaOperationResult.java @@ -20,24 +20,9 @@ package password.pwm.ldap.schema; -public class SchemaOperationResult +public record SchemaOperationResult( + boolean success, + String operationLog +) { - private boolean success; - private String operationLog; - - public SchemaOperationResult( final boolean success, final String operationLog ) - { - this.success = success; - this.operationLog = operationLog; - } - - public boolean isSuccess( ) - { - return success; - } - - public String getOperationLog( ) - { - return operationLog; - } } diff --git a/server/src/main/java/password/pwm/ldap/search/UserSearchResults.java b/server/src/main/java/password/pwm/ldap/search/UserSearchResults.java index f736c58980..765dfb9cdd 100644 --- a/server/src/main/java/password/pwm/ldap/search/UserSearchResults.java +++ b/server/src/main/java/password/pwm/ldap/search/UserSearchResults.java @@ -34,7 +34,6 @@ import java.util.Objects; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Function; -import java.util.stream.Collectors; public class UserSearchResults { @@ -52,7 +51,7 @@ public UserSearchResults( ) { this.headerAttributeMap = headerAttributeMap == null ? Collections.emptyMap() : Map.copyOf( headerAttributeMap ); - this.results = Map.copyOf( defaultSort( results, headerAttributeMap ) ); + this.results = defaultSort( results, headerAttributeMap ); this.sizeExceeded = sizeExceeded; } @@ -71,12 +70,12 @@ private static Map> defaultSort( final UserIdentitySearchResultComparator comparator = new UserIdentitySearchResultComparator( results, sortAttribute ); - return Collections.unmodifiableMap( results.keySet().stream() + return results.keySet().stream() .sorted( comparator ) - .collect( CollectorUtil.toLinkedMap( + .collect( CollectorUtil.toUnmodifiableLinkedMap( Function.identity(), userIdentity -> CollectionUtil.stripNulls( results.get( userIdentity ) ) - ) ) ); + ) ); } public Map getHeaderAttributeMap( ) @@ -110,14 +109,14 @@ public List> resultsAsJsonOutput( rowMap.put( JSON_KEY_USER_KEY, identityEncoder.apply( userIdentity ) ); rowMap.put( JSON_KEY_ID, idCounter.getAndIncrement() ); - return Map.copyOf( rowMap ); + return Collections.unmodifiableMap( rowMap ); }; return this.getResults().keySet().stream() .filter( Objects::nonNull ) .filter( userIdentity -> ignoreUser == null || !ignoreUser.equals( userIdentity ) ) .map( makeRowMap ) - .collect( Collectors.toUnmodifiableList() ); + .toList(); } private String attributeValue( final UserIdentity userIdentity, final String attributeName ) diff --git a/server/src/main/java/password/pwm/ldap/search/UserSearchService.java b/server/src/main/java/password/pwm/ldap/search/UserSearchService.java index ab4ef970f4..722e6e98c7 100644 --- a/server/src/main/java/password/pwm/ldap/search/UserSearchService.java +++ b/server/src/main/java/password/pwm/ldap/search/UserSearchService.java @@ -243,7 +243,7 @@ public UserSearchResults performMultiUserSearchFromForm( final List formItem, final SessionLabel sessionLabel ) - throws PwmUnrecoverableException, PwmOperationalException + throws PwmUnrecoverableException { final Map attributeHeaderMap = UserSearchResults.fromFormConfiguration( formItem, locale ); final Map> searchResults = performMultiUserSearch( @@ -272,7 +272,7 @@ public Map> performMultiUserSearch( final Collection returnAttributes, final SessionLabel sessionLabel ) - throws PwmUnrecoverableException, PwmOperationalException + throws PwmUnrecoverableException { final Collection ldapProfiles; if ( searchConfiguration.getLdapProfile() != null ) @@ -366,7 +366,7 @@ private Collection makeSearchJobs( final int searchID, final AtomicLoopIntIncrementer jobIncrementer ) - throws PwmUnrecoverableException, PwmOperationalException + throws PwmUnrecoverableException { // check the search configuration data params searchConfiguration.validate(); @@ -478,7 +478,7 @@ else if ( searchConfiguration.getFormValues() != null ) private void validateSpecifiedContext( final SessionLabel sessionLabel, final LdapProfile profile, final String context ) - throws PwmOperationalException, PwmUnrecoverableException + throws PwmUnrecoverableException { Objects.requireNonNull( profile, "ldapProfile can not be null for ldap search context validation" ); Objects.requireNonNull( context, "context can not be null for ldap search context validation" ); diff --git a/server/src/main/java/password/pwm/svc/cache/CacheDebugItem.java b/server/src/main/java/password/pwm/svc/cache/CacheDebugItem.java index 54d6898508..6a2ecca757 100644 --- a/server/src/main/java/password/pwm/svc/cache/CacheDebugItem.java +++ b/server/src/main/java/password/pwm/svc/cache/CacheDebugItem.java @@ -20,9 +20,6 @@ package password.pwm.svc.cache; -import lombok.Builder; - -@Builder record CacheDebugItem( String srcClass, String userIdentity, diff --git a/server/src/main/java/password/pwm/svc/cache/CacheKey.java b/server/src/main/java/password/pwm/svc/cache/CacheKey.java index 1673b3372f..fedea981b7 100644 --- a/server/src/main/java/password/pwm/svc/cache/CacheKey.java +++ b/server/src/main/java/password/pwm/svc/cache/CacheKey.java @@ -20,20 +20,23 @@ package password.pwm.svc.cache; -import lombok.AccessLevel; -import lombok.AllArgsConstructor; -import lombok.Value; import password.pwm.bean.UserIdentity; +import password.pwm.util.java.JavaHelper; import java.util.Objects; -@AllArgsConstructor( access = AccessLevel.PRIVATE ) -@Value -public class CacheKey +public record CacheKey( + Class srcClass, + UserIdentity userIdentity, + String valueID +) { - final Class srcClass; - final UserIdentity userIdentity; - final String valueID; + public CacheKey( final Class srcClass, final UserIdentity userIdentity, final String valueID ) + { + this.srcClass = Objects.requireNonNull( srcClass, "srcClass can not be null" ); + this.userIdentity = userIdentity; + this.valueID = JavaHelper.requireNonEmpty( valueID ); + } public static CacheKey newKey( final Class srcClass, @@ -41,13 +44,6 @@ public static CacheKey newKey( final String valueID ) { - Objects.requireNonNull( srcClass, "srcClass can not be null" ); - Objects.requireNonNull( valueID, "valueID can not be null" ); - - if ( valueID.isEmpty() ) - { - throw new IllegalArgumentException( "valueID can not be empty" ); - } return new CacheKey( srcClass, userIdentity, valueID ); } } diff --git a/server/src/main/java/password/pwm/svc/cache/MemoryCacheStore.java b/server/src/main/java/password/pwm/svc/cache/MemoryCacheStore.java index 3df6bf1634..1241bee894 100644 --- a/server/src/main/java/password/pwm/svc/cache/MemoryCacheStore.java +++ b/server/src/main/java/password/pwm/svc/cache/MemoryCacheStore.java @@ -137,17 +137,16 @@ public List getCacheDebugItems( ) final Instant storeDate = cacheValueWrapper.getExpirationDate(); final String age = Duration.between( storeDate, Instant.now() ).toString(); final int chars = cacheValueWrapper.getPayload().length(); - final String keyClass = cacheKey.getSrcClass() == null ? "null" : cacheKey.getSrcClass().getName(); - final String keyUserID = cacheKey.getUserIdentity() == null ? "null" : cacheKey.getUserIdentity().toDisplayString(); - final String keyValue = cacheKey.getValueID() == null ? "null" : cacheKey.getValueID(); - - final CacheDebugItem cacheDebugItem = CacheDebugItem.builder() - .srcClass( keyClass ) - .userIdentity( keyUserID ) - .valueID( keyValue ) - .age( age ) - .chars( chars ) - .build(); + final String keyClass = cacheKey.srcClass() == null ? "null" : cacheKey.srcClass().getName(); + final String keyUserID = cacheKey.userIdentity() == null ? "null" : cacheKey.userIdentity().toDisplayString(); + final String keyValue = cacheKey.valueID() == null ? "null" : cacheKey.valueID(); + + final CacheDebugItem cacheDebugItem = new CacheDebugItem( + keyClass, + keyUserID, + keyValue, + age, + chars ); items.add( cacheDebugItem ); } @@ -170,7 +169,7 @@ Map storedClassHistogram( final String prefix ) final Map output = new TreeMap<>( ); for ( final CacheKey cacheKey : memoryStore.asMap().keySet() ) { - final String className = cacheKey.getSrcClass() == null ? "n/a" : cacheKey.getSrcClass().getSimpleName(); + final String className = cacheKey.srcClass() == null ? "n/a" : cacheKey.srcClass().getSimpleName(); final String key = prefix + className; final Integer currentValue = output.getOrDefault( key, 0 ); final Integer newValue = currentValue + 1; @@ -186,10 +185,10 @@ public long byteCount() for ( final Map.Entry entry : memoryStore.asMap().entrySet() ) { final CacheKey cacheKey = entry.getKey(); - final UserIdentity userIdentity = cacheKey.getUserIdentity(); - byteCount += userIdentity == null ? 0 : cacheKey.getUserIdentity().toDelimitedKey().length(); - final String valueID = cacheKey.getValueID(); - byteCount += valueID == null ? 0 : cacheKey.getValueID().length(); + final UserIdentity userIdentity = cacheKey.userIdentity(); + byteCount += userIdentity == null ? 0 : cacheKey.userIdentity().toDelimitedKey().length(); + final String valueID = cacheKey.valueID(); + byteCount += valueID == null ? 0 : cacheKey.valueID().length(); final CacheValueWrapper cacheValueWrapper = entry.getValue(); byteCount += cacheValueWrapper.payload.length(); } diff --git a/server/src/main/java/password/pwm/svc/email/EmailConnectionPool.java b/server/src/main/java/password/pwm/svc/email/EmailConnectionPool.java index d7af2c101e..ab4495a2be 100644 --- a/server/src/main/java/password/pwm/svc/email/EmailConnectionPool.java +++ b/server/src/main/java/password/pwm/svc/email/EmailConnectionPool.java @@ -57,7 +57,7 @@ public class EmailConnectionPool public static EmailConnectionPool emptyConnectionPool() { - return new EmailConnectionPool( Collections.emptyList(), EmailServiceSettings.builder().build(), null ); + return new EmailConnectionPool( Collections.emptyList(), EmailServiceSettings.empty(), SessionLabel.SYSTEM_LABEL ); } public EmailConnectionPool( @@ -145,7 +145,7 @@ public void returnEmailConnection( final EmailConnection emailConnection ) private boolean connectionStillValid( final EmailConnection emailConnection ) { - if ( emailConnection.getSentItems() >= settings.getConnectionSendItemLimit() ) + if ( emailConnection.getSentItems() >= settings.connectionSendItemLimit() ) { LOGGER.trace( sessionLabel, () -> "email connection #" + emailConnection.getId() + " has sent " + emailConnection.getSentItems() + " and will be retired" ); @@ -153,7 +153,7 @@ private boolean connectionStillValid( final EmailConnection emailConnection ) } final TimeDuration connectionAge = TimeDuration.fromCurrent( emailConnection.getStartTime() ); - if ( connectionAge.isLongerThan( settings.getConnectionSendItemDuration() ) ) + if ( connectionAge.isLongerThan( settings.connectionSendItemDuration() ) ) { LOGGER.trace( sessionLabel, () -> "email connection #" + emailConnection.getId() + " has lived " + connectionAge.asCompactString() + " and will be retired" ); diff --git a/server/src/main/java/password/pwm/svc/email/EmailService.java b/server/src/main/java/password/pwm/svc/email/EmailService.java index 37ce6b0491..0c2c73bf56 100644 --- a/server/src/main/java/password/pwm/svc/email/EmailService.java +++ b/server/src/main/java/password/pwm/svc/email/EmailService.java @@ -123,10 +123,10 @@ public STATUS postAbstractInit( final PwmApplication pwmApplication, final Domai LOGGER.debug( getSessionLabel(), () -> "starting with settings: " + JsonFactory.get().serialize( emailServiceSettings ) ); final WorkQueueProcessor.Settings settings = WorkQueueProcessor.Settings.builder() - .maxEvents( emailServiceSettings.getQueueMaxItems() ) - .retryDiscardAge( emailServiceSettings.getQueueDiscardAge() ) - .retryInterval( emailServiceSettings.getQueueRetryTimeout() ) - .preThreads( emailServiceSettings.getMaxThreads() ) + .maxEvents( emailServiceSettings.queueMaxItems() ) + .retryDiscardAge( emailServiceSettings.queueDiscardAge() ) + .retryInterval( emailServiceSettings.queueRetryTimeout() ) + .preThreads( emailServiceSettings.maxThreads() ) .build(); final LocalDBStoredQueue localDBStoredQueue = LocalDBStoredQueue.createLocalDBStoredQueue( this.getPwmApplication(), this.getPwmApplication().getLocalDB(), LocalDB.DB.EMAIL_QUEUE ); @@ -282,7 +282,7 @@ private Map stats() } } } - stats.put( "maxThreads", String.valueOf( emailServiceSettings.getMaxThreads() ) ); + stats.put( "maxThreads", String.valueOf( emailServiceSettings.maxThreads() ) ); return Collections.unmodifiableMap( stats ); } @@ -477,7 +477,7 @@ private WorkQueueProcessor.ProcessResult sendItem( final EmailItemBean emailItem } catch ( final MessagingException | PwmException e ) { - if ( EmailServerUtil.examineSendFailure( e, emailServiceSettings.getRetryableStatusResponses(), getSessionLabel() ) ) + if ( EmailServerUtil.examineSendFailure( e, emailServiceSettings.retryableStatusResponses(), getSessionLabel() ) ) { LOGGER.error( getSessionLabel(), () -> "error sending email (" + e.getMessage() + ") " + emailItemBean.toDebugString() + ", will retry" ); diff --git a/server/src/main/java/password/pwm/svc/email/EmailServiceSettings.java b/server/src/main/java/password/pwm/svc/email/EmailServiceSettings.java index c03b14a147..5cad323dfd 100644 --- a/server/src/main/java/password/pwm/svc/email/EmailServiceSettings.java +++ b/server/src/main/java/password/pwm/svc/email/EmailServiceSettings.java @@ -20,8 +20,6 @@ package password.pwm.svc.email; -import lombok.Builder; -import lombok.Value; import password.pwm.AppProperty; import password.pwm.config.AppConfig; import password.pwm.config.PwmSetting; @@ -32,35 +30,44 @@ import java.util.HashSet; import java.util.Set; -@Value -@Builder -public class EmailServiceSettings +record EmailServiceSettings( + TimeDuration connectionSendItemDuration, + TimeDuration queueRetryTimeout, + TimeDuration queueDiscardAge, + int connectionSendItemLimit, + int maxThreads, + int queueMaxItems, + Set retryableStatusResponses +) { - private final TimeDuration connectionSendItemDuration; - private final TimeDuration queueRetryTimeout; - private final TimeDuration queueDiscardAge; - private final int connectionSendItemLimit; - private final int maxThreads; - private final int queueMaxItems; - private final Set retryableStatusResponses; + private static final EmailServiceSettings EMPTY = new EmailServiceSettings( + TimeDuration.ZERO, + TimeDuration.ZERO, + TimeDuration.ZERO, + 0, + 0, + 0, + Set.of() ); + static EmailServiceSettings empty() + { + return EMPTY; + } static EmailServiceSettings fromConfiguration( final AppConfig appConfig ) { - return builder() - .maxThreads( Integer.parseInt( appConfig.readAppProperty( AppProperty.QUEUE_EMAIL_MAX_THREADS ) ) ) - .connectionSendItemDuration( TimeDuration.of( + return new EmailServiceSettings( + TimeDuration.of( Integer.parseInt( appConfig.readAppProperty( AppProperty.QUEUE_EMAIL_MAX_SECONDS_PER_CONNECTION ) ), - TimeDuration.Unit.SECONDS ) ) - .connectionSendItemLimit( Integer.parseInt( appConfig.readAppProperty( AppProperty.QUEUE_EMAIL_MAX_ITEMS_PER_CONNECTION ) ) ) - .queueRetryTimeout( TimeDuration.of( + TimeDuration.Unit.SECONDS ), + TimeDuration.of( Long.parseLong( appConfig.readAppProperty( AppProperty.QUEUE_EMAIL_RETRY_TIMEOUT_MS ) ), - TimeDuration.Unit.MILLISECONDS ) - ) - .queueDiscardAge( TimeDuration.of( appConfig.readSettingAsLong( PwmSetting.EMAIL_MAX_QUEUE_AGE ), TimeDuration.Unit.SECONDS ) ) - .queueMaxItems( Integer.parseInt( appConfig.readAppProperty( AppProperty.QUEUE_EMAIL_MAX_COUNT ) ) ) - .retryableStatusResponses( readRetryableStatusCodes( appConfig ) ) - .build(); + TimeDuration.Unit.MILLISECONDS ), + TimeDuration.of( appConfig.readSettingAsLong( PwmSetting.EMAIL_MAX_QUEUE_AGE ), TimeDuration.Unit.SECONDS ), + Integer.parseInt( appConfig.readAppProperty( AppProperty.QUEUE_EMAIL_MAX_ITEMS_PER_CONNECTION ) ), + Integer.parseInt( appConfig.readAppProperty( AppProperty.QUEUE_EMAIL_MAX_THREADS ) ), + Integer.parseInt( appConfig.readAppProperty( AppProperty.QUEUE_EMAIL_MAX_COUNT ) ), + readRetryableStatusCodes( appConfig ) ); } private static Set readRetryableStatusCodes( final AppConfig appConfig ) diff --git a/server/src/main/java/password/pwm/svc/event/AuditRecord.java b/server/src/main/java/password/pwm/svc/event/AuditRecord.java index 3245230c7d..44fe729b92 100644 --- a/server/src/main/java/password/pwm/svc/event/AuditRecord.java +++ b/server/src/main/java/password/pwm/svc/event/AuditRecord.java @@ -26,23 +26,23 @@ public interface AuditRecord { - AuditEventType getType( ); + AuditEventType type( ); - AuditEvent getEventCode( ); + AuditEvent eventCode( ); - Instant getTimestamp( ); + Instant timestamp( ); - String getMessage( ); + String message( ); - String getGuid( ); + String guid( ); - String getNarrative( ); + String narrative( ); - String getXdasTaxonomy( ); + String xdasTaxonomy( ); - String getXdasOutcome( ); + String xdasOutcome( ); - String getInstance( ); + String instance( ); - DomainID getDomain(); + DomainID domain(); } diff --git a/server/src/main/java/password/pwm/svc/event/AuditRecordData.java b/server/src/main/java/password/pwm/svc/event/AuditRecordData.java index f470d437b2..2ee6c51714 100644 --- a/server/src/main/java/password/pwm/svc/event/AuditRecordData.java +++ b/server/src/main/java/password/pwm/svc/event/AuditRecordData.java @@ -22,32 +22,35 @@ import lombok.AccessLevel; import lombok.Builder; -import lombok.Value; import password.pwm.bean.DomainID; import password.pwm.bean.ProfileID; import java.time.Instant; -@Value -@Builder( access = AccessLevel.PACKAGE, toBuilder = true ) -public class AuditRecordData implements AuditRecord, SystemAuditRecord, UserAuditRecord, HelpdeskAuditRecord +public record AuditRecordData( + AuditEventType type, + AuditEvent eventCode, + String guid, + Instant timestamp, + String message, + String narrative, + String xdasTaxonomy, + String xdasOutcome, + String instance, + String perpetratorID, + String perpetratorDN, + ProfileID perpetratorLdapProfile, + String sourceAddress, + String sourceHost, + String targetID, + String targetDN, + ProfileID targetLdapProfile, + DomainID domain +) + implements AuditRecord, SystemAuditRecord, UserAuditRecord, HelpdeskAuditRecord { - private final AuditEventType type; - private final AuditEvent eventCode; - private final String guid; - private final Instant timestamp; - private final String message; - private final String narrative; - private final String xdasTaxonomy; - private final String xdasOutcome; - private final String instance; - private final String perpetratorID; - private final String perpetratorDN; - private final ProfileID perpetratorLdapProfile; - private final String sourceAddress; - private final String sourceHost; - private final String targetID; - private final String targetDN; - private final ProfileID targetLdapProfile; - private final DomainID domain; + @Builder( access = AccessLevel.PACKAGE, toBuilder = true ) + public AuditRecordData + { + } } diff --git a/server/src/main/java/password/pwm/svc/event/AuditRecordFactory.java b/server/src/main/java/password/pwm/svc/event/AuditRecordFactory.java index 6107e041be..439b19cf3a 100644 --- a/server/src/main/java/password/pwm/svc/event/AuditRecordFactory.java +++ b/server/src/main/java/password/pwm/svc/event/AuditRecordFactory.java @@ -265,7 +265,7 @@ public UserAuditRecord createUserAuditRecord( private String makeNarrativeString( final AuditRecord auditRecord ) { - final PwmDisplayBundle pwmDisplayBundle = auditRecord.getEventCode().getNarrative(); + final PwmDisplayBundle pwmDisplayBundle = auditRecord.eventCode().getNarrative(); String outputString = LocaleHelper.getLocalizedMessage( PwmConstants.DEFAULT_LOCALE, pwmDisplayBundle, pwmApplication.getConfig() ); diff --git a/server/src/main/java/password/pwm/svc/event/AuditService.java b/server/src/main/java/password/pwm/svc/event/AuditService.java index 362876a37b..1289e025a4 100644 --- a/server/src/main/java/password/pwm/svc/event/AuditService.java +++ b/server/src/main/java/password/pwm/svc/event/AuditService.java @@ -46,9 +46,9 @@ import password.pwm.svc.stats.StatisticsClient; import password.pwm.util.i18n.LocaleHelper; import password.pwm.util.java.PwmUtil; -import password.pwm.util.json.JsonFactory; import password.pwm.util.java.StatisticCounterBundle; import password.pwm.util.java.StringUtil; +import password.pwm.util.json.JsonFactory; import password.pwm.util.localdb.LocalDB; import password.pwm.util.logging.PwmLogger; import password.pwm.util.macro.MacroRequest; @@ -153,36 +153,37 @@ public Iterator readVault( ) private void sendAsEmail( final AuditRecord record ) throws PwmUnrecoverableException { - if ( record == null || record.getEventCode() == null ) + if ( record == null || record.eventCode() == null ) { return; } - if ( settings.getAlertFromAddress() == null || settings.getAlertFromAddress().length() < 1 ) + + if ( StringUtil.isEmpty( settings.alertFromAddress() ) ) { return; } - switch ( record.getEventCode().getType() ) + switch ( record.eventCode().getType() ) { case SYSTEM: - for ( final String toAddress : settings.getSystemEmailAddresses() ) + for ( final String toAddress : settings.systemEmailAddresses() ) { - sendAsEmail( getPwmApplication(), record, toAddress, settings.getAlertFromAddress() ); + sendAsEmail( getPwmApplication(), record, toAddress, settings.alertFromAddress() ); } break; case USER: case HELPDESK: { - for ( final String toAddress : settings.getUserEmailAddresses() ) + for ( final String toAddress : settings.userEmailAddresses() ) { - sendAsEmail( getPwmApplication(), record, toAddress, settings.getAlertFromAddress() ); + sendAsEmail( getPwmApplication(), record, toAddress, settings.alertFromAddress() ); } } - break; + break; default: - PwmUtil.unhandledSwitchStatement( record.getEventCode().getType() ); + PwmUtil.unhandledSwitchStatement( record.eventCode().getType() ); } } @@ -199,7 +200,7 @@ private void sendAsEmail( final MacroRequest macroRequest = MacroRequest.forNonUserSpecific( pwmApplication, getSessionLabel() ); String subject = macroRequest.expandMacros( pwmApplication.getConfig().readAppProperty( AppProperty.AUDIT_EVENTS_EMAILSUBJECT ) ); - subject = subject.replace( "%EVENT%", record.getEventCode().getLocalizedString( pwmApplication.getConfig(), PwmConstants.DEFAULT_LOCALE ) ); + subject = subject.replace( "%EVENT%", record.eventCode().getLocalizedString( pwmApplication.getConfig(), PwmConstants.DEFAULT_LOCALE ) ); final String body; { @@ -208,12 +209,12 @@ private void sendAsEmail( body = StringUtil.mapToString( mapRecord, "=", "\n" ); } - final EmailItemBean emailItem = EmailItemBean.builder() - .to( toAddress ) - .from( fromAddress ) - .subject( subject ) - .bodyPlain( body ) - .build(); + final EmailItemBean emailItem = new EmailItemBean( + toAddress, + fromAddress, + subject, + body, + null ); pwmApplication.getEmailQueue().submitEmail( emailItem, null, macroRequest ); statisticCounterBundle.increment( DebugKey.emailsSent ); @@ -247,15 +248,15 @@ public void submit( final SessionLabel sessionLabel, final AuditRecord auditReco return; } - if ( auditRecord.getEventCode() == null ) + if ( auditRecord.eventCode() == null ) { LOGGER.error( sessionLabel, () -> "discarding audit event, missing event type; event=" + jsonRecord ); return; } - if ( !settings.getPermittedEvents().contains( auditRecord.getEventCode() ) ) + if ( !settings.permittedEvents().contains( auditRecord.eventCode() ) ) { - LOGGER.debug( () -> "discarding event, " + auditRecord.getEventCode() + " are being ignored; event=" + jsonRecord ); + LOGGER.debug( () -> "discarding event, " + auditRecord.eventCode() + " are being ignored; event=" + jsonRecord ); return; } @@ -281,13 +282,13 @@ public void submit( final SessionLabel sessionLabel, final AuditRecord auditReco // add to user history record if ( auditRecord instanceof UserAuditRecord ) { - final DomainID domainID = auditRecord.getDomain(); + final DomainID domainID = auditRecord.domain(); if ( domainID != null ) { final PwmDomain pwmDomain = getPwmApplication().domains().get( domainID ); if ( pwmDomain != null ) { - final String perpetratorDN = ( ( UserAuditRecord ) auditRecord ).getPerpetratorDN(); + final String perpetratorDN = ( ( UserAuditRecord ) auditRecord ).perpetratorDN(); if ( StringUtil.notEmpty( perpetratorDN ) ) { pwmDomain.getUserHistoryService().write( sessionLabel, ( UserAuditRecord ) auditRecord ); @@ -358,36 +359,36 @@ public int outputVaultToCsv( final OutputStream outputStream, final Locale local counter++; final List lineOutput = new ArrayList<>(); - lineOutput.add( loopRecord.getEventCode().getType().toString() ); - lineOutput.add( loopRecord.getEventCode().toString() ); - lineOutput.add( StringUtil.toIsoDate( loopRecord.getTimestamp() ) ); - lineOutput.add( loopRecord.getGuid() ); - lineOutput.add( loopRecord.getMessage() == null ? "" : loopRecord.getMessage() ); + lineOutput.add( loopRecord.eventCode().getType().toString() ); + lineOutput.add( loopRecord.eventCode().toString() ); + lineOutput.add( StringUtil.toIsoDate( loopRecord.timestamp() ) ); + lineOutput.add( loopRecord.guid() ); + lineOutput.add( loopRecord.message() == null ? "" : loopRecord.message() ); if ( loopRecord instanceof SystemAuditRecord ) { - lineOutput.add( loopRecord.getInstance() ); + lineOutput.add( loopRecord.instance() ); } if ( loopRecord instanceof UserAuditRecord ) { - lineOutput.add( ( ( UserAuditRecord ) loopRecord ).getPerpetratorID() ); - lineOutput.add( ( ( UserAuditRecord ) loopRecord ).getPerpetratorDN() ); + lineOutput.add( ( ( UserAuditRecord ) loopRecord ).perpetratorID() ); + lineOutput.add( ( ( UserAuditRecord ) loopRecord ).perpetratorDN() ); lineOutput.add( "" ); lineOutput.add( "" ); - lineOutput.add( ( ( UserAuditRecord ) loopRecord ).getSourceAddress() ); - lineOutput.add( ( ( UserAuditRecord ) loopRecord ).getSourceHost() ); + lineOutput.add( ( ( UserAuditRecord ) loopRecord ).sourceAddress() ); + lineOutput.add( ( ( UserAuditRecord ) loopRecord ).sourceHost() ); } if ( loopRecord instanceof HelpdeskAuditRecord ) { - lineOutput.add( ( ( HelpdeskAuditRecord ) loopRecord ).getPerpetratorID() ); - lineOutput.add( ( ( HelpdeskAuditRecord ) loopRecord ).getPerpetratorDN() ); - lineOutput.add( ( ( HelpdeskAuditRecord ) loopRecord ).getTargetID() ); - lineOutput.add( ( ( HelpdeskAuditRecord ) loopRecord ).getTargetDN() ); - lineOutput.add( ( ( HelpdeskAuditRecord ) loopRecord ).getSourceAddress() ); - lineOutput.add( ( ( HelpdeskAuditRecord ) loopRecord ).getSourceHost() ); + lineOutput.add( ( ( HelpdeskAuditRecord ) loopRecord ).perpetratorID() ); + lineOutput.add( ( ( HelpdeskAuditRecord ) loopRecord ).perpetratorDN() ); + lineOutput.add( ( ( HelpdeskAuditRecord ) loopRecord ).targetID() ); + lineOutput.add( ( ( HelpdeskAuditRecord ) loopRecord ).targetDN() ); + lineOutput.add( ( ( HelpdeskAuditRecord ) loopRecord ).sourceAddress() ); + lineOutput.add( ( ( HelpdeskAuditRecord ) loopRecord ).sourceHost() ); } - if ( loopRecord.getDomain() != null ) + if ( loopRecord.domain() != null ) { - lineOutput.add( loopRecord.getDomain().stringValue() ); + lineOutput.add( loopRecord.domain().stringValue() ); } csvPrinter.printRecord( lineOutput ); } diff --git a/server/src/main/java/password/pwm/svc/event/AuditServiceClient.java b/server/src/main/java/password/pwm/svc/event/AuditServiceClient.java index 581e2614bd..f3e91ba4a9 100644 --- a/server/src/main/java/password/pwm/svc/event/AuditServiceClient.java +++ b/server/src/main/java/password/pwm/svc/event/AuditServiceClient.java @@ -36,7 +36,6 @@ public class AuditServiceClient { private static final PwmLogger LOGGER = PwmLogger.forClass( AuditServiceClient.class ); - public static void submit( final PwmApplication pwmApplication, final SessionLabel sessionLabel, final AuditRecord auditRecord ) { Objects.requireNonNull( pwmApplication ); diff --git a/server/src/main/java/password/pwm/svc/event/AuditSettings.java b/server/src/main/java/password/pwm/svc/event/AuditSettings.java index a27ec327d4..706917bac3 100644 --- a/server/src/main/java/password/pwm/svc/event/AuditSettings.java +++ b/server/src/main/java/password/pwm/svc/event/AuditSettings.java @@ -20,11 +20,10 @@ package password.pwm.svc.event; -import lombok.Builder; -import lombok.Value; import password.pwm.AppProperty; import password.pwm.config.AppConfig; import password.pwm.config.PwmSetting; +import password.pwm.util.java.CollectionUtil; import password.pwm.util.java.TimeDuration; import java.util.Collections; @@ -32,28 +31,41 @@ import java.util.List; import java.util.Set; -@Value -@Builder -class AuditSettings +record AuditSettings( + List systemEmailAddresses, + List userEmailAddresses, + String alertFromAddress, + Set permittedEvents, + TimeDuration maxRecordAge, + long maxRecords +) { - private List systemEmailAddresses; - private List userEmailAddresses; - private String alertFromAddress; - private Set permittedEvents; - private TimeDuration maxRecordAge; - private long maxRecords; - + AuditSettings( + final List systemEmailAddresses, + final List userEmailAddresses, + final String alertFromAddress, + final Set permittedEvents, + final TimeDuration maxRecordAge, + final long maxRecords + ) + { + this.systemEmailAddresses = CollectionUtil.stripNulls( systemEmailAddresses ); + this.userEmailAddresses = CollectionUtil.stripNulls( userEmailAddresses ); + this.alertFromAddress = alertFromAddress; + this.permittedEvents = CollectionUtil.stripNulls( permittedEvents ); + this.maxRecordAge = maxRecordAge; + this.maxRecords = maxRecords; + } static AuditSettings fromConfig( final AppConfig appConfig ) { - return AuditSettings.builder() - .systemEmailAddresses( appConfig.readSettingAsStringArray( PwmSetting.AUDIT_EMAIL_SYSTEM_TO ) ) - .userEmailAddresses( appConfig.readSettingAsStringArray( PwmSetting.AUDIT_EMAIL_USER_TO ) ) - .alertFromAddress( appConfig.readAppProperty( AppProperty.AUDIT_EVENTS_EMAILFROM ) ) - .permittedEvents( figurePermittedEvents( appConfig ) ) - .maxRecordAge( TimeDuration.of( appConfig.readSettingAsLong( PwmSetting.EVENTS_AUDIT_MAX_AGE ), TimeDuration.Unit.SECONDS ) ) - .maxRecords( appConfig.readSettingAsLong( PwmSetting.EVENTS_AUDIT_MAX_EVENTS ) ) - .build(); + return new AuditSettings( + appConfig.readSettingAsStringArray( PwmSetting.AUDIT_EMAIL_SYSTEM_TO ), + appConfig.readSettingAsStringArray( PwmSetting.AUDIT_EMAIL_USER_TO ), + appConfig.readAppProperty( AppProperty.AUDIT_EVENTS_EMAILFROM ), + figurePermittedEvents( appConfig ), + TimeDuration.of( appConfig.readSettingAsLong( PwmSetting.EVENTS_AUDIT_MAX_AGE ), TimeDuration.Unit.SECONDS ), + appConfig.readSettingAsLong( PwmSetting.EVENTS_AUDIT_MAX_EVENTS ) ); } private static Set figurePermittedEvents( final AppConfig appConfig ) diff --git a/server/src/main/java/password/pwm/svc/event/CEFAuditFormatter.java b/server/src/main/java/password/pwm/svc/event/CEFAuditFormatter.java index 8d51bd47e0..853177af4e 100644 --- a/server/src/main/java/password/pwm/svc/event/CEFAuditFormatter.java +++ b/server/src/main/java/password/pwm/svc/event/CEFAuditFormatter.java @@ -184,12 +184,12 @@ private String makeCefHeader( final PwmApplication pwmApplication, final Setting appendCefHeader( pwmApplication, cefOutput, PwmConstants.SERVLET_VERSION ); // Device Event Class ID - appendCefHeader( pwmApplication, cefOutput, String.valueOf( auditRecord.getEventCode() ) ); + appendCefHeader( pwmApplication, cefOutput, String.valueOf( auditRecord.eventCode() ) ); // field name appendCefHeader( pwmApplication, cefOutput, LocaleHelper.getLocalizedMessage( PwmConstants.DEFAULT_LOCALE, - auditRecord.getEventCode().getMessage(), + auditRecord.eventCode().getMessage(), pwmApplication.getConfig() ) ); diff --git a/server/src/main/java/password/pwm/svc/event/HelpdeskAuditRecord.java b/server/src/main/java/password/pwm/svc/event/HelpdeskAuditRecord.java index 2bc6308851..148b3fe1a2 100644 --- a/server/src/main/java/password/pwm/svc/event/HelpdeskAuditRecord.java +++ b/server/src/main/java/password/pwm/svc/event/HelpdeskAuditRecord.java @@ -24,9 +24,9 @@ public interface HelpdeskAuditRecord extends UserAuditRecord { - String getTargetID( ); + String targetID( ); - String getTargetDN( ); + String targetDN( ); - ProfileID getTargetLdapProfile( ); + ProfileID targetLdapProfile( ); } diff --git a/server/src/main/java/password/pwm/svc/event/JsonAuditFormatter.java b/server/src/main/java/password/pwm/svc/event/JsonAuditFormatter.java index 2c6cff3c86..0b9b337ca8 100644 --- a/server/src/main/java/password/pwm/svc/event/JsonAuditFormatter.java +++ b/server/src/main/java/password/pwm/svc/event/JsonAuditFormatter.java @@ -52,8 +52,8 @@ public String convertAuditRecordToMessage( else { final AuditRecordData inputRecord = ( ( AuditRecordData ) auditRecord ).toBuilder() - .message( auditRecord.getMessage() == null ? "" : auditRecord.getMessage() ) - .narrative( auditRecord.getNarrative() == null ? "" : auditRecord.getNarrative() ) + .message( auditRecord.message() == null ? "" : auditRecord.message() ) + .narrative( auditRecord.narrative() == null ? "" : auditRecord.narrative() ) .build(); final String truncateMessage = appConfig.readAppProperty( AppProperty.AUDIT_SYSLOG_TRUNCATE_MESSAGE ); @@ -64,8 +64,8 @@ public String convertAuditRecordToMessage( + JsonFactory.get().serialize( copiedRecord.build() ).length() + truncateMessage.length(); final int maxMessageAndNarrativeLength = maxLength - ( shortenedMessageLength + ( truncateMessage.length() * 2 ) ); - int maxMessageLength = inputRecord.getMessage().length(); - int maxNarrativeLength = inputRecord.getNarrative().length(); + int maxMessageLength = inputRecord.message().length(); + int maxNarrativeLength = inputRecord.narrative().length(); { int top = maxMessageAndNarrativeLength; @@ -77,13 +77,13 @@ public String convertAuditRecordToMessage( } } - copiedRecord.message( inputRecord.getMessage().length() > maxMessageLength - ? inputRecord.getMessage().substring( 0, maxMessageLength ) + truncateMessage - : inputRecord.getMessage() ); + copiedRecord.message( inputRecord.message().length() > maxMessageLength + ? inputRecord.message().substring( 0, maxMessageLength ) + truncateMessage + : inputRecord.message() ); - copiedRecord.narrative( inputRecord.getNarrative().length() > maxNarrativeLength - ? inputRecord.getNarrative().substring( 0, maxNarrativeLength ) + truncateMessage - : inputRecord.getNarrative() ); + copiedRecord.narrative( inputRecord.narrative().length() > maxNarrativeLength + ? inputRecord.narrative().substring( 0, maxNarrativeLength ) + truncateMessage + : inputRecord.narrative() ); message.append( JsonFactory.get().serialize( copiedRecord.build() ) ); } diff --git a/server/src/main/java/password/pwm/svc/event/LocalDbAuditVault.java b/server/src/main/java/password/pwm/svc/event/LocalDbAuditVault.java index 9e372186d1..edcc6aae0e 100644 --- a/server/src/main/java/password/pwm/svc/event/LocalDbAuditVault.java +++ b/server/src/main/java/password/pwm/svc/event/LocalDbAuditVault.java @@ -140,7 +140,7 @@ public void remove( ) public String sizeToDebugString( ) { final long storedEvents = this.size(); - final long maxEvents = settings.getMaxRecords(); + final long maxEvents = settings.maxRecords(); final Percent percent = Percent.of( storedEvents, maxEvents ); return storedEvents + " / " + maxEvents + " (" + percent.pretty( 2 ) + ")"; @@ -172,7 +172,7 @@ public void add( final AuditRecord record ) final String jsonRecord = JsonFactory.get().serialize( record ); auditDB.addLast( jsonRecord ); - if ( auditDB.size() > settings.getMaxRecords() ) + if ( auditDB.size() > settings.maxRecords() ) { removeRecords( 1 ); } @@ -184,7 +184,7 @@ private void readOldestRecord( ) { final String stringFirstRecord = auditDB.getFirst(); final AuditRecordData firstRecord = JsonFactory.get().deserialize( stringFirstRecord, AuditRecordData.class ); - oldestRecord = firstRecord.getTimestamp(); + oldestRecord = firstRecord.timestamp(); } } @@ -227,7 +227,7 @@ private boolean trim( final int maxRemovals ) return false; } - if ( auditDB.size() > settings.getMaxRecords() + maxRemovals ) + if ( auditDB.size() > settings.maxRecords() + maxRemovals ) { removeRecords( maxRemovals ); return true; @@ -240,7 +240,7 @@ private boolean trim( final int maxRemovals ) && status == PwmService.STATUS.OPEN ) { - if ( TimeDuration.fromCurrent( oldestRecord ).isLongerThan( settings.getMaxRecordAge() ) ) + if ( TimeDuration.fromCurrent( oldestRecord ).isLongerThan( settings.maxRecordAge() ) ) { removeRecords( 1 ); workActions++; diff --git a/server/src/main/java/password/pwm/svc/event/UserAuditRecord.java b/server/src/main/java/password/pwm/svc/event/UserAuditRecord.java index 3dbaf91e27..921613a343 100644 --- a/server/src/main/java/password/pwm/svc/event/UserAuditRecord.java +++ b/server/src/main/java/password/pwm/svc/event/UserAuditRecord.java @@ -27,13 +27,13 @@ */ public interface UserAuditRecord extends SystemAuditRecord { - String getPerpetratorID(); + String perpetratorID(); - String getPerpetratorDN(); + String perpetratorDN(); - String getSourceAddress(); + String sourceAddress(); - String getSourceHost(); + String sourceHost(); - ProfileID getPerpetratorLdapProfile(); + ProfileID perpetratorLdapProfile(); } diff --git a/server/src/main/java/password/pwm/svc/intruder/IntruderDomainService.java b/server/src/main/java/password/pwm/svc/intruder/IntruderDomainService.java index 57dd516fed..bab82af073 100644 --- a/server/src/main/java/password/pwm/svc/intruder/IntruderDomainService.java +++ b/server/src/main/java/password/pwm/svc/intruder/IntruderDomainService.java @@ -111,7 +111,7 @@ public STATUS postAbstractInit( final PwmApplication pwmApplication, final Domai try { - final DataStore dataStore = initDataStore( pwmApplication, getSessionLabel(), intruderSettings.getIntruderStorageMethod() ); + final DataStore dataStore = initDataStore( pwmApplication, getSessionLabel(), intruderSettings.intruderStorageMethod() ); serviceInfo = ServiceInfoBean.builder().storageMethod( dataStore.getDataStorageMethod() ).build(); initializeRecordManagers(); @@ -178,8 +178,8 @@ private void initializeRecordManagers() for ( final IntruderRecordType type : IntruderRecordType.values() ) { - final IntruderSettings.TypeSettings typeSettings = intruderSettings.getTargetSettings().get( type ); - if ( typeSettings.isConfigured() ) + final IntruderSettings.TypeSettings typeSettings = intruderSettings.targetSettings().get( type ); + if ( typeSettings.configured() ) { LOGGER.trace( getSessionLabel(), () -> "starting record manager for type '" + type + "' with settings: " + typeSettings ); recordManagers.put( type, new IntruderRecordManagerImpl( pwmDomain, type, recordStore, intruderSettings ) ); diff --git a/server/src/main/java/password/pwm/svc/intruder/IntruderRecordManagerImpl.java b/server/src/main/java/password/pwm/svc/intruder/IntruderRecordManagerImpl.java index cc16cbef84..abd5c79473 100644 --- a/server/src/main/java/password/pwm/svc/intruder/IntruderRecordManagerImpl.java +++ b/server/src/main/java/password/pwm/svc/intruder/IntruderRecordManagerImpl.java @@ -59,8 +59,8 @@ class IntruderRecordManagerImpl implements IntruderRecordManager this.secureService = pwmDomain.getSecureService(); this.recordType = recordType; this.recordStore = recordStore; - this.settings = settings.getTargetSettings().get( recordType ); - this.storageHashAlgorithm = settings.getStorageHashAlgorithm(); + this.settings = settings.targetSettings().get( recordType ); + this.storageHashAlgorithm = settings.storageHashAlgorithm(); } @Override @@ -77,12 +77,12 @@ public boolean checkSubject( final String subject ) return false; } - if ( TimeDuration.fromCurrent( record.get().getTimeStamp() ).isLongerThan( settings.getCheckDuration() ) ) + if ( TimeDuration.fromCurrent( record.get().getTimeStamp() ).isLongerThan( settings.checkDuration() ) ) { return false; } - if ( record.get().getAttemptCount() >= settings.getCheckCount() ) + if ( record.get().getAttemptCount() >= settings.checkCount() ) { return true; } @@ -100,7 +100,7 @@ public void markSubject( final String subject ) IntruderRecord record = readIntruderRecord( subject ).orElseGet( () -> new IntruderRecord( domainID, recordType, subject ) ); final TimeDuration age = TimeDuration.fromCurrent( record.getTimeStamp() ); - if ( age.isLongerThan( settings.getCheckDuration() ) ) + if ( age.isLongerThan( settings.checkDuration() ) ) { final IntruderRecord finalRecord = record; LOGGER.debug( () -> "re-setting existing outdated record=" + JsonFactory.get().serialize( finalRecord ) + " (" + age.asCompactString() + ")" ); diff --git a/server/src/main/java/password/pwm/svc/intruder/IntruderSettings.java b/server/src/main/java/password/pwm/svc/intruder/IntruderSettings.java index 235a1a9d53..d302049675 100644 --- a/server/src/main/java/password/pwm/svc/intruder/IntruderSettings.java +++ b/server/src/main/java/password/pwm/svc/intruder/IntruderSettings.java @@ -20,12 +20,11 @@ package password.pwm.svc.intruder; -import lombok.Builder; -import lombok.Value; import password.pwm.AppProperty; import password.pwm.config.DomainConfig; import password.pwm.config.PwmSetting; import password.pwm.config.option.IntruderStorageMethod; +import password.pwm.util.java.CollectionUtil; import password.pwm.util.java.EnumUtil; import password.pwm.util.java.TimeDuration; import password.pwm.util.secure.PwmHashAlgorithm; @@ -34,24 +33,34 @@ import java.util.EnumMap; import java.util.Map; -@Value -@Builder -public class IntruderSettings +public record IntruderSettings( + Map targetSettings, + IntruderStorageMethod intruderStorageMethod, + PwmHashAlgorithm storageHashAlgorithm +) { - private final Map targetSettings; - private final IntruderStorageMethod intruderStorageMethod; - private final PwmHashAlgorithm storageHashAlgorithm; + public IntruderSettings( + final Map targetSettings, + final IntruderStorageMethod intruderStorageMethod, + final PwmHashAlgorithm storageHashAlgorithm + ) + { + this.targetSettings = CollectionUtil.stripNulls( targetSettings ); + this.intruderStorageMethod = intruderStorageMethod; + this.storageHashAlgorithm = storageHashAlgorithm; + } public static IntruderSettings fromConfiguration( final DomainConfig config ) { - final PwmHashAlgorithm storageHashAlgorithm = EnumUtil.readEnumFromString( PwmHashAlgorithm.class, config.readAppProperty( AppProperty.INTRUDER_STORAGE_HASH_ALGORITHM ) ) + final PwmHashAlgorithm storageHashAlgorithm = EnumUtil.readEnumFromString( + PwmHashAlgorithm.class, + config.readAppProperty( AppProperty.INTRUDER_STORAGE_HASH_ALGORITHM ) ) .orElse( PwmHashAlgorithm.SHA256 ); - return IntruderSettings.builder() - .targetSettings( makeTypeSettings( config ) ) - .intruderStorageMethod( config.getAppConfig().readSettingAsEnum( PwmSetting.INTRUDER_STORAGE_METHOD, IntruderStorageMethod.class ) ) - .storageHashAlgorithm( storageHashAlgorithm ) - .build(); + return new IntruderSettings( + makeTypeSettings( config ), + config.getAppConfig().readSettingAsEnum( PwmSetting.INTRUDER_STORAGE_METHOD, IntruderStorageMethod.class ), + storageHashAlgorithm ); } private static Map makeTypeSettings( final DomainConfig config ) @@ -59,40 +68,36 @@ private static Map makeTypeSettings( final Dom final Map targetSettings = new EnumMap<>( IntruderRecordType.class ); { - final TypeSettings settings = TypeSettings.builder() - .checkCount( ( int ) config.readSettingAsLong( PwmSetting.INTRUDER_USER_MAX_ATTEMPTS ) ) - .resetDuration( TimeDuration.of( config.readSettingAsLong( PwmSetting.INTRUDER_USER_RESET_TIME ), TimeDuration.Unit.SECONDS ) ) - .checkDuration( TimeDuration.of( config.readSettingAsLong( PwmSetting.INTRUDER_USER_CHECK_TIME ), TimeDuration.Unit.SECONDS ) ) - .build(); + final TypeSettings settings = new TypeSettings( + TimeDuration.of( config.readSettingAsLong( PwmSetting.INTRUDER_USER_CHECK_TIME ), TimeDuration.Unit.SECONDS ), + Math.toIntExact( config.readSettingAsLong( PwmSetting.INTRUDER_USER_MAX_ATTEMPTS ) ), + TimeDuration.of( config.readSettingAsLong( PwmSetting.INTRUDER_USER_RESET_TIME ), TimeDuration.Unit.SECONDS ) ); targetSettings.put( IntruderRecordType.USERNAME, settings ); targetSettings.put( IntruderRecordType.USER_ID, settings ); } { - final TypeSettings settings = TypeSettings.builder() - .checkCount( ( int ) config.readSettingAsLong( PwmSetting.INTRUDER_ATTRIBUTE_MAX_ATTEMPTS ) ) - .resetDuration( TimeDuration.of( config.readSettingAsLong( PwmSetting.INTRUDER_ATTRIBUTE_RESET_TIME ), TimeDuration.Unit.MILLISECONDS ) ) - .checkDuration( TimeDuration.of( config.readSettingAsLong( PwmSetting.INTRUDER_ATTRIBUTE_CHECK_TIME ), TimeDuration.Unit.MILLISECONDS ) ) - .build(); + final TypeSettings settings = new TypeSettings( + TimeDuration.of( config.readSettingAsLong( PwmSetting.INTRUDER_ATTRIBUTE_CHECK_TIME ), TimeDuration.Unit.MILLISECONDS ), + Math.toIntExact( config.readSettingAsLong( PwmSetting.INTRUDER_ATTRIBUTE_MAX_ATTEMPTS ) ), + TimeDuration.of( config.readSettingAsLong( PwmSetting.INTRUDER_ATTRIBUTE_RESET_TIME ), TimeDuration.Unit.MILLISECONDS ) ); targetSettings.put( IntruderRecordType.ATTRIBUTE, settings ); } { - final TypeSettings settings = TypeSettings.builder() - .checkCount( ( int ) config.readSettingAsLong( PwmSetting.INTRUDER_TOKEN_DEST_MAX_ATTEMPTS ) ) - .resetDuration( TimeDuration.of( config.readSettingAsLong( PwmSetting.INTRUDER_TOKEN_DEST_RESET_TIME ), TimeDuration.Unit.SECONDS ) ) - .checkDuration( TimeDuration.of( config.readSettingAsLong( PwmSetting.INTRUDER_TOKEN_DEST_CHECK_TIME ), TimeDuration.Unit.SECONDS ) ) - .build(); + final TypeSettings settings = new TypeSettings( + TimeDuration.of( config.readSettingAsLong( PwmSetting.INTRUDER_TOKEN_DEST_CHECK_TIME ), TimeDuration.Unit.SECONDS ), + Math.toIntExact( config.readSettingAsLong( PwmSetting.INTRUDER_TOKEN_DEST_MAX_ATTEMPTS ) ), + TimeDuration.of( config.readSettingAsLong( PwmSetting.INTRUDER_TOKEN_DEST_RESET_TIME ), TimeDuration.Unit.SECONDS ) ); targetSettings.put( IntruderRecordType.TOKEN_DEST, settings ); } { - final TypeSettings settings = TypeSettings.builder() - .checkCount( ( int ) config.readSettingAsLong( PwmSetting.INTRUDER_ADDRESS_MAX_ATTEMPTS ) ) - .resetDuration( TimeDuration.of( config.readSettingAsLong( PwmSetting.INTRUDER_ADDRESS_RESET_TIME ), TimeDuration.Unit.SECONDS ) ) - .checkDuration( TimeDuration.of( config.readSettingAsLong( PwmSetting.INTRUDER_ADDRESS_CHECK_TIME ), TimeDuration.Unit.SECONDS ) ) - .build(); + final TypeSettings settings = new TypeSettings( + TimeDuration.of( config.readSettingAsLong( PwmSetting.INTRUDER_ADDRESS_CHECK_TIME ), TimeDuration.Unit.SECONDS ), + Math.toIntExact( config.readSettingAsLong( PwmSetting.INTRUDER_ADDRESS_MAX_ATTEMPTS ) ), + TimeDuration.of( config.readSettingAsLong( PwmSetting.INTRUDER_ADDRESS_RESET_TIME ), TimeDuration.Unit.SECONDS ) ); targetSettings.put( IntruderRecordType.ADDRESS, settings ); } @@ -100,17 +105,17 @@ private static Map makeTypeSettings( final Dom return Collections.unmodifiableMap( targetSettings ); } - @Value - @Builder - public static class TypeSettings + public record TypeSettings( + TimeDuration checkDuration, + int checkCount, + TimeDuration resetDuration + ) { - private TimeDuration checkDuration; - private int checkCount; - private TimeDuration resetDuration; - - boolean isConfigured() + boolean configured() { - return getCheckCount() != 0 && getCheckDuration().asMillis() != 0 && getResetDuration().asMillis() != 0; + return checkCount() != 0 + && checkDuration().asMillis() != 0 + && resetDuration().asMillis() != 0; } } } diff --git a/server/src/main/java/password/pwm/svc/intruder/IntruderSystemService.java b/server/src/main/java/password/pwm/svc/intruder/IntruderSystemService.java index b87d128a0b..9b2d2a5703 100644 --- a/server/src/main/java/password/pwm/svc/intruder/IntruderSystemService.java +++ b/server/src/main/java/password/pwm/svc/intruder/IntruderSystemService.java @@ -141,11 +141,13 @@ public PublicIntruderRecord next() }; } - public List getRecords( final IntruderRecordType recordType, final int maximum ) + public List getRecords( + final IntruderRecordType recordType, + final int maximum ) throws PwmException { return CollectionUtil.iteratorToStream( allRecordIterator() ) - .filter( record -> record.getType() == recordType ) + .filter( record -> record.type() == recordType ) .limit( maximum ) .collect( Collectors.toList() ); } diff --git a/server/src/main/java/password/pwm/svc/intruder/PublicIntruderRecord.java b/server/src/main/java/password/pwm/svc/intruder/PublicIntruderRecord.java index d260bcb2ca..123882ee0e 100644 --- a/server/src/main/java/password/pwm/svc/intruder/PublicIntruderRecord.java +++ b/server/src/main/java/password/pwm/svc/intruder/PublicIntruderRecord.java @@ -20,25 +20,21 @@ package password.pwm.svc.intruder; -import lombok.Builder; -import lombok.Value; import password.pwm.PwmApplication; import password.pwm.bean.DomainID; import java.time.Instant; -@Value -@Builder -public class PublicIntruderRecord +public record PublicIntruderRecord( + IntruderRecordType type, + DomainID domainID, + String subject, + Instant timeStamp, + int attemptCount, + boolean alerted, + LockStatus status +) { - private final IntruderRecordType type; - private final DomainID domainID; - private final String subject; - private final Instant timeStamp; - private final int attemptCount; - private final boolean alerted; - private final LockStatus status; - enum LockStatus { watching, @@ -47,14 +43,14 @@ enum LockStatus static PublicIntruderRecord fromIntruderRecord( final PwmApplication pwmApplication, final IntruderRecord intruderRecord ) { - return PublicIntruderRecord.builder() - .type( intruderRecord.getType() ) - .subject( intruderRecord.getSubject() ) - .timeStamp( intruderRecord.getTimeStamp() ) - .attemptCount( intruderRecord.getAttemptCount() ) - .status( IntruderSystemService.lockStatus( pwmApplication, intruderRecord ) ) - .domainID( intruderRecord.getDomainID() ) - .alerted( intruderRecord.isAlerted() ) - .build(); + return new PublicIntruderRecord( + intruderRecord.getType(), + intruderRecord.getDomainID(), + intruderRecord.getSubject(), + intruderRecord.getTimeStamp(), + intruderRecord.getAttemptCount(), + intruderRecord.isAlerted(), + IntruderSystemService.lockStatus( pwmApplication, intruderRecord ) ); + } } diff --git a/server/src/main/java/password/pwm/svc/node/DatabaseNodeDataService.java b/server/src/main/java/password/pwm/svc/node/DatabaseNodeDataService.java index ca5e46f695..58e2d72973 100644 --- a/server/src/main/java/password/pwm/svc/node/DatabaseNodeDataService.java +++ b/server/src/main/java/password/pwm/svc/node/DatabaseNodeDataService.java @@ -20,7 +20,9 @@ package password.pwm.svc.node; +import password.pwm.AppProperty; import password.pwm.PwmApplication; +import password.pwm.config.AppConfig; import password.pwm.error.PwmError; import password.pwm.error.PwmUnrecoverableException; import password.pwm.svc.PwmService; @@ -28,8 +30,8 @@ import password.pwm.svc.db.DatabaseException; import password.pwm.svc.db.DatabaseTable; import password.pwm.util.java.ClosableIterator; -import password.pwm.util.json.JsonFactory; import password.pwm.util.java.TimeDuration; +import password.pwm.util.json.JsonFactory; import password.pwm.util.logging.PwmLogger; import java.util.LinkedHashMap; @@ -65,7 +67,7 @@ private DatabaseAccessor getDatabaseAccessor() private String localKeyForStoredNode( final StoredNodeData storedNodeData ) throws PwmUnrecoverableException { - final String instanceID = storedNodeData.getInstanceID(); + final String instanceID = storedNodeData.instanceID(); final String hash = pwmApplication.getSecureService().hash( instanceID ); final String truncatedHash = hash.length() > 64 ? hash.substring( 0, 64 ) @@ -92,7 +94,7 @@ public Map readStoredData( ) rawValueInDb.ifPresent( s -> { final StoredNodeData nodeDataInDb = JsonFactory.get().deserialize( s, StoredNodeData.class ); - returnList.put( nodeDataInDb.getInstanceID(), nodeDataInDb ); + returnList.put( nodeDataInDb.instanceID(), nodeDataInDb ); } ); } } @@ -132,8 +134,8 @@ public int purgeOutdatedNodes( final TimeDuration maxNodeAge ) final DatabaseAccessor databaseAccessor = getDatabaseAccessor(); for ( final StoredNodeData storedNodeData : nodeDatas.values() ) { - final TimeDuration recordAge = TimeDuration.fromCurrent( storedNodeData.getTimestamp() ); - final String instanceID = storedNodeData.getInstanceID(); + final TimeDuration recordAge = TimeDuration.fromCurrent( storedNodeData.timestamp() ); + final String instanceID = storedNodeData.instanceID(); if ( recordAge.isLongerThan( maxNodeAge ) ) @@ -153,4 +155,15 @@ public int purgeOutdatedNodes( final TimeDuration maxNodeAge ) return nodesPurged; } + + + public NodeServiceSettings settings( final AppConfig appConfig ) + { + return new NodeServiceSettings( + TimeDuration.of( Integer.parseInt( appConfig.readAppProperty( AppProperty.CLUSTER_DB_HEARTBEAT_SECONDS ) ), TimeDuration.Unit.SECONDS ), + TimeDuration.of( Integer.parseInt( appConfig.readAppProperty( AppProperty.CLUSTER_DB_NODE_TIMEOUT_SECONDS ) ), TimeDuration.Unit.SECONDS ), + TimeDuration.of( Integer.parseInt( appConfig.readAppProperty( AppProperty.CLUSTER_DB_NODE_PURGE_SECONDS ) ), TimeDuration.Unit.SECONDS ) + ); + } + } diff --git a/server/src/main/java/password/pwm/svc/node/LDAPNodeDataService.java b/server/src/main/java/password/pwm/svc/node/LDAPNodeDataService.java index f0cbc4af3b..f17cd30d96 100644 --- a/server/src/main/java/password/pwm/svc/node/LDAPNodeDataService.java +++ b/server/src/main/java/password/pwm/svc/node/LDAPNodeDataService.java @@ -23,18 +23,20 @@ import com.novell.ldapchai.ChaiUser; import com.novell.ldapchai.exception.ChaiException; import lombok.Value; +import password.pwm.AppProperty; import password.pwm.PwmDomain; import password.pwm.bean.ProfileID; import password.pwm.bean.UserIdentity; +import password.pwm.config.AppConfig; import password.pwm.config.PwmSetting; import password.pwm.config.profile.LdapProfile; import password.pwm.error.ErrorInformation; import password.pwm.error.PwmError; import password.pwm.error.PwmUnrecoverableException; import password.pwm.util.java.LazySupplier; -import password.pwm.util.json.JsonFactory; import password.pwm.util.java.StringUtil; import password.pwm.util.java.TimeDuration; +import password.pwm.util.json.JsonFactory; import password.pwm.util.logging.PwmLogger; import java.util.LinkedHashMap; @@ -86,7 +88,7 @@ public Map readStoredData( ) throws PwmUnrecoverableExce { final String rawValue = value.substring( VALUE_PREFIX.length() ); final StoredNodeData storedNodeData = JsonFactory.get().deserialize( rawValue, StoredNodeData.class ); - returnData.put( storedNodeData.getInstanceID(), storedNodeData ); + returnData.put( storedNodeData.instanceID(), storedNodeData ); } } } @@ -103,7 +105,7 @@ public Map readStoredData( ) throws PwmUnrecoverableExce public void writeNodeStatus( final StoredNodeData storedNodeData ) throws PwmUnrecoverableException { final Map currentServerData = readStoredData(); - final StoredNodeData removeNode = currentServerData.get( storedNodeData.getInstanceID() ); + final StoredNodeData removeNode = currentServerData.get( storedNodeData.instanceID() ); final LDAPHelper ldapHelper = ldapHelperSupplier.call(); @@ -140,8 +142,8 @@ public int purgeOutdatedNodes( final TimeDuration maxNodeAge ) throws PwmUnrecov for ( final StoredNodeData storedNodeData : nodeDatas.values() ) { - final TimeDuration recordAge = TimeDuration.fromCurrent( storedNodeData.getTimestamp() ); - final String instanceID = storedNodeData.getInstanceID(); + final TimeDuration recordAge = TimeDuration.fromCurrent( storedNodeData.timestamp() ); + final String instanceID = storedNodeData.instanceID(); if ( recordAge.isLongerThan( maxNodeAge ) ) { @@ -206,4 +208,14 @@ String debugInfo() return "user '" + this.userIdentity.toDisplayString() + "' attribute '" + attr + "'"; } } + + public NodeServiceSettings settings( final AppConfig appConfig ) + { + return new NodeServiceSettings( + TimeDuration.of( Integer.parseInt( appConfig.readAppProperty( AppProperty.CLUSTER_DB_HEARTBEAT_SECONDS ) ), TimeDuration.Unit.SECONDS ), + TimeDuration.of( Integer.parseInt( appConfig.readAppProperty( AppProperty.CLUSTER_DB_NODE_TIMEOUT_SECONDS ) ), TimeDuration.Unit.SECONDS ), + TimeDuration.of( Integer.parseInt( appConfig.readAppProperty( AppProperty.CLUSTER_DB_NODE_PURGE_SECONDS ) ), TimeDuration.Unit.SECONDS ) + ); + } + } diff --git a/server/src/main/java/password/pwm/svc/node/NodeDataServiceProvider.java b/server/src/main/java/password/pwm/svc/node/NodeDataServiceProvider.java index a331ef6ee0..68ea4652fb 100644 --- a/server/src/main/java/password/pwm/svc/node/NodeDataServiceProvider.java +++ b/server/src/main/java/password/pwm/svc/node/NodeDataServiceProvider.java @@ -20,6 +20,7 @@ package password.pwm.svc.node; +import password.pwm.config.AppConfig; import password.pwm.error.PwmUnrecoverableException; import password.pwm.util.java.TimeDuration; @@ -33,4 +34,6 @@ interface NodeDataServiceProvider int purgeOutdatedNodes( TimeDuration maxNodeAge ) throws PwmUnrecoverableException; + + NodeServiceSettings settings( AppConfig appConfig ); } diff --git a/server/src/main/java/password/pwm/svc/node/NodeInfo.java b/server/src/main/java/password/pwm/svc/node/NodeInfo.java index d22b2f4fb1..e845b1866a 100644 --- a/server/src/main/java/password/pwm/svc/node/NodeInfo.java +++ b/server/src/main/java/password/pwm/svc/node/NodeInfo.java @@ -20,26 +20,14 @@ package password.pwm.svc.node; -import lombok.AccessLevel; -import lombok.AllArgsConstructor; -import lombok.Value; - import java.time.Instant; -@Value -@AllArgsConstructor( access = AccessLevel.PACKAGE ) -public class NodeInfo +public record NodeInfo( + String instanceID, + Instant lastSeen, + Instant startupTime, + NodeState nodeState, + boolean configMatch +) { - private String instanceID; - private Instant lastSeen; - private Instant startupTime; - private NodeState nodeState; - private boolean configMatch; - - public enum NodeState - { - master, - online, - offline - } } diff --git a/server/src/main/java/password/pwm/svc/node/NodeMachine.java b/server/src/main/java/password/pwm/svc/node/NodeMachine.java index 4e0e8088d2..4d1b94efd4 100644 --- a/server/src/main/java/password/pwm/svc/node/NodeMachine.java +++ b/server/src/main/java/password/pwm/svc/node/NodeMachine.java @@ -20,10 +20,12 @@ package password.pwm.svc.node; +import password.pwm.PwmConstants; import password.pwm.error.ErrorInformation; import password.pwm.error.PwmError; import password.pwm.error.PwmException; import password.pwm.error.PwmUnrecoverableException; +import password.pwm.util.java.StatisticCounterBundle; import password.pwm.util.java.TimeDuration; import password.pwm.util.logging.PwmLogger; @@ -46,7 +48,15 @@ class NodeMachine private final Map knownNodes = new ConcurrentHashMap<>(); private final NodeServiceSettings settings; - private final NodeServiceStatistics nodeServiceStatistics = new NodeServiceStatistics(); + + private final StatisticCounterBundle nodeServiceStatistics = new StatisticCounterBundle<>( Stats.class ); + + private enum Stats + { + clusterWrites, + clusterReads, + nodePurges + } NodeMachine( final NodeService nodeService, @@ -70,28 +80,28 @@ public List nodes( ) throws PwmUnrecoverableException final String configHash = nodeService.getPwmApp().getConfig().getValueHash(); for ( final StoredNodeData storedNodeData : knownNodes.values() ) { - final boolean configMatch = configHash.equals( storedNodeData.getConfigHash() ); + final boolean configMatch = configHash.equals( storedNodeData.configHash() ); final boolean timedOut = isTimedOut( storedNodeData ); - final NodeInfo.NodeState nodeState = isMaster( storedNodeData ) - ? NodeInfo.NodeState.master + final NodeState nodeState = isMaster( storedNodeData ) + ? NodeState.master : timedOut - ? NodeInfo.NodeState.offline - : NodeInfo.NodeState.online; + ? NodeState.offline + : NodeState.online; - final Instant startupTime = nodeState == NodeInfo.NodeState.offline + final Instant startupTime = nodeState == NodeState.offline ? null - : storedNodeData.getStartupTimestamp(); + : storedNodeData.startupTimestamp(); final NodeInfo nodeInfo = new NodeInfo( - storedNodeData.getInstanceID(), - storedNodeData.getTimestamp(), + storedNodeData.instanceID(), + storedNodeData.timestamp(), startupTime, nodeState, configMatch ); - returnObj.put( nodeInfo.getInstanceID(), nodeInfo ); + returnObj.put( nodeInfo.instanceID(), nodeInfo ); } return List.copyOf( returnObj.values() ); @@ -113,10 +123,10 @@ private String masterInstanceId( ) { if ( !isTimedOut( nodeData ) ) { - if ( nodeData.getStartupTimestamp().isBefore( eldestRecord ) ) + if ( nodeData.startupTimestamp().isBefore( eldestRecord ) ) { - eldestRecord = nodeData.getStartupTimestamp(); - masterID = nodeData.getInstanceID(); + eldestRecord = nodeData.startupTimestamp(); + masterID = nodeData.instanceID(); } } } @@ -133,13 +143,13 @@ public boolean isMaster( ) private boolean isMaster( final StoredNodeData storedNodeData ) { final String masterID = masterInstanceId(); - return storedNodeData.getInstanceID().equals( masterID ); + return storedNodeData.instanceID().equals( masterID ); } private boolean isTimedOut( final StoredNodeData storedNodeData ) { - final TimeDuration age = TimeDuration.fromCurrent( storedNodeData.getTimestamp() ); - return age.isLongerThan( settings.getNodeTimeout() ); + final TimeDuration age = TimeDuration.fromCurrent( storedNodeData.timestamp() ); + return age.isLongerThan( settings.nodeTimeout() ); } public ErrorInformation getLastError( ) @@ -178,7 +188,7 @@ void writeNodeStatus( ) { final StoredNodeData storedNodeData = StoredNodeData.makeNew( nodeService.getPwmApp() ); clusterDataServiceProvider.writeNodeStatus( storedNodeData ); - nodeServiceStatistics.getClusterWrites().incrementAndGet(); + nodeServiceStatistics.increment( Stats.clusterWrites ); } catch ( final PwmException e ) { @@ -195,7 +205,7 @@ void readNodeStatuses( ) { final Map readNodeData = clusterDataServiceProvider.readStoredData(); knownNodes.putAll( readNodeData ); - nodeServiceStatistics.getClusterReads().incrementAndGet(); + nodeServiceStatistics.increment( Stats.clusterReads ); } catch ( final PwmException e ) { @@ -210,8 +220,8 @@ void purgeOutdatedNodes( ) { try { - final int purges = clusterDataServiceProvider.purgeOutdatedNodes( settings.getNodePurgeInterval() ); - nodeServiceStatistics.getNodePurges().addAndGet( purges ); + final int purges = clusterDataServiceProvider.purgeOutdatedNodes( settings.nodePurgeInterval() ); + nodeServiceStatistics.increment( Stats.nodePurges, purges ); } catch ( final PwmException e ) { @@ -222,8 +232,8 @@ void purgeOutdatedNodes( ) } } - public NodeServiceStatistics getNodeServiceStatistics( ) + public Map getNodeServiceStatistics( ) { - return nodeServiceStatistics; + return nodeServiceStatistics.debugStats( PwmConstants.DEFAULT_LOCALE ); } } diff --git a/server/src/main/java/password/pwm/svc/node/NodeService.java b/server/src/main/java/password/pwm/svc/node/NodeService.java index 325938ebeb..b23a5cf0db 100644 --- a/server/src/main/java/password/pwm/svc/node/NodeService.java +++ b/server/src/main/java/password/pwm/svc/node/NodeService.java @@ -34,7 +34,6 @@ import password.pwm.health.HealthRecord; import password.pwm.svc.AbstractPwmService; import password.pwm.svc.PwmService; -import password.pwm.util.java.PwmUtil; import password.pwm.util.json.JsonFactory; import password.pwm.util.logging.PwmLogger; @@ -61,47 +60,37 @@ public STATUS postAbstractInit( final PwmApplication pwmApplication, final Domai return STATUS.CLOSED; } - try + dataStore = pwmApplication.getConfig().readSettingAsEnum( PwmSetting.NODE_SERVICE_STORAGE_MODE, DataStorageMethod.class ); + if ( dataStore == null ) { - final NodeServiceSettings nodeServiceSettings; - final NodeDataServiceProvider clusterDataServiceProvider; - dataStore = pwmApplication.getConfig().readSettingAsEnum( PwmSetting.NODE_SERVICE_STORAGE_MODE, DataStorageMethod.class ); + return STATUS.OPEN; + } - if ( dataStore != null ) - { - switch ( dataStore ) - { - case DB: + try + { + final NodeDataServiceProvider nodeDataServiceProvider = switch ( dataStore ) { - LOGGER.trace( () -> "starting database-backed node service provider" ); - nodeServiceSettings = NodeServiceSettings.fromConfigForDB( pwmApplication.getConfig() ); - clusterDataServiceProvider = new DatabaseNodeDataService( pwmApplication ); - } - break; + case DB -> new DatabaseNodeDataService( pwmApplication ); + case LDAP -> new LDAPNodeDataService( this, pwmApplication.getAdminDomain() ); + default -> throw new IllegalStateException( "no available implementation for store type " + dataStore ); + }; - case LDAP: - { - LOGGER.trace( () -> "starting ldap-backed node service provider" ); - nodeServiceSettings = NodeServiceSettings.fromConfigForLDAP( pwmApplication.getConfig() ); - clusterDataServiceProvider = new LDAPNodeDataService( this, pwmApplication.getAdminDomain() ); - } - break; + LOGGER.trace( getSessionLabel(), () -> "started service provider " + + nodeDataServiceProvider.getClass().getSimpleName() ); - default: - LOGGER.debug( () -> "no suitable storage method configured " ); - PwmUtil.unhandledSwitchStatement( dataStore ); - return STATUS.CLOSED; + final NodeServiceSettings nodeServiceSettings = nodeDataServiceProvider.settings( pwmApplication.getConfig() ); - } + nodeMachine = new NodeMachine( this, nodeDataServiceProvider, nodeServiceSettings ); - nodeMachine = new NodeMachine( this, clusterDataServiceProvider, nodeServiceSettings ); - scheduleFixedRateJob( nodeMachine.getHeartbeatProcess(), nodeServiceSettings.getHeartbeatInterval(), nodeServiceSettings.getHeartbeatInterval() ); - } + scheduleFixedRateJob( + nodeMachine.getHeartbeatProcess(), + nodeServiceSettings.heartbeatInterval(), + nodeServiceSettings.heartbeatInterval() ); } catch ( final PwmUnrecoverableException e ) { setStartupError( e.getErrorInformation() ); - LOGGER.error( () -> "error starting up node service: " + e.getMessage() ); + LOGGER.error( getSessionLabel(), () -> "error starting up node service: " + e.getMessage() ); return STATUS.CLOSED; } catch ( final Exception e ) diff --git a/server/src/main/java/password/pwm/svc/node/NodeServiceSettings.java b/server/src/main/java/password/pwm/svc/node/NodeServiceSettings.java index 471766c2c3..ea7cd11301 100644 --- a/server/src/main/java/password/pwm/svc/node/NodeServiceSettings.java +++ b/server/src/main/java/password/pwm/svc/node/NodeServiceSettings.java @@ -20,36 +20,12 @@ package password.pwm.svc.node; -import lombok.AccessLevel; -import lombok.AllArgsConstructor; -import lombok.Value; -import password.pwm.AppProperty; -import password.pwm.config.AppConfig; import password.pwm.util.java.TimeDuration; -@Value -@AllArgsConstructor( access = AccessLevel.PRIVATE ) -class NodeServiceSettings +record NodeServiceSettings( + TimeDuration heartbeatInterval, + TimeDuration nodeTimeout, + TimeDuration nodePurgeInterval +) { - private final TimeDuration heartbeatInterval; - private final TimeDuration nodeTimeout; - private final TimeDuration nodePurgeInterval; - - static NodeServiceSettings fromConfigForDB( final AppConfig appConfig ) - { - return new NodeServiceSettings( - TimeDuration.of( Integer.parseInt( appConfig.readAppProperty( AppProperty.CLUSTER_DB_HEARTBEAT_SECONDS ) ), TimeDuration.Unit.SECONDS ), - TimeDuration.of( Integer.parseInt( appConfig.readAppProperty( AppProperty.CLUSTER_DB_NODE_TIMEOUT_SECONDS ) ), TimeDuration.Unit.SECONDS ), - TimeDuration.of( Integer.parseInt( appConfig.readAppProperty( AppProperty.CLUSTER_DB_NODE_PURGE_SECONDS ) ), TimeDuration.Unit.SECONDS ) - ); - } - - static NodeServiceSettings fromConfigForLDAP( final AppConfig appConfig ) - { - return new NodeServiceSettings( - TimeDuration.of( Integer.parseInt( appConfig.readAppProperty( AppProperty.CLUSTER_LDAP_HEARTBEAT_SECONDS ) ), TimeDuration.Unit.SECONDS ), - TimeDuration.of( Integer.parseInt( appConfig.readAppProperty( AppProperty.CLUSTER_LDAP_NODE_TIMEOUT_SECONDS ) ), TimeDuration.Unit.SECONDS ), - TimeDuration.of( Integer.parseInt( appConfig.readAppProperty( AppProperty.CLUSTER_LDAP_NODE_PURGE_SECONDS ) ), TimeDuration.Unit.SECONDS ) - ); - } } diff --git a/client/angular/src/modules/peoplesearch/orgchart-export.component.scss b/server/src/main/java/password/pwm/svc/node/NodeState.java similarity index 88% rename from client/angular/src/modules/peoplesearch/orgchart-export.component.scss rename to server/src/main/java/password/pwm/svc/node/NodeState.java index 61f5f398d3..6e6f2e790b 100644 --- a/client/angular/src/modules/peoplesearch/orgchart-export.component.scss +++ b/server/src/main/java/password/pwm/svc/node/NodeState.java @@ -1,4 +1,4 @@ -/*! +/* * Password Management Servlets (PWM) * http://www.pwm-project.org * @@ -18,5 +18,11 @@ * limitations under the License. */ -.ias-styles-root { +package password.pwm.svc.node; + +public enum NodeState +{ + master, + online, + offline } diff --git a/server/src/main/java/password/pwm/svc/node/StoredNodeData.java b/server/src/main/java/password/pwm/svc/node/StoredNodeData.java index 48795e4f95..80ee880870 100644 --- a/server/src/main/java/password/pwm/svc/node/StoredNodeData.java +++ b/server/src/main/java/password/pwm/svc/node/StoredNodeData.java @@ -20,25 +20,20 @@ package password.pwm.svc.node; -import lombok.AccessLevel; -import lombok.AllArgsConstructor; -import lombok.Value; import password.pwm.PwmApplication; import password.pwm.config.stored.StoredConfigurationUtil; import password.pwm.error.PwmUnrecoverableException; import java.time.Instant; -@Value -@AllArgsConstructor( access = AccessLevel.PRIVATE ) -class StoredNodeData +record StoredNodeData( + Instant timestamp, + Instant startupTimestamp, + String instanceID, + String guid, + String configHash +) { - private Instant timestamp; - private Instant startupTimestamp; - private String instanceID; - private String guid; - private String configHash; - static StoredNodeData makeNew( final PwmApplication pwmApplication ) throws PwmUnrecoverableException { diff --git a/server/src/main/java/password/pwm/svc/otp/AbstractOtpOperator.java b/server/src/main/java/password/pwm/svc/otp/AbstractOtpOperator.java index 6e8a625083..3e5991ae6f 100644 --- a/server/src/main/java/password/pwm/svc/otp/AbstractOtpOperator.java +++ b/server/src/main/java/password/pwm/svc/otp/AbstractOtpOperator.java @@ -20,163 +20,23 @@ package password.pwm.svc.otp; -import com.google.gson.JsonSyntaxException; -import password.pwm.AppProperty; import password.pwm.PwmDomain; -import password.pwm.config.DomainConfig; -import password.pwm.config.PwmSetting; -import password.pwm.config.option.OTPStorageFormat; -import password.pwm.error.ErrorInformation; -import password.pwm.error.PwmError; -import password.pwm.error.PwmUnrecoverableException; -import password.pwm.util.java.EnumUtil; -import password.pwm.util.json.JsonFactory; import password.pwm.util.logging.PwmLogger; -import password.pwm.util.secure.PwmBlockAlgorithm; -import password.pwm.util.secure.PwmSecurityKey; -import password.pwm.util.secure.SecureEngine; /** * @author mpieters */ public abstract class AbstractOtpOperator implements OtpOperator { - private static final PwmLogger LOGGER = PwmLogger.forClass( AbstractOtpOperator.class ); protected PwmDomain pwmDomain; - /** - * Compose a single line of OTP information. - * - * @param otpUserRecord input user record - * @return A string formatted record - * @throws PwmUnrecoverableException if the operation fails - */ - public String composeOtpAttribute( final OTPUserRecord otpUserRecord ) - throws PwmUnrecoverableException - { - String value = ""; - if ( otpUserRecord != null ) - { - final DomainConfig config = pwmDomain.getConfig(); - final OTPStorageFormat format = config.readSettingAsEnum( PwmSetting.OTP_SECRET_STORAGEFORMAT, OTPStorageFormat.class ); - switch ( format ) - { - case PWM: - value = JsonFactory.get().serialize( otpUserRecord ); - break; - case OTPURL: - value = OTPUrlUtil.composeOtpUrl( otpUserRecord ); - break; - case BASE32SECRET: - value = otpUserRecord.getSecret(); - break; - case PAM: - value = OTPPamUtil.composePamData( otpUserRecord ); - break; - default: - final String errorStr = String.format( "Unsupported storage format: %s", format ); - final ErrorInformation error = new ErrorInformation( PwmError.ERROR_INVALID_CONFIG, errorStr ); - throw new PwmUnrecoverableException( error ); - } - } - return value; - } - - /** - * Encrypt the given string using the PWM encryption key. - * - * @param unencrypted raw value to be encrypted - * - * @return the encrypted value - * @throws PwmUnrecoverableException if the operation can't be completed - */ - public String encryptAttributeValue( final String unencrypted ) - throws PwmUnrecoverableException - { - final PwmBlockAlgorithm pwmBlockAlgorithm = figureBlockAlg(); - final PwmSecurityKey pwmSecurityKey = pwmDomain.getConfig().getSecurityKey(); - return SecureEngine.encryptToString( unencrypted, pwmSecurityKey, pwmBlockAlgorithm ); - } - - public PwmBlockAlgorithm figureBlockAlg( ) - { - final String otpEncryptionAlgString = pwmDomain.getConfig().readAppProperty( AppProperty.OTP_ENCRYPTION_ALG ); - return EnumUtil.readEnumFromString( PwmBlockAlgorithm.class, otpEncryptionAlgString ) - .orElse( PwmBlockAlgorithm.AES ); - } - - /** - * Decrypt the given string using the PWM encryption key. - * - * @param encrypted value to be encrypted - * - * @return the decrypted value - * @throws PwmUnrecoverableException if the operation can't be completed - */ - public String decryptAttributeValue( final String encrypted ) - throws PwmUnrecoverableException - { - final PwmBlockAlgorithm pwmBlockAlgorithm = figureBlockAlg(); - final PwmSecurityKey pwmSecurityKey = pwmDomain.getConfig().getSecurityKey(); - return SecureEngine.decryptStringValue( encrypted, pwmSecurityKey, pwmBlockAlgorithm ); - } - - public OTPUserRecord decomposeOtpAttribute( final String value ) - { - if ( value == null ) - { - return null; - } - OTPUserRecord otpconfig = null; - /* Try format by format */ - LOGGER.trace( () -> String.format( "detecting format from value: %s", value ) ); - /* - PWM JSON */ - try - { - otpconfig = JsonFactory.get().deserialize( value, OTPUserRecord.class ); - LOGGER.debug( () -> "detected JSON format - returning" ); - return otpconfig; - } - catch ( final JsonSyntaxException ex ) - { - LOGGER.debug( () -> "no JSON format detected - returning" ); - /* So, it's not JSON, try something else */ - /* -- nothing to try, yet; for future use */ - /* no more options */ - } - /* - otpauth:// URL */ - otpconfig = OTPUrlUtil.decomposeOtpUrl( value ); - if ( otpconfig != null ) - { - LOGGER.debug( () -> "detected otpauth URL format - returning" ); - return otpconfig; - } - /* - PAM */ - otpconfig = OTPPamUtil.decomposePamData( value ); - if ( otpconfig != null ) - { - LOGGER.debug( () -> "detected PAM text format - returning" ); - return otpconfig; - } - /* - BASE32 secret */ - if ( value.trim().matches( "^[A-Z2-7\\=]{16}$" ) ) - { - LOGGER.debug( () -> "detected plain Base32 secret - returning" ); - otpconfig = new OTPUserRecord(); - otpconfig.setSecret( value.trim() ); - return otpconfig; - } - - return otpconfig; - } - - public PwmDomain getPwmApplication( ) + public PwmDomain getPwmDomain( ) { return pwmDomain; } - public void setPwmApplication( final PwmDomain pwmDomain ) + public void setPwmDomain( final PwmDomain pwmDomain ) { this.pwmDomain = pwmDomain; } diff --git a/server/src/main/java/password/pwm/svc/otp/DbOtpOperator.java b/server/src/main/java/password/pwm/svc/otp/DbOtpOperator.java index f31d6bd140..bb372b4f18 100644 --- a/server/src/main/java/password/pwm/svc/otp/DbOtpOperator.java +++ b/server/src/main/java/password/pwm/svc/otp/DbOtpOperator.java @@ -30,7 +30,6 @@ import password.pwm.PwmDomain; import password.pwm.bean.SessionLabel; import password.pwm.bean.UserIdentity; -import password.pwm.config.PwmSetting; import password.pwm.error.ErrorInformation; import password.pwm.error.PwmError; import password.pwm.error.PwmException; @@ -40,6 +39,7 @@ import password.pwm.svc.db.DatabaseAccessor; import password.pwm.svc.db.DatabaseException; import password.pwm.svc.db.DatabaseTable; +import password.pwm.util.java.StringUtil; import password.pwm.util.logging.PwmLogger; import java.util.Optional; @@ -54,7 +54,7 @@ public class DbOtpOperator extends AbstractOtpOperator public DbOtpOperator( final PwmDomain pwmDomain ) { - super.setPwmApplication( pwmDomain ); + super.setPwmDomain( pwmDomain ); } @Override @@ -62,7 +62,7 @@ public Optional readOtpUserConfiguration( final SessionLabel sess throws PwmUnrecoverableException { LOGGER.trace( () -> String.format( "Enter: readOtpUserConfiguration(%s, %s)", theUser, userGUID ) ); - if ( userGUID == null || userGUID.length() < 1 ) + if ( StringUtil.isEmpty( userGUID ) ) { throw new PwmUnrecoverableException( new ErrorInformation( PwmError.ERROR_MISSING_GUID, "cannot save otp to db, user does not have a GUID" ) ); } @@ -73,18 +73,11 @@ public Optional readOtpUserConfiguration( final SessionLabel sess final Optional strValue = databaseAccessor.get( DatabaseTable.OTP, userGUID ); if ( strValue.isPresent() ) { - if ( getPwmApplication().getConfig().readSettingAsBoolean( PwmSetting.OTP_SECRET_ENCRYPT ) ) + final Optional otpConfig = OtpServiceUtil.parseStoredOtpRecordValue( sessionLabel, pwmDomain, strValue.get() ); + if ( otpConfig.isPresent() ) { - final String decryptAttributeValue = decryptAttributeValue( strValue.get() ); - if ( decryptAttributeValue != null ) - { - final OTPUserRecord otpConfig = decomposeOtpAttribute( decryptAttributeValue ); - if ( otpConfig != null ) - { - LOGGER.debug( sessionLabel, () -> "found user OTP secret in db: " + otpConfig ); - return Optional.of( otpConfig ); - } - } + LOGGER.debug( sessionLabel, () -> "found user OTP secret in db: " + otpConfig.get() ); + return otpConfig; } } } @@ -115,12 +108,7 @@ public void writeOtpUserConfiguration( try { - String value = composeOtpAttribute( otpConfig ); - if ( getPwmApplication().getConfig().readSettingAsBoolean( PwmSetting.OTP_SECRET_ENCRYPT ) ) - { - LOGGER.debug( pwmRequest, () -> "encrypting OTP secret for storage" ); - value = encryptAttributeValue( value ); - } + final String value = OtpServiceUtil.stringifyOtpRecord( pwmDomain, otpConfig ); final DatabaseAccessor databaseAccessor = pwmDomain.getPwmApplication().getDatabaseAccessor(); databaseAccessor.put( DatabaseTable.OTP, userGUID, value ); LOGGER.debug( pwmRequest, () -> "saved OTP secret for " + theUser + " in remote database (key=" + userGUID + ")" ); @@ -169,10 +157,4 @@ public void clearOtpUserConfiguration( throw pwmOE; } } - - @Override - public void close( ) - { - } - } diff --git a/server/src/main/java/password/pwm/svc/otp/LdapOtpOperator.java b/server/src/main/java/password/pwm/svc/otp/LdapOtpOperator.java index 2a31e8e2e5..c08700b5b0 100644 --- a/server/src/main/java/password/pwm/svc/otp/LdapOtpOperator.java +++ b/server/src/main/java/password/pwm/svc/otp/LdapOtpOperator.java @@ -35,7 +35,6 @@ import password.pwm.error.PwmError; import password.pwm.error.PwmUnrecoverableException; import password.pwm.http.PwmRequest; -import password.pwm.util.java.StringUtil; import password.pwm.util.logging.PwmLogger; import java.util.Optional; @@ -45,12 +44,11 @@ */ public class LdapOtpOperator extends AbstractOtpOperator { - private static final PwmLogger LOGGER = PwmLogger.forClass( LdapOtpOperator.class ); public LdapOtpOperator( final PwmDomain pwmDomain ) { - setPwmApplication( pwmDomain ); + setPwmDomain( pwmDomain ); } /** @@ -64,7 +62,7 @@ public Optional readOtpUserConfiguration( ) throws PwmUnrecoverableException { - final DomainConfig config = getPwmApplication().getConfig(); + final DomainConfig config = getPwmDomain().getConfig(); final LdapProfile ldapProfile = config.getLdapProfiles().get( userIdentity.getLdapProfileID() ); final String ldapStorageAttribute = ldapProfile.readSettingAsString( PwmSetting.OTP_SECRET_LDAP_ATTRIBUTE ); if ( ldapStorageAttribute == null || ldapStorageAttribute.length() < 1 ) @@ -77,16 +75,7 @@ public Optional readOtpUserConfiguration( { final ChaiUser theUser = pwmDomain.getProxiedChaiUser( sessionLabel, userIdentity ); final String value = theUser.readStringAttribute( ldapStorageAttribute ); - if ( StringUtil.notEmpty( value ) && config.readSettingAsBoolean( PwmSetting.OTP_SECRET_ENCRYPT ) ) - { - final String decryptAttributeValue = decryptAttributeValue( value ); - - if ( decryptAttributeValue != null ) - { - final OTPUserRecord otp = decomposeOtpAttribute( decryptAttributeValue ); - return Optional.ofNullable( otp ); - } - } + return OtpServiceUtil.parseStoredOtpRecordValue( sessionLabel, pwmDomain, value ); } catch ( final ChaiOperationException | ChaiUnavailableException e ) { @@ -94,7 +83,6 @@ public Optional readOtpUserConfiguration( final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_INTERNAL, errorMsg ); throw new PwmUnrecoverableException( errorInformation ); } - return Optional.empty(); } @Override @@ -115,19 +103,11 @@ public void writeOtpUserConfiguration( final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_INVALID_CONFIG, errorMsg ); throw new PwmUnrecoverableException( errorInformation ); } - String value = composeOtpAttribute( otpConfig ); - if ( value == null || value.length() == 0 ) - { - final String errorMsg = "Invalid value for OTP secret, unable to store"; - final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_INVALID_CONFIG, errorMsg ); - throw new PwmUnrecoverableException( errorInformation ); - } + + final String value = OtpServiceUtil.stringifyOtpRecord( pwmDomain, otpConfig ); + try { - if ( config.readSettingAsBoolean( PwmSetting.OTP_SECRET_ENCRYPT ) ) - { - value = encryptAttributeValue( value ); - } final ChaiUser theUser = pwmRequest == null ? pwmDomain.getProxiedChaiUser( null, userIdentity ) : pwmRequest.getClientConnectionHolder().getActor( userIdentity ); @@ -199,12 +179,4 @@ public void clearOtpUserConfiguration( throw new PwmUnrecoverableException( new ErrorInformation( PwmError.ERROR_DIRECTORY_UNAVAILABLE, e.getMessage() ) ); } } - - /** - * Close the operator. Does nothing in this case. - */ - @Override - public void close( ) - { - } } diff --git a/server/src/main/java/password/pwm/svc/otp/LocalDbOtpOperator.java b/server/src/main/java/password/pwm/svc/otp/LocalDbOtpOperator.java index a89e505bc3..84ba892b2d 100644 --- a/server/src/main/java/password/pwm/svc/otp/LocalDbOtpOperator.java +++ b/server/src/main/java/password/pwm/svc/otp/LocalDbOtpOperator.java @@ -31,13 +31,13 @@ import password.pwm.bean.SessionLabel; import password.pwm.bean.UserIdentity; import password.pwm.config.DomainConfig; -import password.pwm.config.PwmSetting; import password.pwm.error.ErrorInformation; import password.pwm.error.PwmError; import password.pwm.error.PwmException; import password.pwm.error.PwmOperationalException; import password.pwm.error.PwmUnrecoverableException; import password.pwm.http.PwmRequest; +import password.pwm.util.java.StringUtil; import password.pwm.util.localdb.LocalDB; import password.pwm.util.localdb.LocalDBException; import password.pwm.util.logging.PwmLogger; @@ -56,7 +56,7 @@ public class LocalDbOtpOperator extends AbstractOtpOperator public LocalDbOtpOperator( final PwmDomain pwmDomain ) { this.localDB = pwmDomain.getPwmApplication().getLocalDB(); - setPwmApplication( pwmDomain ); + setPwmDomain( pwmDomain ); } @Override @@ -82,22 +82,15 @@ public Optional readOtpUserConfiguration( try { - final DomainConfig config = this.getPwmApplication().getConfig(); + final DomainConfig config = this.getPwmDomain().getConfig(); final Optional value = localDB.get( LocalDB.DB.OTP_SECRET, userGUID ); if ( value.isPresent() ) { - if ( config.readSettingAsBoolean( PwmSetting.OTP_SECRET_ENCRYPT ) ) + final Optional otpConfig = OtpServiceUtil.parseStoredOtpRecordValue( sessionLabel, pwmDomain, value.get() ); + if ( otpConfig.isPresent() ) { - final String decryptAttributeValue = decryptAttributeValue( value.get() ); - if ( decryptAttributeValue != null ) - { - final OTPUserRecord otpConfig = decomposeOtpAttribute( decryptAttributeValue ); - if ( otpConfig != null ) - { - LOGGER.debug( sessionLabel, () -> "found user OTP secret in LocalDB: " + otpConfig ); - return Optional.of( otpConfig ); - } - } + LOGGER.debug( sessionLabel, () -> "found user OTP secret in LocalDB: " + otpConfig.get() ); + return otpConfig; } } } @@ -121,7 +114,7 @@ public void writeOtpUserConfiguration( throws PwmUnrecoverableException { LOGGER.trace( pwmRequest, () -> String.format( "Enter: writeOtpUserConfiguration(%s, %s, %s)", theUser, userGUID, otpConfig ) ); - if ( userGUID == null || userGUID.length() < 1 ) + if ( StringUtil.isEmpty( userGUID ) ) { throw new PwmUnrecoverableException( new ErrorInformation( PwmError.ERROR_MISSING_GUID, "cannot save otp to localDB, user does not have a pwmGUID" ) ); } @@ -135,14 +128,8 @@ public void writeOtpUserConfiguration( try { - final DomainConfig config = this.getPwmApplication().getConfig(); - String value = composeOtpAttribute( otpConfig ); - if ( config.readSettingAsBoolean( PwmSetting.OTP_SECRET_ENCRYPT ) ) - { - LOGGER.debug( pwmRequest, () -> "Encrypting OTP secret for storage" ); - value = encryptAttributeValue( value ); - } - + final DomainConfig config = this.getPwmDomain().getConfig(); + final String value = OtpServiceUtil.stringifyOtpRecord( pwmDomain, otpConfig ); localDB.put( LocalDB.DB.OTP_SECRET, userGUID, value ); LOGGER.info( pwmRequest, () -> "saved OTP secret for user in LocalDB" ); } @@ -188,11 +175,4 @@ public void clearOtpUserConfiguration( throw pwmOE; } } - - @Override - public void close( ) - { - // No operation - } - } diff --git a/server/src/main/java/password/pwm/svc/otp/OTPUserRecord.java b/server/src/main/java/password/pwm/svc/otp/OTPUserRecord.java index f27b52a03d..43167be372 100644 --- a/server/src/main/java/password/pwm/svc/otp/OTPUserRecord.java +++ b/server/src/main/java/password/pwm/svc/otp/OTPUserRecord.java @@ -40,12 +40,12 @@ public class OTPUserRecord private Type type = Type.TOTP; private String version = CURRENT_VERSION; - @Data - public static class RecoveryInfo + public record RecoveryInfo( + String salt, + String hashMethod, + int hashCount + ) { - private String salt; - private String hashMethod; - private int hashCount; } public enum Type @@ -56,10 +56,10 @@ public enum Type TOTP, } - @Data - public static class RecoveryCode + public record RecoveryCode( + String hash, + boolean used + ) { - private String hashCode; - private boolean used; - } + } } diff --git a/server/src/main/java/password/pwm/svc/otp/OtpOperator.java b/server/src/main/java/password/pwm/svc/otp/OtpOperator.java index b8397c732f..48df8a06f6 100644 --- a/server/src/main/java/password/pwm/svc/otp/OtpOperator.java +++ b/server/src/main/java/password/pwm/svc/otp/OtpOperator.java @@ -61,6 +61,4 @@ void clearOtpUserConfiguration( String userGuid ) throws PwmUnrecoverableException; - - void close( ); } diff --git a/server/src/main/java/password/pwm/svc/otp/OtpService.java b/server/src/main/java/password/pwm/svc/otp/OtpService.java index a5564a663b..d6513735ce 100644 --- a/server/src/main/java/password/pwm/svc/otp/OtpService.java +++ b/server/src/main/java/password/pwm/svc/otp/OtpService.java @@ -130,35 +130,47 @@ public boolean validateToken( LOGGER.error( sessionLabel, () -> "error checking otp secret: " + e.getMessage() ); } + final List remainingCodes = new ArrayList<>(); if ( !otpCorrect && allowRecoveryCodes && otpUserRecord.getRecoveryCodes() != null && otpUserRecord.getRecoveryInfo() != null ) { final OTPUserRecord.RecoveryInfo recoveryInfo = otpUserRecord.getRecoveryInfo(); final String userHashedInput = doRecoveryHash( userInput, recoveryInfo ); for ( final OTPUserRecord.RecoveryCode code : otpUserRecord.getRecoveryCodes() ) { - if ( code.getHashCode().equals( userInput ) || code.getHashCode().equals( userHashedInput ) ) + if ( code.hash().equals( userInput ) || code.hash().equals( userHashedInput ) ) { - if ( code.isUsed() ) + if ( code.used() ) { throw new PwmOperationalException( PwmError.ERROR_OTP_RECOVERY_USED, "recovery code has been previously used" ); } - code.setUsed( true ); - try - { - pwmDomain.getOtpService().writeOTPUserConfiguration( null, userIdentity, otpUserRecord ); - } - catch ( final ChaiUnavailableException e ) - { - throw new PwmUnrecoverableException( new ErrorInformation( PwmError.ERROR_WRITING_OTP_SECRET, e.getMessage() ) ); - } + remainingCodes.add( new OTPUserRecord.RecoveryCode( code.hash(), true ) ); otpCorrect = true; } + else + { + remainingCodes.add( code ); + } } } - return otpCorrect; + if ( otpCorrect ) + { + try + { + otpUserRecord.setRecoveryCodes( remainingCodes ); + pwmDomain.getOtpService().writeOTPUserConfiguration( null, userIdentity, otpUserRecord ); + return true; + } + catch ( final ChaiUnavailableException e ) + { + throw new PwmUnrecoverableException( new ErrorInformation( PwmError.ERROR_WRITING_OTP_SECRET, e.getMessage() ) ); + } + + } + + return false; } private List createRawRecoveryCodes( final int numRecoveryCodes, final SessionLabel sessionLabel ) @@ -210,21 +222,23 @@ public List initializeUserRecord( { final int recoveryCodesCount = ( int ) otpProfile.readSettingAsLong( PwmSetting.OTP_RECOVERY_CODES ); rawRecoveryCodes = createRawRecoveryCodes( recoveryCodesCount, sessionLabel ); - final OTPUserRecord.RecoveryInfo recoveryInfo = new OTPUserRecord.RecoveryInfo(); + final OTPUserRecord.RecoveryInfo recoveryInfo; if ( settings.getOtpStorageFormat().supportsHashedRecoveryCodes() ) { LOGGER.trace( sessionLabel, () -> "hashing the recovery codes" ); final int saltCharLength = Integer.parseInt( pwmDomain.getConfig().readAppProperty( AppProperty.OTP_SALT_CHARLENGTH ) ); - recoveryInfo.setSalt( pwmRandom.alphaNumericString( saltCharLength ) ); - recoveryInfo.setHashCount( settings.getRecoveryHashIterations() ); - recoveryInfo.setHashMethod( settings.getRecoveryHashMethod() ); + recoveryInfo = new OTPUserRecord.RecoveryInfo( + pwmRandom.alphaNumericString( saltCharLength ), + settings.getRecoveryHashMethod(), + settings.getRecoveryHashIterations() ); } else { LOGGER.trace( sessionLabel, () -> "not hashing the recovery codes" ); - recoveryInfo.setSalt( null ); - recoveryInfo.setHashCount( 0 ); - recoveryInfo.setHashMethod( null ); + recoveryInfo = new OTPUserRecord.RecoveryInfo( + null, + null, + 0 ); } otpUserRecord.setRecoveryInfo( recoveryInfo ); @@ -241,9 +255,9 @@ public List initializeUserRecord( { hashedCode = rawCode; } - final OTPUserRecord.RecoveryCode recoveryCode = new OTPUserRecord.RecoveryCode(); - recoveryCode.setHashCode( hashedCode ); - recoveryCode.setUsed( false ); + final OTPUserRecord.RecoveryCode recoveryCode = new OTPUserRecord.RecoveryCode( + hashedCode, + false ); recoveryCodeList.add( recoveryCode ); } otpUserRecord.setRecoveryCodes( recoveryCodeList ); @@ -280,11 +294,11 @@ public String doRecoveryHash( throw new IllegalStateException( "unable to load " + algorithm + " message digest algorithm: " + e.getMessage() ); } - final String raw = recoveryInfo.getSalt() == null + final String raw = recoveryInfo.salt() == null ? input.trim() - : recoveryInfo.getSalt().trim() + input.trim(); + : recoveryInfo.salt().trim() + input.trim(); - final int hashCount = recoveryInfo.getHashCount(); + final int hashCount = recoveryInfo.hashCount(); byte[] hashedBytes = raw.getBytes( PwmConstants.DEFAULT_CHARSET ); for ( int i = 0; i < hashCount; i++ ) { @@ -296,10 +310,6 @@ public String doRecoveryHash( @Override public void shutdownImpl( ) { - for ( final OtpOperator operator : operatorMap.values() ) - { - operator.close(); - } operatorMap.clear(); } @@ -313,7 +323,7 @@ public OTPUserRecord readOTPUserConfiguration( final SessionLabel sessionLabel, final UserIdentity userIdentity ) - throws PwmUnrecoverableException, ChaiUnavailableException + throws PwmUnrecoverableException { OTPUserRecord otpConfig = null; final DomainConfig config = pwmDomain.getConfig(); @@ -354,8 +364,8 @@ public OTPUserRecord readOTPUserConfiguration( final Supplier msg = () -> finalOtpConfig == null ? "no otp record found for user " + userIdentity.toDisplayString() : "loaded otp record for user " + userIdentity.toDisplayString() - + " [recordType=" + finalOtpConfig.getType() + ", identifier=" + finalOtpConfig.getIdentifier() + ", timestamp=" - + StringUtil.toIsoDate( finalOtpConfig.getTimestamp() ) + "]"; + + " [recordType=" + finalOtpConfig.getType() + ", identifier=" + finalOtpConfig.getIdentifier() + ", timestamp=" + + StringUtil.toIsoDate( finalOtpConfig.getTimestamp() ) + "]"; LOGGER.trace( sessionLabel, msg, TimeDuration.fromCurrent( methodStartTime ) ); } diff --git a/server/src/main/java/password/pwm/svc/otp/OtpServiceUtil.java b/server/src/main/java/password/pwm/svc/otp/OtpServiceUtil.java new file mode 100644 index 0000000000..259e1233eb --- /dev/null +++ b/server/src/main/java/password/pwm/svc/otp/OtpServiceUtil.java @@ -0,0 +1,173 @@ +/* + * Password Management Servlets (PWM) + * http://www.pwm-project.org + * + * Copyright (c) 2006-2009 Novell, Inc. + * Copyright (c) 2009-2021 The PWM Project + * + * 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. + */ + +package password.pwm.svc.otp; + +import password.pwm.AppProperty; +import password.pwm.PwmDomain; +import password.pwm.bean.SessionLabel; +import password.pwm.config.DomainConfig; +import password.pwm.config.PwmSetting; +import password.pwm.config.option.OTPStorageFormat; +import password.pwm.error.PwmUnrecoverableException; +import password.pwm.util.java.EnumUtil; +import password.pwm.util.java.StringUtil; +import password.pwm.util.logging.PwmLogger; +import password.pwm.util.secure.PwmBlockAlgorithm; +import password.pwm.util.secure.PwmSecurityKey; +import password.pwm.util.secure.SecureEngine; + +import java.util.Objects; +import java.util.Optional; + +final class OtpServiceUtil +{ + private static final PwmLogger LOGGER = PwmLogger.forClass( OtpServiceUtil.class ); + + private OtpServiceUtil() + { + } + + static String stringifyOtpRecord( final PwmDomain pwmDomain, final OTPUserRecord otpUserRecord ) + throws PwmUnrecoverableException + { + Objects.requireNonNull( pwmDomain ); + Objects.requireNonNull( otpUserRecord ); + + final DomainConfig config = pwmDomain.getConfig(); + final OTPStorageFormat format = config.readSettingAsEnum( PwmSetting.OTP_SECRET_STORAGEFORMAT, OTPStorageFormat.class ); + + final String value; + value = format.getFormatter().stringifyRecord( otpUserRecord ); + + if ( config.readSettingAsBoolean( PwmSetting.OTP_SECRET_ENCRYPT ) ) + { + return encryptAttributeValue( pwmDomain, value ); + } + + return value; + } + + /** + * Encrypt the given string using the PWM encryption key. + * + * @param unencrypted raw value to be encrypted + * + * @return the encrypted value + * @throws PwmUnrecoverableException if the operation can't be completed + */ + private static String encryptAttributeValue( final PwmDomain pwmDomain, final String unencrypted ) + throws PwmUnrecoverableException + { + final PwmBlockAlgorithm pwmBlockAlgorithm = figureBlockAlg( pwmDomain ); + final PwmSecurityKey pwmSecurityKey = pwmDomain.getConfig().getSecurityKey(); + return SecureEngine.encryptToString( unencrypted, pwmSecurityKey, pwmBlockAlgorithm ); + } + + private static PwmBlockAlgorithm figureBlockAlg( final PwmDomain pwmDomain ) + { + final String otpEncryptionAlgString = pwmDomain.getConfig().readAppProperty( AppProperty.OTP_ENCRYPTION_ALG ); + return EnumUtil.readEnumFromString( PwmBlockAlgorithm.class, otpEncryptionAlgString ) + .orElse( PwmBlockAlgorithm.AES ); + } + + /** + * Decrypt the given string using the PWM encryption key. + * + * @param encrypted value to be encrypted + * + * @return the decrypted value + * @throws PwmUnrecoverableException if the operation can't be completed + */ + private static String decryptAttributeValue( final PwmDomain pwmDomain, final String encrypted ) + throws PwmUnrecoverableException + { + final PwmBlockAlgorithm pwmBlockAlgorithm = figureBlockAlg( pwmDomain ); + final PwmSecurityKey pwmSecurityKey = pwmDomain.getConfig().getSecurityKey(); + return SecureEngine.decryptStringValue( encrypted, pwmSecurityKey, pwmBlockAlgorithm ); + } + + static Optional parseStoredOtpRecordValue( + final SessionLabel sessionLabel, + final PwmDomain pwmDomain, + final String value + ) + { + if ( StringUtil.isEmpty( value ) ) + { + return Optional.empty(); + } + + final String effectiveValue; + if ( pwmDomain.getConfig().readSettingAsBoolean( PwmSetting.OTP_SECRET_ENCRYPT ) ) + { + try + { + effectiveValue = decryptAttributeValue( pwmDomain, value ); + } + catch ( final PwmUnrecoverableException e ) + { + LOGGER.debug( sessionLabel, () -> "unable to decrypt user otp record: " + e.getMessage() ); + return Optional.empty(); + } + } + else + { + effectiveValue = value; + } + + return decomposeOtpAttribute( sessionLabel, pwmDomain, effectiveValue ); + } + + private static Optional decomposeOtpAttribute( + final SessionLabel sessionLabel, + final PwmDomain pwmDomain, + final String value + ) + { + final OTPStorageFormat preferredFormat = pwmDomain.getConfig().readSettingAsEnum( PwmSetting.OTP_SECRET_STORAGEFORMAT, OTPStorageFormat.class ); + { + final Optional preferredRecord = preferredFormat.getFormatter().parseStringRecord( sessionLabel, pwmDomain, value ); + if ( preferredRecord.isPresent() ) + { + return preferredRecord; + } + } + + for ( final OTPStorageFormat format : OTPStorageFormat.values() ) + { + if ( format != preferredFormat ) + { + final Optional record = format.getFormatter() + .parseStringRecord( sessionLabel, pwmDomain, value ); + + if ( record.isPresent() ) + { + LOGGER.debug( sessionLabel, () -> "otp decoded using configured preferred format " + format.name() ); + return record; + } + } + } + + LOGGER.trace( sessionLabel, () -> "could not parse stored otp attribute value using any format type " ); + + return Optional.empty(); + } +} diff --git a/server/src/main/java/password/pwm/svc/otp/formatter/Base32Formatter.java b/server/src/main/java/password/pwm/svc/otp/formatter/Base32Formatter.java new file mode 100644 index 0000000000..10b87dd35d --- /dev/null +++ b/server/src/main/java/password/pwm/svc/otp/formatter/Base32Formatter.java @@ -0,0 +1,53 @@ +/* + * Password Management Servlets (PWM) + * http://www.pwm-project.org + * + * Copyright (c) 2006-2009 Novell, Inc. + * Copyright (c) 2009-2021 The PWM Project + * + * 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. + */ + +package password.pwm.svc.otp.formatter; + +import password.pwm.PwmDomain; +import password.pwm.bean.SessionLabel; +import password.pwm.svc.otp.OTPUserRecord; + +import java.util.Optional; + +public class Base32Formatter implements OtpFormatter +{ + @Override + public Optional parseStringRecord( + final SessionLabel sessionLabel, + final PwmDomain pwmDomain, + final String value + ) + { + if ( value.trim().matches( "^[A-Z2-7\\=]{16}$" ) ) + { + final OTPUserRecord otpUserRecord = new OTPUserRecord(); + otpUserRecord.setSecret( value.trim() ); + return Optional.of( otpUserRecord ); + } + + return Optional.empty(); + } + + @Override + public String stringifyRecord( final OTPUserRecord otpUserRecord ) + { + return otpUserRecord.getSecret(); + } +} diff --git a/server/src/main/java/password/pwm/svc/node/NodeServiceStatistics.java b/server/src/main/java/password/pwm/svc/otp/formatter/OtpFormatter.java similarity index 63% rename from server/src/main/java/password/pwm/svc/node/NodeServiceStatistics.java rename to server/src/main/java/password/pwm/svc/otp/formatter/OtpFormatter.java index 6576d63744..a0235decc5 100644 --- a/server/src/main/java/password/pwm/svc/node/NodeServiceStatistics.java +++ b/server/src/main/java/password/pwm/svc/otp/formatter/OtpFormatter.java @@ -18,16 +18,21 @@ * limitations under the License. */ -package password.pwm.svc.node; +package password.pwm.svc.otp.formatter; -import lombok.Value; +import password.pwm.PwmDomain; +import password.pwm.bean.SessionLabel; +import password.pwm.svc.otp.OTPUserRecord; -import java.util.concurrent.atomic.AtomicInteger; +import java.util.Optional; -@Value -public class NodeServiceStatistics +public interface OtpFormatter { - private final AtomicInteger clusterWrites = new AtomicInteger( 0 ); - private final AtomicInteger clusterReads = new AtomicInteger( 0 ); - private final AtomicInteger nodePurges = new AtomicInteger( 0 ); + Optional parseStringRecord( + SessionLabel sessionLabel, + PwmDomain pwmDomain, + String value + ); + + String stringifyRecord( OTPUserRecord otpUserRecord ); } diff --git a/server/src/main/java/password/pwm/svc/otp/OTPPamUtil.java b/server/src/main/java/password/pwm/svc/otp/formatter/PamOtpFormatter.java similarity index 79% rename from server/src/main/java/password/pwm/svc/otp/OTPPamUtil.java rename to server/src/main/java/password/pwm/svc/otp/formatter/PamOtpFormatter.java index 9debe2a057..94f30fc8be 100644 --- a/server/src/main/java/password/pwm/svc/otp/OTPPamUtil.java +++ b/server/src/main/java/password/pwm/svc/otp/formatter/PamOtpFormatter.java @@ -18,23 +18,26 @@ * limitations under the License. */ -package password.pwm.svc.otp; +package password.pwm.svc.otp.formatter; +import password.pwm.PwmDomain; +import password.pwm.bean.SessionLabel; +import password.pwm.svc.otp.OTPUserRecord; import password.pwm.util.logging.PwmLogger; import java.util.ArrayList; import java.util.Arrays; import java.util.Iterator; import java.util.List; +import java.util.Optional; /** * @author Menno Pieters */ -public class OTPPamUtil +public class PamOtpFormatter implements OtpFormatter { - - private static final PwmLogger LOGGER = PwmLogger.forClass( OTPPamUtil.class ); + private static final PwmLogger LOGGER = PwmLogger.forClass( PamOtpFormatter.class ); /** * Split the string in lines; separate by CR, LF or CRLF. @@ -54,9 +57,13 @@ public static List splitLines( final String text ) return list; } - public static OTPUserRecord decomposePamData( final String otpInfo ) + public Optional parseStringRecord( + final SessionLabel sessionLabel, + final PwmDomain pwmDomain, + final String value + ) { - final List lines = splitLines( otpInfo ); + final List lines = splitLines( value ); if ( lines.size() >= 2 ) { final Iterator iterator = lines.iterator(); @@ -86,9 +93,7 @@ else if ( option.matches( "^HOTP_COUNTER\\s+\\d+$" ) ) } else if ( line.matches( "^\\d{8}$" ) ) { - final OTPUserRecord.RecoveryCode code = new OTPUserRecord.RecoveryCode(); - code.setUsed( false ); - code.setHashCode( line ); + final OTPUserRecord.RecoveryCode code = new OTPUserRecord.RecoveryCode( line, false ); recoveryCodes.add( code ); } else @@ -106,39 +111,39 @@ else if ( line.matches( "^\\d{8}$" ) ) else { LOGGER.debug( () -> String.format( "%d recovery codes read.", recoveryCodes.size() ) ); - final OTPUserRecord.RecoveryInfo recoveryInfo = new OTPUserRecord.RecoveryInfo(); - recoveryInfo.setHashCount( 0 ); - recoveryInfo.setSalt( null ); - recoveryInfo.setHashMethod( null ); + final OTPUserRecord.RecoveryInfo recoveryInfo = new OTPUserRecord.RecoveryInfo( + null, + null, + 0 ); otp.setRecoveryInfo( recoveryInfo ); otp.setRecoveryCodes( recoveryCodes ); } - return otp; + return Optional.of( otp ); } } - return null; + return Optional.empty(); } /** * Create a string representation to be stored by the operator. * - * @param otp the record with OTP configuration + * @param otpUserRecord the record with OTP configuration * @return the string representation of the OTP record */ - public static String composePamData( final OTPUserRecord otp ) + public String stringifyRecord( final OTPUserRecord otpUserRecord ) { - if ( otp == null ) + if ( otpUserRecord == null ) { return ""; } - final String secret = otp.getSecret(); - final OTPUserRecord.Type type = otp.getType(); - final List recoveryCodes = otp.getRecoveryCodes(); + final String secret = otpUserRecord.getSecret(); + final OTPUserRecord.Type type = otpUserRecord.getType(); + final List recoveryCodes = otpUserRecord.getRecoveryCodes(); final StringBuilder pamData = new StringBuilder(); pamData.append( secret ).append( '\n' ); if ( OTPUserRecord.Type.HOTP == type ) { - pamData.append( String.format( "\" HOTP_COUNTER %d%n", otp.getAttemptCount() ) ); + pamData.append( String.format( "\" HOTP_COUNTER %d%n", otpUserRecord.getAttemptCount() ) ); } else { @@ -149,9 +154,9 @@ public static String composePamData( final OTPUserRecord otp ) // The codes are assumed to be non-hashed for ( final OTPUserRecord.RecoveryCode code : recoveryCodes ) { - if ( !code.isUsed() ) + if ( !code.used() ) { - pamData.append( code.getHashCode() ).append( '\n' ); + pamData.append( code.hash() ).append( '\n' ); } } } diff --git a/server/src/main/java/password/pwm/svc/otp/formatter/PwmJsonFormatter.java b/server/src/main/java/password/pwm/svc/otp/formatter/PwmJsonFormatter.java new file mode 100644 index 0000000000..33b963020e --- /dev/null +++ b/server/src/main/java/password/pwm/svc/otp/formatter/PwmJsonFormatter.java @@ -0,0 +1,59 @@ +/* + * Password Management Servlets (PWM) + * http://www.pwm-project.org + * + * Copyright (c) 2006-2009 Novell, Inc. + * Copyright (c) 2009-2021 The PWM Project + * + * 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. + */ + +package password.pwm.svc.otp.formatter; + +import password.pwm.PwmDomain; +import password.pwm.bean.SessionLabel; +import password.pwm.svc.otp.OTPUserRecord; +import password.pwm.util.json.JsonFactory; +import password.pwm.util.logging.PwmLogger; + +import java.util.Optional; + +public class PwmJsonFormatter implements OtpFormatter +{ + private static final PwmLogger LOGGER = PwmLogger.forClass( PwmJsonFormatter.class ); + + @Override + public Optional parseStringRecord( + final SessionLabel sessionLabel, + final PwmDomain pwmDomain, + final String value + ) + { + try + { + return Optional.of( JsonFactory.get().deserialize( value, OTPUserRecord.class ) ); + } + catch ( final Exception e ) + { + LOGGER.trace( sessionLabel, () -> "error decoding stored OTP format as JSON: " + e.getMessage() ); + } + + return Optional.empty(); + } + + @Override + public String stringifyRecord( final OTPUserRecord otpUserRecord ) + { + return JsonFactory.get().serialize( otpUserRecord ); + } +} diff --git a/server/src/main/java/password/pwm/svc/otp/OTPUrlUtil.java b/server/src/main/java/password/pwm/svc/otp/formatter/UrlOtpFormatter.java similarity index 58% rename from server/src/main/java/password/pwm/svc/otp/OTPUrlUtil.java rename to server/src/main/java/password/pwm/svc/otp/formatter/UrlOtpFormatter.java index 4ea1d956ab..d08e64a36a 100644 --- a/server/src/main/java/password/pwm/svc/otp/OTPUrlUtil.java +++ b/server/src/main/java/password/pwm/svc/otp/formatter/UrlOtpFormatter.java @@ -18,34 +18,38 @@ * limitations under the License. */ -package password.pwm.svc.otp; +package password.pwm.svc.otp.formatter; +import password.pwm.PwmDomain; +import password.pwm.bean.SessionLabel; +import password.pwm.svc.otp.OTPUserRecord; + +import java.util.Optional; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * @author mpieters */ -public class OTPUrlUtil +public class UrlOtpFormatter implements OtpFormatter { - - public static final String OTP_URL_PATTERN = "^otpauth:\\/\\/(totp|hotp)\\/(.*)\\?secret=([A-Z2-7\\=]{16})$"; - public static final int OTP_URL_GROUPS = 3; - public static final int OTP_URL_TYPE = 1; - public static final int OTP_URL_IDENT = 2; - public static final int OTP_URL_SECRET = 3; + private static final String OTP_URL_PATTERN = "^otpauth:\\/\\/(totp|hotp)\\/(.*)\\?secret=([A-Z2-7\\=]{16})$"; + private static final int OTP_URL_GROUPS = 3; + private static final int OTP_URL_TYPE = 1; + private static final int OTP_URL_IDENT = 2; + private static final int OTP_URL_SECRET = 3; /** * Convert a OTPUserRecord object into an otpauth:// url. - * @param otp a valid otp user record + * @param otpUserRecord a valid otp user record * * @return a valid otp url string. */ - public static String composeOtpUrl( final OTPUserRecord otp ) + public String stringifyRecord( final OTPUserRecord otpUserRecord ) { - final String ident = otp.getIdentifier(); - final String secret = otp.getSecret(); - final String otptype = otp.getType().toString(); + final String ident = otpUserRecord.getIdentifier(); + final String secret = otpUserRecord.getSecret(); + final String otptype = otpUserRecord.getType().toString(); return String.format( "otpauth://%s/%s?secret=%s", otptype.toLowerCase(), ident, secret ); } @@ -53,24 +57,29 @@ public static String composeOtpUrl( final OTPUserRecord otp ) /** * Read a string with an otpauth:// url and convert to an OTPUserRecord object. * - * @param otpInfo otp input url string. + * @param value otp input url string. * @return a user recorded generated from the input string. */ - public static OTPUserRecord decomposeOtpUrl( final String otpInfo ) + @Override + public Optional parseStringRecord( + final SessionLabel sessionLabel, + final PwmDomain pwmDomain, + final String value + ) { - OTPUserRecord otp = null; final Pattern pattern = Pattern.compile( OTP_URL_PATTERN ); - final Matcher matcher = pattern.matcher( otpInfo ); + final Matcher matcher = pattern.matcher( value ); if ( matcher.matches() && matcher.groupCount() == OTP_URL_GROUPS ) { final String type = matcher.group( OTP_URL_TYPE ); final String ident = matcher.group( OTP_URL_IDENT ); final String secret = matcher.group( OTP_URL_SECRET ); - otp = new OTPUserRecord(); + final OTPUserRecord otp = new OTPUserRecord(); otp.setType( OTPUserRecord.Type.valueOf( type.toUpperCase() ) ); otp.setIdentifier( ident ); otp.setSecret( secret ); + return Optional.of( otp ); } - return otp; + return Optional.empty(); } } diff --git a/server/src/main/java/password/pwm/svc/pwnotify/PwNotifyEngine.java b/server/src/main/java/password/pwm/svc/pwnotify/PwNotifyEngine.java index 3b66354b58..b6501f7103 100644 --- a/server/src/main/java/password/pwm/svc/pwnotify/PwNotifyEngine.java +++ b/server/src/main/java/password/pwm/svc/pwnotify/PwNotifyEngine.java @@ -154,8 +154,8 @@ void executeJob( ) log( "starting job, beginning ldap search" ); final List matchingUsers = UserPermissionUtility.discoverMatchingUsers( pwmDomain, - permissionList, pwNotifyService.getSessionLabel(), settings.getMaxLdapSearchSize(), - settings.getSearchTimeout() + permissionList, pwNotifyService.getSessionLabel(), settings.maxLdapSearchSize(), + settings.searchTimeout() ); log( "ldap search complete, examining users..." ); @@ -272,7 +272,7 @@ private int figureNextDayInterval( { final long maxSecondsAfterExpiration = TimeDuration.DAY.as( TimeDuration.Unit.SECONDS ); int nextDayInterval = -1; - for ( final int configuredDayInterval : settings.getNotificationIntervals() ) + for ( final int configuredDayInterval : settings.notificationIntervals() ) { final Instant futureConfiguredDayInterval = Instant.now().plus( configuredDayInterval, ChronoUnit.DAYS ); final long secondsUntilConfiguredInterval = Duration.between( Instant.now(), futureConfiguredDayInterval ).abs().getSeconds(); diff --git a/server/src/main/java/password/pwm/svc/pwnotify/PwNotifyService.java b/server/src/main/java/password/pwm/svc/pwnotify/PwNotifyService.java index 3d9607287e..c22a5edaa0 100644 --- a/server/src/main/java/password/pwm/svc/pwnotify/PwNotifyService.java +++ b/server/src/main/java/password/pwm/svc/pwnotify/PwNotifyService.java @@ -179,21 +179,21 @@ private Instant figureNextJobExecutionTime() if ( pwNotifyStoredJobState != null ) { // never run, or last job not successful. - if ( pwNotifyStoredJobState.getLastCompletion() == null || pwNotifyStoredJobState.getLastError() != null ) + if ( pwNotifyStoredJobState.lastCompletion() == null || pwNotifyStoredJobState.lastError() != null ) { return Instant.now().plus( 1, ChronoUnit.MINUTES ); } // more than 24hr ago. - final long maxSeconds = settings.getMaximumSkipWindow().as( TimeDuration.Unit.SECONDS ); - if ( Duration.between( Instant.now(), pwNotifyStoredJobState.getLastCompletion() ).abs().getSeconds() > maxSeconds ) + final long maxSeconds = settings.maximumSkipWindow().as( TimeDuration.Unit.SECONDS ); + if ( Duration.between( Instant.now(), pwNotifyStoredJobState.lastCompletion() ).abs().getSeconds() > maxSeconds ) { return Instant.now(); } } final Instant nextZuluZeroTime = PwmScheduler.nextZuluZeroTime(); - final Instant adjustedNextZuluZeroTime = nextZuluZeroTime.plus( settings.getZuluOffset().as( TimeDuration.Unit.SECONDS ), ChronoUnit.SECONDS ); + final Instant adjustedNextZuluZeroTime = nextZuluZeroTime.plus( settings.zuluOffset().as( TimeDuration.Unit.SECONDS ), ChronoUnit.SECONDS ); final Instant previousAdjustedZuluZeroTime = adjustedNextZuluZeroTime.minus( 1, ChronoUnit.DAYS ); if ( previousAdjustedZuluZeroTime.isAfter( Instant.now() ) ) @@ -224,7 +224,7 @@ protected List serviceHealthCheck( ) final PwNotifyStoredJobState pwNotifyStoredJobState = storageService.readStoredJobState(); if ( pwNotifyStoredJobState != null ) { - final ErrorInformation errorInformation = pwNotifyStoredJobState.getLastError(); + final ErrorInformation errorInformation = pwNotifyStoredJobState.lastError(); if ( errorInformation != null ) { returnRecords.add( HealthRecord.forMessage( DomainID.systemId(), HealthMessage.PwNotify_Failure, errorInformation.toDebugStr() ) ); diff --git a/server/src/main/java/password/pwm/svc/pwnotify/PwNotifySettings.java b/server/src/main/java/password/pwm/svc/pwnotify/PwNotifySettings.java index 96db8406e7..539e61ec09 100644 --- a/server/src/main/java/password/pwm/svc/pwnotify/PwNotifySettings.java +++ b/server/src/main/java/password/pwm/svc/pwnotify/PwNotifySettings.java @@ -20,49 +20,43 @@ package password.pwm.svc.pwnotify; -import lombok.Builder; -import lombok.Value; import password.pwm.AppProperty; import password.pwm.config.DomainConfig; import password.pwm.config.PwmSetting; import password.pwm.util.java.TimeDuration; -import java.math.BigDecimal; import java.util.List; -import java.util.stream.Collectors; -@Value -@Builder -class PwNotifySettings +record PwNotifySettings( + List notificationIntervals, + TimeDuration maximumSkipWindow, + TimeDuration zuluOffset, + int maxLdapSearchSize, + TimeDuration searchTimeout +) { - private final List notificationIntervals; - private final TimeDuration maximumSkipWindow; - private final TimeDuration zuluOffset; - private final int maxLdapSearchSize; - private final TimeDuration searchTimeout; - private final int batchCount; - private final BigDecimal batchTimeMultiplier; static PwNotifySettings fromConfiguration( final DomainConfig domainConfig ) { - final PwNotifySettingsBuilder builder = PwNotifySettings.builder(); - { - final List timeDurations = domainConfig.readSettingAsStringArray( PwmSetting.PW_EXPY_NOTIFY_INTERVAL ).stream() - .map( Integer::parseInt ) - .sorted() - .collect( Collectors.toUnmodifiableList() ); + final List notificationIntervals = domainConfig.readSettingAsStringArray( PwmSetting.PW_EXPY_NOTIFY_INTERVAL ).stream() + .map( Integer::parseInt ) + .sorted() + .toList(); + + final TimeDuration maximumSkipWindow = TimeDuration.of( + Long.parseLong( domainConfig.readAppProperty( AppProperty.PWNOTIFY_MAX_SKIP_RERUN_WINDOW_SECONDS ) ), + TimeDuration.Unit.SECONDS ); + + final TimeDuration searchTimeout = TimeDuration.of( + Long.parseLong( domainConfig.readAppProperty( AppProperty.REPORTING_LDAP_SEARCH_TIMEOUT_MS ) ), + TimeDuration.Unit.MILLISECONDS ); + + return new PwNotifySettings( + notificationIntervals, + maximumSkipWindow, + TimeDuration.of( domainConfig.readSettingAsLong( PwmSetting.PW_EXPY_NOTIFY_JOB_OFFSET ), TimeDuration.Unit.SECONDS ), + Integer.parseInt( domainConfig.readAppProperty( AppProperty.PWNOTIFY_MAX_LDAP_SEARCH_SIZE ) ), + searchTimeout ); - builder.notificationIntervals( timeDurations ); - } - - builder.searchTimeout( TimeDuration.of( Long.parseLong( domainConfig.readAppProperty( AppProperty.REPORTING_LDAP_SEARCH_TIMEOUT_MS ) ), TimeDuration.Unit.MILLISECONDS ) ); - builder.zuluOffset( TimeDuration.of( domainConfig.readSettingAsLong( PwmSetting.PW_EXPY_NOTIFY_JOB_OFFSET ), TimeDuration.Unit.SECONDS ) ); - builder.batchCount( Integer.parseInt( domainConfig.readAppProperty( AppProperty.PWNOTIFY_BATCH_COUNT ) ) ); - builder.maxLdapSearchSize( Integer.parseInt( domainConfig.readAppProperty( AppProperty.PWNOTIFY_MAX_LDAP_SEARCH_SIZE ) ) ); - builder.batchTimeMultiplier( new BigDecimal( domainConfig.readAppProperty( AppProperty.PWNOTIFY_BATCH_DELAY_TIME_MULTIPLIER ) ) ); - builder.maximumSkipWindow( TimeDuration.of( - Long.parseLong( domainConfig.readAppProperty( AppProperty.PWNOTIFY_MAX_SKIP_RERUN_WINDOW_SECONDS ) ), TimeDuration.Unit.SECONDS ) ); - - return builder.build(); } } diff --git a/server/src/main/java/password/pwm/svc/pwnotify/PwNotifyStoredJobState.java b/server/src/main/java/password/pwm/svc/pwnotify/PwNotifyStoredJobState.java index ef91525f64..bef7214625 100644 --- a/server/src/main/java/password/pwm/svc/pwnotify/PwNotifyStoredJobState.java +++ b/server/src/main/java/password/pwm/svc/pwnotify/PwNotifyStoredJobState.java @@ -21,18 +21,20 @@ package password.pwm.svc.pwnotify; import lombok.Builder; -import lombok.Value; import password.pwm.error.ErrorInformation; import java.time.Instant; -@Value -@Builder -public class PwNotifyStoredJobState +public record PwNotifyStoredJobState( + Instant lastStart, + Instant lastCompletion, + String serverInstance, + ErrorInformation lastError, + boolean jobSuccess +) { - private Instant lastStart; - private Instant lastCompletion; - private String serverInstance; - private ErrorInformation lastError; - private boolean jobSuccess; + @Builder + public PwNotifyStoredJobState + { + } } diff --git a/server/src/main/java/password/pwm/svc/report/ReportCsvRecordWriter.java b/server/src/main/java/password/pwm/svc/report/ReportCsvRecordWriter.java index 3813fd4da3..a14f0c85ce 100644 --- a/server/src/main/java/password/pwm/svc/report/ReportCsvRecordWriter.java +++ b/server/src/main/java/password/pwm/svc/report/ReportCsvRecordWriter.java @@ -123,10 +123,10 @@ static void outputRecordRow( csvRow.add( userReportRecord.getResponseFormatType() == null ? naField : userReportRecord.getResponseFormatType().toString() ); - csvRow.add( userReportRecord.getPasswordStatus().isExpired() ? trueField : falseField ); - csvRow.add( userReportRecord.getPasswordStatus().isPreExpired() ? trueField : falseField ); - csvRow.add( userReportRecord.getPasswordStatus().isViolatesPolicy() ? trueField : falseField ); - csvRow.add( userReportRecord.getPasswordStatus().isWarnPeriod() ? trueField : falseField ); + csvRow.add( userReportRecord.getPasswordStatus().expired() ? trueField : falseField ); + csvRow.add( userReportRecord.getPasswordStatus().preExpired() ? trueField : falseField ); + csvRow.add( userReportRecord.getPasswordStatus().violatesPolicy() ? trueField : falseField ); + csvRow.add( userReportRecord.getPasswordStatus().warnPeriod() ? trueField : falseField ); csvRow.add( userReportRecord.isRequiresPasswordUpdate() ? trueField : falseField ); csvRow.add( userReportRecord.isRequiresResponseUpdate() ? trueField : falseField ); csvRow.add( userReportRecord.isRequiresProfileUpdate() ? trueField : falseField ); diff --git a/server/src/main/java/password/pwm/svc/report/ReportProcess.java b/server/src/main/java/password/pwm/svc/report/ReportProcess.java index 413b4a2dfe..1f861dee12 100644 --- a/server/src/main/java/password/pwm/svc/report/ReportProcess.java +++ b/server/src/main/java/password/pwm/svc/report/ReportProcess.java @@ -393,13 +393,13 @@ public ReportProcessStatus getStatus( final Locale locale ) final List list = new ArrayList<>(); if ( inProgress.get() ) { - list.add( new DisplayElement( "status", DisplayElement.Type.string, + list.add( DisplayElement.create( "status", DisplayElement.Type.string, "Status", "In Progress" ) ); - list.add( new DisplayElement( "recordCount", DisplayElement.Type.number, + list.add( DisplayElement.create( "recordCount", DisplayElement.Type.number, "Record Count", String.valueOf( recordCounter.longValue() ) ) ); - list.add( new DisplayElement( "duration", DisplayElement.Type.string, + list.add( DisplayElement.create( "duration", DisplayElement.Type.string, "Duration", PwmTimeUtil.asLongString( TimeDuration.fromCurrent( startTime ), locale ) ) ); if ( recordCounter.get() > 0 ) @@ -407,14 +407,14 @@ public ReportProcessStatus getStatus( final Locale locale ) final String rate = processRateMeter.rawEps() .multiply( new BigDecimal( "60" ) ) .setScale( 2, RoundingMode.UP ).toString(); - list.add( new DisplayElement( "eventRate", DisplayElement.Type.number, + list.add( DisplayElement.create( "eventRate", DisplayElement.Type.number, "Users / Minute", String.valueOf( rate ) ) ); } } else { - list.add( new DisplayElement( "status", DisplayElement.Type.string, "Status", "Idle" ) ); + list.add( DisplayElement.create( "status", DisplayElement.Type.string, "Status", "Idle" ) ); } return new ReportProcessStatus( List.copyOf( list ), inProgress.get() ); diff --git a/server/src/main/java/password/pwm/svc/report/ReportProcessStatus.java b/server/src/main/java/password/pwm/svc/report/ReportProcessStatus.java index f14c30d33b..529a850af0 100644 --- a/server/src/main/java/password/pwm/svc/report/ReportProcessStatus.java +++ b/server/src/main/java/password/pwm/svc/report/ReportProcessStatus.java @@ -20,18 +20,28 @@ package password.pwm.svc.report; -import lombok.Builder; -import lombok.Value; import password.pwm.http.bean.DisplayElement; +import password.pwm.util.java.CollectionUtil; -import java.util.Collections; import java.util.List; -@Value -@Builder -public class ReportProcessStatus +public record ReportProcessStatus( + List presentable, + boolean reportInProgress +) { - @Builder.Default - private List presentable = Collections.singletonList( new DisplayElement( "status", DisplayElement.Type.string, "Status", "Idle" ) ); - private boolean reportInProgress; + private static final ReportProcessStatus IDLE = new ReportProcessStatus( + List.of( DisplayElement.create( "status", DisplayElement.Type.string, "Status", "Idle" ) ), + false ); + + public ReportProcessStatus( final List presentable, final boolean reportInProgress ) + { + this.presentable = CollectionUtil.stripNulls( presentable ); + this.reportInProgress = reportInProgress; + } + + public static ReportProcessStatus getIdle() + { + return IDLE; + } } diff --git a/server/src/main/java/password/pwm/svc/report/ReportSummaryCalculator.java b/server/src/main/java/password/pwm/svc/report/ReportSummaryCalculator.java index 808adf333d..a4617d5f90 100644 --- a/server/src/main/java/password/pwm/svc/report/ReportSummaryCalculator.java +++ b/server/src/main/java/password/pwm/svc/report/ReportSummaryCalculator.java @@ -237,15 +237,15 @@ public void accept( final UserReportRecord userReportRecord, final ReportSummary { if ( userReportRecord.getPasswordStatus() != null ) { - if ( userReportRecord.getPasswordStatus().isExpired() ) + if ( userReportRecord.getPasswordStatus().expired() ) { reportSummaryData.getCounterStats().increment( SummaryCounterStat.pwExpired ); } - if ( userReportRecord.getPasswordStatus().isPreExpired() ) + if ( userReportRecord.getPasswordStatus().preExpired() ) { reportSummaryData.getCounterStats().increment( SummaryCounterStat.pwPreExpired ); } - if ( userReportRecord.getPasswordStatus().isWarnPeriod() ) + if ( userReportRecord.getPasswordStatus().warnPeriod() ) { reportSummaryData.getCounterStats().increment( SummaryCounterStat.pwWarnPeriod ); } diff --git a/server/src/main/java/password/pwm/svc/stats/StatisticsUtils.java b/server/src/main/java/password/pwm/svc/stats/StatisticsUtils.java index 688f10d4fc..2e8c7daa9e 100644 --- a/server/src/main/java/password/pwm/svc/stats/StatisticsUtils.java +++ b/server/src/main/java/password/pwm/svc/stats/StatisticsUtils.java @@ -46,7 +46,7 @@ public class StatisticsUtils public enum CsvOutputFlag { - includeHeader; + includeHeader } public static int outputStatsToCsv( @@ -141,7 +141,7 @@ public static List statsDisplayElementsForBundle( ) { return Statistic.sortedValues( locale ).stream() - .map( stat -> new DisplayElement( + .map( stat -> DisplayElement.create( keyPrefix + stat.getKey(), DisplayElement.Type.number, stat.getLabel( locale ), @@ -156,7 +156,7 @@ public static List avgStatsDisplayElementsForBundle( ) { return EnumUtil.enumStream( AvgStatistic.class ) - .map( stat -> new DisplayElement( + .map( stat -> DisplayElement.create( keyPrefix + stat.getKey(), DisplayElement.Type.string, stat.getLabel( locale ), diff --git a/server/src/main/java/password/pwm/svc/token/DataStoreTokenMachine.java b/server/src/main/java/password/pwm/svc/token/DataStoreTokenMachine.java index f73b0aa748..3af1e7ba71 100644 --- a/server/src/main/java/password/pwm/svc/token/DataStoreTokenMachine.java +++ b/server/src/main/java/password/pwm/svc/token/DataStoreTokenMachine.java @@ -116,18 +116,18 @@ private boolean testIfTokenNeedsPurging( final TokenPayload theToken ) { return false; } - final Instant issueDate = theToken.getIssueTime(); + final Instant issueDate = theToken.issueTime(); if ( issueDate == null ) { LOGGER.error( () -> "retrieved token has no issueDate, marking as purgable: " + JsonFactory.get().serialize( theToken ) ); return true; } - if ( theToken.getExpiration() == null ) + if ( theToken.expiration() == null ) { LOGGER.error( () -> "retrieved token has no expiration, marking as purgable: " + JsonFactory.get().serialize( theToken ) ); return true; } - return theToken.getExpiration().isBefore( Instant.now() ); + return theToken.expiration().isBefore( Instant.now() ); } @Override diff --git a/server/src/main/java/password/pwm/svc/token/LdapTokenMachine.java b/server/src/main/java/password/pwm/svc/token/LdapTokenMachine.java index 0b9e978467..125547b757 100644 --- a/server/src/main/java/password/pwm/svc/token/LdapTokenMachine.java +++ b/server/src/main/java/password/pwm/svc/token/LdapTokenMachine.java @@ -141,7 +141,7 @@ public void storeToken( final TokenKey tokenKey, final TokenPayload tokenPayload final String md5sumToken = tokenKey.getStoredHash(); final String encodedTokenPayload = tokenService.toEncryptedString( tokenPayload ); - final UserIdentity userIdentity = tokenPayload.getUserIdentity(); + final UserIdentity userIdentity = tokenPayload.userIdentity(); final ChaiUser chaiUser = pwmDomain.getProxiedChaiUser( tokenService.getSessionLabel(), userIdentity ); chaiUser.writeStringAttribute( tokenAttribute, md5sumToken + KEY_VALUE_DELIMITER + encodedTokenPayload ); } @@ -160,7 +160,7 @@ public void removeToken( final TokenKey tokenKey ) final Optional payload = retrieveToken( null, tokenKey ); if ( payload.isPresent() ) { - final UserIdentity userIdentity = payload.get().getUserIdentity(); + final UserIdentity userIdentity = payload.get().userIdentity(); try { final ChaiUser chaiUser = pwmDomain.getProxiedChaiUser( tokenService.getSessionLabel(), userIdentity ); diff --git a/server/src/main/java/password/pwm/svc/token/TokenPayload.java b/server/src/main/java/password/pwm/svc/token/TokenPayload.java index 5148586ae7..1e03ba4478 100644 --- a/server/src/main/java/password/pwm/svc/token/TokenPayload.java +++ b/server/src/main/java/password/pwm/svc/token/TokenPayload.java @@ -21,55 +21,55 @@ package password.pwm.svc.token; import com.google.gson.annotations.SerializedName; -import lombok.Value; import password.pwm.bean.TokenDestinationItem; import password.pwm.bean.UserIdentity; +import password.pwm.util.java.CollectionUtil; import password.pwm.util.java.StringUtil; import password.pwm.util.json.JsonFactory; import java.time.Instant; -import java.util.Collections; import java.util.HashMap; import java.util.Map; +import java.util.Objects; -@Value -public class TokenPayload -{ - @SerializedName( "t" ) - private final Instant issueTime; - - @SerializedName( "e" ) - private final Instant expiration; +public record TokenPayload( + @SerializedName( "n" ) + String name, - @SerializedName( "n" ) - private final String name; + @SerializedName( "e" ) + Instant expiration, - @SerializedName( "p" ) - private final Map data; + @SerializedName( "t" ) + Instant issueTime, - @SerializedName( "user" ) - private final UserIdentity userIdentity; + @SerializedName( "p" ) + Map data, - @SerializedName( "d" ) - private final TokenDestinationItem destination; + @SerializedName( "user" ) + UserIdentity userIdentity, - @SerializedName( "g" ) - private final String guid; + @SerializedName( "d" ) + TokenDestinationItem destination, - TokenPayload( + @SerializedName( "g" ) + String guid +) +{ + public TokenPayload( final String name, final Instant expiration, + final Instant issueTime, final Map data, - final UserIdentity user, + final UserIdentity userIdentity, final TokenDestinationItem destination, final String guid ) { - this.issueTime = Instant.now(); - this.expiration = expiration; - this.data = data == null ? Collections.emptyMap() : Collections.unmodifiableMap( data ); this.name = name; - this.userIdentity = user; + this.expiration = Objects.requireNonNull( expiration ); + this.issueTime = Objects.requireNonNull( issueTime ); + this.data = CollectionUtil.stripNulls( data ); + this.userIdentity = userIdentity; this.destination = destination; this.guid = guid; } @@ -80,12 +80,12 @@ public String toDebugString( ) final Map debugMap = new HashMap<>(); debugMap.put( "issueTime", StringUtil.toIsoDate( issueTime ) ); debugMap.put( "expiration", StringUtil.toIsoDate( expiration ) ); - debugMap.put( "name", getName() ); - if ( getUserIdentity() != null ) + debugMap.put( "name", name() ); + if ( userIdentity() != null ) { - debugMap.put( "user", getUserIdentity().toDisplayString() ); + debugMap.put( "user", userIdentity().toDisplayString() ); } - debugMap.put( "guid", getGuid() ); + debugMap.put( "guid", guid() ); return JsonFactory.get().serializeMap( debugMap ); } } diff --git a/server/src/main/java/password/pwm/svc/token/TokenService.java b/server/src/main/java/password/pwm/svc/token/TokenService.java index e276287518..ef442e6d52 100644 --- a/server/src/main/java/password/pwm/svc/token/TokenService.java +++ b/server/src/main/java/password/pwm/svc/token/TokenService.java @@ -134,7 +134,15 @@ public TokenPayload createTokenPayload( { final String guid = PwmRandom.getInstance().randomUUID().toString(); final Instant expiration = lifetime.incrementFromInstant( Instant.now() ); - return new TokenPayload( name.name(), expiration, data, userIdentity, destination, guid ); + + return new TokenPayload( + name.name(), + expiration, + Instant.now(), + data, + userIdentity, + destination, + guid ); } @Override @@ -246,7 +254,7 @@ public String generateNewToken( final TokenPayload tokenPayload, final SessionLa final AuditRecord auditRecord = AuditRecordFactory.make( sessionLabel, pwmDomain ).createUserAuditRecord( AuditEvent.TOKEN_ISSUED, - tokenPayload.getUserIdentity(), + tokenPayload.userIdentity(), sessionLabel, JsonFactory.get().serialize( tokenPayload ) ); @@ -264,7 +272,7 @@ private void markTokenAsClaimed( ) throws PwmUnrecoverableException { - if ( tokenPayload == null || tokenPayload.getUserIdentity() == null ) + if ( tokenPayload == null || tokenPayload.userIdentity() == null ) { return; } @@ -286,7 +294,7 @@ private void markTokenAsClaimed( final AuditRecord auditRecord = AuditRecordFactory.make( sessionLabel, pwmDomain ).createUserAuditRecord( AuditEvent.TOKEN_CLAIMED, - tokenPayload.getUserIdentity(), + tokenPayload.userIdentity(), sessionLabel, JsonFactory.get().serialize( tokenPayload ) ); @@ -377,18 +385,18 @@ private boolean testIfTokenIsExpired( final SessionLabel sessionLabel, final Tok { return false; } - final Instant issueDate = theToken.getIssueTime(); + final Instant issueDate = theToken.issueTime(); if ( issueDate == null ) { LOGGER.error( sessionLabel, () -> "retrieved token has no issueDate, marking as expired: " + theToken.toDebugString() ); return true; } - if ( theToken.getExpiration() == null ) + if ( theToken.expiration() == null ) { LOGGER.error( sessionLabel, () -> "retrieved token has no expiration timestamp, marking as expired: " + theToken.toDebugString() ); return true; } - return theToken.getExpiration().isBefore( Instant.now() ); + return theToken.expiration().isBefore( Instant.now() ); } private static String makeRandomCode( final DomainConfig config ) @@ -565,9 +573,9 @@ public TokenPayload processUserEnteredCode( tokenType, userEnteredCode ); - if ( tokenPayload.getDestination() != null && StringUtil.notEmpty( tokenPayload.getDestination().getValue() ) ) + if ( tokenPayload.destination() != null && StringUtil.notEmpty( tokenPayload.destination().getValue() ) ) { - pwmDomain.getIntruderService().clear( IntruderRecordType.TOKEN_DEST, tokenPayload.getDestination().getValue() ); + pwmDomain.getIntruderService().clear( IntruderRecordType.TOKEN_DEST, tokenPayload.destination().getValue() ); } markTokenAsClaimed( tokenMachine.keyFromKey( userEnteredCode ), sessionLabel, tokenPayload ); stats.increment( StatsKey.tokenValidationsPassed ); @@ -627,7 +635,7 @@ private TokenPayload processUserEnteredCodeImpl( if ( tokenType != null && pwmDomain.getTokenService().supportsName() ) { - if ( !tokenType.matchesName( tokenPayload.getName() ) ) + if ( !tokenType.matchesName( tokenPayload.name() ) ) { final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_TOKEN_INCORRECT, "incorrect token/name format" ); throw new PwmOperationalException( errorInformation ); @@ -635,20 +643,20 @@ private TokenPayload processUserEnteredCodeImpl( } // check current session identity - if ( tokenPayload.getUserIdentity() != null && sessionUserIdentity != null ) + if ( tokenPayload.userIdentity() != null && sessionUserIdentity != null ) { - if ( !tokenPayload.getUserIdentity().canonicalEquals( sessionLabel, sessionUserIdentity, pwmDomain.getPwmApplication() ) ) + if ( !tokenPayload.userIdentity().canonicalEquals( sessionLabel, sessionUserIdentity, pwmDomain.getPwmApplication() ) ) { - final String errorMsg = "user in session '" + sessionUserIdentity + "' entered code for user '" + tokenPayload.getUserIdentity() + "', counting as invalid attempt"; + final String errorMsg = "user in session '" + sessionUserIdentity + "' entered code for user '" + tokenPayload.userIdentity() + "', counting as invalid attempt"; throw new PwmOperationalException( PwmError.ERROR_TOKEN_INCORRECT, errorMsg ); } } // check if password-last-modified is same as when tried to read it before. if ( verifyPwModifyTime - && tokenPayload.getUserIdentity() != null - && tokenPayload.getData() != null - && tokenPayload.getData().containsKey( PwmConstants.TOKEN_KEY_PWD_CHG_DATE ) + && tokenPayload.userIdentity() != null + && tokenPayload.data() != null + && tokenPayload.data().containsKey( PwmConstants.TOKEN_KEY_PWD_CHG_DATE ) ) { try @@ -656,13 +664,13 @@ private TokenPayload processUserEnteredCodeImpl( final Instant userLastPasswordChange = PasswordUtility.determinePwdLastModified( pwmDomain, sessionLabel, - tokenPayload.getUserIdentity() ); + tokenPayload.userIdentity() ); - final String dateStringInToken = tokenPayload.getData().get( PwmConstants.TOKEN_KEY_PWD_CHG_DATE ); + final String dateStringInToken = tokenPayload.data().get( PwmConstants.TOKEN_KEY_PWD_CHG_DATE ); LOGGER.trace( sessionLabel, () -> "tokenPayload=" + tokenPayload.toDebugString() + ", sessionUser=" + ( sessionUserIdentity == null ? "null" : sessionUserIdentity.toDisplayString() ) - + ", payloadUserIdentity=" + tokenPayload.getUserIdentity().toDisplayString() + + ", payloadUserIdentity=" + tokenPayload.userIdentity().toDisplayString() + ", userLastPasswordChange=" + StringUtil.toIsoDate( userLastPasswordChange ) + ", dateStringInToken=" + dateStringInToken ); diff --git a/server/src/main/java/password/pwm/svc/token/TokenUtil.java b/server/src/main/java/password/pwm/svc/token/TokenUtil.java index f9c4778000..b51f8f1a6b 100644 --- a/server/src/main/java/password/pwm/svc/token/TokenUtil.java +++ b/server/src/main/java/password/pwm/svc/token/TokenUtil.java @@ -161,24 +161,24 @@ public static TokenPayload checkEnteredCode( ); if ( tokenPayload != null ) { - if ( !tokenType.matchesName( tokenPayload.getName() ) ) + if ( !tokenType.matchesName( tokenPayload.name() ) ) { - final String errorMsg = "expecting email token type but received : " + tokenPayload.getName(); + final String errorMsg = "expecting email token type but received : " + tokenPayload.name(); throw PwmUnrecoverableException.newException( PwmError.ERROR_TOKEN_INCORRECT, errorMsg ); } if ( tokenEntryType == TokenService.TokenEntryType.authenticated ) { - if ( tokenPayload.getUserIdentity() == null ) + if ( tokenPayload.userIdentity() == null ) { final String errorMsg = "missing userID for received token"; throw PwmUnrecoverableException.newException( PwmError.ERROR_TOKEN_INCORRECT, errorMsg ); } - if ( !userIdentity.canonicalEquals( pwmRequestContext.getSessionLabel(), tokenPayload.getUserIdentity(), pwmDomain.getPwmApplication() ) ) + if ( !userIdentity.canonicalEquals( pwmRequestContext.getSessionLabel(), tokenPayload.userIdentity(), pwmDomain.getPwmApplication() ) ) { final String errorMsg = "received token is not for currently authenticated user, received token is for: " - + tokenPayload.getUserIdentity().toDisplayString(); + + tokenPayload.userIdentity().toDisplayString(); throw PwmUnrecoverableException.newException( PwmError.ERROR_TOKEN_INCORRECT, errorMsg ); } } @@ -186,7 +186,7 @@ public static TokenPayload checkEnteredCode( if ( tokenDestinationItem != null ) { final String currentTokenDest = tokenDestinationItem.getValue(); - final TokenDestinationItem payloadTokenDest = tokenPayload.getDestination(); + final TokenDestinationItem payloadTokenDest = tokenPayload.destination(); if ( payloadTokenDest != null && !StringUtil.nullSafeEquals( currentTokenDest, payloadTokenDest.getValue() ) ) { final String errorMsg = "token is for destination '" + currentTokenDest diff --git a/server/src/main/java/password/pwm/svc/userhistory/DatabaseUserHistory.java b/server/src/main/java/password/pwm/svc/userhistory/DatabaseUserHistory.java index d064392602..257e82a277 100644 --- a/server/src/main/java/password/pwm/svc/userhistory/DatabaseUserHistory.java +++ b/server/src/main/java/password/pwm/svc/userhistory/DatabaseUserHistory.java @@ -64,14 +64,14 @@ public void updateUserHistory( final SessionLabel sessionLabel, final UserAuditR { // user info final UserIdentity userIdentity; - if ( auditRecord instanceof HelpdeskAuditRecord && auditRecord.getType() == AuditEventType.HELPDESK ) + if ( auditRecord instanceof HelpdeskAuditRecord && auditRecord.type() == AuditEventType.HELPDESK ) { final HelpdeskAuditRecord helpdeskAuditRecord = ( HelpdeskAuditRecord ) auditRecord; - userIdentity = UserIdentity.create( helpdeskAuditRecord.getTargetDN(), helpdeskAuditRecord.getTargetLdapProfile(), auditRecord.getDomain() ); + userIdentity = UserIdentity.create( helpdeskAuditRecord.targetDN(), helpdeskAuditRecord.targetLdapProfile(), auditRecord.domain() ); } else { - userIdentity = UserIdentity.create( auditRecord.getPerpetratorDN(), auditRecord.getPerpetratorLdapProfile(), auditRecord.getDomain() ); + userIdentity = UserIdentity.create( auditRecord.perpetratorDN(), auditRecord.perpetratorLdapProfile(), auditRecord.domain() ); } final String guid = LdapOperationsHelper.readLdapGuidValue( pwmDomain, sessionLabel, userIdentity ) diff --git a/server/src/main/java/password/pwm/svc/userhistory/LdapXmlUserHistory.java b/server/src/main/java/password/pwm/svc/userhistory/LdapXmlUserHistory.java index 1425cd7d4e..5f22d60743 100644 --- a/server/src/main/java/password/pwm/svc/userhistory/LdapXmlUserHistory.java +++ b/server/src/main/java/password/pwm/svc/userhistory/LdapXmlUserHistory.java @@ -102,14 +102,14 @@ private void updateUserHistoryImpl( final SessionLabel sessionLabel, final UserA { // user info final UserIdentity userIdentity; - if ( auditRecord instanceof HelpdeskAuditRecord && auditRecord.getType() == AuditEventType.HELPDESK ) + if ( auditRecord instanceof HelpdeskAuditRecord && auditRecord.type() == AuditEventType.HELPDESK ) { final HelpdeskAuditRecord helpdeskAuditRecord = ( HelpdeskAuditRecord ) auditRecord; - userIdentity = UserIdentity.create( helpdeskAuditRecord.getTargetDN(), helpdeskAuditRecord.getTargetLdapProfile(), auditRecord.getDomain() ); + userIdentity = UserIdentity.create( helpdeskAuditRecord.targetDN(), helpdeskAuditRecord.targetLdapProfile(), auditRecord.domain() ); } else { - userIdentity = UserIdentity.create( auditRecord.getPerpetratorDN(), auditRecord.getPerpetratorLdapProfile(), auditRecord.getDomain() ); + userIdentity = UserIdentity.create( auditRecord.perpetratorDN(), auditRecord.perpetratorLdapProfile(), auditRecord.domain() ); } final ChaiUser theUser = pwmDomain.getProxiedChaiUser( sessionLabel, userIdentity ); @@ -349,11 +349,11 @@ public static class StoredEvent public static StoredEvent fromAuditRecord( final UserAuditRecord auditRecord ) { return new StoredEvent( - auditRecord.getEventCode(), - auditRecord.getTimestamp().toEpochMilli(), - auditRecord.getMessage(), - auditRecord.getSourceAddress(), - auditRecord.getSourceHost() + auditRecord.eventCode(), + auditRecord.timestamp().toEpochMilli(), + auditRecord.message(), + auditRecord.sourceAddress(), + auditRecord.sourceHost() ); } } diff --git a/server/src/main/java/password/pwm/svc/userhistory/UserHistoryService.java b/server/src/main/java/password/pwm/svc/userhistory/UserHistoryService.java index fd3cee7529..fa886351e1 100644 --- a/server/src/main/java/password/pwm/svc/userhistory/UserHistoryService.java +++ b/server/src/main/java/password/pwm/svc/userhistory/UserHistoryService.java @@ -177,9 +177,9 @@ public void write( final SessionLabel sessionLabel, final UserAuditRecord auditR throws PwmUnrecoverableException { // add to user history record - if ( settings.getUserStoredEvents().contains( auditRecord.getEventCode() ) ) + if ( settings.getUserStoredEvents().contains( auditRecord.eventCode() ) ) { - final String perpetratorDN = auditRecord.getPerpetratorDN(); + final String perpetratorDN = auditRecord.perpetratorDN(); if ( StringUtil.notEmpty( perpetratorDN ) ) { userHistoryStore.updateUserHistory( sessionLabel, auditRecord ); diff --git a/server/src/main/java/password/pwm/user/UserInfoBean.java b/server/src/main/java/password/pwm/user/UserInfoBean.java index ec023e7e91..b4a0c62605 100644 --- a/server/src/main/java/password/pwm/user/UserInfoBean.java +++ b/server/src/main/java/password/pwm/user/UserInfoBean.java @@ -75,7 +75,7 @@ public class UserInfoBean implements UserInfo private final Map profileIDs; @Builder.Default - private final PasswordStatus passwordStatus = PasswordStatus.builder().build(); + private final PasswordStatus passwordStatus = new PasswordStatus( false, false, false, false ); @Builder.Default private final PwmPasswordPolicy passwordPolicy = PwmPasswordPolicy.defaultPolicy(); diff --git a/server/src/main/java/password/pwm/util/BasicAuthInfo.java b/server/src/main/java/password/pwm/util/BasicAuthInfo.java index 19fee81591..058d034f05 100644 --- a/server/src/main/java/password/pwm/util/BasicAuthInfo.java +++ b/server/src/main/java/password/pwm/util/BasicAuthInfo.java @@ -20,7 +20,6 @@ package password.pwm.util; -import lombok.Value; import password.pwm.AppProperty; import password.pwm.PwmConstants; import password.pwm.PwmDomain; @@ -39,20 +38,20 @@ * * @author Jason D. Rivard */ -@Value -public class BasicAuthInfo +public record BasicAuthInfo( + String username, + PasswordData password +) { private static final PwmLogger LOGGER = PwmLogger.forClass( BasicAuthInfo.class ); - private final String username; - private final PasswordData password; - /** * Extracts the basic auth info from the header. * * @param pwmDomain a reference to the application * @param pwmRequest http servlet request - * @return a BasicAuthInfo object containing username/password, or null if the "Authorization" header doesn't exist or is malformed + * @return a BasicAuthInfo object containing username/password, or null if the "Authorization" header doesn't + * exist or is malformed */ public static Optional parseAuthHeader( final PwmDomain pwmDomain, @@ -129,9 +128,9 @@ public String toAuthHeader( ) throws PwmUnrecoverableException { final StringBuilder sb = new StringBuilder(); - sb.append( this.getUsername() ); + sb.append( this.username() ); sb.append( ':' ); - sb.append( this.getPassword().getStringValue() ); + sb.append( this.password().getStringValue() ); sb.replace( 0, sb.length(), StringUtil.base64Encode( sb.toString().getBytes( PwmConstants.DEFAULT_CHARSET ) ) ); diff --git a/server/src/main/java/password/pwm/util/DailySummaryJob.java b/server/src/main/java/password/pwm/util/DailySummaryJob.java index dd71bf7ddd..3d16326bd3 100644 --- a/server/src/main/java/password/pwm/util/DailySummaryJob.java +++ b/server/src/main/java/password/pwm/util/DailySummaryJob.java @@ -20,8 +20,6 @@ package password.pwm.util; -import lombok.Builder; -import lombok.Value; import org.apache.commons.text.WordUtils; import password.pwm.AppProperty; import password.pwm.PwmApplication; @@ -60,25 +58,22 @@ public DailySummaryJob( final PwmApplication pwmDomain ) this.pwmApplication = pwmDomain; } - @Value - @Builder - static class DailySummaryJobSettings + record DailySummaryJobSettings( + boolean reportingEnableDailyJob, + boolean dailySummaryJobsEnabled, + List toAddress, + String fromAddress, + String siteUrl + ) { - private final boolean reportingEnableDailyJob; - private final boolean dailySummaryJobsEnabled; - private final List toAddress; - private final String fromAddress; - private final String siteUrl; - static DailySummaryJobSettings fromConfig( final DomainConfig config ) { - return DailySummaryJobSettings.builder() - .dailySummaryJobsEnabled( config.getAppConfig().readSettingAsBoolean( PwmSetting.EVENTS_ALERT_DAILY_SUMMARY ) ) - .toAddress( config.getAppConfig().readSettingAsStringArray( PwmSetting.AUDIT_EMAIL_SYSTEM_TO ) ) - .fromAddress( config.getAppConfig().readAppProperty( AppProperty.AUDIT_EVENTS_EMAILFROM ) ) - .siteUrl( config.getAppConfig().readSettingAsString( PwmSetting.PWM_SITE_URL ) ) - .reportingEnableDailyJob( config.getAppConfig().readSettingAsBoolean( PwmSetting.REPORTING_ENABLE_DAILY_JOB ) ) - .build(); + return new DailySummaryJobSettings( + config.getAppConfig().readSettingAsBoolean( PwmSetting.REPORTING_ENABLE_DAILY_JOB ), + config.getAppConfig().readSettingAsBoolean( PwmSetting.EVENTS_ALERT_DAILY_SUMMARY ), + config.getAppConfig().readSettingAsStringArray( PwmSetting.AUDIT_EMAIL_SYSTEM_TO ), + config.getAppConfig().readAppProperty( AppProperty.AUDIT_EVENTS_EMAILFROM ), + config.getAppConfig().readSettingAsString( PwmSetting.PWM_SITE_URL ) ); } } @@ -96,12 +91,12 @@ public void run() { LOGGER.error( () -> "error while generating daily alert statistics: " + e.getMessage() ); } - } + } } private static void alertDailyStats( - final PwmDomain pwmDomain, - final DailySummaryJobSettings settings + final PwmDomain pwmDomain, + final DailySummaryJobSettings settings ) throws PwmUnrecoverableException { @@ -123,9 +118,9 @@ private static void alertDailyStats( final Locale locale = PwmConstants.DEFAULT_LOCALE; - for ( final String toAddress : settings.getToAddress() ) + for ( final String toAddress : settings.toAddress() ) { - final String fromAddress = settings.getFromAddress(); + final String fromAddress = settings.fromAddress(); final String subject = Display.getLocalizedMessage( locale, Display.Title_Application, pwmDomain.getConfig() ) + " - Daily Summary"; final StringBuilder textBody = new StringBuilder(); final StringBuilder htmlBody = new StringBuilder(); @@ -150,7 +145,7 @@ private static void makeEmailBody( // server info final Map metadata = new LinkedHashMap<>(); metadata.put( "Instance ID", pwmDomain.getPwmApplication().getInstanceID() ); - metadata.put( "Site URL", settings.getSiteUrl() ); + metadata.put( "Site URL", settings.siteUrl() ); metadata.put( "Timestamp", StringUtil.toIsoDate( Instant.now() ) ); metadata.put( "Up Time", PwmTimeUtil.asLongString( TimeDuration.fromCurrent( pwmDomain.getPwmApplication().getStartupTime() ) ) ); @@ -285,8 +280,8 @@ private static boolean checkIfEnabled( final PwmDomain pwmDomain, final DailySum return false; } - final List toAddress = settings.getToAddress(); - final String fromAddress = settings.getFromAddress(); + final List toAddress = settings.toAddress(); + final String fromAddress = settings.fromAddress(); if ( CollectionUtil.isEmpty( toAddress ) || StringUtil.isEmpty( toAddress.get( 0 ) ) ) { @@ -298,7 +293,7 @@ private static boolean checkIfEnabled( final PwmDomain pwmDomain, final DailySum return false; } - return settings.isDailySummaryJobsEnabled(); + return settings.dailySummaryJobsEnabled(); } private static String stripHtmlTags( final String input ) diff --git a/server/src/main/java/password/pwm/util/PropertyConfigurationImporter.java b/server/src/main/java/password/pwm/util/PropertyConfigurationImporter.java index a4279567ee..c6fb642ce2 100644 --- a/server/src/main/java/password/pwm/util/PropertyConfigurationImporter.java +++ b/server/src/main/java/password/pwm/util/PropertyConfigurationImporter.java @@ -136,11 +136,11 @@ public StoredConfiguration readConfiguration( final InputStream propertiesInput // static values modifySetting( modifier, PwmSetting.TEMPLATE_LDAP, null, new StringValue( - inputMap.getOrDefault( PropertyKey.TEMPLATE_LDAP.name( ), PropertyKey.TEMPLATE_LDAP.getDefaultValue() ) ) ); + inputMap.getOrDefault( PropertyKey.TEMPLATE_LDAP.name( ), PropertyKey.TEMPLATE_LDAP.getDefaultValue() ) ) ); if ( inputMap.containsKey( PropertyKey.DISPLAY_THEME.name( ) ) ) { modifySetting( modifier, PwmSetting.PASSWORD_POLICY_SOURCE, null, new StringValue( - inputMap.get( PropertyKey.DISPLAY_THEME.name( ) ) ) ); + inputMap.get( PropertyKey.DISPLAY_THEME.name( ) ) ) ); } modifySetting( modifier, PwmSetting.DISPLAY_HOME_BUTTON, null, BooleanValue.of( false ) ); @@ -274,12 +274,11 @@ private StoredValue makeAdminPermissions( ) final String value = inputMap.get( propertyKey.name() ); if ( StringUtil.notEmpty( value ) ) { - permissions.add( UserPermission.builder() - .type( UserPermissionType.ldapQuery ) - .ldapProfileID( LDAP_PROFILE ) - .ldapQuery( filter ) - .ldapBase( value ) - .build() ); + permissions.add( new UserPermission( + UserPermissionType.ldapQuery, + LDAP_PROFILE, + filter, + value ) ); } } diff --git a/server/src/main/java/password/pwm/util/SampleDataGenerator.java b/server/src/main/java/password/pwm/util/SampleDataGenerator.java index 6964511e5b..971fd40fb4 100644 --- a/server/src/main/java/password/pwm/util/SampleDataGenerator.java +++ b/server/src/main/java/password/pwm/util/SampleDataGenerator.java @@ -29,6 +29,7 @@ import password.pwm.bean.LoginInfoBean; import password.pwm.bean.ProfileID; import password.pwm.bean.ResponseInfoBean; +import password.pwm.bean.SessionLabel; import password.pwm.bean.UserIdentity; import password.pwm.config.AppConfig; import password.pwm.config.PwmSetting; @@ -171,12 +172,12 @@ public static MacroRequest sampleMacroRequest( final Path applicationPath ) loginInfoBean.setUserIdentity( userInfoBean.getUserIdentity() ); loginInfoBean.setUserCurrentPassword( PasswordData.forStringValue( "PaSSw0rd" ) ); - return MacroRequest.builder() - .pwmApplication( makeSamplePwmApp( applicationPath ) ) - .userInfo( userInfoBean ) - .targetUserInfo( targetUserInfoBean ) - .loginInfoBean( loginInfoBean ) - .build(); + return MacroRequest.forTargetUser( + makeSamplePwmApp( applicationPath ), + SessionLabel.TEST_SESSION_LABEL, + userInfoBean, + loginInfoBean, + targetUserInfoBean ); } diff --git a/server/src/main/java/password/pwm/util/Validator.java b/server/src/main/java/password/pwm/util/Validator.java index 6e6eb6dad0..2a572534fa 100644 --- a/server/src/main/java/password/pwm/util/Validator.java +++ b/server/src/main/java/password/pwm/util/Validator.java @@ -64,11 +64,15 @@ public static void validatePwmFormID( final PwmRequest pwmRequest ) ); if ( formNonce == null ) { - throw new PwmUnrecoverableException( new ErrorInformation( PwmError.ERROR_INVALID_FORMID, "form nonce missing" ) ); + throw new PwmUnrecoverableException( new ErrorInformation( PwmError.ERROR_INVALID_FORMID, + "form nonce missing on request to " + + pwmRequest.getURLwithQueryString() ) ); } if ( !pwmSession.getLoginInfoBean().getGuid().equals( formNonce.getSessionGUID() ) ) { - throw new PwmUnrecoverableException( new ErrorInformation( PwmError.ERROR_INVALID_FORMID, "form nonce incorrect" ) ); + throw new PwmUnrecoverableException( new ErrorInformation( PwmError.ERROR_INVALID_FORMID, + "form nonce incorrect on request to " + + pwmRequest.getURLwithQueryString() ) ); } } } diff --git a/server/src/main/java/password/pwm/util/cli/commands/LdapSchemaExtendCommand.java b/server/src/main/java/password/pwm/util/cli/commands/LdapSchemaExtendCommand.java index cf5379eff2..a8c2051318 100644 --- a/server/src/main/java/password/pwm/util/cli/commands/LdapSchemaExtendCommand.java +++ b/server/src/main/java/password/pwm/util/cli/commands/LdapSchemaExtendCommand.java @@ -120,8 +120,8 @@ private void doSchemaExtension( final String ldapUrl, final String bindDN, final out( "beginning extension check" ); final SchemaOperationResult operationResult = SchemaManager.extendSchema( chaiProvider ); - out( operationResult.getOperationLog() ); - final boolean checkOk = operationResult.isSuccess(); + out( operationResult.operationLog() ); + final boolean checkOk = operationResult.success(); if ( checkOk ) { diff --git a/server/src/main/java/password/pwm/util/cli/commands/TokenInfoCommand.java b/server/src/main/java/password/pwm/util/cli/commands/TokenInfoCommand.java index c3d389fec8..bb1d54ec39 100644 --- a/server/src/main/java/password/pwm/util/cli/commands/TokenInfoCommand.java +++ b/server/src/main/java/password/pwm/util/cli/commands/TokenInfoCommand.java @@ -70,13 +70,13 @@ else if ( tokenPayload == null ) } else { - out( " name: " + tokenPayload.getName() ); - out( " user: " + tokenPayload.getUserIdentity() ); - out( "issued: " + StringUtil.toIsoDate( tokenPayload.getIssueTime() ) ); - out( "expire: " + StringUtil.toIsoDate( tokenPayload.getExpiration() ) ); - for ( final String key : tokenPayload.getData().keySet() ) + out( " name: " + tokenPayload.name() ); + out( " user: " + tokenPayload.userIdentity() ); + out( "issued: " + StringUtil.toIsoDate( tokenPayload.issueTime() ) ); + out( "expire: " + StringUtil.toIsoDate( tokenPayload.expiration() ) ); + for ( final String key : tokenPayload.data().keySet() ) { - final String value = tokenPayload.getData().get( key ); + final String value = tokenPayload.data().get( key ); out( " payload key: " + key ); out( " value: " + value ); } diff --git a/server/src/main/java/password/pwm/util/debug/LDAPPermissionItemGenerator.java b/server/src/main/java/password/pwm/util/debug/LDAPPermissionItemGenerator.java index b04715e6f5..48be32d892 100644 --- a/server/src/main/java/password/pwm/util/debug/LDAPPermissionItemGenerator.java +++ b/server/src/main/java/password/pwm/util/debug/LDAPPermissionItemGenerator.java @@ -62,11 +62,11 @@ public void outputItem( final DomainDebugItemRequest debugItemInput, final Outpu for ( final LdapPermissionCalculator.PermissionRecord record : ldapPermissionCalculator.getPermissionRecords() ) { final List dataRow = new ArrayList<>(); - dataRow.add( record.getAttribute() ); - dataRow.add( record.getActor() == null ? "" : record.getActor().toString() ); - dataRow.add( record.getAccess() == null ? "" : record.getAccess().toString() ); - dataRow.add( record.getPwmSetting() == null ? "" : record.getPwmSetting().getKey() ); - dataRow.add( record.getProfile() == null ? "" : record.getProfile().stringValue() ); + dataRow.add( record.attribute() ); + dataRow.add( record.actor() == null ? "" : record.actor().toString() ); + dataRow.add( record.access() == null ? "" : record.access().toString() ); + dataRow.add( record.pwmSetting() == null ? "" : record.pwmSetting().getKey() ); + dataRow.add( record.profile() == null ? "" : record.profile().stringValue() ); csvPrinter.printRecord( dataRow ); } csvPrinter.flush(); diff --git a/server/src/main/java/password/pwm/util/debug/ThreadDumpDebugItemGenerator.java b/server/src/main/java/password/pwm/util/debug/ThreadDumpDebugItemGenerator.java index 46bd277bcd..4ef351dfe5 100644 --- a/server/src/main/java/password/pwm/util/debug/ThreadDumpDebugItemGenerator.java +++ b/server/src/main/java/password/pwm/util/debug/ThreadDumpDebugItemGenerator.java @@ -39,7 +39,11 @@ public String getFilename() public void outputItem( final AppDebugItemRequest debugItemInput, final OutputStream outputStream ) throws IOException { - final ThreadInfo[] threads = ManagementFactory.getThreadMXBean().dumpAllThreads( true, true ); + final ThreadInfo[] threads = ManagementFactory.getThreadMXBean().dumpAllThreads( + true, + true, + Integer.MAX_VALUE ); + for ( final ThreadInfo threadInfo : threads ) { DebugGenerator.writeString( outputStream, JavaHelper.threadInfoToString( threadInfo ) + '\n' ); diff --git a/server/src/main/java/password/pwm/util/json/GsonJsonAdaptors.java b/server/src/main/java/password/pwm/util/json/GsonJsonAdaptors.java index 9730551b00..4e90db94c0 100644 --- a/server/src/main/java/password/pwm/util/json/GsonJsonAdaptors.java +++ b/server/src/main/java/password/pwm/util/json/GsonJsonAdaptors.java @@ -268,6 +268,10 @@ private static class ProfileIDTypeAdaptor implements JsonSerializer, public ProfileID deserialize( final JsonElement json, final Type typeOfT, final JsonDeserializationContext context ) throws JsonParseException { final String sValue = json.getAsString(); + if ( StringUtil.isEmpty( sValue ) ) + { + return null; + } return ProfileID.create( sValue ); } diff --git a/server/src/main/java/password/pwm/util/localdb/XodusLocalDB.java b/server/src/main/java/password/pwm/util/localdb/XodusLocalDB.java index 97b422b26a..f01d03c3f6 100644 --- a/server/src/main/java/password/pwm/util/localdb/XodusLocalDB.java +++ b/server/src/main/java/password/pwm/util/localdb/XodusLocalDB.java @@ -37,7 +37,6 @@ import password.pwm.PwmConstants; import password.pwm.error.ErrorInformation; import password.pwm.error.PwmError; -import password.pwm.util.java.ConditionalTaskExecutor; import password.pwm.util.java.StringUtil; import password.pwm.util.java.TimeDuration; import password.pwm.util.json.JsonFactory; @@ -67,7 +66,6 @@ public class XodusLocalDB implements LocalDBProvider { private static final PwmLogger LOGGER = PwmLogger.forClass( XodusLocalDB.class ); - private static final TimeDuration STATS_OUTPUT_INTERVAL = TimeDuration.DAY; private static final String FILE_SUB_PATH = "xodus"; private static final String README_FILENAME = "README.TXT"; @@ -99,12 +97,6 @@ public String getKeyName( ) private final Map cachedStoreObjects = new EnumMap<>( LocalDB.DB.class ); - private final ConditionalTaskExecutor outputLogExecutor = ConditionalTaskExecutor.forPeriodicTask( - this::outputStats, - STATS_OUTPUT_INTERVAL.asDuration(), - TimeDuration.MINUTE.asDuration() - ); - private BindMachine bindMachine = new BindMachine( BindMachine.DEFAULT_ENABLE_COMPRESSION, BindMachine.DEFAULT_MIN_COMPRESSION_LENGTH ); @@ -183,11 +175,14 @@ public void init( } outputReadme( dbDirectory.resolve( FILE_SUB_PATH ).resolve( README_FILENAME ) ); + + outputStats(); } @Override public void close( ) throws LocalDBException { + outputStats(); final Instant startTime = Instant.now(); if ( environment != null && environment.isOpen() ) { @@ -385,7 +380,6 @@ public void putAll( final LocalDB.DB db, final Map keyValueMap ) store.put( transaction, k, v ); } } ); - outputLogExecutor.conditionallyExecuteTask(); } @@ -506,8 +500,6 @@ private void checkStatus( final boolean writeOperation ) throws LocalDBException { throw new LocalDBException( new ErrorInformation( PwmError.ERROR_LOCALDB_UNAVAILABLE, "cannot perform operation, localdb is in read-only mode" ) ); } - - outputLogExecutor.conditionallyExecuteTask(); } private void outputStats( ) diff --git a/server/src/main/java/password/pwm/util/macro/ExternalRestMacro.java b/server/src/main/java/password/pwm/util/macro/ExternalRestMacro.java index 261cf1c07c..ead177333a 100644 --- a/server/src/main/java/password/pwm/util/macro/ExternalRestMacro.java +++ b/server/src/main/java/password/pwm/util/macro/ExternalRestMacro.java @@ -66,8 +66,8 @@ public String replaceValue( final MacroRequest macroRequestInfo ) { - final PwmApplication pwmApplication = macroRequestInfo.getPwmApplication(); - final UserInfo userInfoBean = macroRequestInfo.getUserInfo(); + final PwmApplication pwmApplication = macroRequestInfo.pwmApplication(); + final UserInfo userInfoBean = macroRequestInfo.userInfo(); final String inputString = matchValue.substring( 11, matchValue.length() - 1 ); final Map sendData = new HashMap<>(); @@ -80,8 +80,8 @@ public String replaceValue( { final MacroRequest macroRequest = MacroRequest.forUser( pwmApplication, - macroRequestInfo.getUserLocale(), - macroRequestInfo.getSessionLabel(), + macroRequestInfo.userLocale(), + macroRequestInfo.sessionLabel(), userInfoBean.getUserIdentity() ); final PublicUserInfoBean publicUserInfoBean = UserInfoBean.toPublicUserInfoBean( @@ -97,7 +97,7 @@ public String replaceValue( final String requestBody = JsonFactory.get().serializeMap( sendData ); final String responseBody = RestClientHelper.makeOutboundRestWSCall( pwmDomain, - macroRequestInfo.getSessionLabel(), + macroRequestInfo.sessionLabel(), PwmConstants.DEFAULT_LOCALE, url, requestBody ); diff --git a/server/src/main/java/password/pwm/util/macro/MacroMachine.java b/server/src/main/java/password/pwm/util/macro/MacroMachine.java index b4b5a3f7de..67a3a8cfb7 100644 --- a/server/src/main/java/password/pwm/util/macro/MacroMachine.java +++ b/server/src/main/java/password/pwm/util/macro/MacroMachine.java @@ -126,12 +126,12 @@ public static String expandMacros( //First the User macros if ( scopes.contains( Macro.Scope.User ) ) { - if ( macroRequest.getPwmApplication() != null - && macroRequest.getUserInfo() != null - && macroRequest.getUserInfo().getUserIdentity() != null ) + if ( macroRequest.pwmApplication() != null + && macroRequest.userInfo() != null + && macroRequest.userInfo().getUserIdentity() != null ) { - final DomainID domainID = macroRequest.getUserInfo().getUserIdentity().getDomainID(); - final PwmDomain pwmDomain = macroRequest.getPwmApplication().domains().get( domainID ); + final DomainID domainID = macroRequest.userInfo().getUserIdentity().getDomainID(); + final PwmDomain pwmDomain = macroRequest.pwmApplication().domains().get( domainID ); macroImplementations.putAll( makeExternalImplementations( pwmDomain ) ); } } @@ -174,7 +174,7 @@ private static void doRequest( if ( replaceWorkData.getWorkingString().equals( replaceWorkData.getOriginalString() ) ) { LOGGER.warn( - replaceWorkData.getMacroRequestInfo().getSessionLabel(), + replaceWorkData.getMacroRequestInfo().sessionLabel(), () -> "macro replace was called but input string was not modified. " + " macro=" + pwmMacro.getClass().getName() + ", pattern=" + pwmMacro.getRegExPattern().toString() ); break; @@ -191,8 +191,8 @@ private static String doReplace( final MacroRequest macroRequestInfo ) { - final SessionLabel sessionLabel = macroRequestInfo.getSessionLabel(); - final PwmApplication pwmApplication = macroRequestInfo.getPwmApplication(); + final SessionLabel sessionLabel = macroRequestInfo.sessionLabel(); + final PwmApplication pwmApplication = macroRequestInfo.pwmApplication(); final Instant startTime = Instant.now(); final String matchedStr = matcher.group(); final int startPos = matcher.start(); @@ -208,7 +208,7 @@ private static String doReplace( LOGGER.debug( sessionLabel, () -> "macro parse error replacing macro '" + matchedStr + "', error: " + e.getMessage() ); if ( pwmApplication != null ) { - replaceStr = "[" + e.getErrorInformation().toUserStr( PwmConstants.DEFAULT_LOCALE, macroRequestInfo.getPwmApplication().getConfig() ) + "]"; + replaceStr = "[" + e.getErrorInformation().toUserStr( PwmConstants.DEFAULT_LOCALE, macroRequestInfo.pwmApplication().getConfig() ) + "]"; } else { @@ -225,7 +225,7 @@ private static String doReplace( return input; } - final MacroReplacer macroReplacer = macroRequestInfo.getMacroReplacer(); + final MacroReplacer macroReplacer = macroRequestInfo.macroReplacer(); if ( macroReplacer != null ) { try @@ -259,7 +259,7 @@ private static Set effectiveScopesForRequest( final MacroRequest ma final Set scopes = EnumSet.noneOf( Macro.Scope.class ); scopes.add( Macro.Scope.Static ); - final PwmApplication pwmApplication = macroRequestInfo.getPwmApplication(); + final PwmApplication pwmApplication = macroRequestInfo.pwmApplication(); final PwmApplicationMode mode = pwmApplication != null ? pwmApplication.getApplicationMode() : PwmApplicationMode.ERROR; if ( @@ -270,12 +270,12 @@ private static Set effectiveScopesForRequest( final MacroRequest ma scopes.add( Macro.Scope.System ); } - if ( macroRequestInfo.getUserInfo() != null ) + if ( macroRequestInfo.userInfo() != null ) { scopes.add( Macro.Scope.User ); } - if ( macroRequestInfo.getTargetUserInfo() != null ) + if ( macroRequestInfo.targetUserInfo() != null ) { scopes.add( Macro.Scope.TargetUser ); } diff --git a/server/src/main/java/password/pwm/util/macro/MacroRequest.java b/server/src/main/java/password/pwm/util/macro/MacroRequest.java index 3e4652616a..1c2a78b2af 100644 --- a/server/src/main/java/password/pwm/util/macro/MacroRequest.java +++ b/server/src/main/java/password/pwm/util/macro/MacroRequest.java @@ -20,34 +20,30 @@ package password.pwm.util.macro; -import lombok.Builder; -import lombok.Value; import password.pwm.PwmApplication; import password.pwm.PwmConstants; import password.pwm.bean.LoginInfoBean; import password.pwm.bean.SessionLabel; import password.pwm.bean.UserIdentity; import password.pwm.error.PwmUnrecoverableException; -import password.pwm.http.PwmRequestContext; import password.pwm.http.PwmRequest; -import password.pwm.user.UserInfo; +import password.pwm.http.PwmRequestContext; import password.pwm.ldap.UserInfoFactory; +import password.pwm.user.UserInfo; import java.util.Locale; -@Value -@Builder( toBuilder = true ) -public class MacroRequest +public record MacroRequest( + PwmApplication pwmApplication, + SessionLabel sessionLabel, + UserInfo userInfo, + LoginInfoBean loginInfoBean, + MacroReplacer macroReplacer, + UserInfo targetUserInfo, + Locale userLocale +) { - private final PwmApplication pwmApplication; - private final SessionLabel sessionLabel; - private final UserInfo userInfo; - private final LoginInfoBean loginInfoBean; - private final MacroReplacer macroReplacer; - private final UserInfo targetUserInfo; - private final Locale userLocale; - - private MacroRequest( + public MacroRequest( final PwmApplication pwmApplication, final SessionLabel sessionLabel, final UserInfo userInfo, @@ -109,6 +105,17 @@ public static MacroRequest forUser( return new MacroRequest( pwmApplication, sessionLabel, userInfo, loginInfoBean, null, null, null ); } + public static MacroRequest forTargetUser( + final PwmApplication pwmApplication, + final SessionLabel sessionLabel, + final UserInfo userInfo, + final LoginInfoBean loginInfoBean, + final UserInfo targetUserInfo + ) + { + return new MacroRequest( pwmApplication, sessionLabel, userInfo, loginInfoBean, null, targetUserInfo, null ); + } + public static MacroRequest forUser( final PwmApplication pwmApplication, final SessionLabel sessionLabel, @@ -132,6 +139,7 @@ public static MacroRequest forUser( return new MacroRequest( pwmApplication, sessionLabel, userInfoBean, null, null, null, userLocale ); } + public static MacroRequest forUser( final PwmApplication pwmApplication, final Locale userLocale, @@ -157,5 +165,4 @@ public String expandMacros( final String input ) { return MacroMachine.expandMacros( this, input ); } - } diff --git a/server/src/main/java/password/pwm/util/macro/SystemMacros.java b/server/src/main/java/password/pwm/util/macro/SystemMacros.java index 4c901cf727..21b8319220 100644 --- a/server/src/main/java/password/pwm/util/macro/SystemMacros.java +++ b/server/src/main/java/password/pwm/util/macro/SystemMacros.java @@ -31,8 +31,7 @@ import password.pwm.util.logging.PwmLogger; import password.pwm.util.secure.PwmRandom; -import java.net.MalformedURLException; -import java.net.URL; +import java.net.URI; import java.time.Instant; import java.time.ZoneOffset; import java.time.format.DateTimeFormatter; @@ -82,11 +81,11 @@ public String replaceValue( final MacroRequest request ) { - final PwmApplication pwmApplication = request.getPwmApplication(); + final PwmApplication pwmApplication = request.pwmApplication(); if ( pwmApplication == null ) { - LOGGER.error( request.getSessionLabel(), () -> "could not replace value for '" + matchValue + "', pwmApplication is null" ); + LOGGER.error( request.sessionLabel(), () -> "could not replace value for '" + matchValue + "', pwmApplication is null" ); return ""; } @@ -192,7 +191,7 @@ public String replaceValue( final MacroRequest request ) { - return request.getPwmApplication().getConfig().readSettingAsString( PwmSetting.PWM_SITE_URL ); + return request.pwmApplication().getConfig().readSettingAsString( PwmSetting.PWM_SITE_URL ); } } @@ -234,11 +233,11 @@ public String replaceValue( { try { - final String siteUrl = request.getPwmApplication().getConfig().readSettingAsString( PwmSetting.PWM_SITE_URL ); - final URL url = new URL( siteUrl ); - return url.getHost(); + final String siteUrl = request.pwmApplication().getConfig().readSettingAsString( PwmSetting.PWM_SITE_URL ); + final URI uri = URI.create( siteUrl ); + return uri.getHost(); } - catch ( final MalformedURLException e ) + catch ( final Exception e ) { LOGGER.error( () -> "unable to parse configured/detected site URL: " + e.getMessage() ); } @@ -272,7 +271,7 @@ public String replaceValue( int length = 1; if ( parameters.size() > 0 && !parameters.get( 0 ).isEmpty() ) { - final int maxLengthPermitted = Integer.parseInt( request.getPwmApplication().getConfig().readAppProperty( AppProperty.MACRO_RANDOM_CHAR_MAX_LENGTH ) ); + final int maxLengthPermitted = Integer.parseInt( request.pwmApplication().getConfig().readAppProperty( AppProperty.MACRO_RANDOM_CHAR_MAX_LENGTH ) ); try { length = Integer.parseInt( parameters.get( 0 ) ); @@ -397,7 +396,7 @@ public String replaceValue( final String matchValue, final MacroRequest request throws MacroParseException { String contextName = "[context]"; - final PwmApplication pwmApplication = request.getPwmApplication(); + final PwmApplication pwmApplication = request.pwmApplication(); if ( pwmApplication != null ) { final PwmEnvironment pwmEnvironment = pwmApplication.getPwmEnvironment(); diff --git a/server/src/main/java/password/pwm/util/macro/UserMacros.java b/server/src/main/java/password/pwm/util/macro/UserMacros.java index 041a742338..ba4e86b28a 100644 --- a/server/src/main/java/password/pwm/util/macro/UserMacros.java +++ b/server/src/main/java/password/pwm/util/macro/UserMacros.java @@ -30,6 +30,7 @@ import password.pwm.config.profile.LdapProfile; import password.pwm.error.PwmUnrecoverableException; import password.pwm.user.UserInfo; +import password.pwm.util.java.JavaHelper; import password.pwm.util.java.StringUtil; import password.pwm.util.java.TimeDuration; import password.pwm.util.logging.PwmLogger; @@ -47,26 +48,11 @@ public class UserMacros { private static final PwmLogger LOGGER = PwmLogger.forClass( UserMacros.class ); - static final List USER_MACROS = List.of( - new UserIDMacro(), - new DomainIdMacro(), - new UserLdapProfileIDMacro(), - new UserLdapProfileNameMacro(), - new UserLdapMacro(), - new UserPwExpirationTimeMacro(), - new UserDaysUntilPwExpireMacro(), - new UserEmailMacro(), - new UserPasswordMacro(), - new OtpSetupTimeMacro(), - new ResponseSetupTimeMacro(), - new DefaultDomainEmailFromAddressMacro(), - - - new TargetUserIDMacro(), - new TargetUserLdapMacro(), - new TargetUserPwExpirationTimeMacro(), - new TargetUserDaysUntilPwExpireMacro(), - new TargetUserEmailMacro() ); + static final List USER_MACROS = JavaHelper.instancesOfSealedInterface( UserMacro.class ); + + sealed interface UserMacro extends Macro + { + } abstract static class AbstractUserMacro extends AbstractMacro { @@ -168,13 +154,13 @@ private String readLdapValue( } catch ( final PwmUnrecoverableException e ) { - LOGGER.trace( macroRequest.getSessionLabel(), () -> "could not replace value for '" + matchValue + "', ldap error: " + e.getMessage() ); + LOGGER.trace( macroRequest.sessionLabel(), () -> "could not replace value for '" + matchValue + "', ldap error: " + e.getMessage() ); return ""; } if ( ldapValue == null || ldapValue.length() < 1 ) { - LOGGER.trace( macroRequest.getSessionLabel(), () -> "could not replace value for '" + matchValue + "', user does not have value for '" + ldapAttr + "'" ); + LOGGER.trace( macroRequest.sessionLabel(), () -> "could not replace value for '" + matchValue + "', user does not have value for '" + ldapAttr + "'" ); return ""; } } @@ -200,8 +186,8 @@ private static int readLengthParam( } final int maxLengthPermitted = Integer.parseInt( - macroRequest.getPwmApplication() != null - ? macroRequest.getPwmApplication().getConfig().readAppProperty( AppProperty.MACRO_LDAP_ATTR_CHAR_MAX_LENGTH ) + macroRequest.pwmApplication() != null + ? macroRequest.pwmApplication().getConfig().readAppProperty( AppProperty.MACRO_LDAP_ATTR_CHAR_MAX_LENGTH ) : AppProperty.MACRO_LDAP_ATTR_CHAR_MAX_LENGTH.getDefaultValue() ); @@ -223,7 +209,7 @@ else if ( length <= 0 ) abstract List ignoreWords(); } - public static class UserLdapMacro extends AbstractUserLdapMacro + public static final class UserLdapMacro extends AbstractUserLdapMacro implements UserMacro { private static final Pattern PATTERN = Pattern.compile( "@(User:LDAP|LDAP)" + PATTERN_OPTIONAL_PARAMETER_MATCH + "@" ); @@ -236,7 +222,7 @@ public Pattern getRegExPattern( ) @Override Optional loadUserInfo( final MacroRequest macroRequest ) { - return Optional.ofNullable( macroRequest.getUserInfo() ); + return Optional.ofNullable( macroRequest.userInfo() ); } @Override @@ -246,7 +232,7 @@ List ignoreWords() } } - public static class TargetUserLdapMacro extends AbstractUserLdapMacro + public static final class TargetUserLdapMacro extends AbstractUserLdapMacro implements UserMacro { private static final Pattern PATTERN = Pattern.compile( "@TargetUser:LDAP" + PATTERN_OPTIONAL_PARAMETER_MATCH + "@" ); @@ -265,7 +251,7 @@ public Scope getScope() @Override Optional loadUserInfo( final MacroRequest macroRequest ) { - return Optional.ofNullable( macroRequest.getTargetUserInfo() ); + return Optional.ofNullable( macroRequest.targetUserInfo() ); } @Override @@ -300,7 +286,7 @@ public String replaceValue( } catch ( final PwmUnrecoverableException e ) { - LOGGER.error( macroRequest.getSessionLabel(), () -> "error reading username during macro replacement: " + e.getMessage() ); + LOGGER.error( macroRequest.sessionLabel(), () -> "error reading username during macro replacement: " + e.getMessage() ); return ""; } } @@ -332,7 +318,7 @@ public String replaceValue( } catch ( final PwmUnrecoverableException e ) { - LOGGER.error( macroRequest.getSessionLabel(), () -> "error reading pwdExpirationTime during macro replacement: " + e.getMessage() ); + LOGGER.error( macroRequest.sessionLabel(), () -> "error reading pwdExpirationTime during macro replacement: " + e.getMessage() ); return ""; } @@ -344,7 +330,7 @@ public String replaceValue( abstract List ignoreWords(); } - public static class UserIDMacro extends AbstractUserIDMacro + public static final class UserIDMacro extends AbstractUserIDMacro implements UserMacro { private static final Pattern PATTERN = Pattern.compile( "@User:ID@" ); @@ -358,11 +344,11 @@ public Pattern getRegExPattern( ) @Override Optional loadUserInfo( final MacroRequest macroRequest ) { - return Optional.ofNullable( macroRequest.getUserInfo() ); + return Optional.ofNullable( macroRequest.userInfo() ); } } - public static class UserLdapProfileIDMacro extends AbstractUserMacro + public static final class UserLdapProfileIDMacro extends AbstractUserMacro implements UserMacro { private static final Pattern PATTERN = Pattern.compile( "@User:LdapProfile:ID@" ); @@ -379,7 +365,7 @@ public String replaceValue( ) { - final UserInfo userInfo = macroRequest.getUserInfo(); + final UserInfo userInfo = macroRequest.userInfo(); if ( userInfo != null ) { @@ -394,7 +380,7 @@ public String replaceValue( } } - public static class DomainIdMacro extends AbstractUserMacro + public static final class DomainIdMacro extends AbstractUserMacro implements UserMacro { private static final Pattern PATTERN = Pattern.compile( "@User:Domain:ID@" ); @@ -411,7 +397,7 @@ public String replaceValue( ) { - final UserInfo userInfo = macroRequest.getUserInfo(); + final UserInfo userInfo = macroRequest.userInfo(); if ( userInfo != null ) { @@ -426,7 +412,7 @@ public String replaceValue( } } - public static class UserLdapProfileNameMacro extends AbstractUserMacro + public static final class UserLdapProfileNameMacro extends AbstractUserMacro implements UserMacro { private static final Pattern PATTERN = Pattern.compile( "@User:LdapProfile:Name@" ); @@ -443,17 +429,17 @@ public String replaceValue( ) { - final UserInfo userInfo = macroRequest.getUserInfo(); + final UserInfo userInfo = macroRequest.userInfo(); if ( userInfo != null ) { final UserIdentity userIdentity = userInfo.getUserIdentity(); if ( userIdentity != null ) { - final LdapProfile ldapProfile = userIdentity.getLdapProfile( macroRequest.getPwmApplication().getConfig() ); + final LdapProfile ldapProfile = userIdentity.getLdapProfile( macroRequest.pwmApplication().getConfig() ); if ( ldapProfile != null ) { - final Locale userLocale = macroRequest.getUserLocale() == null ? PwmConstants.DEFAULT_LOCALE : macroRequest.getUserLocale(); + final Locale userLocale = macroRequest.userLocale() == null ? PwmConstants.DEFAULT_LOCALE : macroRequest.userLocale(); return ldapProfile.getDisplayName( userLocale ); } } @@ -463,7 +449,7 @@ public String replaceValue( } } - public static class TargetUserIDMacro extends AbstractUserIDMacro + public static final class TargetUserIDMacro extends AbstractUserIDMacro implements UserMacro { private static final Pattern PATTERN = Pattern.compile( "@TargetUser:ID@" ); @@ -482,11 +468,11 @@ public Scope getScope() @Override Optional loadUserInfo( final MacroRequest macroRequest ) { - return Optional.ofNullable( macroRequest.getTargetUserInfo() ); + return Optional.ofNullable( macroRequest.targetUserInfo() ); } } - public static class UserPwExpirationTimeMacro extends AbstractUserPwExpirationTimeMacro + public static final class UserPwExpirationTimeMacro extends AbstractUserPwExpirationTimeMacro implements UserMacro { private static final Pattern PATTERN = Pattern.compile( "@User:PwExpireTime" + PATTERN_OPTIONAL_PARAMETER_MATCH + "@" ); @@ -499,7 +485,7 @@ public Pattern getRegExPattern( ) @Override Optional loadUserInfo( final MacroRequest macroRequest ) { - return Optional.ofNullable( macroRequest.getUserInfo() ); + return Optional.ofNullable( macroRequest.userInfo() ); } @Override @@ -509,7 +495,7 @@ List ignoreWords() } } - public static class TargetUserPwExpirationTimeMacro extends AbstractUserPwExpirationTimeMacro + public static final class TargetUserPwExpirationTimeMacro extends AbstractUserPwExpirationTimeMacro implements UserMacro { private static final Pattern PATTERN = Pattern.compile( "@TargetUser:PwExpireTime" + PATTERN_OPTIONAL_PARAMETER_MATCH + "@" ); @@ -528,7 +514,7 @@ public Scope getScope() @Override Optional loadUserInfo( final MacroRequest macroRequest ) { - return Optional.ofNullable( macroRequest.getTargetUserInfo() ); + return Optional.ofNullable( macroRequest.targetUserInfo() ); } @Override @@ -538,7 +524,7 @@ List ignoreWords() } } - public static class UserDaysUntilPwExpireMacro extends AbstractUserDaysUntilPwExpireMacro + public static final class UserDaysUntilPwExpireMacro extends AbstractUserDaysUntilPwExpireMacro implements UserMacro { private static final Pattern PATTERN = Pattern.compile( "@User:DaysUntilPwExpire@" ); @@ -551,11 +537,11 @@ public Pattern getRegExPattern( ) @Override Optional loadUserInfo( final MacroRequest macroRequest ) { - return Optional.ofNullable( macroRequest.getUserInfo() ); + return Optional.ofNullable( macroRequest.userInfo() ); } } - public static class TargetUserDaysUntilPwExpireMacro extends AbstractUserDaysUntilPwExpireMacro + public static final class TargetUserDaysUntilPwExpireMacro extends AbstractUserDaysUntilPwExpireMacro implements UserMacro { private static final Pattern PATTERN = Pattern.compile( "@TargetUser:DaysUntilPwExpire@" ); @@ -574,7 +560,7 @@ public Scope getScope() @Override Optional loadUserInfo( final MacroRequest macroRequest ) { - return Optional.ofNullable( macroRequest.getTargetUserInfo() ); + return Optional.ofNullable( macroRequest.targetUserInfo() ); } } @@ -602,7 +588,7 @@ public String replaceValue( } catch ( final PwmUnrecoverableException e ) { - LOGGER.error( macroRequest.getSessionLabel(), () -> "error reading pwdExpirationTime during macro replacement: " + e.getMessage() ); + LOGGER.error( macroRequest.sessionLabel(), () -> "error reading pwdExpirationTime during macro replacement: " + e.getMessage() ); return ""; } } @@ -637,7 +623,7 @@ public String replaceValue( } catch ( final PwmUnrecoverableException e ) { - LOGGER.error( macroRequest.getSessionLabel(), () -> "error reading user email address during macro replacement: " + e.getMessage() ); + LOGGER.error( macroRequest.sessionLabel(), () -> "error reading user email address during macro replacement: " + e.getMessage() ); return ""; } @@ -647,7 +633,7 @@ public String replaceValue( abstract Optional loadUserInfo( MacroRequest macroRequest ); } - public static class UserEmailMacro extends AbstractUserEmailMacro + public static final class UserEmailMacro extends AbstractUserEmailMacro implements UserMacro { private static final Pattern PATTERN = Pattern.compile( "@User:Email@" ); @@ -660,11 +646,11 @@ public Pattern getRegExPattern( ) @Override Optional loadUserInfo( final MacroRequest macroRequest ) { - return Optional.ofNullable( macroRequest.getUserInfo() ); + return Optional.ofNullable( macroRequest.userInfo() ); } } - public static class TargetUserEmailMacro extends AbstractUserEmailMacro + public static final class TargetUserEmailMacro extends AbstractUserEmailMacro implements UserMacro { private static final Pattern PATTERN = Pattern.compile( "@TargetUser:Email@" ); @@ -683,11 +669,11 @@ public Scope getScope() @Override Optional loadUserInfo( final MacroRequest macroRequest ) { - return Optional.ofNullable( macroRequest.getTargetUserInfo() ); + return Optional.ofNullable( macroRequest.targetUserInfo() ); } } - public static class UserPasswordMacro extends AbstractUserMacro + public static final class UserPasswordMacro extends AbstractUserMacro implements UserMacro { private static final Pattern PATTERN = Pattern.compile( "@User:Password@" ); private static final Set FLAGS = Collections.singleton( MacroDefinitionFlag.SensitiveValue ); @@ -704,7 +690,7 @@ public String replaceValue( final MacroRequest request ) { - final LoginInfoBean loginInfoBean = request.getLoginInfoBean(); + final LoginInfoBean loginInfoBean = request.loginInfoBean(); try { @@ -717,7 +703,7 @@ public String replaceValue( } catch ( final PwmUnrecoverableException e ) { - LOGGER.error( request.getSessionLabel(), () -> "error decrypting in memory password during macro replacement: " + e.getMessage() ); + LOGGER.error( request.sessionLabel(), () -> "error decrypting in memory password during macro replacement: " + e.getMessage() ); return ""; } } @@ -729,7 +715,7 @@ public Set flags( ) } } - public static class DefaultDomainEmailFromAddressMacro extends AbstractUserMacro + public static final class DefaultDomainEmailFromAddressMacro extends AbstractUserMacro implements UserMacro { private static final Pattern PATTERN = Pattern.compile( "@DefaultEmailFromAddress@" ); @@ -746,7 +732,7 @@ public String replaceValue( ) throws MacroParseException { - final UserInfo userInfo = request.getUserInfo(); + final UserInfo userInfo = request.userInfo(); if ( userInfo == null ) { throw new MacroParseException( "[DefaultEmailFromAddress]: userInfo unspecified on macro request" ); @@ -764,7 +750,7 @@ public String replaceValue( throw new MacroParseException( "[DefaultEmailFromAddress]: domain unspecified on macro request" ); } - final PwmDomain pwmDomain = request.getPwmApplication().domains().get( domainID ); + final PwmDomain pwmDomain = request.pwmApplication().domains().get( domainID ); if ( pwmDomain == null ) { throw new MacroParseException( "[DefaultEmailFromAddress]: domain invalid on macro request" ); @@ -774,7 +760,7 @@ public String replaceValue( } } - public static class OtpSetupTimeMacro extends AbstractUserMacro + public static final class OtpSetupTimeMacro extends AbstractUserMacro implements UserMacro { private static final Pattern PATTERN = Pattern.compile( "@OtpSetupTime" + PATTERN_OPTIONAL_PARAMETER_MATCH + "@" ); @@ -791,7 +777,7 @@ public String replaceValue( final String matchValue, final MacroRequest request Instant otpSetupTime = null; try { - final UserInfo userInfo = request.getUserInfo(); + final UserInfo userInfo = request.userInfo(); if ( userInfo != null && userInfo.getOtpUserRecord() != null && userInfo.getOtpUserRecord().getTimestamp() != null ) { otpSetupTime = userInfo.getOtpUserRecord().getTimestamp(); @@ -799,14 +785,14 @@ public String replaceValue( final String matchValue, final MacroRequest request } catch ( final PwmUnrecoverableException e ) { - LOGGER.error( request.getSessionLabel(), () -> "error reading otp setup time during macro replacement: " + e.getMessage() ); + LOGGER.error( request.sessionLabel(), () -> "error reading otp setup time during macro replacement: " + e.getMessage() ); } return processTimeOutputMacro( otpSetupTime, matchValue, Collections.singletonList( "OtpSetupTime" ) ); } } - public static class ResponseSetupTimeMacro extends AbstractUserMacro + public static final class ResponseSetupTimeMacro extends AbstractUserMacro implements UserMacro { private static final Pattern PATTERN = Pattern.compile( "@ResponseSetupTime" + PATTERN_OPTIONAL_PARAMETER_MATCH + "@" ); @@ -823,7 +809,7 @@ public String replaceValue( final String matchValue, final MacroRequest request Instant responseSetupTime = null; try { - final UserInfo userInfo = request.getUserInfo(); + final UserInfo userInfo = request.userInfo(); if ( userInfo != null && userInfo.getResponseInfoBean() != null && userInfo.getResponseInfoBean().getTimestamp() != null ) { responseSetupTime = userInfo.getResponseInfoBean().getTimestamp(); @@ -831,7 +817,7 @@ public String replaceValue( final String matchValue, final MacroRequest request } catch ( final PwmUnrecoverableException e ) { - LOGGER.error( request.getSessionLabel(), () -> "error reading response setup time macro replacement: " + e.getMessage() ); + LOGGER.error( request.sessionLabel(), () -> "error reading response setup time macro replacement: " + e.getMessage() ); } return processTimeOutputMacro( responseSetupTime, matchValue, Collections.singletonList( "ResponseSetupTime" ) ); diff --git a/server/src/main/java/password/pwm/util/password/PasswordUtility.java b/server/src/main/java/password/pwm/util/password/PasswordUtility.java index bbd91e4436..c405e9d5d1 100644 --- a/server/src/main/java/password/pwm/util/password/PasswordUtility.java +++ b/server/src/main/java/password/pwm/util/password/PasswordUtility.java @@ -1465,7 +1465,7 @@ public static boolean isPasswordWithinMinimumLifetimeImpl( return false; } - if ( passwordStatus.isExpired() || passwordStatus.isPreExpired() || passwordStatus.isWarnPeriod() ) + if ( passwordStatus.expired() || passwordStatus.preExpired() || passwordStatus.warnPeriod() ) { LOGGER.debug( sessionLabel, () -> "current password is too young, but skipping enforcement of minimum lifetime check because current password is expired" ); return false; diff --git a/server/src/main/java/password/pwm/util/password/PwmPasswordRuleUtil.java b/server/src/main/java/password/pwm/util/password/PwmPasswordRuleUtil.java index c4f999a322..079b583e34 100644 --- a/server/src/main/java/password/pwm/util/password/PwmPasswordRuleUtil.java +++ b/server/src/main/java/password/pwm/util/password/PwmPasswordRuleUtil.java @@ -23,6 +23,8 @@ import org.apache.commons.lang3.StringUtils; import password.pwm.util.java.StringUtil; +import java.util.List; + class PwmPasswordRuleUtil { private PwmPasswordRuleUtil() @@ -69,7 +71,7 @@ public static boolean containsDisallowedValue( final String password, final Stri { if ( disallowedValue.length() >= threshold ) { - final String[] disallowedValueChunks = StringUtil.createStringChunks( disallowedValue, threshold ); + final List disallowedValueChunks = StringUtil.createStringChunks( disallowedValue, threshold ); for ( final String chunk : disallowedValueChunks ) { if ( StringUtils.containsIgnoreCase( password, chunk ) ) diff --git a/server/src/main/java/password/pwm/util/secure/BCrypt.java b/server/src/main/java/password/pwm/util/secure/BCrypt.java index a991b0fc17..4fea42c8d7 100644 --- a/server/src/main/java/password/pwm/util/secure/BCrypt.java +++ b/server/src/main/java/password/pwm/util/secure/BCrypt.java @@ -20,869 +20,33 @@ package password.pwm.util.secure; -import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import org.bouncycastle.crypto.generators.OpenBSDBCrypt; -import password.pwm.AppProperty; import password.pwm.config.AppConfig; -import password.pwm.util.logging.PwmLogger; -import java.io.UnsupportedEncodingException; -import java.security.SecureRandom; - -@SuppressWarnings( "all" ) -@SuppressFBWarnings() public class BCrypt { - private static final PwmLogger LOGGER = PwmLogger.forClass( BCrypt.class ); + private final AppConfig appConfig; - public static String hashPassword( final String password ) + private BCrypt( final AppConfig appConfig ) { - final int bcryptRounds = 10; - final byte[] salt = new byte[ 16 ]; - ( new SecureRandom() ).nextBytes( salt ); - return OpenBSDBCrypt.generate( password.toLowerCase().toCharArray(), salt, bcryptRounds ); + this.appConfig = appConfig; } - public static boolean testAnswer( final String password, final String hashedPassword, final AppConfig appConfig ) + public static BCrypt createBCrypt( final AppConfig appConfig ) { - final char[] pwCharArray = password.toLowerCase().toCharArray(); - final boolean bsdCryptPass = OpenBSDBCrypt.checkPassword( hashedPassword, pwCharArray ); - if ( !bsdCryptPass ) - { - final boolean enableJBCrypt = Boolean.parseBoolean( appConfig.readAppProperty( AppProperty.CONFIG_JBCRYPT_PWLIB_ENABLE ) ); - if ( enableJBCrypt ) - { - // legacy check, older jbcrypt library used previously incorrectly encoded some characters - // including special ascii characters. - try - { - return JBCrypt.checkpw(password, hashedPassword); - } - catch (Exception e) - { - LOGGER.debug( () -> "error while checking bcypt password: " + e.getMessage() ); - return false; - } - } - } - return bsdCryptPass; + return new BCrypt( appConfig ); } - /** - * BCrypt implements OpenBSD-style Blowfish password hashing using - * the scheme described in "A Future-Adaptable Password Scheme" by - * Niels Provos and David Mazieres. - *

    - * This password hashing system tries to thwart off-line password - * cracking using a computationally-intensive hashing algorithm, - * based on Bruce Schneier's Blowfish cipher. The work factor of - * the algorithm is parameterised, so it can be increased as - * computers read faster. - *

    - * Usage is really simple. To hash a password for the first time, - * call the hashpw method with a random salt, like this: - *

    - * - * String pw_hash = BCrypt.hashpw(plain_password, BCrypt.gensalt());
    - *
    - *

    - * To check whether a plaintext password matches one that has been - * hashed previously, use the checkpw method: - *

    - * - * if (BCrypt.checkpw(candidate_password, stored_hash))
    - *     System.out.println("It matches");
    - * else
    - *     System.out.println("It does not match");
    - *
    - *

    - * The gensalt() method takes an optional parameter (log_rounds) - * that determines the computational complexity of the hashing: - *

    - * - * String strong_salt = BCrypt.gensalt(10)
    - * String stronger_salt = BCrypt.gensalt(12)
    - *
    - *

    - * The amount of work increases exponentially (2**log_rounds), so - * each increment is twice as much work. The default log_rounds is - * 10, and the valid range is 4 to 31. - * - * @author Damien Miller - * @version 0.2 - */ - @SuppressFBWarnings( "INT_BAD_COMPARISON_WITH_NONNEGATIVE_VALUE" ) - private static class JBCrypt + public String hashPassword( final String password ) { - // BCrypt parameters - private static final int GENSALT_DEFAULT_LOG2_ROUNDS = 10; - private static final int BCRYPT_SALT_LEN = 16; - - // Blowfish parameters - private static final int BLOWFISH_NUM_ROUNDS = 16; - - // Initial contents of key schedule - private static final int P_orig[] = { - 0x243f6a88, 0x85a308d3, 0x13198a2e, 0x03707344, - 0xa4093822, 0x299f31d0, 0x082efa98, 0xec4e6c89, - 0x452821e6, 0x38d01377, 0xbe5466cf, 0x34e90c6c, - 0xc0ac29b7, 0xc97c50dd, 0x3f84d5b5, 0xb5470917, - 0x9216d5d9, 0x8979fb1b - }; - private static final int S_orig[] = { - 0xd1310ba6, 0x98dfb5ac, 0x2ffd72db, 0xd01adfb7, - 0xb8e1afed, 0x6a267e96, 0xba7c9045, 0xf12c7f99, - 0x24a19947, 0xb3916cf7, 0x0801f2e2, 0x858efc16, - 0x636920d8, 0x71574e69, 0xa458fea3, 0xf4933d7e, - 0x0d95748f, 0x728eb658, 0x718bcd58, 0x82154aee, - 0x7b54a41d, 0xc25a59b5, 0x9c30d539, 0x2af26013, - 0xc5d1b023, 0x286085f0, 0xca417918, 0xb8db38ef, - 0x8e79dcb0, 0x603a180e, 0x6c9e0e8b, 0xb01e8a3e, - 0xd71577c1, 0xbd314b27, 0x78af2fda, 0x55605c60, - 0xe65525f3, 0xaa55ab94, 0x57489862, 0x63e81440, - 0x55ca396a, 0x2aab10b6, 0xb4cc5c34, 0x1141e8ce, - 0xa15486af, 0x7c72e993, 0xb3ee1411, 0x636fbc2a, - 0x2ba9c55d, 0x741831f6, 0xce5c3e16, 0x9b87931e, - 0xafd6ba33, 0x6c24cf5c, 0x7a325381, 0x28958677, - 0x3b8f4898, 0x6b4bb9af, 0xc4bfe81b, 0x66282193, - 0x61d809cc, 0xfb21a991, 0x487cac60, 0x5dec8032, - 0xef845d5d, 0xe98575b1, 0xdc262302, 0xeb651b88, - 0x23893e81, 0xd396acc5, 0x0f6d6ff3, 0x83f44239, - 0x2e0b4482, 0xa4842004, 0x69c8f04a, 0x9e1f9b5e, - 0x21c66842, 0xf6e96c9a, 0x670c9c61, 0xabd388f0, - 0x6a51a0d2, 0xd8542f68, 0x960fa728, 0xab5133a3, - 0x6eef0b6c, 0x137a3be4, 0xba3bf050, 0x7efb2a98, - 0xa1f1651d, 0x39af0176, 0x66ca593e, 0x82430e88, - 0x8cee8619, 0x456f9fb4, 0x7d84a5c3, 0x3b8b5ebe, - 0xe06f75d8, 0x85c12073, 0x401a449f, 0x56c16aa6, - 0x4ed3aa62, 0x363f7706, 0x1bfedf72, 0x429b023d, - 0x37d0d724, 0xd00a1248, 0xdb0fead3, 0x49f1c09b, - 0x075372c9, 0x80991b7b, 0x25d479d8, 0xf6e8def7, - 0xe3fe501a, 0xb6794c3b, 0x976ce0bd, 0x04c006ba, - 0xc1a94fb6, 0x409f60c4, 0x5e5c9ec2, 0x196a2463, - 0x68fb6faf, 0x3e6c53b5, 0x1339b2eb, 0x3b52ec6f, - 0x6dfc511f, 0x9b30952c, 0xcc814544, 0xaf5ebd09, - 0xbee3d004, 0xde334afd, 0x660f2807, 0x192e4bb3, - 0xc0cba857, 0x45c8740f, 0xd20b5f39, 0xb9d3fbdb, - 0x5579c0bd, 0x1a60320a, 0xd6a100c6, 0x402c7279, - 0x679f25fe, 0xfb1fa3cc, 0x8ea5e9f8, 0xdb3222f8, - 0x3c7516df, 0xfd616b15, 0x2f501ec8, 0xad0552ab, - 0x323db5fa, 0xfd238760, 0x53317b48, 0x3e00df82, - 0x9e5c57bb, 0xca6f8ca0, 0x1a87562e, 0xdf1769db, - 0xd542a8f6, 0x287effc3, 0xac6732c6, 0x8c4f5573, - 0x695b27b0, 0xbbca58c8, 0xe1ffa35d, 0xb8f011a0, - 0x10fa3d98, 0xfd2183b8, 0x4afcb56c, 0x2dd1d35b, - 0x9a53e479, 0xb6f84565, 0xd28e49bc, 0x4bfb9790, - 0xe1ddf2da, 0xa4cb7e33, 0x62fb1341, 0xcee4c6e8, - 0xef20cada, 0x36774c01, 0xd07e9efe, 0x2bf11fb4, - 0x95dbda4d, 0xae909198, 0xeaad8e71, 0x6b93d5a0, - 0xd08ed1d0, 0xafc725e0, 0x8e3c5b2f, 0x8e7594b7, - 0x8ff6e2fb, 0xf2122b64, 0x8888b812, 0x900df01c, - 0x4fad5ea0, 0x688fc31c, 0xd1cff191, 0xb3a8c1ad, - 0x2f2f2218, 0xbe0e1777, 0xea752dfe, 0x8b021fa1, - 0xe5a0cc0f, 0xb56f74e8, 0x18acf3d6, 0xce89e299, - 0xb4a84fe0, 0xfd13e0b7, 0x7cc43b81, 0xd2ada8d9, - 0x165fa266, 0x80957705, 0x93cc7314, 0x211a1477, - 0xe6ad2065, 0x77b5fa86, 0xc75442f5, 0xfb9d35cf, - 0xebcdaf0c, 0x7b3e89a0, 0xd6411bd3, 0xae1e7e49, - 0x00250e2d, 0x2071b35e, 0x226800bb, 0x57b8e0af, - 0x2464369b, 0xf009b91e, 0x5563911d, 0x59dfa6aa, - 0x78c14389, 0xd95a537f, 0x207d5ba2, 0x02e5b9c5, - 0x83260376, 0x6295cfa9, 0x11c81968, 0x4e734a41, - 0xb3472dca, 0x7b14a94a, 0x1b510052, 0x9a532915, - 0xd60f573f, 0xbc9bc6e4, 0x2b60a476, 0x81e67400, - 0x08ba6fb5, 0x571be91f, 0xf296ec6b, 0x2a0dd915, - 0xb6636521, 0xe7b9f9b6, 0xff34052e, 0xc5855664, - 0x53b02d5d, 0xa99f8fa1, 0x08ba4799, 0x6e85076a, - 0x4b7a70e9, 0xb5b32944, 0xdb75092e, 0xc4192623, - 0xad6ea6b0, 0x49a7df7d, 0x9cee60b8, 0x8fedb266, - 0xecaa8c71, 0x699a17ff, 0x5664526c, 0xc2b19ee1, - 0x193602a5, 0x75094c29, 0xa0591340, 0xe4183a3e, - 0x3f54989a, 0x5b429d65, 0x6b8fe4d6, 0x99f73fd6, - 0xa1d29c07, 0xefe830f5, 0x4d2d38e6, 0xf0255dc1, - 0x4cdd2086, 0x8470eb26, 0x6382e9c6, 0x021ecc5e, - 0x09686b3f, 0x3ebaefc9, 0x3c971814, 0x6b6a70a1, - 0x687f3584, 0x52a0e286, 0xb79c5305, 0xaa500737, - 0x3e07841c, 0x7fdeae5c, 0x8e7d44ec, 0x5716f2b8, - 0xb03ada37, 0xf0500c0d, 0xf01c1f04, 0x0200b3ff, - 0xae0cf51a, 0x3cb574b2, 0x25837a58, 0xdc0921bd, - 0xd19113f9, 0x7ca92ff6, 0x94324773, 0x22f54701, - 0x3ae5e581, 0x37c2dadc, 0xc8b57634, 0x9af3dda7, - 0xa9446146, 0x0fd0030e, 0xecc8c73e, 0xa4751e41, - 0xe238cd99, 0x3bea0e2f, 0x3280bba1, 0x183eb331, - 0x4e548b38, 0x4f6db908, 0x6f420d03, 0xf60a04bf, - 0x2cb81290, 0x24977c79, 0x5679b072, 0xbcaf89af, - 0xde9a771f, 0xd9930810, 0xb38bae12, 0xdccf3f2e, - 0x5512721f, 0x2e6b7124, 0x501adde6, 0x9f84cd87, - 0x7a584718, 0x7408da17, 0xbc9f9abc, 0xe94b7d8c, - 0xec7aec3a, 0xdb851dfa, 0x63094366, 0xc464c3d2, - 0xef1c1847, 0x3215d908, 0xdd433b37, 0x24c2ba16, - 0x12a14d43, 0x2a65c451, 0x50940002, 0x133ae4dd, - 0x71dff89e, 0x10314e55, 0x81ac77d6, 0x5f11199b, - 0x043556f1, 0xd7a3c76b, 0x3c11183b, 0x5924a509, - 0xf28fe6ed, 0x97f1fbfa, 0x9ebabf2c, 0x1e153c6e, - 0x86e34570, 0xeae96fb1, 0x860e5e0a, 0x5a3e2ab3, - 0x771fe71c, 0x4e3d06fa, 0x2965dcb9, 0x99e71d0f, - 0x803e89d6, 0x5266c825, 0x2e4cc978, 0x9c10b36a, - 0xc6150eba, 0x94e2ea78, 0xa5fc3c53, 0x1e0a2df4, - 0xf2f74ea7, 0x361d2b3d, 0x1939260f, 0x19c27960, - 0x5223a708, 0xf71312b6, 0xebadfe6e, 0xeac31f66, - 0xe3bc4595, 0xa67bc883, 0xb17f37d1, 0x018cff28, - 0xc332ddef, 0xbe6c5aa5, 0x65582185, 0x68ab9802, - 0xeecea50f, 0xdb2f953b, 0x2aef7dad, 0x5b6e2f84, - 0x1521b628, 0x29076170, 0xecdd4775, 0x619f1510, - 0x13cca830, 0xeb61bd96, 0x0334fe1e, 0xaa0363cf, - 0xb5735c90, 0x4c70a239, 0xd59e9e0b, 0xcbaade14, - 0xeecc86bc, 0x60622ca7, 0x9cab5cab, 0xb2f3846e, - 0x648b1eaf, 0x19bdf0ca, 0xa02369b9, 0x655abb50, - 0x40685a32, 0x3c2ab4b3, 0x319ee9d5, 0xc021b8f7, - 0x9b540b19, 0x875fa099, 0x95f7997e, 0x623d7da8, - 0xf837889a, 0x97e32d77, 0x11ed935f, 0x16681281, - 0x0e358829, 0xc7e61fd6, 0x96dedfa1, 0x7858ba99, - 0x57f584a5, 0x1b227263, 0x9b83c3ff, 0x1ac24696, - 0xcdb30aeb, 0x532e3054, 0x8fd948e4, 0x6dbc3128, - 0x58ebf2ef, 0x34c6ffea, 0xfe28ed61, 0xee7c3c73, - 0x5d4a14d9, 0xe864b7e3, 0x42105d14, 0x203e13e0, - 0x45eee2b6, 0xa3aaabea, 0xdb6c4f15, 0xfacb4fd0, - 0xc742f442, 0xef6abbb5, 0x654f3b1d, 0x41cd2105, - 0xd81e799e, 0x86854dc7, 0xe44b476a, 0x3d816250, - 0xcf62a1f2, 0x5b8d2646, 0xfc8883a0, 0xc1c7b6a3, - 0x7f1524c3, 0x69cb7492, 0x47848a0b, 0x5692b285, - 0x095bbf00, 0xad19489d, 0x1462b174, 0x23820e00, - 0x58428d2a, 0x0c55f5ea, 0x1dadf43e, 0x233f7061, - 0x3372f092, 0x8d937e41, 0xd65fecf1, 0x6c223bdb, - 0x7cde3759, 0xcbee7460, 0x4085f2a7, 0xce77326e, - 0xa6078084, 0x19f8509e, 0xe8efd855, 0x61d99735, - 0xa969a7aa, 0xc50c06c2, 0x5a04abfc, 0x800bcadc, - 0x9e447a2e, 0xc3453484, 0xfdd56705, 0x0e1e9ec9, - 0xdb73dbd3, 0x105588cd, 0x675fda79, 0xe3674340, - 0xc5c43465, 0x713e38d8, 0x3d28f89e, 0xf16dff20, - 0x153e21e7, 0x8fb03d4a, 0xe6e39f2b, 0xdb83adf7, - 0xe93d5a68, 0x948140f7, 0xf64c261c, 0x94692934, - 0x411520f7, 0x7602d4f7, 0xbcf46b2e, 0xd4a20068, - 0xd4082471, 0x3320f46a, 0x43b7d4b7, 0x500061af, - 0x1e39f62e, 0x97244546, 0x14214f74, 0xbf8b8840, - 0x4d95fc1d, 0x96b591af, 0x70f4ddd3, 0x66a02f45, - 0xbfbc09ec, 0x03bd9785, 0x7fac6dd0, 0x31cb8504, - 0x96eb27b3, 0x55fd3941, 0xda2547e6, 0xabca0a9a, - 0x28507825, 0x530429f4, 0x0a2c86da, 0xe9b66dfb, - 0x68dc1462, 0xd7486900, 0x680ec0a4, 0x27a18dee, - 0x4f3ffea2, 0xe887ad8c, 0xb58ce006, 0x7af4d6b6, - 0xaace1e7c, 0xd3375fec, 0xce78a399, 0x406b2a42, - 0x20fe9e35, 0xd9f385b9, 0xee39d7ab, 0x3b124e8b, - 0x1dc9faf7, 0x4b6d1856, 0x26a36631, 0xeae397b2, - 0x3a6efa74, 0xdd5b4332, 0x6841e7f7, 0xca7820fb, - 0xfb0af54e, 0xd8feb397, 0x454056ac, 0xba489527, - 0x55533a3a, 0x20838d87, 0xfe6ba9b7, 0xd096954b, - 0x55a867bc, 0xa1159a58, 0xcca92963, 0x99e1db33, - 0xa62a4a56, 0x3f3125f9, 0x5ef47e1c, 0x9029317c, - 0xfdf8e802, 0x04272f70, 0x80bb155c, 0x05282ce3, - 0x95c11548, 0xe4c66d22, 0x48c1133f, 0xc70f86dc, - 0x07f9c9ee, 0x41041f0f, 0x404779a4, 0x5d886e17, - 0x325f51eb, 0xd59bc0d1, 0xf2bcc18f, 0x41113564, - 0x257b7834, 0x602a9c60, 0xdff8e8a3, 0x1f636c1b, - 0x0e12b4c2, 0x02e1329e, 0xaf664fd1, 0xcad18115, - 0x6b2395e0, 0x333e92e1, 0x3b240b62, 0xeebeb922, - 0x85b2a20e, 0xe6ba0d99, 0xde720c8c, 0x2da2f728, - 0xd0127845, 0x95b794fd, 0x647d0862, 0xe7ccf5f0, - 0x5449a36f, 0x877d48fa, 0xc39dfd27, 0xf33e8d1e, - 0x0a476341, 0x992eff74, 0x3a6f6eab, 0xf4f8fd37, - 0xa812dc60, 0xa1ebddf8, 0x991be14c, 0xdb6e6b0d, - 0xc67b5510, 0x6d672c37, 0x2765d43b, 0xdcd0e804, - 0xf1290dc7, 0xcc00ffa3, 0xb5390f92, 0x690fed0b, - 0x667b9ffb, 0xcedb7d9c, 0xa091cf0b, 0xd9155ea3, - 0xbb132f88, 0x515bad24, 0x7b9479bf, 0x763bd6eb, - 0x37392eb3, 0xcc115979, 0x8026e297, 0xf42e312d, - 0x6842ada7, 0xc66a2b3b, 0x12754ccc, 0x782ef11c, - 0x6a124237, 0xb79251e7, 0x06a1bbe6, 0x4bfb6350, - 0x1a6b1018, 0x11caedfa, 0x3d25bdd8, 0xe2e1c3c9, - 0x44421659, 0x0a121386, 0xd90cec6e, 0xd5abea2a, - 0x64af674e, 0xda86a85f, 0xbebfe988, 0x64e4c3fe, - 0x9dbc8057, 0xf0f7c086, 0x60787bf8, 0x6003604d, - 0xd1fd8346, 0xf6381fb0, 0x7745ae04, 0xd736fccc, - 0x83426b33, 0xf01eab71, 0xb0804187, 0x3c005e5f, - 0x77a057be, 0xbde8ae24, 0x55464299, 0xbf582e61, - 0x4e58f48f, 0xf2ddfda2, 0xf474ef38, 0x8789bdc2, - 0x5366f9c3, 0xc8b38e74, 0xb475f255, 0x46fcd9b9, - 0x7aeb2661, 0x8b1ddf84, 0x846a0e79, 0x915f95e2, - 0x466e598e, 0x20b45770, 0x8cd55591, 0xc902de4c, - 0xb90bace1, 0xbb8205d0, 0x11a86248, 0x7574a99e, - 0xb77f19b6, 0xe0a9dc09, 0x662d09a1, 0xc4324633, - 0xe85a1f02, 0x09f0be8c, 0x4a99a025, 0x1d6efe10, - 0x1ab93d1d, 0x0ba5a4df, 0xa186f20f, 0x2868f169, - 0xdcb7da83, 0x573906fe, 0xa1e2ce9b, 0x4fcd7f52, - 0x50115e01, 0xa70683fa, 0xa002b5c4, 0x0de6d027, - 0x9af88c27, 0x773f8641, 0xc3604c06, 0x61a806b5, - 0xf0177a28, 0xc0f586e0, 0x006058aa, 0x30dc7d62, - 0x11e69ed7, 0x2338ea63, 0x53c2dd94, 0xc2c21634, - 0xbbcbee56, 0x90bcb6de, 0xebfc7da1, 0xce591d76, - 0x6f05e409, 0x4b7c0188, 0x39720a3d, 0x7c927c24, - 0x86e3725f, 0x724d9db9, 0x1ac15bb4, 0xd39eb8fc, - 0xed545578, 0x08fca5b5, 0xd83d7cd3, 0x4dad0fc4, - 0x1e50ef5e, 0xb161e6f8, 0xa28514d9, 0x6c51133c, - 0x6fd5c7e7, 0x56e14ec4, 0x362abfce, 0xddc6c837, - 0xd79a3234, 0x92638212, 0x670efa8e, 0x406000e0, - 0x3a39ce37, 0xd3faf5cf, 0xabc27737, 0x5ac52d1b, - 0x5cb0679e, 0x4fa33742, 0xd3822740, 0x99bc9bbe, - 0xd5118e9d, 0xbf0f7315, 0xd62d1c7e, 0xc700c47b, - 0xb78c1b6b, 0x21a19045, 0xb26eb1be, 0x6a366eb4, - 0x5748ab2f, 0xbc946e79, 0xc6a376d2, 0x6549c2c8, - 0x530ff8ee, 0x468dde7d, 0xd5730a1d, 0x4cd04dc6, - 0x2939bbdb, 0xa9ba4650, 0xac9526e8, 0xbe5ee304, - 0xa1fad5f0, 0x6a2d519a, 0x63ef8ce2, 0x9a86ee22, - 0xc089c2b8, 0x43242ef6, 0xa51e03aa, 0x9cf2d0a4, - 0x83c061ba, 0x9be96a4d, 0x8fe51550, 0xba645bd6, - 0x2826a2f9, 0xa73a3ae1, 0x4ba99586, 0xef5562e9, - 0xc72fefd3, 0xf752f7da, 0x3f046f69, 0x77fa0a59, - 0x80e4a915, 0x87b08601, 0x9b09e6ad, 0x3b3ee593, - 0xe990fd5a, 0x9e34d797, 0x2cf0b7d9, 0x022b8b51, - 0x96d5ac3a, 0x017da67d, 0xd1cf3ed6, 0x7c7d2d28, - 0x1f9f25cf, 0xadf2b89b, 0x5ad6b472, 0x5a88f54c, - 0xe029ac71, 0xe019a5e6, 0x47b0acfd, 0xed93fa9b, - 0xe8d3c48d, 0x283b57cc, 0xf8d56629, 0x79132e28, - 0x785f0191, 0xed756055, 0xf7960e44, 0xe3d35e8c, - 0x15056dd4, 0x88f46dba, 0x03a16125, 0x0564f0bd, - 0xc3eb9e15, 0x3c9057a2, 0x97271aec, 0xa93a072a, - 0x1b3f6d9b, 0x1e6321f5, 0xf59c66fb, 0x26dcf319, - 0x7533d928, 0xb155fdf5, 0x03563482, 0x8aba3cbb, - 0x28517711, 0xc20ad9f8, 0xabcc5167, 0xccad925f, - 0x4de81751, 0x3830dc8e, 0x379d5862, 0x9320f991, - 0xea7a90c2, 0xfb3e7bce, 0x5121ce64, 0x774fbe32, - 0xa8b6e37e, 0xc3293d46, 0x48de5369, 0x6413e680, - 0xa2ae0810, 0xdd6db224, 0x69852dfd, 0x09072166, - 0xb39a460a, 0x6445c0dd, 0x586cdecf, 0x1c20c8ae, - 0x5bbef7dd, 0x1b588d40, 0xccd2017f, 0x6bb4e3bb, - 0xdda26a7e, 0x3a59ff45, 0x3e350a44, 0xbcb4cdd5, - 0x72eacea8, 0xfa6484bb, 0x8d6612ae, 0xbf3c6f47, - 0xd29be463, 0x542f5d9e, 0xaec2771b, 0xf64e6370, - 0x740e0d8d, 0xe75b1357, 0xf8721671, 0xaf537d5d, - 0x4040cb08, 0x4eb4e2cc, 0x34d2466a, 0x0115af84, - 0xe1b00428, 0x95983a1d, 0x06b89fb4, 0xce6ea048, - 0x6f3f3b82, 0x3520ab82, 0x011a1d4b, 0x277227f8, - 0x611560b1, 0xe7933fdc, 0xbb3a792b, 0x344525bd, - 0xa08839e1, 0x51ce794b, 0x2f32c9b7, 0xa01fbac9, - 0xe01cc87e, 0xbcc7d1f6, 0xcf0111c3, 0xa1e8aac7, - 0x1a908749, 0xd44fbd9a, 0xd0dadecb, 0xd50ada38, - 0x0339c32a, 0xc6913667, 0x8df9317c, 0xe0b12b4f, - 0xf79e59b7, 0x43f5bb3a, 0xf2d519ff, 0x27d9459c, - 0xbf97222c, 0x15e6fc2a, 0x0f91fc71, 0x9b941525, - 0xfae59361, 0xceb69ceb, 0xc2a86459, 0x12baa8d1, - 0xb6c1075e, 0xe3056a0c, 0x10d25065, 0xcb03a442, - 0xe0ec6e0e, 0x1698db3b, 0x4c98a0be, 0x3278e964, - 0x9f1f9532, 0xe0d392df, 0xd3a0342b, 0x8971f21e, - 0x1b0a7441, 0x4ba3348c, 0xc5be7120, 0xc37632d8, - 0xdf359f8d, 0x9b992f2e, 0xe60b6f47, 0x0fe3f11d, - 0xe54cda54, 0x1edad891, 0xce6279cf, 0xcd3e7e6f, - 0x1618b166, 0xfd2c1d05, 0x848fd2c5, 0xf6fb2299, - 0xf523f357, 0xa6327623, 0x93a83531, 0x56cccd02, - 0xacf08162, 0x5a75ebb5, 0x6e163697, 0x88d273cc, - 0xde966292, 0x81b949d0, 0x4c50901b, 0x71c65614, - 0xe6c6c7bd, 0x327a140a, 0x45e1d006, 0xc3f27b9a, - 0xc9aa53fd, 0x62a80f00, 0xbb25bfe2, 0x35bdd2f6, - 0x71126905, 0xb2040222, 0xb6cbcf7c, 0xcd769c2b, - 0x53113ec0, 0x1640e3d3, 0x38abbd60, 0x2547adf0, - 0xba38209c, 0xf746ce76, 0x77afa1c5, 0x20756060, - 0x85cbfe4e, 0x8ae88dd8, 0x7aaaf9b0, 0x4cf9aa7e, - 0x1948c25c, 0x02fb8a8c, 0x01c36ae4, 0xd6ebe1f9, - 0x90d4f869, 0xa65cdea0, 0x3f09252d, 0xc208e69f, - 0xb74e6132, 0xce77e25b, 0x578fdfe3, 0x3ac372e6 - }; - - // bcrypt IV: "OrpheanBeholderScryDoubt" - static private final int bf_crypt_ciphertext[] = { - 0x4f727068, 0x65616e42, 0x65686f6c, - 0x64657253, 0x63727944, 0x6f756274 - }; - - // Table for Base64 encoding - static private final char base64_code[] = { - '.', '/', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', - 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', - 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', - 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', - 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', - '6', '7', '8', '9' - }; - - // Table for Base64 decoding - static private final byte index_64[] = { - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, 0, 1, 54, 55, - 56, 57, 58, 59, 60, 61, 62, 63, -1, -1, - -1, -1, -1, -1, -1, 2, 3, 4, 5, 6, - 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, - 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, - -1, -1, -1, -1, -1, -1, 28, 29, 30, - 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, - 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, - 51, 52, 53, -1, -1, -1, -1, -1 - }; - - // Expanded Blowfish key - private int P[]; - private int S[]; - - /** - * Encode a byte array using bcrypt's slightly-modified base64 - * encoding scheme. Note that this is *not* compatible with - * the standard MIME-base64 encoding. - * - * @param d the byte array to encode - * @param len the number of bytes to encode - * @throws IllegalArgumentException if the length is invalid - * @return base64-encoded string - */ - private static String encode_base64( byte d[], int len ) - throws IllegalArgumentException - { - int off = 0; - StringBuffer rs = new StringBuffer(); - int c1, c2; - - if ( len <= 0 || len > d.length ) - { - throw new IllegalArgumentException( "Invalid len" ); - } - - while ( off < len ) - { - c1 = d[ off++ ] & 0xff; - rs.append( base64_code[ ( c1 >> 2 ) & 0x3f ] ); - c1 = ( c1 & 0x03 ) << 4; - if ( off >= len ) - { - rs.append( base64_code[ c1 & 0x3f ] ); - break; - } - c2 = d[ off++ ] & 0xff; - c1 |= ( c2 >> 4 ) & 0x0f; - rs.append( base64_code[ c1 & 0x3f ] ); - c1 = ( c2 & 0x0f ) << 2; - if ( off >= len ) - { - rs.append( base64_code[ c1 & 0x3f ] ); - break; - } - c2 = d[ off++ ] & 0xff; - c1 |= ( c2 >> 6 ) & 0x03; - rs.append( base64_code[ c1 & 0x3f ] ); - rs.append( base64_code[ c2 & 0x3f ] ); - } - return rs.toString(); - } - - /** - * Look up the 3 bits base64-encoded by the specified character, - * range-checking againt conversion table - * - * @param x the base64-encoded value - * @return the decoded value of x - */ - private static byte char64( char x ) - { - if ( ( int ) x < 0 || ( int ) x > index_64.length ) - { - return -1; - } - return index_64[ ( int ) x ]; - } - - /** - * Decode a string encoded using bcrypt's base64 scheme to a - * byte array. Note that this is *not* compatible with - * the standard MIME-base64 encoding. - * - * @param s the string to decode - * @param maxolen the maximum number of bytes to decode - * @throws IllegalArgumentException if maxolen is invalid - * @return an array containing the decoded bytes - */ - private static byte[] decode_base64( String s, int maxolen ) - throws IllegalArgumentException - { - StringBuffer rs = new StringBuffer(); - int off = 0, slen = s.length(), olen = 0; - byte ret[]; - byte c1, c2, c3, c4, o; - - if ( maxolen <= 0 ) - { - throw new IllegalArgumentException( "Invalid maxolen" ); - } - - while ( off < slen - 1 && olen < maxolen ) - { - c1 = char64( s.charAt( off++ ) ); - c2 = char64( s.charAt( off++ ) ); - if ( c1 == -1 || c2 == -1 ) - { - break; - } - o = ( byte ) ( c1 << 2 ); - o |= ( c2 & 0x30 ) >> 4; - rs.append( ( char ) o ); - if ( ++olen >= maxolen || off >= slen ) - { - break; - } - c3 = char64( s.charAt( off++ ) ); - if ( c3 == -1 ) - { - break; - } - o = ( byte ) ( ( c2 & 0x0f ) << 4 ); - o |= ( c3 & 0x3c ) >> 2; - rs.append( ( char ) o ); - if ( ++olen >= maxolen || off >= slen ) - { - break; - } - c4 = char64( s.charAt( off++ ) ); - o = ( byte ) ( ( c3 & 0x03 ) << 6 ); - o |= c4; - rs.append( ( char ) o ); - ++olen; - } - - ret = new byte[ olen ]; - for ( off = 0; off < olen; off++ ) - { - ret[ off ] = ( byte ) rs.charAt( off ); - } - return ret; - } - - /** - * Blowfish encipher a single 64-bit block encoded as - * two 32-bit halves - * - * @param lr an array containing the two 32-bit half blocks - * @param off the position in the array of the blocks - */ - private final void encipher( int lr[], int off ) - { - int i, n, l = lr[ off ], r = lr[ off + 1 ]; - - l ^= P[ 0 ]; - for ( i = 0; i <= BLOWFISH_NUM_ROUNDS - 2; ) - { - // Feistel substitution on left word - n = S[ ( l >> 24 ) & 0xff ]; - n += S[ 0x100 | ( ( l >> 16 ) & 0xff ) ]; - n ^= S[ 0x200 | ( ( l >> 8 ) & 0xff ) ]; - n += S[ 0x300 | ( l & 0xff ) ]; - r ^= n ^ P[ ++i ]; - - // Feistel substitution on right word - n = S[ ( r >> 24 ) & 0xff ]; - n += S[ 0x100 | ( ( r >> 16 ) & 0xff ) ]; - n ^= S[ 0x200 | ( ( r >> 8 ) & 0xff ) ]; - n += S[ 0x300 | ( r & 0xff ) ]; - l ^= n ^ P[ ++i ]; - } - lr[ off ] = r ^ P[ BLOWFISH_NUM_ROUNDS + 1 ]; - lr[ off + 1 ] = l; - } - - /** - * Cycically extract a word of key material - * - * @param data the string to extract the data from - * @param offp a "pointer" (as a one-entry array) to the - * current offset into data - * @return the next word of material from data - */ - private static int streamtoword( byte data[], int offp[] ) - { - int i; - int word = 0; - int off = offp[ 0 ]; - - for ( i = 0; i < 4; i++ ) - { - word = ( word << 8 ) | ( data[ off ] & 0xff ); - off = ( off + 1 ) % data.length; - } - - offp[ 0 ] = off; - return word; - } - - /** - * Initialise the Blowfish key schedule - */ - private void init_key( ) - { - P = ( int[] ) P_orig.clone(); - S = ( int[] ) S_orig.clone(); - } - - /** - * Key the Blowfish cipher - * - * @param key an array containing the key - */ - private void key( byte key[] ) - { - int i; - int koffp[] = { 0 }; - int lr[] = { 0, 0 }; - int plen = P.length, slen = S.length; - - for ( i = 0; i < plen; i++ ) - { - P[ i ] = P[ i ] ^ streamtoword( key, koffp ); - } - - for ( i = 0; i < plen; i += 2 ) - { - encipher( lr, 0 ); - P[ i ] = lr[ 0 ]; - P[ i + 1 ] = lr[ 1 ]; - } - - for ( i = 0; i < slen; i += 2 ) - { - encipher( lr, 0 ); - S[ i ] = lr[ 0 ]; - S[ i + 1 ] = lr[ 1 ]; - } - } - - /** - * Perform the "enhanced key schedule" step described by - * Provos and Mazieres in "A Future-Adaptable Password Scheme" - * http://www.openbsd.org/papers/bcrypt-paper.ps - * - * @param data salt information - * @param key password information - */ - private void ekskey( byte data[], byte key[] ) - { - int i; - int koffp[] = { 0 }, doffp[] = { 0 }; - int lr[] = { 0, 0 }; - int plen = P.length, slen = S.length; - - for ( i = 0; i < plen; i++ ) - { - P[ i ] = P[ i ] ^ streamtoword( key, koffp ); - } - - for ( i = 0; i < plen; i += 2 ) - { - lr[ 0 ] ^= streamtoword( data, doffp ); - lr[ 1 ] ^= streamtoword( data, doffp ); - encipher( lr, 0 ); - P[ i ] = lr[ 0 ]; - P[ i + 1 ] = lr[ 1 ]; - } - - for ( i = 0; i < slen; i += 2 ) - { - lr[ 0 ] ^= streamtoword( data, doffp ); - lr[ 1 ] ^= streamtoword( data, doffp ); - encipher( lr, 0 ); - S[ i ] = lr[ 0 ]; - S[ i + 1 ] = lr[ 1 ]; - } - } - - /** - * Perform the central password hashing step in the - * bcrypt scheme - * - * @param password the password to hash - * @param salt the binary salt to hash with the password - * @param log_rounds the binary logarithm of the number - * of rounds of hashing to apply - * @return an array containing the binary hashed password - */ - private byte[] crypt_raw( byte password[], byte salt[], int log_rounds ) - { - int rounds, i, j; - int cdata[] = ( int[] ) bf_crypt_ciphertext.clone(); - int clen = cdata.length; - byte ret[]; - - if ( log_rounds < 4 || log_rounds > 31 ) - { - throw new IllegalArgumentException( "Bad number of rounds" ); - } - rounds = 1 << log_rounds; - if ( salt.length != BCRYPT_SALT_LEN ) - { - throw new IllegalArgumentException( "Bad salt length" ); - } - - init_key(); - ekskey( salt, password ); - for ( i = 0; i < rounds; i++ ) - { - key( password ); - key( salt ); - } - - for ( i = 0; i < 64; i++ ) - { - for ( j = 0; j < ( clen >> 1 ); j++ ) - { - encipher( cdata, j << 1 ); - } - } - - ret = new byte[ clen * 4 ]; - for ( i = 0, j = 0; i < clen; i++ ) - { - ret[ j++ ] = ( byte ) ( ( cdata[ i ] >> 24 ) & 0xff ); - ret[ j++ ] = ( byte ) ( ( cdata[ i ] >> 16 ) & 0xff ); - ret[ j++ ] = ( byte ) ( ( cdata[ i ] >> 8 ) & 0xff ); - ret[ j++ ] = ( byte ) ( cdata[ i ] & 0xff ); - } - return ret; - } - - /** - * Hash a password using the OpenBSD bcrypt scheme - * - * @param password the password to hash - * @param salt the salt to hash with (perhaps generated - * using BCrypt.gensalt) - * @return the hashed password - */ - public static String hashpw( String password, String salt ) - { - JBCrypt B; - String real_salt; - byte passwordb[], saltb[], hashed[]; - char minor = ( char ) 0; - int rounds, off = 0; - StringBuffer rs = new StringBuffer(); - - if ( salt.charAt( 0 ) != '$' || salt.charAt( 1 ) != '2' ) - { - throw new IllegalArgumentException( "Invalid salt version" ); - } - if ( salt.charAt( 2 ) == '$' ) - { - off = 3; - } - else - { - minor = salt.charAt( 2 ); - if ( minor != 'a' || salt.charAt( 3 ) != '$' ) - { - throw new IllegalArgumentException( "Invalid salt revision" ); - } - off = 4; - } - - // Extract number of rounds - if ( salt.charAt( off + 2 ) > '$' ) - { - throw new IllegalArgumentException( "Missing salt rounds" ); - } - rounds = Integer.parseInt( salt.substring( off, off + 2 ) ); - - real_salt = salt.substring( off + 3, off + 25 ); - try - { - passwordb = ( password + ( minor >= 'a' ? "\000" : "" ) ).getBytes( "UTF-8" ); - } - catch ( UnsupportedEncodingException uee ) - { - throw new AssertionError( "UTF-8 is not supported" ); - } - - saltb = decode_base64( real_salt, BCRYPT_SALT_LEN ); - - B = new JBCrypt(); - hashed = B.crypt_raw( passwordb, saltb, rounds ); - - rs.append( "$2" ); - if ( minor >= 'a' ) - { - rs.append( minor ); - } - rs.append( "$" ); - if ( rounds < 10 ) - { - rs.append( "0" ); - } - rs.append( Integer.toString( rounds ) ); - rs.append( "$" ); - rs.append( encode_base64( saltb, saltb.length ) ); - rs.append( encode_base64( hashed, - bf_crypt_ciphertext.length * 4 - 1 ) ); - return rs.toString(); - } - - /** - * Generate a salt for use with the BCrypt.hashpw() method - * - * @param log_rounds the log2 of the number of rounds of - * hashing to apply - the work factor therefore increases as - * 2**log_rounds. - * @param random an instance of SecureRandom to use - * @return an encoded salt value - */ - public static String gensalt( int log_rounds, SecureRandom random ) - { - StringBuffer rs = new StringBuffer(); - byte rnd[] = new byte[ BCRYPT_SALT_LEN ]; - - random.nextBytes( rnd ); - - rs.append( "$2a$" ); - if ( log_rounds < 10 ) - { - rs.append( "0" ); - } - rs.append( Integer.toString( log_rounds ) ); - rs.append( "$" ); - rs.append( encode_base64( rnd, rnd.length ) ); - return rs.toString(); - } - - /** - * Generate a salt for use with the BCrypt.hashpw() method - * - * @param log_rounds the log2 of the number of rounds of - * hashing to apply - the work factor therefore increases as - * 2**log_rounds. - * @return an encoded salt value - */ - public static String gensalt( int log_rounds ) - { - return gensalt( log_rounds, new SecureRandom() ); - } - - /** - * Generate a salt for use with the BCrypt.hashpw() method, - * selecting a reasonable default for the number of hashing - * rounds to apply - * - * @return an encoded salt value - */ - public static String gensalt( ) - { - return gensalt( GENSALT_DEFAULT_LOG2_ROUNDS ); - } + final int bcryptRounds = 10; + final byte[] salt = PwmRandom.getInstance().newBytes( 16 ); + return OpenBSDBCrypt.generate( password.toLowerCase().toCharArray(), salt, bcryptRounds ); + } - /** - * Check that a plaintext password matches a previously hashed - * one - * - * @param plaintext the plaintext password to verify - * @param hashed the previously-hashed password - * @return true if the passwords match, false otherwise - */ - public static boolean checkpw( String plaintext, String hashed ) - { - return ( hashed.compareTo( hashpw( plaintext, hashed ) ) == 0 ); - } + public boolean testAnswer( final String password, final String hashedPassword ) + { + final char[] pwCharArray = password.toLowerCase().toCharArray(); + return OpenBSDBCrypt.checkPassword( hashedPassword, pwCharArray ); } } diff --git a/server/src/main/java/password/pwm/util/secure/PromiscuousTrustManager.java b/server/src/main/java/password/pwm/util/secure/PromiscuousTrustManager.java index e19815c03a..7a3bbe8afe 100644 --- a/server/src/main/java/password/pwm/util/secure/PromiscuousTrustManager.java +++ b/server/src/main/java/password/pwm/util/secure/PromiscuousTrustManager.java @@ -64,7 +64,8 @@ private void logMsg( final X509Certificate[] certs, final String authType ) { try { - LOGGER.debug( () -> "promiscuous trusting certificate during authType=" + authType + ", subject=" + cert.getSubjectDN().toString() ); + LOGGER.debug( () -> "promiscuous trusting certificate during authType=" + + authType + ", cert=" + X509Utils.makeDebugText( cert ) ); } catch ( final Exception e ) { diff --git a/server/src/main/java/password/pwm/util/secure/PwmTrustManager.java b/server/src/main/java/password/pwm/util/secure/PwmTrustManager.java index edc3df3063..009726f8be 100644 --- a/server/src/main/java/password/pwm/util/secure/PwmTrustManager.java +++ b/server/src/main/java/password/pwm/util/secure/PwmTrustManager.java @@ -189,7 +189,8 @@ private void doSelfSignedValidation( } if ( !certTrusted ) { - final String errorMsg = "server certificate {subject=" + loopCert.getSubjectDN().getName() + "} does not match a certificate in the " + final String errorMsg = "server certificate {cert=" + X509Utils.makeDebugText( loopCert ) + + "} does not match a certificate in the " + PwmConstants.PWM_APP_NAME + " configuration trust store."; throw new CertificateException( errorMsg ); } diff --git a/server/src/main/java/password/pwm/util/secure/self/SelfCertFactory.java b/server/src/main/java/password/pwm/util/secure/self/SelfCertFactory.java index e796d42b82..425006442d 100644 --- a/server/src/main/java/password/pwm/util/secure/self/SelfCertFactory.java +++ b/server/src/main/java/password/pwm/util/secure/self/SelfCertFactory.java @@ -110,7 +110,7 @@ private static Optional loadExistingStoredCert( final PwmApplica private static boolean evaluateExistingStoredCert( final StoredCertData storedCertData, final SelfCertSettings settings ) { final String cnName = makeSubjectName( settings ); - if ( !cnName.equals( storedCertData.getX509Certificate().getSubjectDN().getName() ) ) + if ( !cnName.equals( storedCertData.getX509Certificate().getSubjectX500Principal().getName() ) ) { LOGGER.info( () -> "replacing stored self cert, subject name does not match configured site url" ); return false; @@ -131,14 +131,14 @@ else if ( storedCertData.getX509Certificate().getNotAfter().before( new Date() ) private static String makeSubjectName( final SelfCertSettings settings ) { - if ( !StringUtil.isEmpty( settings.getSubjectAlternateName() ) ) + if ( !StringUtil.isEmpty( settings.subjectAlternateName() ) ) { - return settings.getSubjectAlternateName(); + return settings.subjectAlternateName(); } String cnName = PwmConstants.PWM_APP_NAME.toLowerCase() + ".example.com"; { - final String siteURL = settings.getSiteUrl(); + final String siteURL = settings.siteUrl(); if ( StringUtil.notEmpty( siteURL ) ) { try diff --git a/server/src/main/java/password/pwm/util/secure/self/SelfCertGenerator.java b/server/src/main/java/password/pwm/util/secure/self/SelfCertGenerator.java index ef69c150dc..dff7c6e178 100644 --- a/server/src/main/java/password/pwm/util/secure/self/SelfCertGenerator.java +++ b/server/src/main/java/password/pwm/util/secure/self/SelfCertGenerator.java @@ -36,8 +36,8 @@ import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.bouncycastle.operator.ContentSigner; import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder; -import password.pwm.util.java.PwmUtil; import password.pwm.util.java.PwmDateFormat; +import password.pwm.util.java.PwmUtil; import password.pwm.util.logging.PwmLogger; import java.math.BigInteger; @@ -46,6 +46,7 @@ import java.security.SecureRandom; import java.security.Security; import java.security.cert.X509Certificate; +import java.time.Duration; import java.time.Instant; import java.util.Date; import java.util.concurrent.TimeUnit; @@ -88,14 +89,14 @@ private X509Certificate generateV3Certificate( final KeyPair pair, final String // 2 days in the past final Date notBefore = new Date( System.currentTimeMillis() - TimeUnit.DAYS.toMillis( 2 ) ); - final long futureSeconds = settings.getFutureSeconds(); - final Date notAfter = new Date( System.currentTimeMillis() + ( futureSeconds * 1000 ) ); + final Duration futureSeconds = settings.futureSeconds(); + final Instant notAfter = Instant.now().plus( futureSeconds ); final X509v3CertificateBuilder certGen = new JcaX509v3CertificateBuilder( subjectName.build(), serialNumber, notBefore, - notAfter, + Date.from( notAfter ), subjectName.build(), pair.getPublic() ); @@ -144,8 +145,8 @@ private BigInteger makeSerialNumber() private KeyPair generateRSAKeyPair( ) throws Exception { - final KeyPairGenerator kpGen = KeyPairGenerator.getInstance( settings.getKeyAlg(), "BC" ); - kpGen.initialize( settings.getKeySize(), secureRandom ); + final KeyPairGenerator kpGen = KeyPairGenerator.getInstance( settings.keyAlg(), "BC" ); + kpGen.initialize( settings.keySize(), secureRandom ); return kpGen.generateKeyPair(); } diff --git a/server/src/main/java/password/pwm/util/secure/self/SelfCertSettings.java b/server/src/main/java/password/pwm/util/secure/self/SelfCertSettings.java index 189653c48f..d9e15ea9be 100644 --- a/server/src/main/java/password/pwm/util/secure/self/SelfCertSettings.java +++ b/server/src/main/java/password/pwm/util/secure/self/SelfCertSettings.java @@ -20,39 +20,85 @@ package password.pwm.util.secure.self; -import lombok.Builder; -import lombok.Value; import password.pwm.AppProperty; import password.pwm.PwmConstants; import password.pwm.config.AppConfig; import password.pwm.config.PwmSetting; -import password.pwm.util.java.TimeDuration; +import password.pwm.util.logging.PwmLogger; -@Value -@Builder -public class SelfCertSettings -{ - private String subjectAlternateName; +import java.net.URI; +import java.time.Duration; +import java.time.temporal.ChronoUnit; +import java.util.Objects; - @Builder.Default - private int keySize = 1024; +public record SelfCertSettings( + String subjectAlternateName, + int keySize, + String keyAlg, + Duration futureSeconds, + String siteUrl +) +{ + public SelfCertSettings( + final String subjectAlternateName, + final int keySize, + final String keyAlg, + final Duration futureSeconds, + final String siteUrl + ) + { + this.subjectAlternateName = Objects.requireNonNull( subjectAlternateName ); + this.keySize = keySize; + this.keyAlg = Objects.requireNonNull( keyAlg ); + this.futureSeconds = Objects.requireNonNull( futureSeconds ); + this.siteUrl = URI.create( siteUrl ).toString(); + } - @Builder.Default - private String keyAlg = "RSA"; + private static final URI EXAMPLE_URI = URI.create( "https://" + PwmConstants.PWM_APP_NAME.toLowerCase() + ".example.com" ); + private static final SelfCertSettings EXAMPLE = new SelfCertSettings( + EXAMPLE_URI.getHost(), + 1024, + "RSA", + Duration.of( 30, ChronoUnit.DAYS ), + EXAMPLE_URI.toString() ); - @Builder.Default - private long futureSeconds = TimeDuration.of( 30, TimeDuration.Unit.DAYS ).as( TimeDuration.Unit.SECONDS ); - @Builder.Default - private String siteUrl = "http://" + PwmConstants.PWM_APP_NAME.toLowerCase() + ".example.com"; + public static SelfCertSettings example() + { + return EXAMPLE; + } public static SelfCertSettings fromConfiguration ( final AppConfig config ) { - return SelfCertSettings.builder() - .keySize( Integer.parseInt( config.readAppProperty( AppProperty.SECURITY_HTTPSSERVER_SELF_KEY_SIZE ) ) ) - .keyAlg( config.readAppProperty( AppProperty.SECURITY_HTTPSSERVER_SELF_ALG ) ) - .futureSeconds( Long.parseLong( config.readAppProperty( AppProperty.SECURITY_HTTPSSERVER_SELF_FUTURESECONDS ) ) ) - .siteUrl( config.readSettingAsString( PwmSetting.PWM_SITE_URL ) ) - .build(); + final URI configuredSiteUrl = calculateConfiguredSiteUrl( config ); + final Duration futureSeconds = Duration.of( + Long.parseLong( config.readAppProperty( AppProperty.SECURITY_HTTPSSERVER_SELF_FUTURESECONDS ) ), + ChronoUnit.SECONDS ); + + return new SelfCertSettings( + configuredSiteUrl.getHost(), + Integer.parseInt( config.readAppProperty( AppProperty.SECURITY_HTTPSSERVER_SELF_KEY_SIZE ) ), + config.readAppProperty( AppProperty.SECURITY_HTTPSSERVER_SELF_ALG ), + futureSeconds, + configuredSiteUrl.toString() ); + + } + + public static URI calculateConfiguredSiteUrl( final AppConfig config ) + { + final String configuredSiteUrl = config.readSettingAsString( PwmSetting.PWM_SITE_URL ); + + try + { + return URI.create( configuredSiteUrl ); + } + catch ( final Exception e ) + { + PwmLogger.forClass( SelfCertSettings.class ).warn( () -> "configured site URL (" + + PwmSetting.PWM_SITE_URL.toMenuLocationDebug( null, PwmConstants.DEFAULT_LOCALE ) + + ") is not a valid url: " + e.getMessage() ); + } + + return EXAMPLE_URI; } } diff --git a/server/src/main/java/password/pwm/ws/client/rest/form/FormDataRequestBean.java b/server/src/main/java/password/pwm/ws/client/rest/form/FormDataRequestBean.java index 8e0a04481b..cfb220d6ea 100644 --- a/server/src/main/java/password/pwm/ws/client/rest/form/FormDataRequestBean.java +++ b/server/src/main/java/password/pwm/ws/client/rest/form/FormDataRequestBean.java @@ -20,35 +20,29 @@ package password.pwm.ws.client.rest.form; -import lombok.Builder; -import lombok.Value; import password.pwm.bean.ProfileID; import password.pwm.config.value.data.FormConfiguration; import java.util.List; import java.util.Map; -@Value -@Builder -public class FormDataRequestBean +public record FormDataRequestBean( + Map formValues, + List formConfigurations, + FormInfo formInfo, + String userDN, + ProfileID ldapProfileID +) { - private Map formValues; - private List formConfigurations; - - @Value - @Builder - public static class FormInfo + public record FormInfo( + FormType module, + ProfileID moduleProfileID, + Mode mode, + String sessionID + ) { - private FormType module; - private ProfileID moduleProfileID; - private Mode mode; - private String sessionID; } - private FormInfo formInfo; - private String userDN; - private ProfileID ldapProfileID; - public enum FormType { NewUser, diff --git a/server/src/main/java/password/pwm/ws/client/rest/form/FormDataResponseBean.java b/server/src/main/java/password/pwm/ws/client/rest/form/FormDataResponseBean.java index 1460a961e8..8a10aef79d 100644 --- a/server/src/main/java/password/pwm/ws/client/rest/form/FormDataResponseBean.java +++ b/server/src/main/java/password/pwm/ws/client/rest/form/FormDataResponseBean.java @@ -20,17 +20,13 @@ package password.pwm.ws.client.rest.form; -import lombok.Builder; -import lombok.Value; - import java.util.Map; -@Value -@Builder -public class FormDataResponseBean +public record FormDataResponseBean( + boolean error, + String errorMessage, + String errorDetail, + Map formValues +) { - private boolean error; - private String errorMessage; - private String errorDetail; - private Map formValues; } diff --git a/server/src/main/java/password/pwm/ws/server/RestAuthenticationProcessor.java b/server/src/main/java/password/pwm/ws/server/RestAuthenticationProcessor.java index eb083b53e8..672e74c4f7 100644 --- a/server/src/main/java/password/pwm/ws/server/RestAuthenticationProcessor.java +++ b/server/src/main/java/password/pwm/ws/server/RestAuthenticationProcessor.java @@ -78,7 +78,7 @@ public RestAuthentication readRestAuthentication( ) throws PwmUnrecoverableExcep LOGGER.trace( sessionLabel, () -> "authenticating with named secret '" + name + "'" ); final Set usages = CollectionUtil.copyToEnumSet( CollectionUtil.readEnumSetFromStringCollection( WebServiceUsage.class, - pwmDomain.getConfig().readSettingAsNamedPasswords( PwmSetting.WEBSERVICES_EXTERNAL_SECRET ).get( name ).getUsage() + pwmDomain.getConfig().readSettingAsNamedPasswords( PwmSetting.WEBSERVICES_EXTERNAL_SECRET ).get( name ).usage() ), WebServiceUsage.class ); return new RestAuthentication( RestAuthenticationType.NAMED_SECRET, @@ -149,12 +149,12 @@ private Optional readNamedSecretName( ) final Optional basicAuthInfo = BasicAuthInfo.parseAuthHeader( pwmDomain, httpServletRequest ); if ( basicAuthInfo.isPresent() ) { - final String basicAuthUsername = basicAuthInfo.get().getUsername(); + final String basicAuthUsername = basicAuthInfo.get().username(); final Map secrets = pwmDomain.getConfig().readSettingAsNamedPasswords( PwmSetting.WEBSERVICES_EXTERNAL_SECRET ); final NamedSecretData namedSecretData = secrets.get( basicAuthUsername ); if ( namedSecretData != null ) { - if ( namedSecretData.getPassword().equals( basicAuthInfo.get().getPassword() ) ) + if ( namedSecretData.password().equals( basicAuthInfo.get().password() ) ) { return Optional.of( basicAuthUsername ); } @@ -203,7 +203,7 @@ private Optional readLdapUserIdentity( ) throws PwmUnrecoverableEx final UserSearchService userSearchService = pwmDomain.getUserSearchEngine(); try { - return Optional.of( userSearchService.resolveUsername( basicAuthInfo.get().getUsername(), null, null, sessionLabel ) ); + return Optional.of( userSearchService.resolveUsername( basicAuthInfo.get().username(), null, null, sessionLabel ) ); } catch ( final PwmOperationalException e ) { @@ -217,7 +217,7 @@ private ChaiProvider authenticateUser( final UserIdentity userIdentity ) final BasicAuthInfo basicAuthInfo = BasicAuthInfo.parseAuthHeader( pwmDomain, httpServletRequest ) .orElseThrow(); - final AuthenticationResult authenticationResult = SimpleLdapAuthenticator.authenticateUser( pwmDomain, sessionLabel, userIdentity, basicAuthInfo.getPassword() ); + final AuthenticationResult authenticationResult = SimpleLdapAuthenticator.authenticateUser( pwmDomain, sessionLabel, userIdentity, basicAuthInfo.password() ); return authenticationResult.getUserProvider(); } diff --git a/server/src/main/java/password/pwm/ws/server/RestResultBean.java b/server/src/main/java/password/pwm/ws/server/RestResultBean.java index 9fae0c1959..2312d9eb30 100644 --- a/server/src/main/java/password/pwm/ws/server/RestResultBean.java +++ b/server/src/main/java/password/pwm/ws/server/RestResultBean.java @@ -127,7 +127,7 @@ public static RestResultBean fromError( return fromError( errorInformation, pwmRequest.getPwmDomain(), pwmRequest.getLocale(), pwmRequest.getDomainConfig(), forceDetail ); } - public static RestResultBean fromError( + public static RestResultBean fromError( final ErrorInformation errorInformation, final PwmRequest pwmRequest ) diff --git a/server/src/main/java/password/pwm/ws/server/RestServlet.java b/server/src/main/java/password/pwm/ws/server/RestServlet.java index 36cb3e44b7..daa99ce45e 100644 --- a/server/src/main/java/password/pwm/ws/server/RestServlet.java +++ b/server/src/main/java/password/pwm/ws/server/RestServlet.java @@ -24,7 +24,6 @@ import com.novell.ldapchai.exception.ChaiUnavailableException; import com.novell.ldapchai.provider.ChaiProvider; import lombok.Data; -import lombok.Value; import password.pwm.PwmApplication; import password.pwm.PwmApplicationMode; import password.pwm.PwmConstants; @@ -497,13 +496,13 @@ private static void outputLastHopeError( final String msg, final HttpServletResp } } - @Value - public static class TargetUserIdentity - { - private RestRequest restRequest; - private UserIdentity userIdentity; - private boolean self; + public record TargetUserIdentity( + RestRequest restRequest, + UserIdentity userIdentity, + boolean self + ) + { public ChaiProvider getChaiProvider( ) throws PwmUnrecoverableException { return restRequest.getChaiProvider( userIdentity.getLdapProfileID() ); diff --git a/server/src/main/java/password/pwm/ws/server/rest/RestChallengesServer.java b/server/src/main/java/password/pwm/ws/server/rest/RestChallengesServer.java index 2d8f1da010..5217e18b91 100644 --- a/server/src/main/java/password/pwm/ws/server/rest/RestChallengesServer.java +++ b/server/src/main/java/password/pwm/ws/server/rest/RestChallengesServer.java @@ -175,22 +175,22 @@ public RestResultBean doFormGetChallengeData( final RestRequest restRequest ) final ChaiUser chaiUser = targetUserIdentity.getChaiUser(); final Locale userLocale = restRequest.getLocale(); final CrService crService = restRequest.getDomain().getCrService(); - responseSet = crService.readUserResponseSet( restRequest.getSessionLabel(), targetUserIdentity.getUserIdentity(), chaiUser ).orElseThrow(); + responseSet = crService.readUserResponseSet( restRequest.getSessionLabel(), targetUserIdentity.userIdentity(), chaiUser ).orElseThrow(); final PwmPasswordPolicy passwordPolicy = PasswordUtility.readPasswordPolicyForUser( restRequest.getDomain(), restRequest.getSessionLabel(), - targetUserIdentity.getUserIdentity(), + targetUserIdentity.userIdentity(), chaiUser ); final ChallengeProfile challengeProfile = crService.readUserChallengeProfile( restRequest.getSessionLabel(), - targetUserIdentity.getUserIdentity(), + targetUserIdentity.userIdentity(), chaiUser, passwordPolicy, userLocale ); - outputUsername = targetUserIdentity.getUserIdentity().toDelimitedKey(); + outputUsername = targetUserIdentity.userIdentity().toDelimitedKey(); // build output final JsonChallengesData jsonData = new JsonChallengesData(); @@ -258,7 +258,7 @@ public RestResultBean doSetChallengeDataJson( final RestRequest restRequest ) final UserIdentity userIdentity; final CrService crService = restRequest.getDomain().getCrService(); - userIdentity = targetUserIdentity.getUserIdentity(); + userIdentity = targetUserIdentity.userIdentity(); chaiUser = targetUserIdentity.getChaiUser(); final ChallengeProfile challengeProfile = crService.readUserChallengeProfile( @@ -310,7 +310,7 @@ private RestResultBean doDeleteChallengeData( final RestRequest restRequest, fin final CrService crService = restRequest.getDomain().getCrService(); crService.clearResponses( restRequest.getSessionLabel(), - targetUserIdentity.getUserIdentity(), + targetUserIdentity.userIdentity(), chaiUser ); // update statistics diff --git a/server/src/main/java/password/pwm/ws/server/rest/RestCheckPasswordServer.java b/server/src/main/java/password/pwm/ws/server/rest/RestCheckPasswordServer.java index 742fe02553..1a9be84c40 100644 --- a/server/src/main/java/password/pwm/ws/server/rest/RestCheckPasswordServer.java +++ b/server/src/main/java/password/pwm/ws/server/rest/RestCheckPasswordServer.java @@ -23,7 +23,6 @@ import lombok.AllArgsConstructor; import lombok.Getter; -import lombok.NoArgsConstructor; import password.pwm.PwmConstants; import password.pwm.bean.UserIdentity; import password.pwm.config.option.WebServiceUsage; @@ -69,40 +68,36 @@ public class RestCheckPasswordServer extends RestServlet private static final String FIELD_PASSWORD_2 = "password2"; private static final String FIELD_USERNAME = "username"; - @Getter - @AllArgsConstructor - @NoArgsConstructor - public static class JsonInput + public record JsonInput( + String password1, + String password2, + String username, + String verificationState + ) { - public String password1; - public String password2; - public String username; } - @Getter - @AllArgsConstructor - @NoArgsConstructor - public static class JsonOutput + public record JsonOutput( + int version, + int strength, + PasswordUtility.PasswordCheckInfo.MatchStatus match, + String message, + boolean passed, + int errorCode + ) { - public int version; - public int strength; - public PasswordUtility.PasswordCheckInfo.MatchStatus match; - public String message; - public boolean passed; - public int errorCode; public static JsonOutput fromPasswordCheckInfo( final PasswordUtility.PasswordCheckInfo checkInfo ) { - final JsonOutput outputMap = new JsonOutput(); - outputMap.version = 2; - outputMap.strength = checkInfo.getStrength(); - outputMap.match = checkInfo.getMatch(); - outputMap.message = checkInfo.getMessage(); - outputMap.passed = checkInfo.isPassed(); - outputMap.errorCode = checkInfo.getErrorCode(); - return outputMap; + return new JsonOutput( + 2, + checkInfo.getStrength(), + checkInfo.getMatch(), + checkInfo.getMessage(), + checkInfo.isPassed(), + checkInfo.getErrorCode() ); } } @@ -113,19 +108,20 @@ public void preCheckRequest( final RestRequest request ) throws PwmUnrecoverable } @RestMethodHandler( method = HttpMethod.POST, consumes = HttpContentType.form, produces = HttpContentType.json ) - public RestResultBean doPasswordRuleCheckFormPost( final RestRequest restRequest ) + public RestResultBean doPasswordRuleCheckFormPost( final RestRequest restRequest ) throws PwmUnrecoverableException { final JsonInput jsonInput = new JsonInput( restRequest.readParameterAsString( FIELD_PASSWORD_1, PwmHttpRequestWrapper.Flag.BypassValidation ), restRequest.readParameterAsString( FIELD_PASSWORD_2, PwmHttpRequestWrapper.Flag.BypassValidation ), - restRequest.readParameterAsString( FIELD_USERNAME, PwmHttpRequestWrapper.Flag.BypassValidation ) + restRequest.readParameterAsString( FIELD_USERNAME, PwmHttpRequestWrapper.Flag.BypassValidation ), + null ); return doOperation( restRequest, jsonInput ); } @RestMethodHandler( method = HttpMethod.POST, consumes = HttpContentType.json, produces = HttpContentType.json ) - public RestResultBean doPasswordRuleCheckJsonPost( final RestRequest restRequest ) + public RestResultBean doPasswordRuleCheckJsonPost( final RestRequest restRequest ) throws PwmUnrecoverableException, IOException { @@ -135,21 +131,22 @@ public RestResultBean doPasswordRuleCheckJsonPost( final RestRequest restRequest jsonInput = new JsonInput( RestUtility.readValueFromJsonAndParam( - jsonBody == null ? null : jsonBody.getPassword1(), + jsonBody == null ? null : jsonBody.password1(), restRequest.readParameterAsString( FIELD_PASSWORD_1 ), FIELD_PASSWORD_1 ).orElse( null ), RestUtility.readValueFromJsonAndParam( - jsonBody == null ? null : jsonBody.getPassword2(), + jsonBody == null ? null : jsonBody.password2(), restRequest.readParameterAsString( FIELD_PASSWORD_2 ), FIELD_PASSWORD_2 ).orElse( null ), RestUtility.readValueFromJsonAndParam( - jsonBody == null ? null : jsonBody.getUsername(), + jsonBody == null ? null : jsonBody.username(), restRequest.readParameterAsString( FIELD_USERNAME ), FIELD_USERNAME, RestUtility.ReadValueFlag.optional - ).orElse( null ) + ).orElse( null ), + null ); } @@ -161,33 +158,33 @@ public RestResultBean doOperation( final RestRequest restRequest, final JsonInpu { final Instant startTime = Instant.now(); - if ( StringUtil.isEmpty( jsonInput.getPassword1() ) ) + if ( StringUtil.isEmpty( jsonInput.password1() ) ) { final String errorMessage = "missing field '" + FIELD_PASSWORD_1 + "'"; final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_FIELD_REQUIRED, errorMessage, new String[] { FIELD_PASSWORD_1, } - ); + ); return RestResultBean.fromError( restRequest, errorInformation ); } try { - final TargetUserIdentity targetUserIdentity = RestUtility.resolveRequestedUsername( restRequest, jsonInput.getUsername() ); + final TargetUserIdentity targetUserIdentity = RestUtility.resolveRequestedUsername( restRequest, jsonInput.username() ); final UserInfo userInfo = UserInfoFactory.newUserInfo( restRequest.getPwmApplication(), restRequest.getSessionLabel(), restRequest.getLocale(), - targetUserIdentity.getUserIdentity(), + targetUserIdentity.userIdentity(), targetUserIdentity.getChaiProvider() ); final PasswordCheckRequest checkRequest = new PasswordCheckRequest( - targetUserIdentity.getUserIdentity(), - StringUtil.isEmpty( jsonInput.getPassword1() ) ? null : new PasswordData( jsonInput.getPassword1() ), - StringUtil.isEmpty( jsonInput.getPassword2() ) ? null : new PasswordData( jsonInput.getPassword2() ), + targetUserIdentity.userIdentity(), + StringUtil.isEmpty( jsonInput.password1() ) ? null : new PasswordData( jsonInput.password1() ), + StringUtil.isEmpty( jsonInput.password2() ) ? null : new PasswordData( jsonInput.password2() ), userInfo ); diff --git a/server/src/main/java/password/pwm/ws/server/rest/RestHealthServer.java b/server/src/main/java/password/pwm/ws/server/rest/RestHealthServer.java index 0400b6234f..70120bd25d 100644 --- a/server/src/main/java/password/pwm/ws/server/rest/RestHealthServer.java +++ b/server/src/main/java/password/pwm/ws/server/rest/RestHealthServer.java @@ -96,11 +96,10 @@ public static PublicHealthData processGetHealthCheckData( final List healthRecords = new ArrayList<>( healthService.getHealthRecords() ); final List healthRecordBeans = PublicHealthRecord.fromHealthRecords( healthRecords, locale, pwmDomain.getConfig() ); - return PublicHealthData.builder() - .timestamp( healthService.getLastHealthCheckTime() ) - .overall( healthService.getMostSevereHealthStatus().toString() ) - .records( healthRecordBeans ) - .build(); + return new PublicHealthData( + healthService.getLastHealthCheckTime(), + healthService.getMostSevereHealthStatus().toString(), + healthRecordBeans ); } } diff --git a/server/src/main/java/password/pwm/ws/server/rest/RestProfileServer.java b/server/src/main/java/password/pwm/ws/server/rest/RestProfileServer.java index 34803f74cb..c1616bb943 100644 --- a/server/src/main/java/password/pwm/ws/server/rest/RestProfileServer.java +++ b/server/src/main/java/password/pwm/ws/server/rest/RestProfileServer.java @@ -138,7 +138,7 @@ private static RestResultBean doGetProfileDataImpl( restRequest.getPwmApplication(), restRequest.getSessionLabel(), restRequest.getLocale(), - targetUserIdentity.getUserIdentity(), + targetUserIdentity.userIdentity(), targetUserIdentity.getChaiProvider() ); @@ -181,7 +181,7 @@ private static UpdateProfileProfile getProfile( final RestRequest restRequest, f final ProfileID updateProfileID = ProfileUtility.discoverProfileIDForUser( restRequest.getDomain(), restRequest.getSessionLabel(), - targetUserIdentity.getUserIdentity(), + targetUserIdentity.userIdentity(), ProfileDefinition.UpdateAttributes ).orElseThrow( () -> new PwmUnrecoverableException( PwmError.ERROR_NO_PROFILE_ASSIGNED ) ); @@ -209,7 +209,7 @@ private static RestResultBean doPostProfileDataImpl( final boolean result = UserPermissionUtility.testUserPermission( restRequest.getDomain(), restRequest.getSessionLabel(), - targetUserIdentity.getUserIdentity(), + targetUserIdentity.userIdentity(), userPermission ); @@ -242,7 +242,7 @@ private static RestResultBean doPostProfileDataImpl( restRequest.getPwmApplication(), restRequest.getSessionLabel(), restRequest.getLocale(), - targetUserIdentity.getUserIdentity(), + targetUserIdentity.userIdentity(), targetUserIdentity.getChaiProvider() ); @@ -250,7 +250,7 @@ private static RestResultBean doPostProfileDataImpl( restRequest.getPwmApplication(), restRequest.getLocale(), restRequest.getSessionLabel(), - targetUserIdentity.getUserIdentity() + targetUserIdentity.userIdentity() ); UpdateProfileUtil.doProfileUpdate( diff --git a/server/src/main/java/password/pwm/ws/server/rest/RestRandomPasswordServer.java b/server/src/main/java/password/pwm/ws/server/rest/RestRandomPasswordServer.java index c033fcc959..fb53403ede 100644 --- a/server/src/main/java/password/pwm/ws/server/rest/RestRandomPasswordServer.java +++ b/server/src/main/java/password/pwm/ws/server/rest/RestRandomPasswordServer.java @@ -20,7 +20,6 @@ package password.pwm.ws.server.rest; -import lombok.Data; import password.pwm.PwmConstants; import password.pwm.PwmDomain; import password.pwm.config.option.WebServiceUsage; @@ -62,21 +61,32 @@ public class RestRandomPasswordServer extends RestServlet { private static final PwmLogger LOGGER = PwmLogger.forClass( RestRandomPasswordServer.class ); - @Data - public static class JsonOutput + public record JsonOutput( + String password + ) { - private String password; } - @Data - public static class JsonInput + public record JsonInput( + String username, + int strength, + int minLength, + int maxLength, + String chars, + boolean noUser + ) { - private String username; - private int strength; - private int minLength; - private int maxLength; - private String chars; - private boolean noUser; + static JsonInput fromRequestParameters( final RestRequest restRequest ) + throws PwmUnrecoverableException + { + return new JsonInput( + restRequest.readParameterAsString( "username", PwmHttpRequestWrapper.Flag.BypassValidation ), + restRequest.readParameterAsInt( "strength", 0 ), + restRequest.readParameterAsInt( "minLength", 0 ), + restRequest.readParameterAsInt( "maxLength", 0 ), + restRequest.readParameterAsString( "chars", PwmHttpRequestWrapper.Flag.BypassValidation ), + restRequest.readParameterAsBoolean( "noUser" ) ); + } } @Override @@ -85,12 +95,12 @@ public void preCheckRequest( final RestRequest request ) throws PwmUnrecoverable } @RestMethodHandler( method = HttpMethod.POST, consumes = HttpContentType.form, produces = HttpContentType.json ) - public RestResultBean doPostRandomPasswordForm( final RestRequest restRequest ) + public RestResultBean doPostRandomPasswordForm( final RestRequest restRequest ) throws PwmUnrecoverableException { /* Check for parameter conflicts. */ if ( restRequest.hasParameter( "username" ) - && ( restRequest.hasParameter( "strength" ) || restRequest.hasParameter( "minLength" ) || restRequest.hasParameter( "chars" ) ) ) + && ( restRequest.hasParameter( "strength" ) || restRequest.hasParameter( "minLength" ) || restRequest.hasParameter( "chars" ) ) ) { LOGGER.error( restRequest.getSessionLabel(), () -> "REST parameter conflict. The username parameter cannot be specified if strength, minLength or chars parameters are specified." ); @@ -99,13 +109,7 @@ public RestResultBean doPostRandomPasswordForm( final RestRequest restRequest ) return RestResultBean.fromError( restRequest, errorInformation ); } - final JsonInput jsonInput = new JsonInput(); - jsonInput.username = restRequest.readParameterAsString( "username", PwmHttpRequestWrapper.Flag.BypassValidation ); - jsonInput.strength = restRequest.readParameterAsInt( "strength", 0 ); - jsonInput.maxLength = restRequest.readParameterAsInt( "maxLength", 0 ); - jsonInput.minLength = restRequest.readParameterAsInt( "minLength", 0 ); - jsonInput.chars = restRequest.readParameterAsString( "chars", PwmHttpRequestWrapper.Flag.BypassValidation ); - jsonInput.noUser = restRequest.readParameterAsBoolean( "noUser" ); + final JsonInput jsonInput = JsonInput.fromRequestParameters( restRequest ); try { @@ -132,7 +136,7 @@ public RestResultBean doPlainRandomPassword( final RestRequest restRequest ) { /* Check for parameter conflicts. */ if ( restRequest.hasParameter( "username" ) - && ( restRequest.hasParameter( "strength" ) || restRequest.hasParameter( "minLength" ) || restRequest.hasParameter( "chars" ) ) ) + && ( restRequest.hasParameter( "strength" ) || restRequest.hasParameter( "minLength" ) || restRequest.hasParameter( "chars" ) ) ) { LOGGER.error( restRequest.getSessionLabel(), () -> "REST parameter conflict. The username parameter cannot be specified if strength, minLength or chars parameters are specified." ); @@ -141,18 +145,12 @@ public RestResultBean doPlainRandomPassword( final RestRequest restRequest ) return RestResultBean.fromError( restRequest, errorInformation ); } - final JsonInput jsonInput = new JsonInput(); - jsonInput.username = restRequest.readParameterAsString( "username", PwmHttpRequestWrapper.Flag.BypassValidation ); - jsonInput.strength = restRequest.readParameterAsInt( "strength", 0 ); - jsonInput.maxLength = restRequest.readParameterAsInt( "maxLength", 0 ); - jsonInput.minLength = restRequest.readParameterAsInt( "minLength", 0 ); - jsonInput.chars = restRequest.readParameterAsString( "chars", PwmHttpRequestWrapper.Flag.BypassValidation ); - jsonInput.noUser = restRequest.readParameterAsBoolean( "noUser" ); + final JsonInput jsonInput = JsonInput.fromRequestParameters( restRequest ); try { final JsonOutput jsonOutput = doOperation( restRequest, jsonInput ); - return RestResultBean.withData( jsonOutput.getPassword(), String.class ); + return RestResultBean.withData( jsonOutput.password(), String.class ); } catch ( final Exception e ) { @@ -197,17 +195,17 @@ private static JsonOutput doOperation( { final PwmPasswordPolicy pwmPasswordPolicy; - if ( jsonInput.isNoUser() || StringUtil.isEmpty( jsonInput.getUsername() ) ) + if ( jsonInput.noUser() || StringUtil.isEmpty( jsonInput.username() ) ) { pwmPasswordPolicy = PwmPasswordPolicy.defaultPolicy(); } else { - final TargetUserIdentity targetUserIdentity = RestUtility.resolveRequestedUsername( restRequest, jsonInput.getUsername() ); + final TargetUserIdentity targetUserIdentity = RestUtility.resolveRequestedUsername( restRequest, jsonInput.username() ); pwmPasswordPolicy = PasswordUtility.readPasswordPolicyForUser( restRequest.getDomain(), restRequest.getSessionLabel(), - targetUserIdentity.getUserIdentity(), + targetUserIdentity.userIdentity(), targetUserIdentity.getChaiUser() ); } @@ -216,8 +214,7 @@ private static JsonOutput doOperation( restRequest.getSessionLabel(), randomConfig, restRequest.getDomain() ); - final JsonOutput outputMap = new JsonOutput(); - outputMap.password = randomPassword.getStringValue(); + final JsonOutput outputMap = new JsonOutput( randomPassword.getStringValue() ); StatisticsClient.incrementStat( restRequest.getDomain(), Statistic.REST_RANDOMPASSWORD ); @@ -234,21 +231,21 @@ public static RandomGeneratorConfig jsonInputToRandomConfig( final RandomGeneratorConfigRequest.RandomGeneratorConfigRequestBuilder randomConfigBuilder = RandomGeneratorConfigRequest.builder(); - if ( jsonInput.getStrength() > 0 && jsonInput.getStrength() <= 100 ) + if ( jsonInput.strength() > 0 && jsonInput.strength() <= 100 ) { - randomConfigBuilder.minimumStrength( jsonInput.getStrength() ); + randomConfigBuilder.minimumStrength( jsonInput.strength() ); } - if ( jsonInput.getMinLength() > 0 && jsonInput.getMinLength() <= 100 * 1024 ) + if ( jsonInput.minLength() > 0 && jsonInput.minLength() <= 100 * 1024 ) { - randomConfigBuilder.minimumLength( jsonInput.getMinLength() ); + randomConfigBuilder.minimumLength( jsonInput.minLength() ); } - if ( jsonInput.getMaxLength() > 0 && jsonInput.getMaxLength() <= 100 * 1024 ) + if ( jsonInput.maxLength() > 0 && jsonInput.maxLength() <= 100 * 1024 ) { - randomConfigBuilder.maximumLength( jsonInput.getMaxLength() ); + randomConfigBuilder.maximumLength( jsonInput.maxLength() ); } - if ( jsonInput.getChars() != null ) + if ( jsonInput.chars() != null ) { - final String inputChars = jsonInput.getChars(); + final String inputChars = jsonInput.chars(); final List charValues = new ArrayList<>( inputChars.length() ); for ( int i = 0; i < inputChars.length(); i++ ) { diff --git a/server/src/main/java/password/pwm/ws/server/rest/RestSetPasswordServer.java b/server/src/main/java/password/pwm/ws/server/rest/RestSetPasswordServer.java index b4696e50de..beaef76ba8 100644 --- a/server/src/main/java/password/pwm/ws/server/rest/RestSetPasswordServer.java +++ b/server/src/main/java/password/pwm/ws/server/rest/RestSetPasswordServer.java @@ -20,7 +20,6 @@ package password.pwm.ws.server.rest; -import lombok.Value; import password.pwm.PwmConstants; import password.pwm.config.option.WebServiceUsage; import password.pwm.config.profile.PwmPasswordPolicy; @@ -48,7 +47,6 @@ import password.pwm.ws.server.RestWebServer; import javax.servlet.annotation.WebServlet; -import java.io.IOException; import java.util.Optional; @WebServlet( @@ -66,12 +64,12 @@ public class RestSetPasswordServer extends RestServlet private static final String FIELD_USERNAME = "username"; private static final String FIELD_PASSWORD = "password"; - @Value - public static class JsonInputData + public record JsonInputData( + String username, + String password, + boolean random + ) { - private String username; - private String password; - private boolean random; } @Override @@ -81,7 +79,7 @@ public void preCheckRequest( final RestRequest request ) throws PwmUnrecoverable } @RestMethodHandler( method = HttpMethod.POST, consumes = HttpContentType.form, produces = HttpContentType.json ) - public RestResultBean doPostSetPasswordForm( + public RestResultBean doPostSetPasswordForm( final RestRequest restRequest ) throws PwmUnrecoverableException @@ -96,8 +94,8 @@ public RestResultBean doPostSetPasswordForm( } @RestMethodHandler( method = HttpMethod.POST, consumes = HttpContentType.json, produces = HttpContentType.json ) - public RestResultBean doPostSetPasswordJson( final RestRequest restRequest ) - throws IOException, PwmUnrecoverableException + public RestResultBean doPostSetPasswordJson( final RestRequest restRequest ) + throws PwmUnrecoverableException { final JsonInputData jsonInput; { @@ -105,19 +103,19 @@ public RestResultBean doPostSetPasswordJson( final RestRequest restRequest ) jsonInput = new JsonInputData( RestUtility.readValueFromJsonAndParam( - jsonBody == null ? null : jsonBody.getUsername(), + jsonBody == null ? null : jsonBody.username(), restRequest.readParameterAsString( FIELD_USERNAME ), FIELD_USERNAME, RestUtility.ReadValueFlag.optional ).orElse( null ), RestUtility.readValueFromJsonAndParam( - jsonBody == null ? null : jsonBody.getPassword(), + jsonBody == null ? null : jsonBody.password(), restRequest.readParameterAsString( FIELD_PASSWORD ), FIELD_PASSWORD, RestUtility.ReadValueFlag.optional ).orElse( null ), Boolean.parseBoolean( RestUtility.readValueFromJsonAndParam( - jsonBody == null ? "" : String.valueOf( jsonBody.isRandom() ), + jsonBody == null ? "" : String.valueOf( jsonBody.random() ), restRequest.readParameterAsString( FIELD_RANDOM ), FIELD_RANDOM, RestUtility.ReadValueFlag.optional @@ -134,8 +132,8 @@ private static RestResultBean doSetPassword( ) { - final String password = jsonInputData.getPassword(); - final boolean random = jsonInputData.isRandom(); + final String password = jsonInputData.password(); + final boolean random = jsonInputData.random(); if ( ( password == null || password.length() < 1 ) && !random ) { @@ -169,7 +167,7 @@ private static RestResultBean doSetPassword( final PwmPasswordPolicy passwordPolicy = PasswordUtility.readPasswordPolicyForUser( restRequest.getDomain(), restRequest.getSessionLabel(), - targetUserIdentity.getUserIdentity(), + targetUserIdentity.userIdentity(), targetUserIdentity.getChaiUser() ); newPassword = PasswordUtility.generateRandom( restRequest.getSessionLabel(), @@ -183,10 +181,10 @@ private static RestResultBean doSetPassword( } final PasswordData oldPassword; - if ( targetUserIdentity.isSelf() ) + if ( targetUserIdentity.self() ) { final Optional basicAuthInfo = BasicAuthInfo.parseAuthHeader( restRequest.getDomain(), restRequest.getHttpServletRequest() ); - oldPassword = basicAuthInfo.map( BasicAuthInfo::getPassword ).orElse( null ); + oldPassword = basicAuthInfo.map( BasicAuthInfo::password ).orElse( null ); } else { @@ -196,7 +194,7 @@ private static RestResultBean doSetPassword( final UserInfo userInfo = UserInfoFactory.newUserInfoUsingProxy( restRequest.getPwmApplication(), restRequest.getSessionLabel(), - targetUserIdentity.getUserIdentity(), + targetUserIdentity.userIdentity(), restRequest.getLocale() ); @@ -210,7 +208,7 @@ private static RestResultBean doSetPassword( ); StatisticsClient.incrementStat( restRequest.getDomain(), Statistic.REST_SETPASSWORD ); - final JsonInputData jsonResultData = new JsonInputData( targetUserIdentity.getUserIdentity().toDelimitedKey(), null, random ); + final JsonInputData jsonResultData = new JsonInputData( targetUserIdentity.userIdentity().toDelimitedKey(), null, random ); return RestResultBean.forSuccessMessage( jsonResultData, restRequest, Message.Success_PasswordChange ); } catch ( final PwmException e ) diff --git a/server/src/main/java/password/pwm/ws/server/rest/RestStatusServer.java b/server/src/main/java/password/pwm/ws/server/rest/RestStatusServer.java index 697ac295e3..16144ab2e4 100644 --- a/server/src/main/java/password/pwm/ws/server/rest/RestStatusServer.java +++ b/server/src/main/java/password/pwm/ws/server/rest/RestStatusServer.java @@ -81,14 +81,14 @@ public RestResultBean doGetStatusData( final RestRequest restRequest ) restRequest.getPwmApplication(), restRequest.getSessionLabel(), restRequest.getLocale(), - targetUserIdentity.getUserIdentity(), + targetUserIdentity.userIdentity(), chaiProvider ); final MacroRequest macroRequest = MacroRequest.forUser( restRequest.getPwmApplication(), restRequest.getLocale(), restRequest.getSessionLabel(), - targetUserIdentity.getUserIdentity() + targetUserIdentity.userIdentity() ); final PublicUserInfoBean publicUserInfoBean = UserInfoBean.toPublicUserInfoBean( diff --git a/server/src/main/java/password/pwm/ws/server/rest/RestVerifyOtpServer.java b/server/src/main/java/password/pwm/ws/server/rest/RestVerifyOtpServer.java index 1542f4180e..d47e8200cd 100644 --- a/server/src/main/java/password/pwm/ws/server/rest/RestVerifyOtpServer.java +++ b/server/src/main/java/password/pwm/ws/server/rest/RestVerifyOtpServer.java @@ -20,8 +20,6 @@ package password.pwm.ws.server.rest; -import com.novell.ldapchai.exception.ChaiUnavailableException; -import lombok.Value; import password.pwm.PwmConstants; import password.pwm.config.option.WebServiceUsage; import password.pwm.error.ErrorInformation; @@ -54,11 +52,11 @@ public class RestVerifyOtpServer extends RestServlet { - @Value - public static class JsonPutOtpInput + public record JsonPutOtpInput( + String token, + String username + ) { - public String token; - public String username; } @Override @@ -79,40 +77,38 @@ public RestResultBean doSetOtpDataJson( final RestRequest restRequest ) jsonInput = new RestVerifyOtpServer.JsonPutOtpInput( RestUtility.readValueFromJsonAndParam( - jsonBody == null ? null : jsonBody.getToken(), + jsonBody == null ? null : jsonBody.token(), restRequest.readParameterAsString( "token" ), "token" ).orElse( null ), RestUtility.readValueFromJsonAndParam( - jsonBody == null ? null : jsonBody.getUsername(), + jsonBody == null ? null : jsonBody.username(), restRequest.readParameterAsString( "username" ), "username" ).orElse( null ) ); } - final TargetUserIdentity targetUserIdentity = RestUtility.resolveRequestedUsername( restRequest, jsonInput.getUsername() ); + final TargetUserIdentity targetUserIdentity = RestUtility.resolveRequestedUsername( restRequest, jsonInput.username() ); try { final OtpService otpService = restRequest.getDomain().getOtpService(); - final OTPUserRecord otpUserRecord = otpService.readOTPUserConfiguration( restRequest.getSessionLabel(), targetUserIdentity.getUserIdentity() ); + final OTPUserRecord otpUserRecord = otpService.readOTPUserConfiguration( + restRequest.getSessionLabel(), + targetUserIdentity.userIdentity() ); final boolean verified = otpUserRecord != null && otpService.validateToken( restRequest.getSessionLabel(), - targetUserIdentity.getUserIdentity(), + targetUserIdentity.userIdentity(), otpUserRecord, - jsonInput.getToken(), + jsonInput.token(), false ); StatisticsClient.incrementStat( restRequest.getDomain(), Statistic.REST_VERIFYOTP ); return RestResultBean.forSuccessMessage( verified, restRequest, Message.Success_Unknown ); } - catch ( final ChaiUnavailableException e ) - { - throw PwmUnrecoverableException.fromChaiException( e ); - } catch ( final PwmOperationalException e ) { final String errorMsg = "unexpected error reading json input: " + e.getMessage(); diff --git a/server/src/main/java/password/pwm/ws/server/rest/RestVerifyResponsesServer.java b/server/src/main/java/password/pwm/ws/server/rest/RestVerifyResponsesServer.java index 935e30949e..0eefb4d6b8 100644 --- a/server/src/main/java/password/pwm/ws/server/rest/RestVerifyResponsesServer.java +++ b/server/src/main/java/password/pwm/ws/server/rest/RestVerifyResponsesServer.java @@ -116,13 +116,13 @@ public RestResultBean doSetChallengeDataJson( final RestRequest restRequest ) th final TargetUserIdentity targetUserIdentity = RestUtility.resolveRequestedUsername( restRequest, username ); LOGGER.debug( restRequest.getSessionLabel(), () -> "beginning /verifyresponses REST service against " - + ( targetUserIdentity.isSelf() ? "self" : targetUserIdentity.getUserIdentity().toDisplayString() ) ); + + ( targetUserIdentity.self() ? "self" : targetUserIdentity.userIdentity().toDisplayString() ) ); try { final Optional responseSet = restRequest.getDomain().getCrService().readUserResponseSet( restRequest.getSessionLabel(), - targetUserIdentity.getUserIdentity(), + targetUserIdentity.userIdentity(), targetUserIdentity.getChaiUser() ); diff --git a/server/src/main/java/password/pwm/ws/server/rest/bean/PublicHealthData.java b/server/src/main/java/password/pwm/ws/server/rest/bean/PublicHealthData.java index 242b601fcc..dc5e1de44e 100644 --- a/server/src/main/java/password/pwm/ws/server/rest/bean/PublicHealthData.java +++ b/server/src/main/java/password/pwm/ws/server/rest/bean/PublicHealthData.java @@ -20,19 +20,25 @@ package password.pwm.ws.server.rest.bean; -import lombok.Builder; -import lombok.Value; +import password.pwm.util.java.CollectionUtil; import java.time.Instant; import java.util.List; -@Value -@Builder -public class PublicHealthData +public record PublicHealthData( + Instant timestamp, + String overall, + List records +) { - @Builder.Default - public Instant timestamp = Instant.now(); - - public String overall; - public List records; + public PublicHealthData( + final Instant timestamp, + final String overall, + final List records + ) + { + this.timestamp = timestamp; + this.overall = overall; + this.records = CollectionUtil.stripNulls( records ); + } } diff --git a/server/src/main/resources/password/pwm/AppProperty.properties b/server/src/main/resources/password/pwm/AppProperty.properties index 9141dde5ef..d22c756bb2 100644 --- a/server/src/main/resources/password/pwm/AppProperty.properties +++ b/server/src/main/resources/password/pwm/AppProperty.properties @@ -68,11 +68,10 @@ config.login.history.maxEvents=10 config.fileScanFrequencyMS=5017 config.newuser.passwordPolicyCacheMS=3600000 config.theme=pwm -config.enableJbCryptPwLibrary=true configEditor.blockOldIE=true configEditor.userPermission.matchResultsLimit=5000 configEditor.idleTimeoutSeconds=900 -configEditor.settingFunction.timeoutMs=5000 +configEditor.settingFunction.timeoutMs=30000 configGuide.idleTimeoutSeconds=3600 configManager.zipDebug.maxLogBytes=50000000 configManager.zipDebug.maxLogSeconds=120 @@ -173,7 +172,6 @@ localdb.logWriter.maxTrimSize=5001 localdb.reloadWhenAppRestarted=false macro.randomChar.maxLength=100 macro.ldapAttr.maxLength=100 -logging.cspReport.enable=true logging.devOutput.enable=false logging.extra.periodicThreadDumpIntervalSeconds=0 logging.logOutputMode=traditional @@ -228,8 +226,6 @@ peoplesearch.values.maxCount=100 peoplesearch.view.detail.links= photo.clientCacheTimeSeconds=3600 photo.internalHttpProxy.enable=true -pwNotify.batch.count=100 -pwNotify.batch.delayTimeMultiplier=0.1 pwNotify.maxLdapSearchSize=1000000 pwNotify.maxSkipRerunWindowSeconds=86400 queue.email.retryTimeoutMs=10000 @@ -255,8 +251,8 @@ security.http.stripHeaderRegex=\\n|\\r|(?ism)%0A|%0D security.http.performCsrfHeaderChecks=false security.http.promiscuousEnable=false security.http.permittedUserPhotoMimeTypes=image/gif,image/png,image/jpeg -security.http.permittedUrlPathCharacters=^[a-zA-Z0-9-_]*$ -security.http.config.cspHeader=default-src 'self'; object-src 'none'; img-src 'self' data:; style-src 'self' 'unsafe-inline'; script-src 'self' 'unsafe-eval' 'unsafe-inline'; report-uri @PwmContextPath@/public/command/cspReport +security.http.permittedUrlPathCharacters=^[a-zA-Z0-9-\.!_=]*$ +security.http.config.cspHeader=default-src 'self'; object-src 'none'; img-src 'self' data:; style-src 'self' 'unsafe-inline'; script-src 'self' 'nonce-%NONCE%'; base-uri 'self'; security.httpsServer.selfCert.futureSeconds=63113904 security.httpsServer.selfCert.alg=RSA security.httpsServer.selfCert.keySize=2048 diff --git a/server/src/main/resources/password/pwm/DomainProperty.properties b/server/src/main/resources/password/pwm/DomainProperty.properties index 2e4c1a489d..9c922f46ec 100644 --- a/server/src/main/resources/password/pwm/DomainProperty.properties +++ b/server/src/main/resources/password/pwm/DomainProperty.properties @@ -69,6 +69,7 @@ ldap.password.change.self.enable=true ldap.password.change.helpdesk.enable=true ldap.guid.pattern=@UUID@ ldap.browser.maxEntries=1000 +ldap.browser.maxThreads=10 ldap.search.paging.enable=auto ldap.search.paging.size=500 ldap.search.parallel.enable=true diff --git a/server/src/main/resources/password/pwm/config/PwmSetting.xml b/server/src/main/resources/password/pwm/config/PwmSetting.xml index e674d1dda6..a3a8d3f75b 100644 --- a/server/src/main/resources/password/pwm/config/PwmSetting.xml +++ b/server/src/main/resources/password/pwm/config/PwmSetting.xml @@ -1819,7 +1819,7 @@

    tabindex="5"/> -
    " class="tabContent"> +
    "> <% for (final DisplayElement displayElement : accountInformationBean.getAccountInfo()) { %> <% request.setAttribute("displayElement", displayElement); %> @@ -61,7 +61,7 @@ <% if (!CollectionUtil.isEmpty(accountInformationBean.getFormData())) { %> -
    "/>" class="tabContent"> +
    "/>">
    <% for (final DisplayElement displayElement : accountInformationBean.getFormData()) { %> @@ -75,7 +75,7 @@ <% if (!CollectionUtil.isEmpty(accountInformationBean.getPasswordRules())) { %> -
    " class="tabContent"> +
    ">
    @@ -97,8 +97,8 @@ <% if (!CollectionUtil.isEmpty(accountInformationBean.getAuditData())) {%> -
    " class="tabContent"> -
    +
    "> +
    <% for (final AccountInformationBean.ActivityRecord record : accountInformationBean.getAuditData()) { %> diff --git a/webapp/src/main/webapp/WEB-INF/jsp/activateuser-agreement.jsp b/webapp/src/main/webapp/WEB-INF/jsp/activateuser-agreement.jsp index fa6b866a12..4105a6afd6 100644 --- a/webapp/src/main/webapp/WEB-INF/jsp/activateuser-agreement.jsp +++ b/webapp/src/main/webapp/WEB-INF/jsp/activateuser-agreement.jsp @@ -70,25 +70,10 @@
    - - - + <%@ include file="fragment/footer.jsp" %> diff --git a/webapp/src/main/webapp/WEB-INF/jsp/admin-activity.jsp b/webapp/src/main/webapp/WEB-INF/jsp/admin-activity.jsp index 5c09ffcd5d..209289a22a 100644 --- a/webapp/src/main/webapp/WEB-INF/jsp/admin-activity.jsp +++ b/webapp/src/main/webapp/WEB-INF/jsp/admin-activity.jsp @@ -104,8 +104,8 @@
    -
    -
    +
    +
    <% checked = false; %> <% } %> @@ -192,28 +192,10 @@
    - - - <%@ include file="/WEB-INF/jsp/fragment/footer.jsp" %> diff --git a/webapp/src/main/webapp/WEB-INF/jsp/admin-dashboard.jsp b/webapp/src/main/webapp/WEB-INF/jsp/admin-dashboard.jsp index d12c3b28fc..67ac3ca91b 100644 --- a/webapp/src/main/webapp/WEB-INF/jsp/admin-dashboard.jsp +++ b/webapp/src/main/webapp/WEB-INF/jsp/admin-dashboard.jsp @@ -249,9 +249,9 @@
    - + - - - + <% } %>
    - Domain - + Domain + Service @@ -384,15 +384,12 @@ <%= threadData.getState() %>
    @@ -455,7 +452,7 @@ <% if ( appDashboardData.getNodeStorageMethod() != null ) { %>

    Node communication method: <%= appDashboardData.getNodeStorageMethod() %> -

    +

    <% } %>
    <% } else { %> @@ -464,34 +461,34 @@
    - - -
    - -
    -
    - -
    + + +
    + +
    +
    + +
    -
    - - - - -
    Local Debug Log
    -
    -
    -
    - - +
    + + + + +
    Local Debug Log
    +
    +
    +
    + + +
    -
    @@ -499,44 +496,40 @@
    - - - + PWM_MAIN.addEventHandler('button-showLocalDBCounts','click',function(){ + PWM_MAIN.showWaitDialog({loadFunction:function(){ + PWM_MAIN.gotoUrl('dashboard?showLocalDBCounts=true'); + }}) + }); + PWM_MAIN.addEventHandler('button-showThreadDetails','click',function(){ + PWM_MAIN.showWaitDialog({loadFunction:function(){ + PWM_MAIN.gotoUrl('dashboard?showThreadDetails=true'); + }}) + }); + <% for (final AppDashboardData.ServiceData loopService : appDashboardData.getServices()) { %> + <% if (!CollectionUtil.isEmpty(loopService.getDebugData())) { %> + PWM_MAIN.addEventHandler('serviceName-<%=loopService.getGuid()%>','click',function(){ + let tableText = ''; + <% for (final Map.Entry entry : loopService.getDebugData().entrySet()) { %> + tableText += '' + + ''; + <% } %> + tableText += '
    <%=StringUtil.escapeJS(entry.getKey())%><%=StringUtil.escapeJS(entry.getValue())%>
    '; + PWM_MAIN.showDialog({title:'Debug Properties',text:tableText}); + }); + <% } %> + <% } %> + + PWM_ADMIN.initPwNotifyPage(); + + <%@ include file="/WEB-INF/jsp/fragment/footer.jsp" %> - diff --git a/webapp/src/main/webapp/WEB-INF/jsp/admin-reporting.jsp b/webapp/src/main/webapp/WEB-INF/jsp/admin-reporting.jsp index c12e92e546..f5f8446330 100644 --- a/webapp/src/main/webapp/WEB-INF/jsp/admin-reporting.jsp +++ b/webapp/src/main/webapp/WEB-INF/jsp/admin-reporting.jsp @@ -89,15 +89,12 @@
    - - - + <% JspUtility.setFlag(pageContext, PwmRequestFlag.HIDE_LOCALE); %> <%@ include file="/WEB-INF/jsp/fragment/footer.jsp" %> diff --git a/webapp/src/main/webapp/WEB-INF/jsp/admin-statistics.jsp b/webapp/src/main/webapp/WEB-INF/jsp/admin-statistics.jsp index 5b2bacad10..b0be8d64ef 100644 --- a/webapp/src/main/webapp/WEB-INF/jsp/admin-statistics.jsp +++ b/webapp/src/main/webapp/WEB-INF/jsp/admin-statistics.jsp @@ -49,8 +49,6 @@ <%@ include file="/WEB-INF/jsp/fragment/header.jsp" %>
    - - <% final String PageName = JspUtility.localizedString(pageContext,"Title_Statistics",Admin.class);%> @@ -108,13 +106,10 @@
    - - - + <% JspUtility.setFlag(pageContext, PwmRequestFlag.HIDE_LOCALE); %> <%@ include file="/WEB-INF/jsp/fragment/footer.jsp" %> diff --git a/webapp/src/main/webapp/WEB-INF/jsp/admin-system-certificates.jsp b/webapp/src/main/webapp/WEB-INF/jsp/admin-system-certificates.jsp index 39291ac13b..655588d76f 100644 --- a/webapp/src/main/webapp/WEB-INF/jsp/admin-system-certificates.jsp +++ b/webapp/src/main/webapp/WEB-INF/jsp/admin-system-certificates.jsp @@ -33,6 +33,7 @@ <%@ page language="java" session="true" isThreadSafe="true" contentType="text/html" %> <% JspUtility.setFlag(pageContext, PwmRequestFlag.INCLUDE_CONFIG_CSS);%> +<% JspUtility.setFlag(pageContext, PwmRequestFlag.INCLUDE_DOJO); %> <%@ taglib uri="pwm" prefix="pwm" %> "/>" dir=""> <%@ include file="fragment/header.jsp" %> @@ -55,70 +56,65 @@
    - - - - - - +
    <%@ include file="fragment/footer.jsp" %>
    diff --git a/webapp/src/main/webapp/WEB-INF/jsp/admin-tokenlookup.jsp b/webapp/src/main/webapp/WEB-INF/jsp/admin-tokenlookup.jsp index 9d15a24385..40edbb7064 100644 --- a/webapp/src/main/webapp/WEB-INF/jsp/admin-tokenlookup.jsp +++ b/webapp/src/main/webapp/WEB-INF/jsp/admin-tokenlookup.jsp @@ -96,7 +96,7 @@ Name - <%= tokenPayload.getName() %> + <%= tokenPayload.name() %> @@ -104,7 +104,7 @@ UserDN - <%= tokenPayload.getUserIdentity() %> + <%= tokenPayload.userIdentity() %> @@ -112,7 +112,7 @@ Issue Date - <%= StringUtil.toIsoDate(tokenPayload.getIssueTime()) %> + <%= StringUtil.toIsoDate(tokenPayload.issueTime()) %> @@ -120,7 +120,7 @@ Expiration Date - <%= StringUtil.toIsoDate(tokenPayload.getExpiration()) %> + <%= StringUtil.toIsoDate(tokenPayload.expiration()) %> @@ -128,7 +128,7 @@ Destination(s) - <% TokenDestinationItem tokenDestinationItem = tokenPayload.getDestination(); %> + <% TokenDestinationItem tokenDestinationItem = tokenPayload.destination(); %> <% if ( tokenDestinationItem != null ) { %> @@ -147,13 +147,13 @@ - + - + - + - + - + - + - + @@ -175,7 +177,7 @@ @@ -183,7 +185,7 @@ @@ -191,7 +193,7 @@ @@ -199,7 +201,7 @@ Password Readable From LDAP @@ -207,7 +209,7 @@ Requires New Password @@ -215,7 +217,7 @@ Requires Response Setup @@ -223,7 +225,7 @@ Requires OTP Setup @@ -231,7 +233,7 @@ Requires Profile Update @@ -239,12 +241,12 @@ Password is Within Minimum Lifetime - +
    - <%for (final String key : tokenPayload.getData().keySet()) { %> + <%for (final String key : tokenPayload.data().keySet()) { %> <% } %> diff --git a/webapp/src/main/webapp/WEB-INF/jsp/admin-user-debug.jsp b/webapp/src/main/webapp/WEB-INF/jsp/admin-user-debug.jsp index f7d3f2742d..7f026d1b99 100644 --- a/webapp/src/main/webapp/WEB-INF/jsp/admin-user-debug.jsp +++ b/webapp/src/main/webapp/WEB-INF/jsp/admin-user-debug.jsp @@ -39,6 +39,7 @@ <%@ page import="password.pwm.config.PwmSetting" %> <%@ page import="password.pwm.svc.PwmService" %> <%@ page import="password.pwm.http.servlet.admin.domain.DomainAdminUserDebugServlet" %> +<%@ page import="password.pwm.user.UserInfo" %> <%@ page language="java" session="true" isThreadSafe="true" contentType="text/html" %> <%@ taglib uri="pwm" prefix="pwm" %> @@ -54,6 +55,7 @@ <%@ include file="fragment/admin-modular-nav.jsp" %> <% final UserDebugDataBean userDebugDataBean = (UserDebugDataBean)JspUtility.getAttribute(pageContext, PwmRequestAttribute.UserDebugData); %> + <% final UserInfo userInfo = (UserInfo)JspUtility.getAttribute(pageContext, PwmRequestAttribute.UserDebugInfo); %> <% if (userDebugDataBean == null) { %> <%@ include file="/WEB-INF/jsp/fragment/message.jsp" %> - <% final PublicUserInfoBean userInfo = userDebugDataBean.getPublicUserInfoBean(); %> - <% if (userInfo != null) { %> + <% final PublicUserInfoBean publicUserInfoBean = userDebugDataBean.publicUserInfoBean(); %> + <% if (publicUserInfoBean != null) { %>
    <%=key%> - <%=tokenPayload.getData().get(key)%> + <%=tokenPayload.data().get(key)%>
    - + - + - + - +
    Identity
    UserDN<%=JspUtility.friendlyWrite(pageContext, userInfo.getUserDN())%><%=JspUtility.friendlyWrite(pageContext, publicUserInfoBean.getUserDN())%>
    Ldap Profile<%=JspUtility.friendlyWrite(pageContext, userInfo.getLdapProfile())%><%=JspUtility.friendlyWrite(pageContext, publicUserInfoBean.getLdapProfile())%>
    Username<%=JspUtility.friendlyWrite(pageContext, userInfo.getUserID())%><%=JspUtility.friendlyWrite(pageContext, publicUserInfoBean.getUserID())%>
    <%=PwmConstants.PWM_APP_NAME%> GUID<%=JspUtility.friendlyWrite(pageContext, userInfo.getUserGUID())%><%=JspUtility.friendlyWrite(pageContext, publicUserInfoBean.getUserGUID())%>

    @@ -101,73 +103,73 @@
    Last Login Time - <%=JspUtility.friendlyWrite(pageContext, userInfo.getLastLoginTime())%> - <% if ( userInfo.getLastLoginTime() != null ) { %> - ( <%=TimeDuration.fromCurrent(userInfo.getLastLoginTime()).asCompactString()%> ) + <%=JspUtility.friendlyWrite(pageContext, publicUserInfoBean.getLastLoginTime())%> + <% if ( publicUserInfoBean.getLastLoginTime() != null ) { %> + ( <%=TimeDuration.fromCurrent(publicUserInfoBean.getLastLoginTime()).asCompactString()%> ) <% } %>
    Account Expiration Time - <%=JspUtility.friendlyWrite(pageContext, userInfo.getAccountExpirationTime())%> - <% if ( userInfo.getAccountExpirationTime() != null ) { %> - ( <%=TimeDuration.fromCurrent(userInfo.getAccountExpirationTime()).asCompactString()%> ) + <%=JspUtility.friendlyWrite(pageContext, publicUserInfoBean.getAccountExpirationTime())%> + <% if ( publicUserInfoBean.getAccountExpirationTime() != null ) { %> + ( <%=TimeDuration.fromCurrent(publicUserInfoBean.getAccountExpirationTime()).asCompactString()%> ) <% } %>
    Password Expiration - <%=JspUtility.friendlyWrite(pageContext, userInfo.getPasswordExpirationTime())%> - <% if ( userInfo.getPasswordExpirationTime() != null ) { %> - ( <%=TimeDuration.fromCurrent(userInfo.getPasswordExpirationTime()).asCompactString()%> ) + <%=JspUtility.friendlyWrite(pageContext, publicUserInfoBean.getPasswordExpirationTime())%> + <% if ( publicUserInfoBean.getPasswordExpirationTime() != null ) { %> + ( <%=TimeDuration.fromCurrent(publicUserInfoBean.getPasswordExpirationTime()).asCompactString()%> ) <% } %>
    Password Last Modified - <%=JspUtility.friendlyWrite(pageContext, userInfo.getPasswordLastModifiedTime())%> - <% if ( userInfo.getPasswordLastModifiedTime() != null ) { %> - ( <%=TimeDuration.fromCurrent(userInfo.getPasswordLastModifiedTime()).asCompactString()%> ) + <%=JspUtility.friendlyWrite(pageContext, publicUserInfoBean.getPasswordLastModifiedTime())%> + <% if ( publicUserInfoBean.getPasswordLastModifiedTime() != null ) { %> + ( <%=TimeDuration.fromCurrent(publicUserInfoBean.getPasswordLastModifiedTime()).asCompactString()%> ) <% } %>
    Email Address 1<%=JspUtility.friendlyWrite(pageContext, userInfo.getUserEmailAddress())%><%=JspUtility.friendlyWrite(pageContext, publicUserInfoBean.getUserEmailAddress())%>
    Email Address 2<%=JspUtility.friendlyWrite(pageContext, userInfo.getUserEmailAddress2())%><%=JspUtility.friendlyWrite(pageContext, publicUserInfoBean.getUserEmailAddress2())%>
    Email Address 3<%=JspUtility.friendlyWrite(pageContext, userInfo.getUserEmailAddress3())%><%=JspUtility.friendlyWrite(pageContext, publicUserInfoBean.getUserEmailAddress3())%>
    Phone Number 1<%=JspUtility.friendlyWrite(pageContext, userDebugDataBean.getUserInfo().getUserSmsNumber())%><%=JspUtility.friendlyWrite(pageContext, userInfo.getUserSmsNumber())%>
    Phone Number 2<%=JspUtility.friendlyWrite(pageContext, userDebugDataBean.getUserInfo().getUserSmsNumber2())%><%=JspUtility.friendlyWrite(pageContext, userInfo.getUserSmsNumber2())%>
    Phone Number 3<%=JspUtility.friendlyWrite(pageContext, userDebugDataBean.getUserInfo().getUserSmsNumber3())%><%=JspUtility.friendlyWrite(pageContext, userInfo.getUserSmsNumber3())%>
    Username<%=JspUtility.friendlyWrite(pageContext, userInfo.getUserID())%><%=JspUtility.friendlyWrite(pageContext, publicUserInfoBean.getUserID())%>
    - <%= JspUtility.friendlyWrite(pageContext, userInfo.getPasswordStatus().isExpired()) %> + <%= JspUtility.friendlyWrite(pageContext, publicUserInfoBean.getPasswordStatus().expired()) %>
    - <%= JspUtility.friendlyWrite(pageContext, userInfo.getPasswordStatus().isPreExpired()) %> + <%= JspUtility.friendlyWrite(pageContext, publicUserInfoBean.getPasswordStatus().preExpired()) %>
    - <%= JspUtility.friendlyWrite(pageContext, userInfo.getPasswordStatus().isWarnPeriod()) %> + <%= JspUtility.friendlyWrite(pageContext, publicUserInfoBean.getPasswordStatus().warnPeriod()) %>
    - <%= JspUtility.friendlyWrite(pageContext, userInfo.getPasswordStatus().isViolatesPolicy()) %> + <%= JspUtility.friendlyWrite(pageContext, publicUserInfoBean.getPasswordStatus().violatesPolicy()) %>
    - <%= JspUtility.friendlyWrite(pageContext, userDebugDataBean.isPasswordReadable()) %> + <%= JspUtility.friendlyWrite(pageContext, userDebugDataBean.passwordReadable()) %>
    - <%= JspUtility.friendlyWrite(pageContext, userInfo.isRequiresNewPassword()) %> + <%= JspUtility.friendlyWrite(pageContext, publicUserInfoBean.isRequiresNewPassword()) %>
    - <%= JspUtility.friendlyWrite(pageContext, userInfo.isRequiresResponseConfig()) %> + <%= JspUtility.friendlyWrite(pageContext, publicUserInfoBean.isRequiresResponseConfig()) %>
    - <%= JspUtility.friendlyWrite(pageContext, userInfo.isRequiresOtpConfig()) %> + <%= JspUtility.friendlyWrite(pageContext, publicUserInfoBean.isRequiresOtpConfig()) %>
    - <%= JspUtility.friendlyWrite(pageContext, userInfo.isRequiresUpdateProfile()) %> + <%= JspUtility.friendlyWrite(pageContext, publicUserInfoBean.isRequiresUpdateProfile()) %>
    - <%= JspUtility.friendlyWrite(pageContext, userDebugDataBean.isPasswordWithinMinimumLifetime()) %> + <%= JspUtility.friendlyWrite(pageContext, userDebugDataBean.passwordWithinMinimumLifetime()) %>
    Stored Language<%=JspUtility.friendlyWrite(pageContext, userInfo.getLanguage() )%><%=JspUtility.friendlyWrite(pageContext, publicUserInfoBean.getLanguage() )%>

    @@ -254,7 +256,7 @@ Password Notification Status - <% if ( userDebugDataBean.getPwNotifyUserStatus() == null ) { %> + <% if ( userDebugDataBean.pwNotifyUserStatus() == null ) { %> Last Notification Sent @@ -263,19 +265,19 @@ Last Notification Sent - <%=JspUtility.friendlyWrite(pageContext, userDebugDataBean.getPwNotifyUserStatus().getLastNotice())%> + <%=JspUtility.friendlyWrite(pageContext, userDebugDataBean.pwNotifyUserStatus().getLastNotice())%> Last Notification Password Expiration Time - <%=JspUtility.friendlyWrite(pageContext, userDebugDataBean.getPwNotifyUserStatus().getExpireTime())%> + <%=JspUtility.friendlyWrite(pageContext, userDebugDataBean.pwNotifyUserStatus().getExpireTime())%> Last Notification Interval - <%=userDebugDataBean.getPwNotifyUserStatus().getInterval()%> + <%=userDebugDataBean.pwNotifyUserStatus().getInterval()%> <% } %> @@ -298,10 +300,10 @@ - <% for (final ProfileDefinition profileDefinition : userDebugDataBean.getProfiles().keySet()) { %> + <% for (final ProfileDefinition profileDefinition : userDebugDataBean.profiles().keySet()) { %> <%=profileDefinition%> - <%=JspUtility.friendlyWrite(pageContext, userDebugDataBean.getProfiles().get(profileDefinition).stringValue())%> + <%=JspUtility.friendlyWrite(pageContext, userDebugDataBean.profiles().get(profileDefinition).stringValue())%> <% } %> @@ -319,10 +321,10 @@ - <% for (final Permission permission : userDebugDataBean.getPermissions().keySet()) { %> + <% for (final Permission permission : userDebugDataBean.permissions().keySet()) { %> <%=permission%> - <%=JspUtility.friendlyWrite(pageContext, userDebugDataBean.getPermissions().get(permission))%> + <%=JspUtility.friendlyWrite(pageContext, userDebugDataBean.permissions().get(permission))%> <% } %> @@ -335,10 +337,10 @@ Password Policy - <% PwmPasswordPolicy userPolicy = userDebugDataBean.getUserInfo().getPasswordPolicy(); %> + <% PwmPasswordPolicy userPolicy = userInfo.getPasswordPolicy(); %> <% if (userPolicy != null) { %> - <% PwmPasswordPolicy configPolicy = userDebugDataBean.getConfiguredPasswordPolicy(); %> - <% PwmPasswordPolicy ldapPolicy = userDebugDataBean.getLdapPasswordPolicy(); %> + <% PwmPasswordPolicy configPolicy = userDebugDataBean.configuredPasswordPolicy(); %> + <% PwmPasswordPolicy ldapPolicy = userDebugDataBean.ldapPasswordPolicy(); %> @@ -386,7 +388,7 @@ - <% final ResponseInfoBean responseInfoBean = userDebugDataBean.getUserInfo().getResponseInfoBean(); %> + <% final ResponseInfoBean responseInfoBean = userDebugDataBean.responseInfoBean(); %> <% if (responseInfoBean == null) { %> @@ -480,7 +482,7 @@ - <% final ChallengeProfile challengeProfile = userDebugDataBean.getUserInfo().getChallengeProfile(); %> + <% final ChallengeProfile challengeProfile = userInfo.getChallengeProfile(); %> <% if ( challengeProfile == null ) { %> diff --git a/webapp/src/main/webapp/WEB-INF/jsp/changepassword-agreement.jsp b/webapp/src/main/webapp/WEB-INF/jsp/changepassword-agreement.jsp index f8ea62989e..4ec0a50c9b 100644 --- a/webapp/src/main/webapp/WEB-INF/jsp/changepassword-agreement.jsp +++ b/webapp/src/main/webapp/WEB-INF/jsp/changepassword-agreement.jsp @@ -42,7 +42,7 @@

    <% final PasswordStatus passwordStatus = JspUtility.getPwmSession(pageContext).getUserInfo().getPasswordStatus(); %> - <% if (passwordStatus.isExpired() || passwordStatus.isPreExpired() || passwordStatus.isViolatesPolicy()) { %> + <% if (passwordStatus.expired() || passwordStatus.preExpired() || passwordStatus.violatesPolicy()) { %>


    <% } %> <%@ include file="fragment/message.jsp" %> @@ -80,26 +80,10 @@
    - - - + <%@ include file="fragment/footer.jsp" %> diff --git a/webapp/src/main/webapp/WEB-INF/jsp/changepassword-form.jsp b/webapp/src/main/webapp/WEB-INF/jsp/changepassword-form.jsp index 8941a7477e..dd6d230750 100644 --- a/webapp/src/main/webapp/WEB-INF/jsp/changepassword-form.jsp +++ b/webapp/src/main/webapp/WEB-INF/jsp/changepassword-form.jsp @@ -42,7 +42,7 @@

    - <% if (passwordStatus.isExpired() || passwordStatus.isPreExpired() || passwordStatus.isViolatesPolicy()) { %> + <% if (passwordStatus.expired() || passwordStatus.preExpired() || passwordStatus.violatesPolicy()) { %>


    <% } %>

    @@ -63,7 +63,7 @@ - <% if (!passwordStatus.isExpired() && !passwordStatus.isPreExpired() && !passwordStatus.isViolatesPolicy()) { %> + <% if (!passwordStatus.expired() && !passwordStatus.preExpired() && !passwordStatus.violatesPolicy()) { %> <%@ include file="/WEB-INF/jsp/fragment/cancel-button.jsp" %> <% } %> diff --git a/webapp/src/main/webapp/WEB-INF/jsp/changepassword-wait.jsp b/webapp/src/main/webapp/WEB-INF/jsp/changepassword-wait.jsp index 847d30ca56..6f51b1655a 100644 --- a/webapp/src/main/webapp/WEB-INF/jsp/changepassword-wait.jsp +++ b/webapp/src/main/webapp/WEB-INF/jsp/changepassword-wait.jsp @@ -68,14 +68,10 @@
    - - - - + <%@ include file="/WEB-INF/jsp/fragment/footer.jsp" %> diff --git a/webapp/src/main/webapp/WEB-INF/jsp/changepassword.jsp b/webapp/src/main/webapp/WEB-INF/jsp/changepassword.jsp index 69fd975aba..0a7dd427fb 100644 --- a/webapp/src/main/webapp/WEB-INF/jsp/changepassword.jsp +++ b/webapp/src/main/webapp/WEB-INF/jsp/changepassword.jsp @@ -79,16 +79,10 @@
    - - - - - - + <%@ include file="fragment/footer.jsp" %> diff --git a/webapp/src/main/webapp/WEB-INF/jsp/configeditor.jsp b/webapp/src/main/webapp/WEB-INF/jsp/configeditor.jsp index 3319a09581..b96bd227dc 100644 --- a/webapp/src/main/webapp/WEB-INF/jsp/configeditor.jsp +++ b/webapp/src/main/webapp/WEB-INF/jsp/configeditor.jsp @@ -185,33 +185,12 @@ --%> - - - - - - - - - - - - - - - - - - -<%--/ Provide the angular code we made specifically for this page:--%> - + <%@ include file="fragment/footer.jsp" %> diff --git a/webapp/src/main/webapp/WEB-INF/jsp/configguide-cr_policy.jsp b/webapp/src/main/webapp/WEB-INF/jsp/configguide-cr_policy.jsp index 987531baa7..63d767afc5 100644 --- a/webapp/src/main/webapp/WEB-INF/jsp/configguide-cr_policy.jsp +++ b/webapp/src/main/webapp/WEB-INF/jsp/configguide-cr_policy.jsp @@ -80,7 +80,6 @@ function initPage() { PWM_CFGEDIT.initConfigSettingsDefinition(function(){ - PWM_VAR['outstandingOperations'] = 0; ChallengeSettingHandler.init('<%=PwmSetting.CHALLENGE_RANDOM_CHALLENGES.getKey()%>'); }); } diff --git a/webapp/src/main/webapp/WEB-INF/jsp/configguide-database.jsp b/webapp/src/main/webapp/WEB-INF/jsp/configguide-database.jsp index 34377bc39b..70ea530de9 100644 --- a/webapp/src/main/webapp/WEB-INF/jsp/configguide-database.jsp +++ b/webapp/src/main/webapp/WEB-INF/jsp/configguide-database.jsp @@ -58,16 +58,16 @@ <% final FileValue.FileInfo fileInfo = configGuideBean.getDatabaseDriver().toInfoMap().iterator().next(); %>
    Stored Responses
    Stored Responses
    Challenge Profile
    Assigned Profile
    - + - + - + - +
    Name<%=fileInfo.getName()%>Name<%=fileInfo.name()%>
    Type<%=fileInfo.getType()%>Type<%=fileInfo.type()%>
    Size<%=fileInfo.getSize()%>Size<%=fileInfo.size()%>
    sha512<%=fileInfo.getSha512sum()%>sha512<%=fileInfo.sha512sum()%>
    <% } %> @@ -217,8 +217,8 @@ uploadOptions['nextFunction'] = function() { PWM_MAIN.gotoUrl('config-guide'); }; - PWM_MAIN.IdleTimeoutHandler.cancelCountDownTimer(); - UILibrary.uploadFileDialog(uploadOptions); + PWM_MAIN.cancelCountDownTimer(); + PWM_UILibrary.uploadFileDialog(uploadOptions); } diff --git a/webapp/src/main/webapp/WEB-INF/jsp/configguide-ldap_admins.jsp b/webapp/src/main/webapp/WEB-INF/jsp/configguide-ldap_admins.jsp index d716aee4af..12a2b8dd39 100644 --- a/webapp/src/main/webapp/WEB-INF/jsp/configguide-ldap_admins.jsp +++ b/webapp/src/main/webapp/WEB-INF/jsp/configguide-ldap_admins.jsp @@ -113,7 +113,7 @@ }); PWM_MAIN.addEventHandler('button-browse-adminGroup','click',function(){ - UILibrary.editLdapDN(function(value){ + PWM_UILibrary.editLdapDN(function(value){ PWM_MAIN.getObject('<%=ConfigGuideFormField.PARAM_LDAP_ADMIN_USER%>').value = value; handleFormActivity(); }) diff --git a/webapp/src/main/webapp/WEB-INF/jsp/configguide-ldap_context.jsp b/webapp/src/main/webapp/WEB-INF/jsp/configguide-ldap_context.jsp index 718ade8d11..70aac5744c 100644 --- a/webapp/src/main/webapp/WEB-INF/jsp/configguide-ldap_context.jsp +++ b/webapp/src/main/webapp/WEB-INF/jsp/configguide-ldap_context.jsp @@ -97,7 +97,7 @@ PWM_MAIN.addEventHandler('healthBody','click',function(){loadHealth()}); PWM_MAIN.addEventHandler('button-browse-context','click',function(){ - UILibrary.editLdapDN(function(value){ + PWM_UILibrary.editLdapDN(function(value){ PWM_MAIN.getObject('<%=ConfigGuideFormField.PARAM_LDAP_CONTEXT%>').value = value; handleFormActivity(); }) diff --git a/webapp/src/main/webapp/WEB-INF/jsp/configguide-ldap_proxy.jsp b/webapp/src/main/webapp/WEB-INF/jsp/configguide-ldap_proxy.jsp index 390c257f20..b9e11faede 100644 --- a/webapp/src/main/webapp/WEB-INF/jsp/configguide-ldap_proxy.jsp +++ b/webapp/src/main/webapp/WEB-INF/jsp/configguide-ldap_proxy.jsp @@ -113,7 +113,7 @@ PWM_MAIN.addEventHandler('healthBody','click',function(){loadHealth()}); PWM_MAIN.addEventHandler('button-browse-adminDN','click',function(){ - UILibrary.editLdapDN(function(value){ + PWM_UILibrary.editLdapDN(function(value){ PWM_MAIN.getObject('<%=ConfigGuideFormField.PARAM_LDAP_PROXY_DN%>').value = value; handleFormActivity(); }) diff --git a/webapp/src/main/webapp/WEB-INF/jsp/configguide-ldap_schema.jsp b/webapp/src/main/webapp/WEB-INF/jsp/configguide-ldap_schema.jsp index 6a75479439..c6209e7209 100644 --- a/webapp/src/main/webapp/WEB-INF/jsp/configguide-ldap_schema.jsp +++ b/webapp/src/main/webapp/WEB-INF/jsp/configguide-ldap_schema.jsp @@ -49,8 +49,8 @@ try { final PwmDomain pwmDomain = JspUtility.getPwmRequest(pageContext).getPwmDomain(); final SchemaOperationResult schemaManager = ConfigGuideUtils.extendSchema( pwmDomain, configGuideBean, false); - existingSchemaGood = schemaManager.isSuccess(); - schemaActivityLog = schemaManager.getOperationLog(); + existingSchemaGood = schemaManager.success(); + schemaActivityLog = schemaManager.operationLog(); } catch (Exception e) { schemaActivityLog = "unable to check schema: " + e.getMessage(); } diff --git a/webapp/src/main/webapp/WEB-INF/jsp/configguide-ldap_testuser.jsp b/webapp/src/main/webapp/WEB-INF/jsp/configguide-ldap_testuser.jsp index ac2447ec14..48154d5da3 100644 --- a/webapp/src/main/webapp/WEB-INF/jsp/configguide-ldap_testuser.jsp +++ b/webapp/src/main/webapp/WEB-INF/jsp/configguide-ldap_testuser.jsp @@ -117,7 +117,7 @@ PWM_MAIN.addEventHandler('healthBody','click',function(){loadHealth()}); PWM_MAIN.addEventHandler('button-browse-testUser','click',function(){ - UILibrary.editLdapDN(function(value){ + PWM_UILibrary.editLdapDN(function(value){ PWM_MAIN.getObject('<%=ConfigGuideFormField.PARAM_LDAP_TEST_USER%>').value = value; handleFormActivity(); }) diff --git a/webapp/src/main/webapp/WEB-INF/jsp/configguide-password.jsp b/webapp/src/main/webapp/WEB-INF/jsp/configguide-password.jsp index c4b5d23074..80c50e8a7f 100644 --- a/webapp/src/main/webapp/WEB-INF/jsp/configguide-password.jsp +++ b/webapp/src/main/webapp/WEB-INF/jsp/configguide-password.jsp @@ -83,7 +83,7 @@ checkIfNextEnabled(); }; - UILibrary.passwordDialogPopup({writeFunction:writeFunction}) + PWM_UILibrary.passwordDialogPopup({writeFunction:writeFunction}) }); checkIfNextEnabled(); diff --git a/webapp/src/main/webapp/WEB-INF/jsp/configmanager-localdb.jsp b/webapp/src/main/webapp/WEB-INF/jsp/configmanager-localdb.jsp index 51d899e846..0e24b9d571 100644 --- a/webapp/src/main/webapp/WEB-INF/jsp/configmanager-localdb.jsp +++ b/webapp/src/main/webapp/WEB-INF/jsp/configmanager-localdb.jsp @@ -185,7 +185,6 @@ -
    <%@ include file="fragment/footer.jsp" %>
    diff --git a/webapp/src/main/webapp/WEB-INF/jsp/configmanager-login.jsp b/webapp/src/main/webapp/WEB-INF/jsp/configmanager-login.jsp index 29f19a6e56..05592f2b94 100755 --- a/webapp/src/main/webapp/WEB-INF/jsp/configmanager-login.jsp +++ b/webapp/src/main/webapp/WEB-INF/jsp/configmanager-login.jsp @@ -47,7 +47,6 @@ "/>" dir=""> <%@ include file="fragment/header.jsp" %> -
    - - - - - - +
    <%@ include file="fragment/footer.jsp" %>
    diff --git a/webapp/src/main/webapp/WEB-INF/jsp/configmanager.jsp b/webapp/src/main/webapp/WEB-INF/jsp/configmanager.jsp index 8a081ee5f6..15c66cec9e 100644 --- a/webapp/src/main/webapp/WEB-INF/jsp/configmanager.jsp +++ b/webapp/src/main/webapp/WEB-INF/jsp/configmanager.jsp @@ -138,15 +138,6 @@
    - - - @@ -157,33 +148,12 @@ - - - "> - - - @@ -196,29 +166,12 @@ Configuration Summary - - - - "> - - - @@ -227,32 +180,44 @@ LDAP Permissions - - - -
    - - - - - - +
    <%@ include file="fragment/footer.jsp" %>
    diff --git a/webapp/src/main/webapp/WEB-INF/jsp/deleteaccount-agreement.jsp b/webapp/src/main/webapp/WEB-INF/jsp/deleteaccount-agreement.jsp index e49fcda74b..4bee55e1d2 100644 --- a/webapp/src/main/webapp/WEB-INF/jsp/deleteaccount-agreement.jsp +++ b/webapp/src/main/webapp/WEB-INF/jsp/deleteaccount-agreement.jsp @@ -70,27 +70,10 @@
    - - - + <%@ include file="fragment/footer.jsp" %> diff --git a/webapp/src/main/webapp/WEB-INF/jsp/forgottenpassword-agreement.jsp b/webapp/src/main/webapp/WEB-INF/jsp/forgottenpassword-agreement.jsp index 4c152016d1..8db32a5753 100644 --- a/webapp/src/main/webapp/WEB-INF/jsp/forgottenpassword-agreement.jsp +++ b/webapp/src/main/webapp/WEB-INF/jsp/forgottenpassword-agreement.jsp @@ -69,25 +69,10 @@
    - - - + <%@ include file="fragment/footer.jsp" %> diff --git a/webapp/src/main/webapp/WEB-INF/jsp/forgottenpassword-attributes.jsp b/webapp/src/main/webapp/WEB-INF/jsp/forgottenpassword-attributes.jsp index 37331603d7..9ea431fe88 100644 --- a/webapp/src/main/webapp/WEB-INF/jsp/forgottenpassword-attributes.jsp +++ b/webapp/src/main/webapp/WEB-INF/jsp/forgottenpassword-attributes.jsp @@ -69,20 +69,10 @@ this is handled this way so on browsers where hiding fields is not possible, the
    - - - - + <%@ include file="fragment/footer.jsp" %> diff --git a/webapp/src/main/webapp/WEB-INF/jsp/forgottenpassword-entertoken.jsp b/webapp/src/main/webapp/WEB-INF/jsp/forgottenpassword-entertoken.jsp index 24702f068c..cf5698f834 100644 --- a/webapp/src/main/webapp/WEB-INF/jsp/forgottenpassword-entertoken.jsp +++ b/webapp/src/main/webapp/WEB-INF/jsp/forgottenpassword-entertoken.jsp @@ -52,35 +52,6 @@


    - - - <% } %>
    <%@ include file="/WEB-INF/jsp/fragment/message.jsp" %> @@ -120,6 +91,10 @@
    + <%@ include file="fragment/footer.jsp" %> diff --git a/webapp/src/main/webapp/WEB-INF/jsp/forgottenpassword-responses.jsp b/webapp/src/main/webapp/WEB-INF/jsp/forgottenpassword-responses.jsp index fcef4bec21..641ee70a86 100644 --- a/webapp/src/main/webapp/WEB-INF/jsp/forgottenpassword-responses.jsp +++ b/webapp/src/main/webapp/WEB-INF/jsp/forgottenpassword-responses.jsp @@ -79,16 +79,11 @@ this is handled this way so on browsers where hiding fields is not possible, the
    - - - - + + <%@ include file="fragment/footer.jsp" %> diff --git a/webapp/src/main/webapp/WEB-INF/jsp/fragment/admin-nav.jsp b/webapp/src/main/webapp/WEB-INF/jsp/fragment/admin-nav.jsp index 8bd8ae24b7..e0f6d4f5a2 100644 --- a/webapp/src/main/webapp/WEB-INF/jsp/fragment/admin-nav.jsp +++ b/webapp/src/main/webapp/WEB-INF/jsp/fragment/admin-nav.jsp @@ -22,7 +22,7 @@ See the README.TXT file in WEB-INF/jsp before making changes. --%> - +<%@ page import="password.pwm.http.tag.value.PwmValue" %> <%@ page import="password.pwm.http.servlet.admin.SystemAdminServlet" %> <%@ page import="password.pwm.http.servlet.PwmServletDefinition" %> <%@ page import="password.pwm.http.tag.conditional.PwmIfTest" %> @@ -30,19 +30,12 @@ <%@ taglib uri="pwm" prefix="pwm" %> <% final SystemAdminServlet.Page currentPage = SystemAdminServlet.Page.forUrl(JspUtility.getPwmRequest(pageContext).getURL()).orElseThrow(); %> - - " rel="stylesheet" type="text/css"/> " rel="stylesheet" type="text/css"/> - - - +
    <% boolean selected = currentPage == SystemAdminServlet.Page.dashboard; %>
    diff --git a/webapp/src/main/webapp/WEB-INF/jsp/fragment/cancel-button.jsp b/webapp/src/main/webapp/WEB-INF/jsp/fragment/cancel-button.jsp index d9770c3461..a86ff506f6 100644 --- a/webapp/src/main/webapp/WEB-INF/jsp/fragment/cancel-button.jsp +++ b/webapp/src/main/webapp/WEB-INF/jsp/fragment/cancel-button.jsp @@ -24,26 +24,28 @@ <%@ page import="password.pwm.http.tag.conditional.PwmIfTest" %> +<%@ page import="password.pwm.http.tag.value.PwmValue" %> <%@ page language="java" session="true" isThreadSafe="true" contentType="text/html" %> <%@ taglib uri="pwm" prefix="pwm" %> - - - <%-- ie doesn't support 'from' attribute on buttons, so handle with this script --%> - - - + + + diff --git a/webapp/src/main/webapp/WEB-INF/jsp/fragment/captcha-embed.jsp b/webapp/src/main/webapp/WEB-INF/jsp/fragment/captcha-embed.jsp index 8971cac187..282ae37a7f 100644 --- a/webapp/src/main/webapp/WEB-INF/jsp/fragment/captcha-embed.jsp +++ b/webapp/src/main/webapp/WEB-INF/jsp/fragment/captcha-embed.jsp @@ -43,12 +43,12 @@ - - - " rel="stylesheet" type="text/css"/> + "/>" href="" rel="stylesheet" type="text/css"/> - - - + + + - + diff --git a/webapp/src/main/webapp/WEB-INF/jsp/fragment/forgottenpassword-cancel.jsp b/webapp/src/main/webapp/WEB-INF/jsp/fragment/forgottenpassword-cancel.jsp index b5336f0260..912984b4d2 100644 --- a/webapp/src/main/webapp/WEB-INF/jsp/fragment/forgottenpassword-cancel.jsp +++ b/webapp/src/main/webapp/WEB-INF/jsp/fragment/forgottenpassword-cancel.jsp @@ -26,6 +26,7 @@ <%@ page import="password.pwm.http.servlet.PwmServletDefinition" %> <%@ page import="password.pwm.http.servlet.forgottenpw.ForgottenPasswordServlet" %> <%@ page import="password.pwm.http.tag.conditional.PwmIfTest" %> +<%@ page import="password.pwm.http.tag.value.PwmValue" %> <%@ page language="java" session="true" isThreadSafe="true" contentType="text/html" %> <%@ taglib uri="pwm" prefix="pwm" %> @@ -34,13 +35,10 @@ - - - + diff --git a/webapp/src/main/webapp/WEB-INF/jsp/fragment/form.jsp b/webapp/src/main/webapp/WEB-INF/jsp/fragment/form.jsp index ed0dc4f1f4..f39da84351 100644 --- a/webapp/src/main/webapp/WEB-INF/jsp/fragment/form.jsp +++ b/webapp/src/main/webapp/WEB-INF/jsp/fragment/form.jsp @@ -126,33 +126,28 @@ - - - + <% if (!StringUtil.isEmpty( currentValue) ) { %> - - - + <% } %>
    <% } %> @@ -184,44 +179,29 @@ <%if(loopConfiguration.isRequired()){%> required="required"<%}%> <%if(loopConfiguration.isReadonly()){%> readonly="readonly"<%}%> maxlength="<%=loopConfiguration.getMaximumLength()%>"/> - - - - <% } %> + + <% } %> <% } %> - <% if (loopConfiguration.getJavascript() != null && loopConfiguration.getJavascript().length() > 0) { %> - - - <% } %> <% if (loopConfiguration.getRegexError(formLocale) != null && loopConfiguration.getRegexError(formLocale).length() > 0) { %> - - - + <% } %> diff --git a/webapp/src/main/webapp/WEB-INF/jsp/fragment/header-common.jsp b/webapp/src/main/webapp/WEB-INF/jsp/fragment/header-common.jsp index 5cf7ad45a9..c704b35da4 100644 --- a/webapp/src/main/webapp/WEB-INF/jsp/fragment/header-common.jsp +++ b/webapp/src/main/webapp/WEB-INF/jsp/fragment/header-common.jsp @@ -45,10 +45,12 @@ data-session-id="" data-jsp-name="" data-url-context="" + data-domain="" data-pwmFormID="" data-clientEtag=""> + "/> "/> " rel="stylesheet" type="text/css" media="screen"/> @@ -61,8 +63,3 @@ " rel="stylesheet" type="text/css" media="screen"/> - - - diff --git a/webapp/src/main/webapp/WEB-INF/jsp/fragment/header-menu.jsp b/webapp/src/main/webapp/WEB-INF/jsp/fragment/header-menu.jsp index 81ae55e210..b8b74d1e5d 100644 --- a/webapp/src/main/webapp/WEB-INF/jsp/fragment/header-menu.jsp +++ b/webapp/src/main/webapp/WEB-INF/jsp/fragment/header-menu.jsp @@ -27,16 +27,13 @@ <%@ page import="password.pwm.PwmConstants" %> <%@ page import="password.pwm.http.servlet.PwmServletDefinition" %> <%@ page import="password.pwm.http.tag.conditional.PwmIfTest" %> +<%@ page import="password.pwm.http.PwmRequestAttribute" %> +<%@ page import="password.pwm.http.tag.value.PwmValue" %> - - - - - +
    diff --git a/webapp/src/main/webapp/WEB-INF/jsp/fragment/ldap-permissions.jsp b/webapp/src/main/webapp/WEB-INF/jsp/fragment/ldap-permissions.jsp index 9773a49077..dff5509bd5 100644 --- a/webapp/src/main/webapp/WEB-INF/jsp/fragment/ldap-permissions.jsp +++ b/webapp/src/main/webapp/WEB-INF/jsp/fragment/ldap-permissions.jsp @@ -71,8 +71,8 @@ <% final Set menuLocations = new TreeSet<>(); for (final LdapPermissionCalculator.PermissionRecord record : entry.getValue().get(access)) { - if (record.getPwmSetting() != null) { - menuLocations.add(record.getPwmSetting().toMenuLocationDebug(record.getProfile(), JspUtility.locale(request))); + if (record.pwmSetting() != null) { + menuLocations.add(record.pwmSetting().toMenuLocationDebug(record.profile(), JspUtility.locale(request))); } else { menuLocations.add(LocaleHelper.getLocalizedMessage(Display.Value_NotApplicable, JspUtility.getPwmRequest(pageContext))); } diff --git a/webapp/src/main/webapp/WEB-INF/jsp/fullpagehealth.jsp b/webapp/src/main/webapp/WEB-INF/jsp/fullpagehealth.jsp index 6e02b5519d..0fe711b632 100644 --- a/webapp/src/main/webapp/WEB-INF/jsp/fullpagehealth.jsp +++ b/webapp/src/main/webapp/WEB-INF/jsp/fullpagehealth.jsp @@ -143,7 +143,8 @@ //drawNextSprite(); //return; } - var timeOutTime = 1000 - (PWM_GLOBAL['epsActivityCount'] != null ? Math.floor(PWM_GLOBAL['epsActivityCount']) : 0); + //var timeOutTime = 1000 - (PWM_GLOBAL['epsActivityCount'] != null ? Math.floor(PWM_GLOBAL['epsActivityCount']) : 0); + let timeOutTime = 1000; timeOutTime = timeOutTime < 100 ? 100 : timeOutTime; setTimeout(function(){ drawNextSprite(); diff --git a/webapp/src/main/webapp/WEB-INF/jsp/helpdesk-detail.jsp b/webapp/src/main/webapp/WEB-INF/jsp/helpdesk-detail.jsp new file mode 100644 index 0000000000..60e9f9fc92 --- /dev/null +++ b/webapp/src/main/webapp/WEB-INF/jsp/helpdesk-detail.jsp @@ -0,0 +1,208 @@ +<%-- + ~ Password Management Servlets (PWM) + ~ http://www.pwm-project.org + ~ + ~ Copyright (c) 2006-2009 Novell, Inc. + ~ Copyright (c) 2009-2021 The PWM Project + ~ + ~ 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. +--%> +<%-- + THIS FILE IS NOT INTENDED FOR END USER MODIFICATION. + See the README.TXT file in WEB-INF/jsp before making changes. +--%> + + +<%@ page import="password.pwm.http.tag.value.PwmValue" %> +<%@ page import="password.pwm.http.servlet.PwmServletDefinition" %> +<%@ page import="password.pwm.http.servlet.command.CommandServlet" %> +<%@ page import="password.pwm.http.bean.DisplayElement" %> +<%@ page import="password.pwm.util.java.CollectionUtil" %> +<%@ page import="password.pwm.http.servlet.accountinfo.AccountInformationBean" %> +<%@ page import="password.pwm.util.java.StringUtil" %> +<%@ page import="password.pwm.i18n.Display" %> +<%@ page import="password.pwm.http.servlet.helpdesk.HelpdeskUserDetail" %> +<%@ page import="password.pwm.http.PwmRequestAttribute" %> +<%@ page import="password.pwm.http.servlet.helpdesk.HelpdeskServlet" %> +<%@ page import="password.pwm.http.servlet.helpdesk.HelpdeskDetailButton" %> +<%@ page import="password.pwm.http.servlet.helpdesk.HelpdeskClientData" %> +<%@ page import="java.util.Map" %> + +<%@ page language="java" session="true" isThreadSafe="true" contentType="text/html; charset=UTF-8" %> +<%@ taglib uri="pwm" prefix="pwm" %> +<% final String userKey = (String)JspUtility.getAttribute(pageContext, PwmRequestAttribute.HelpdeskUserKey); %> +<% final HelpdeskClientData clientData = ( HelpdeskClientData ) JspUtility.getAttribute(pageContext, PwmRequestAttribute.HelpdeskClientData);%> +<% final HelpdeskUserDetail detailBean = ( HelpdeskUserDetail )JspUtility.getAttribute(pageContext, PwmRequestAttribute.HelpdeskDetailInfo);%> +"/>" dir=""> +<%@ include file="/WEB-INF/jsp/fragment/header.jsp" %> + +
    + + + +
    +
    +
    +
    +
    +
    + + + + <% if (detailBean.visibleButtons().contains( HelpdeskDetailButton.changePassword)) {%> + <% final boolean changePasswordEnabled = detailBean.enabledButtons().contains( HelpdeskDetailButton.changePassword);%> + + <% } %> + <% if (detailBean.visibleButtons().contains( HelpdeskDetailButton.unlock)) {%> + <% final boolean unlockEnabled = detailBean.enabledButtons().contains( HelpdeskDetailButton.unlock);%> + + <% } %> + <% if (detailBean.visibleButtons().contains( HelpdeskDetailButton.clearResponses)) {%> + <% final boolean clearResponsesEnabled = detailBean.enabledButtons().contains( HelpdeskDetailButton.clearResponses);%> + + <% } %> + <% if (detailBean.visibleButtons().contains( HelpdeskDetailButton.clearOtpSecret)) {%> + <% final boolean clearOtpEnabled = detailBean.enabledButtons().contains( HelpdeskDetailButton.clearOtpSecret);%> + + <% } %> + <% if (detailBean.visibleButtons().contains( HelpdeskDetailButton.verification)) {%> + <% final boolean verificationEnabled = detailBean.enabledButtons().contains( HelpdeskDetailButton.verification);%> + + <% } %> + <% if (detailBean.visibleButtons().contains( HelpdeskDetailButton.deleteUser)) {%> + <% final boolean deleteEnabled = detailBean.enabledButtons().contains( HelpdeskDetailButton.deleteUser);%> + + <% } %> + <% for ( final Map.Entry entry : clientData.actions().entrySet()) {%> + + <% } %> +
    +
    +
    +
    + <% if (!CollectionUtil.isEmpty(detailBean.profileData())) { %> + > + +
    "> + + <% for (final DisplayElement displayElement : detailBean.profileData()) { %> + <% request.setAttribute("displayElement", displayElement); %> + + <% } %> +
    +
    + <% } %> + <% if (!CollectionUtil.isEmpty(detailBean.statusData())) { %> + > + +
    "> + + <% for (final DisplayElement displayElement : detailBean.statusData()) { %> + <% request.setAttribute("displayElement", displayElement); %> + + <% } %> +
    +
    + <% } %> + <% if (!CollectionUtil.isEmpty(detailBean.userHistory())) { %> + > + +
    "> + + <% for (final DisplayElement displayElement : detailBean.userHistory() ) { %> + <% request.setAttribute("displayElement", displayElement); %> + + <% } %> +
    +
    + <% } %> + <% if (!CollectionUtil.isEmpty(detailBean.helpdeskResponses())) { %> + + +
    "> + + <% for (final DisplayElement displayElement : detailBean.helpdeskResponses()) { %> + <% request.setAttribute("displayElement", displayElement); %> + + <% } %> +
    +
    + <% } %> + <% if (!CollectionUtil.isEmpty(detailBean.passwordPolicyRules())) { %> + + +
    "> + + <% for (final DisplayElement displayElement : detailBean.passwordPolicyRules()) { %> + <% request.setAttribute("displayElement", displayElement); %> + + <% } %> +
    +
    + <% } %> +
    +
    +
    +
    +
    +
    +
    +
    + + + + +" rel="stylesheet" type="text/css" media="screen"/> + + + + diff --git a/webapp/src/main/webapp/WEB-INF/jsp/helpdesk-search.jsp b/webapp/src/main/webapp/WEB-INF/jsp/helpdesk-search.jsp new file mode 100644 index 0000000000..9bfbe2245e --- /dev/null +++ b/webapp/src/main/webapp/WEB-INF/jsp/helpdesk-search.jsp @@ -0,0 +1,88 @@ +<%-- + ~ Password Management Servlets (PWM) + ~ http://www.pwm-project.org + ~ + ~ Copyright (c) 2006-2009 Novell, Inc. + ~ Copyright (c) 2009-2021 The PWM Project + ~ + ~ 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. +--%> +<%-- + THIS FILE IS NOT INTENDED FOR END USER MODIFICATION. + See the README.TXT file in WEB-INF/jsp before making changes. +--%> + +<%@ page import="password.pwm.http.PwmRequestAttribute" %> +<%@ page import="password.pwm.http.JspUtility" %> +<%@ page import="password.pwm.http.PwmRequestAttribute" %> + + +<%@ page language="java" session="true" isThreadSafe="true" contentType="text/html" %> +<%@ taglib uri="pwm" prefix="pwm" %> +"/>" dir=""> +<%@ include file="/WEB-INF/jsp/fragment/header.jsp" %> + +
    + + + +
    +
    + +
    +
    +
    +
    +
    + + +" rel="stylesheet" type="text/css" media="screen"/> + + + + + diff --git a/webapp/src/main/webapp/WEB-INF/jsp/helpdesk.jsp b/webapp/src/main/webapp/WEB-INF/jsp/helpdesk.jsp deleted file mode 100644 index f592778315..0000000000 --- a/webapp/src/main/webapp/WEB-INF/jsp/helpdesk.jsp +++ /dev/null @@ -1,59 +0,0 @@ -<%-- - ~ Password Management Servlets (PWM) - ~ http://www.pwm-project.org - ~ - ~ Copyright (c) 2006-2009 Novell, Inc. - ~ Copyright (c) 2009-2021 The PWM Project - ~ - ~ 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. ---%> -<%-- - THIS FILE IS NOT INTENDED FOR END USER MODIFICATION. - See the README.TXT file in WEB-INF/jsp before making changes. ---%> - - -<%@ page import="password.pwm.http.JspUtility" %> -<%@ page import="password.pwm.http.PwmRequestAttribute" %> - - -<%@ page language="java" session="true" isThreadSafe="true" contentType="text/html" %> -<%@ taglib uri="pwm" prefix="pwm" %> -"/>" dir=""> - - <%@ include file="/WEB-INF/jsp/fragment/header-common.jsp" %> - "/> - - -
    - - - -
    -
    - - -
    -
    -
    - - - - - - - - diff --git a/webapp/src/main/webapp/WEB-INF/jsp/login.jsp b/webapp/src/main/webapp/WEB-INF/jsp/login.jsp index fa38946c96..df28c22a71 100644 --- a/webapp/src/main/webapp/WEB-INF/jsp/login.jsp +++ b/webapp/src/main/webapp/WEB-INF/jsp/login.jsp @@ -170,28 +170,13 @@ <%@ include file="/WEB-INF/jsp/fragment/cancel-form.jsp" %> -<% if (CaptchaUtility.captchaEnabledForRequest(JspUtility.getPwmRequest(pageContext))) { %> -<% if (CaptchaUtility.readCaptchaMode( JspUtility.getPwmRequest( pageContext ) ) == CaptchaUtility.CaptchaMode.V3 ) { %> - - - -<% } %> -<% } else { %> - - - +<% if (!CaptchaUtility.captchaEnabledForRequest(JspUtility.getPwmRequest(pageContext))) { %> + <% } %> <%@ include file="fragment/footer.jsp" %> diff --git a/webapp/src/main/webapp/WEB-INF/jsp/newuser-agreement.jsp b/webapp/src/main/webapp/WEB-INF/jsp/newuser-agreement.jsp index 638f8c6615..ef9c62449a 100644 --- a/webapp/src/main/webapp/WEB-INF/jsp/newuser-agreement.jsp +++ b/webapp/src/main/webapp/WEB-INF/jsp/newuser-agreement.jsp @@ -71,25 +71,10 @@
    - - - + <%@ include file="fragment/footer.jsp" %> diff --git a/webapp/src/main/webapp/WEB-INF/jsp/newuser-profilechoice.jsp b/webapp/src/main/webapp/WEB-INF/jsp/newuser-profilechoice.jsp index 4b153c1189..1d9d42ec11 100644 --- a/webapp/src/main/webapp/WEB-INF/jsp/newuser-profilechoice.jsp +++ b/webapp/src/main/webapp/WEB-INF/jsp/newuser-profilechoice.jsp @@ -88,13 +88,6 @@
    - - - <%@ include file="fragment/footer.jsp" %> diff --git a/webapp/src/main/webapp/WEB-INF/jsp/newuser-wait.jsp b/webapp/src/main/webapp/WEB-INF/jsp/newuser-wait.jsp index 38b1cdaca5..0874af7fb3 100644 --- a/webapp/src/main/webapp/WEB-INF/jsp/newuser-wait.jsp +++ b/webapp/src/main/webapp/WEB-INF/jsp/newuser-wait.jsp @@ -72,14 +72,10 @@
    - - - - <%@ include file="/WEB-INF/jsp/fragment/footer.jsp" %> diff --git a/webapp/src/main/webapp/WEB-INF/jsp/newuser.jsp b/webapp/src/main/webapp/WEB-INF/jsp/newuser.jsp index 54738f0909..43aaa751fc 100644 --- a/webapp/src/main/webapp/WEB-INF/jsp/newuser.jsp +++ b/webapp/src/main/webapp/WEB-INF/jsp/newuser.jsp @@ -74,21 +74,17 @@
    - - - - - + <%@ include file="fragment/footer.jsp" %> diff --git a/webapp/src/main/webapp/WEB-INF/jsp/setupotpsecret-existing.jsp b/webapp/src/main/webapp/WEB-INF/jsp/setupotpsecret-existing.jsp index 73980ff8b1..240b3e6363 100644 --- a/webapp/src/main/webapp/WEB-INF/jsp/setupotpsecret-existing.jsp +++ b/webapp/src/main/webapp/WEB-INF/jsp/setupotpsecret-existing.jsp @@ -67,14 +67,10 @@
    - - - - + <%@ include file="/WEB-INF/jsp/fragment/cancel-form.jsp" %> <%@ include file="fragment/footer.jsp" %> diff --git a/webapp/src/main/webapp/WEB-INF/jsp/setupotpsecret-success.jsp b/webapp/src/main/webapp/WEB-INF/jsp/setupotpsecret-success.jsp index 02272b4a10..e7574e1432 100644 --- a/webapp/src/main/webapp/WEB-INF/jsp/setupotpsecret-success.jsp +++ b/webapp/src/main/webapp/WEB-INF/jsp/setupotpsecret-success.jsp @@ -38,7 +38,6 @@ "/>" dir=""> <%@ include file="fragment/header.jsp" %> -
    diff --git a/webapp/src/main/webapp/WEB-INF/jsp/setupotpsecret-test.jsp b/webapp/src/main/webapp/WEB-INF/jsp/setupotpsecret-test.jsp index 5a920dae50..5e16e02460 100644 --- a/webapp/src/main/webapp/WEB-INF/jsp/setupotpsecret-test.jsp +++ b/webapp/src/main/webapp/WEB-INF/jsp/setupotpsecret-test.jsp @@ -27,13 +27,11 @@ <%@ page import="password.pwm.http.tag.conditional.PwmIfTest" %> <%@ page import="password.pwm.i18n.Display" %> <%@ page import="password.pwm.util.i18n.LocaleHelper" %> -<%@ page import="org.apache.commons.lang3.StringEscapeUtils" %> <%@ page language="java" session="true" isThreadSafe="true" contentType="text/html" %> <%@ taglib uri="pwm" prefix="pwm" %> -<% final SetupOtpBean otpBean = JspUtility.getSessionBean(pageContext,SetupOtpBean.class); %> -<% final int otpTokenLength = PwmRequest.forRequest(request,response).getPwmDomain().getOtpService().getSettings().getOtpTokenLength(); %> +<% final int otpTokenLength = (int)JspUtility.getAttribute( pageContext, PwmRequestAttribute.SetupOtp_TokenLength ); %> "/>" dir=""> <%@ include file="fragment/header.jsp" %> @@ -48,9 +46,9 @@
    - + title="0-9"/>
    @@ -73,24 +71,10 @@
    - - - - <%@ include file="fragment/footer.jsp" %> diff --git a/webapp/src/main/webapp/WEB-INF/jsp/setupotpsecret.jsp b/webapp/src/main/webapp/WEB-INF/jsp/setupotpsecret.jsp index 10bc5adcde..777a71bef3 100644 --- a/webapp/src/main/webapp/WEB-INF/jsp/setupotpsecret.jsp +++ b/webapp/src/main/webapp/WEB-INF/jsp/setupotpsecret.jsp @@ -104,8 +104,6 @@
    - - <%@ include file="/WEB-INF/jsp/fragment/cancel-form.jsp" %> <%@ include file="fragment/footer.jsp" %> diff --git a/webapp/src/main/webapp/WEB-INF/jsp/setupresponses-existing.jsp b/webapp/src/main/webapp/WEB-INF/jsp/setupresponses-existing.jsp index e8be33555a..2b077f41b0 100644 --- a/webapp/src/main/webapp/WEB-INF/jsp/setupresponses-existing.jsp +++ b/webapp/src/main/webapp/WEB-INF/jsp/setupresponses-existing.jsp @@ -27,6 +27,7 @@ <%@ page import="password.pwm.bean.ResponseInfoBean" %> <%@ page import="password.pwm.util.java.StringUtil" %> <%@ page import="password.pwm.http.PwmRequestAttribute" %> +<%@ page import="password.pwm.http.servlet.setupresponses.ResponseMode" %> <%@ page language="java" session="true" isThreadSafe="true" contentType="text/html" %> @@ -78,21 +79,10 @@
    - - - + <%@ include file="/WEB-INF/jsp/fragment/cancel-form.jsp" %> <%@ include file="fragment/footer.jsp" %> diff --git a/webapp/src/main/webapp/WEB-INF/jsp/setupresponses-helpdesk.jsp b/webapp/src/main/webapp/WEB-INF/jsp/setupresponses-helpdesk.jsp index 145d59fb32..4f851547a3 100644 --- a/webapp/src/main/webapp/WEB-INF/jsp/setupresponses-helpdesk.jsp +++ b/webapp/src/main/webapp/WEB-INF/jsp/setupresponses-helpdesk.jsp @@ -74,15 +74,10 @@
    - - - - + <%@ include file="/WEB-INF/jsp/fragment/cancel-form.jsp" %> <%@ include file="fragment/footer.jsp" %> diff --git a/webapp/src/main/webapp/WEB-INF/jsp/setupresponses.jsp b/webapp/src/main/webapp/WEB-INF/jsp/setupresponses.jsp index d6f8c6b949..b2ccd6ee14 100644 --- a/webapp/src/main/webapp/WEB-INF/jsp/setupresponses.jsp +++ b/webapp/src/main/webapp/WEB-INF/jsp/setupresponses.jsp @@ -68,15 +68,10 @@ - - - - + <%@ include file="/WEB-INF/jsp/fragment/cancel-form.jsp" %> <%@ include file="fragment/footer.jsp" %> diff --git a/webapp/src/main/webapp/WEB-INF/jsp/shortcut.jsp b/webapp/src/main/webapp/WEB-INF/jsp/shortcut.jsp index 66ead48794..dcc35f9547 100644 --- a/webapp/src/main/webapp/WEB-INF/jsp/shortcut.jsp +++ b/webapp/src/main/webapp/WEB-INF/jsp/shortcut.jsp @@ -54,14 +54,15 @@

    No shortcuts

    <% } else { %> <% for (final ShortcutItem item : shortcutItems) { %> - > + >
    -
    <%=item.getLabel()%>
    -
    <%=item.getDescription()%>
    +
    <%=item.label()%>
    +
    <%=item.description()%>
    diff --git a/webapp/src/main/webapp/WEB-INF/jsp/updateprofile-agreement.jsp b/webapp/src/main/webapp/WEB-INF/jsp/updateprofile-agreement.jsp index 6ef8c6e4d4..31b555457d 100644 --- a/webapp/src/main/webapp/WEB-INF/jsp/updateprofile-agreement.jsp +++ b/webapp/src/main/webapp/WEB-INF/jsp/updateprofile-agreement.jsp @@ -78,27 +78,10 @@
    - - - + <%@ include file="fragment/footer.jsp" %> diff --git a/webapp/src/main/webapp/WEB-INF/jsp/updateprofile.jsp b/webapp/src/main/webapp/WEB-INF/jsp/updateprofile.jsp index 5ad030cb57..0425d6b2ef 100644 --- a/webapp/src/main/webapp/WEB-INF/jsp/updateprofile.jsp +++ b/webapp/src/main/webapp/WEB-INF/jsp/updateprofile.jsp @@ -68,15 +68,10 @@ - - - - - + <%@ include file="fragment/footer.jsp" %> diff --git a/webapp/src/main/webapp/WEB-INF/pwm-taglib.tld b/webapp/src/main/webapp/WEB-INF/pwm-taglib.tld index 237e126ec1..889c3ef3d1 100644 --- a/webapp/src/main/webapp/WEB-INF/pwm-taglib.tld +++ b/webapp/src/main/webapp/WEB-INF/pwm-taglib.tld @@ -181,6 +181,12 @@ empty Displays the context url path + + domain + password.pwm.http.tag.PwmDomainTag + empty + Displays the domain ID + textFile password.pwm.http.tag.PwmTextFileTag diff --git a/webapp/src/main/webapp/public/reference/index.jsp b/webapp/src/main/webapp/public/reference/index.jsp index 973442bd02..fce788687d 100644 --- a/webapp/src/main/webapp/public/reference/index.jsp +++ b/webapp/src/main/webapp/public/reference/index.jsp @@ -55,11 +55,6 @@ - - - - - <%@ include file="/WEB-INF/jsp/fragment/footer.jsp" %> diff --git a/webapp/src/main/webapp/public/resources/helpdesk.css b/webapp/src/main/webapp/public/resources/helpdesk.css new file mode 100644 index 0000000000..e69de29bb2 diff --git a/webapp/src/main/webapp/public/resources/js/admin-statistics.js b/webapp/src/main/webapp/public/resources/js/admin-statistics.js index 1ec57a6138..6aa089dcf7 100644 --- a/webapp/src/main/webapp/public/resources/js/admin-statistics.js +++ b/webapp/src/main/webapp/public/resources/js/admin-statistics.js @@ -18,11 +18,13 @@ * limitations under the License. */ -var PWM_ADMIN = PWM_ADMIN || {}; -var PWM_MAIN = PWM_MAIN || {}; -var PWM_GLOBAL = PWM_GLOBAL || {}; +const PWM_ADMIN_STATISTICS = {}; -var PWM_ADMIN_STATISTICS = PWM_ADMIN_STATISTICS || {}; +import {PWM_JSLibrary} from "./jslibrary.js"; +import {PWM_UILibrary} from "./uilibrary.js"; +import {PWM_MAIN} from "./main.js"; + +export {PWM_ADMIN_STATISTICS}; PWM_ADMIN_STATISTICS.initStatisticsPage=function() { PWM_MAIN.addEventHandler('statsPeriodForm','change',function() { @@ -31,12 +33,12 @@ PWM_ADMIN_STATISTICS.initStatisticsPage=function() { const url = PWM_MAIN.addParamToUrl(window.location.pathname, 'processAction','readKeys'); const loadFunction = function(data) { - const selectElement = PWM_MAIN.getObject('statsPeriodSelect'); + const selectElement = PWM_JSLibrary.getElement('statsPeriodSelect'); if (data['data'] && data['data']) { const keys = data['data']; let optionsHtml = ''; - PWM_MAIN.JSLibrary.forEachInObject(keys, function (key, value) { + PWM_JSLibrary.forEachInObject(keys, function (key, value) { optionsHtml += ''; }); selectElement.innerHTML = optionsHtml; @@ -47,18 +49,18 @@ PWM_ADMIN_STATISTICS.initStatisticsPage=function() { PWM_MAIN.ajaxRequest(url,loadFunction); }; -PWM_ADMIN_STATISTICS.refreshStatistics=function() { +PWM_ADMIN_STATISTICS.refreshStatistics=async function() { const waitInnerHtml = '' - + PWM_MAIN.showString('Display_PleaseWait') + + await PWM_MAIN.getDisplayString('Display_PleaseWait') + '' - const tableElement = PWM_MAIN.getObject('statisticsTable'); - const averageTableElement = PWM_MAIN.getObject('averageStatisticsTable'); + const tableElement = PWM_JSLibrary.getElement('statisticsTable'); + const averageTableElement = PWM_JSLibrary.getElement('averageStatisticsTable'); tableElement.innerHTML = waitInnerHtml; averageTableElement.innerHTML = waitInnerHtml; - const currentStatKey = PWM_MAIN.JSLibrary.readValueOfSelectElement('statsPeriodSelect'); + const currentStatKey = PWM_JSLibrary.readValueOfSelectElement('statsPeriodSelect'); let url = PWM_MAIN.addParamToUrl(window.location.pathname, 'processAction','readStatistics'); if ( currentStatKey ) { @@ -69,13 +71,13 @@ PWM_ADMIN_STATISTICS.refreshStatistics=function() { if (data['data'] && data['data']['statistics']) { const fields = data['data']['statistics']; - tableElement.innerHTML = UILibrary.displayElementsToTableContents(fields); - UILibrary.initElementsToTableContents(fields); + tableElement.innerHTML = PWM_UILibrary.displayElementsToTableContents(fields); + PWM_UILibrary.initElementsToTableContents(fields); } if (data['data'] && data['data']['averageStatistics']) { const fields = data['data']['averageStatistics']; - averageTableElement.innerHTML = UILibrary.displayElementsToTableContents(fields); - UILibrary.initElementsToTableContents(fields); + averageTableElement.innerHTML = PWM_UILibrary.displayElementsToTableContents(fields); + PWM_UILibrary.initElementsToTableContents(fields); } }; PWM_MAIN.ajaxRequest(url,loadFunction); diff --git a/webapp/src/main/webapp/public/resources/js/admin.js b/webapp/src/main/webapp/public/resources/js/admin.js index ebb034198c..f1a86755b5 100755 --- a/webapp/src/main/webapp/public/resources/js/admin.js +++ b/webapp/src/main/webapp/public/resources/js/admin.js @@ -18,32 +18,40 @@ * limitations under the License. */ -var PWM_ADMIN = PWM_ADMIN || {}; -var PWM_MAIN = PWM_MAIN || {}; -var PWM_GLOBAL = PWM_GLOBAL || {}; +const PWM_ADMIN = {}; + +import {PWM_JSLibrary} from "./jslibrary.js"; +import {PWM_UILibrary} from "./uilibrary.js"; +import {PWM_MAIN} from "./main.js"; + +export {PWM_ADMIN}; + +let healthCheckInProgress = false; + +PWM_ADMIN.initAdminNavMenu = async function() { + const PWM_GLOBAL = await PWM_MAIN.getPwmGlobal(); -PWM_ADMIN.initAdminNavMenu = function() { const makeMenu = function () { require(["dijit/form/DropDownButton", "dijit/DropDownMenu", "dijit/Menu", "dijit/MenuItem", "dijit/PopupMenuItem", "dojo/dom", "dijit/MenuSeparator"], function (DropDownButton, DropDownMenu, Menu, MenuItem, PopupMenuItem, dom, MenuSeparator) { const pMenu = new DropDownMenu({style: "display: none;"}); pMenu.addChild(new MenuItem({ - label: PWM_ADMIN.showString('Title_LogViewer'), + label: PWM_ADMIN.getDisplayString('Title_LogViewer'), id: 'eventLog_dropitem', onClick: function () { PWM_MAIN.gotoUrl(PWM_GLOBAL['url-context'] + '/private/admin/logs'); } })); pMenu.addChild(new MenuItem({ - label: PWM_ADMIN.showString('Title_TokenLookup'), + label: PWM_ADMIN.getDisplayString('Title_TokenLookup'), id: 'tokenLookup_dropitem', onClick: function () { PWM_MAIN.gotoUrl(PWM_GLOBAL['url-context'] + '/private/admin/tokens'); } })); pMenu.addChild(new MenuItem({ - label: PWM_ADMIN.showString('Title_URLReference'), + label: PWM_ADMIN.getDisplayString('Title_URLReference'), id: 'urlReference_dropitem', onClick: function () { PWM_MAIN.gotoUrl(PWM_GLOBAL['url-context'] + '/private/admin/urls'); @@ -101,22 +109,22 @@ PWM_ADMIN.initAdminNavMenu = function() { }); }; - PWM_MAIN.doIfQueryHasResults("#admin-nav-menu-container",makeMenu) + PWM_JSLibrary.doIfQueryHasResults("#admin-nav-menu-container",makeMenu) }; PWM_ADMIN.initDownloadProcessReportZipForm = function() { - PWM_MAIN.doQuery("#reportDownloadButton", function(node){ - PWM_MAIN.addEventHandler(node, "click", function() { - PWM_MAIN.showConfirmDialog({title:"Report Status",text:PWM_ADMIN.showString('Confirm_Report_Start'),okAction:function(){ + PWM_JSLibrary.doQuery("#reportDownloadButton", function(node){ + PWM_MAIN.addEventHandler(node, "click", async function() { + PWM_MAIN.showConfirmDialog({title:"Report Status",text:await PWM_ADMIN.getDisplayString('Confirm_Report_Start'),okAction:function(){ let url = PWM_MAIN.addParamToUrl(window.location.href,'processAction','downloadReportZip'); - url = PWM_MAIN.addParamToUrl(url,'recordCount',PWM_MAIN.getObject('recordCount').value); - url = PWM_MAIN.addParamToUrl(url,'recordType',PWM_MAIN.JSLibrary.readValueOfSelectElement('recordType')); + url = PWM_MAIN.addParamToUrl(url,'recordCount',PWM_JSLibrary.getElement('recordCount').value); + url = PWM_MAIN.addParamToUrl(url,'recordType',PWM_JSLibrary.readValueOfSelectElement('recordType')); window.location.href = url; }}); }) }); - PWM_MAIN.doQuery("#reportCancelButton", function(node){ + PWM_JSLibrary.doQuery("#reportCancelButton", function(node){ PWM_MAIN.addEventHandler(node, "click", function() { const url = PWM_MAIN.addParamToUrl(window.location.href, 'processAction', 'cancelDownload'); PWM_MAIN.ajaxRequest(url, function(){ @@ -127,52 +135,49 @@ PWM_ADMIN.initDownloadProcessReportZipForm = function() { }; PWM_ADMIN.refreshReportProcessStatus=function() { - const url = PWM_MAIN.addParamToUrl(window.location.pathname, 'processAction','reportProcessStatus'); + const url = PWM_MAIN.addParamToUrl(null, 'processAction','reportProcessStatus'); const loadFunction = function(data) { if (data['data'] && data['data']['presentable']) { const fields = data['data']['presentable']; - PWM_MAIN.getObject('statusTable').innerHTML = UILibrary.displayElementsToTableContents(fields); - UILibrary.initElementsToTableContents(fields); + PWM_JSLibrary.getElement('statusTable').innerHTML = PWM_UILibrary.displayElementsToTableContents(fields); + PWM_UILibrary.initElementsToTableContents(fields); } const reportInProgress = data['data']['reportInProgress'] === true; - PWM_MAIN.getObject("reportDownloadButton").disabled = reportInProgress; - PWM_MAIN.getObject("reportCancelButton").disabled = !reportInProgress; - PWM_MAIN.getObject( "downloadReportOptionsFieldset" ).disabled = reportInProgress; + PWM_JSLibrary.getElement("reportDownloadButton").disabled = reportInProgress; + PWM_JSLibrary.getElement("reportCancelButton").disabled = !reportInProgress; + PWM_JSLibrary.getElement( "downloadReportOptionsFieldset" ).disabled = reportInProgress; - if ( reportInProgress === PWM_MAIN.IdleTimeoutHandler.countDownTimerEnabled() ) { - if (reportInProgress) { - PWM_MAIN.IdleTimeoutHandler.cancelCountDownTimer() - } else { - PWM_MAIN.IdleTimeoutHandler.resumeCountDownTimer() - } + if ( reportInProgress ) { + PWM_MAIN.cancelCountDownTimer() } }; const errorFunction = function (error) { + debugger; console.log('error during report status update: ' + error); }; PWM_MAIN.ajaxRequest(url,loadFunction,{errorFunction:errorFunction}); }; -PWM_ADMIN.webSessionHeaders = function() { +PWM_ADMIN.webSessionHeaders = async function() { return [ - {field:"userID",label:PWM_ADMIN.showString('Field_Session_UserID')}, - {field:"ldapProfile",label:PWM_ADMIN.showString('Field_Session_LdapProfile')}, - {field:"userDN",label:PWM_ADMIN.showString('Field_Session_UserDN'),hidden:true}, - {field:"createTime",label:PWM_ADMIN.showString('Field_Session_CreateTime')}, - {field:"lastTime",label:PWM_ADMIN.showString('Field_Session_LastTime')}, - {field:"label",label:PWM_ADMIN.showString('Field_Session_Label')}, - {field:"idle",label:PWM_ADMIN.showString('Field_Session_Idle')}, - {field:"locale",label:PWM_ADMIN.showString('Field_Session_Locale'),hidden:true}, - {field:"srcAddress",label:PWM_ADMIN.showString('Field_Session_SrcAddress')}, - {field:"srcHost",label:PWM_ADMIN.showString('Field_Session_SrcHost'),hidden:true}, - {field:"lastUrl",label:PWM_ADMIN.showString('Field_Session_LastURL'),hidden:true}, - {field:"intruderAttempts",label:PWM_ADMIN.showString('Field_Session_IntruderAttempts'),hidden:true} + {field:"userID",label:await PWM_ADMIN.getDisplayString('Field_Session_UserID')}, + {field:"ldapProfile",label:await PWM_ADMIN.getDisplayString('Field_Session_LdapProfile')}, + {field:"userDN",label:await PWM_ADMIN.getDisplayString('Field_Session_UserDN'),hidden:true}, + {field:"createTime",label:await PWM_ADMIN.getDisplayString('Field_Session_CreateTime')}, + {field:"lastTime",label:await PWM_ADMIN.getDisplayString('Field_Session_LastTime')}, + {field:"label",label:await PWM_ADMIN.getDisplayString('Field_Session_Label')}, + {field:"idle",label:await PWM_ADMIN.getDisplayString('Field_Session_Idle')}, + {field:"locale",label:await PWM_ADMIN.getDisplayString('Field_Session_Locale'),hidden:true}, + {field:"srcAddress",label:await PWM_ADMIN.getDisplayString('Field_Session_SrcAddress')}, + {field:"srcHost",label:await PWM_ADMIN.getDisplayString('Field_Session_SrcHost'),hidden:true}, + {field:"lastUrl",label:await PWM_ADMIN.getDisplayString('Field_Session_LastURL'),hidden:true}, + {field:"intruderAttempts",label:await PWM_ADMIN.getDisplayString('Field_Session_IntruderAttempts'),hidden:true} ]; }; -PWM_ADMIN.initActiveSessionGrid=function() { - const headers = PWM_ADMIN.webSessionHeaders(); +PWM_ADMIN.initActiveSessionGrid=async function() { + const headers = await PWM_ADMIN.webSessionHeaders(); require(["dojo","dojo/_base/declare", "dgrid/Grid", "dgrid/Keyboard", "dgrid/Selection", "dgrid/extensions/ColumnResizer", "dgrid/extensions/ColumnReorder", "dgrid/extensions/ColumnHider", "dgrid/extensions/DijitRegistry"], function(dojo, declare, Grid, Keyboard, Selection, ColumnResizer, ColumnReorder, ColumnHider, DijitRegistry){ @@ -189,7 +194,7 @@ PWM_ADMIN.initActiveSessionGrid=function() { PWM_ADMIN.refreshActiveSessionGrid(); PWM_VAR['activeSessionsGrid'].on(".dgrid-row:click", function(evt){ - PWM_ADMIN.detailView(evt, PWM_ADMIN.webSessionHeaders(), PWM_VAR['activeSessionsGrid']); + PWM_ADMIN.detailView(evt, headers, PWM_VAR['activeSessionsGrid']); }); }); }; @@ -198,7 +203,7 @@ PWM_ADMIN.refreshActiveSessionGrid=function() { const grid = PWM_VAR['activeSessionsGrid']; grid.refresh(); - const maximum = PWM_MAIN.getObject('maxActiveSessionResults').value; + const maximum = PWM_JSLibrary.getElement('maxActiveSessionResults').value; let url = PWM_MAIN.addParamToUrl(window.location.href, "processAction", "sessionData"); url = PWM_MAIN.addParamToUrl(url,'maximum',maximum); const loadFunction = function (data) { @@ -208,18 +213,19 @@ PWM_ADMIN.refreshActiveSessionGrid=function() { PWM_MAIN.ajaxRequest(url,loadFunction,{method:'GET'}); }; -PWM_ADMIN.intruderHeaders = function(){ +PWM_ADMIN.intruderHeaders = async function(){ return [ - {field:"domainID",label:PWM_ADMIN.showString('Field_Intruder_Domain')}, - {field:"subject",label:PWM_ADMIN.showString('Field_Intruder_Subject')}, - {field:"timeStamp",label:PWM_ADMIN.showString('Field_Intruder_Timestamp')}, - {field:"attemptCount",label:PWM_ADMIN.showString('Field_Intruder_Count')}, - {field:"status",label:PWM_ADMIN.showString('Field_Intruder_Status')} + {field:"domainID",label:await PWM_ADMIN.getDisplayString('Field_Intruder_Domain')}, + {field:"subject",label:await PWM_ADMIN.getDisplayString('Field_Intruder_Subject')}, + {field:"timeStamp",label:await PWM_ADMIN.getDisplayString('Field_Intruder_Timestamp')}, + {field:"attemptCount",label:await PWM_ADMIN.getDisplayString('Field_Intruder_Count')}, + {field:"status",label:await PWM_ADMIN.getDisplayString('Field_Intruder_Status')} ]; }; -PWM_ADMIN.initIntrudersGrid=function() { +PWM_ADMIN.initIntrudersGrid=async function() { + const headers = await PWM_ADMIN.intruderHeaders(); PWM_VAR['intruderRecordTypes'] = ["ADDRESS","USERNAME","USER_ID","ATTRIBUTE","TOKEN_DEST"]; const intruderGridHeaders = PWM_ADMIN.intruderHeaders(); @@ -237,7 +243,7 @@ PWM_ADMIN.initIntrudersGrid=function() { PWM_VAR['intruderGrid'][recordType] = grid; grid.on(".dgrid-row:click", function(evt){ - PWM_ADMIN.detailView(evt, PWM_ADMIN.intruderHeaders(), grid); + PWM_ADMIN.detailView(evt, headers, grid); }); })(i) } @@ -253,7 +259,7 @@ PWM_ADMIN.refreshIntruderGrid=function() { PWM_VAR['intruderGrid'][recordType].refresh(); } try { - var maximum = PWM_MAIN.getObject('maxIntruderGridResults').value; + var maximum = PWM_JSLibrary.getElement('maxIntruderGridResults').value; } catch (e) { maximum = 1000; } @@ -268,75 +274,76 @@ PWM_ADMIN.refreshIntruderGrid=function() { PWM_MAIN.ajaxRequest(url,loadFunction,{method:'GET'}); }; -PWM_ADMIN.auditUserHeaders = function() { +PWM_ADMIN.auditUserHeaders = async function() { return [ - {field:"domain",label:PWM_ADMIN.showString('Field_Audit_Domain')}, - {field:"timestamp",label:PWM_ADMIN.showString('Field_Audit_Timestamp')}, - {field:"perpetratorID",label:PWM_ADMIN.showString('Field_Audit_PerpetratorID')}, - {field:"perpetratorDN",label:PWM_ADMIN.showString('Field_Audit_PerpetratorDN'),hidden:true}, - {field:"perpetratorLdapProfile",label:PWM_ADMIN.showString('Field_Audit_PerpetratorLdapProfile'),hidden:true}, - {field:"eventCode",label:PWM_ADMIN.showString('Field_Audit_EventCode')}, - {field:"message",label:PWM_ADMIN.showString('Field_Audit_Message'),hidden:true}, - {field:"sourceAddress",label:PWM_ADMIN.showString('Field_Audit_SourceAddress')}, - {field:"sourceHost",label:PWM_ADMIN.showString('Field_Audit_SourceHost'),hidden:true}, - {field:"guid",label:PWM_ADMIN.showString('Field_Audit_GUID'),hidden:true}, - {field:"narrative",label:PWM_ADMIN.showString('Field_Audit_Narrative')} + {field:"domain",label:await PWM_ADMIN.getDisplayString('Field_Audit_Domain')}, + {field:"timestamp",label:await PWM_ADMIN.getDisplayString('Field_Audit_Timestamp')}, + {field:"perpetratorID",label:await PWM_ADMIN.getDisplayString('Field_Audit_PerpetratorID')}, + {field:"perpetratorDN",label:await PWM_ADMIN.getDisplayString('Field_Audit_PerpetratorDN'),hidden:true}, + {field:"perpetratorLdapProfile",label:await PWM_ADMIN.getDisplayString('Field_Audit_PerpetratorLdapProfile'),hidden:true}, + {field:"eventCode",label:await PWM_ADMIN.getDisplayString('Field_Audit_EventCode')}, + {field:"message",label:await PWM_ADMIN.getDisplayString('Field_Audit_Message'),hidden:true}, + {field:"sourceAddress",label:await PWM_ADMIN.getDisplayString('Field_Audit_SourceAddress')}, + {field:"sourceHost",label:await PWM_ADMIN.getDisplayString('Field_Audit_SourceHost'),hidden:true}, + {field:"guid",label:await PWM_ADMIN.getDisplayString('Field_Audit_GUID'),hidden:true}, + {field:"narrative",label:await PWM_ADMIN.getDisplayString('Field_Audit_Narrative')} ]; }; -PWM_ADMIN.auditHelpdeskHeaders = function() { +PWM_ADMIN.auditHelpdeskHeaders = async function() { return [ - {field:"domain",label:PWM_ADMIN.showString('Field_Audit_Domain')}, - {field:"timestamp",label:PWM_ADMIN.showString('Field_Audit_Timestamp')}, - {field:"perpetratorID",label:PWM_ADMIN.showString('Field_Audit_PerpetratorID')}, - {field:"perpetratorDN",label:PWM_ADMIN.showString('Field_Audit_PerpetratorDN'),hidden:true}, - {field:"perpetratorLdapProfile",label:PWM_ADMIN.showString('Field_Audit_PerpetratorLdapProfile'),hidden:true}, - {field:"eventCode",label:PWM_ADMIN.showString('Field_Audit_EventCode')}, - {field:"message",label:PWM_ADMIN.showString('Field_Audit_Message'),hidden:true}, - {field:"targetID",label:PWM_ADMIN.showString('Field_Audit_TargetID')}, - {field:"targetDN",label:PWM_ADMIN.showString('Field_Audit_TargetDN')}, - {field:"targetLdapProfile",label:PWM_ADMIN.showString('Field_Audit_TargetLdapProfile')}, - {field:"sourceAddress",label:PWM_ADMIN.showString('Field_Audit_SourceAddress')}, - {field:"sourceHost",label:PWM_ADMIN.showString('Field_Audit_SourceHost'),hidden:true}, - {field:"guid",label:PWM_ADMIN.showString('Field_Audit_GUID'),hidden:true}, - {field:"narrative",label:PWM_ADMIN.showString('Field_Audit_Narrative'),hidden:true} + {field:"domain",label:await PWM_ADMIN.getDisplayString('Field_Audit_Domain')}, + {field:"timestamp",label:await PWM_ADMIN.getDisplayString('Field_Audit_Timestamp')}, + {field:"perpetratorID",label:await PWM_ADMIN.getDisplayString('Field_Audit_PerpetratorID')}, + {field:"perpetratorDN",label:await PWM_ADMIN.getDisplayString('Field_Audit_PerpetratorDN'),hidden:true}, + {field:"perpetratorLdapProfile",label:await PWM_ADMIN.getDisplayString('Field_Audit_PerpetratorLdapProfile'),hidden:true}, + {field:"eventCode",label:await PWM_ADMIN.getDisplayString('Field_Audit_EventCode')}, + {field:"message",label:await PWM_ADMIN.getDisplayString('Field_Audit_Message'),hidden:true}, + {field:"targetID",label:await PWM_ADMIN.getDisplayString('Field_Audit_TargetID')}, + {field:"targetDN",label:await PWM_ADMIN.getDisplayString('Field_Audit_TargetDN')}, + {field:"targetLdapProfile",label:await PWM_ADMIN.getDisplayString('Field_Audit_TargetLdapProfile')}, + {field:"sourceAddress",label:await PWM_ADMIN.getDisplayString('Field_Audit_SourceAddress')}, + {field:"sourceHost",label:await PWM_ADMIN.getDisplayString('Field_Audit_SourceHost'),hidden:true}, + {field:"guid",label:await PWM_ADMIN.getDisplayString('Field_Audit_GUID'),hidden:true}, + {field:"narrative",label:await PWM_ADMIN.getDisplayString('Field_Audit_Narrative'),hidden:true} ]; }; -PWM_ADMIN.auditSystemHeaders = function() { +PWM_ADMIN.auditSystemHeaders = async function() { return [ - {field:"timestamp",label:PWM_ADMIN.showString('Field_Audit_Timestamp')}, - {field:"eventCode",label:PWM_ADMIN.showString('Field_Audit_EventCode')}, - {field:"message",label:PWM_ADMIN.showString('Field_Audit_Message')}, - {field:"instance",label:PWM_ADMIN.showString('Field_Audit_Instance'),hidden:true}, - {field:"guid",label:PWM_ADMIN.showString('Field_Audit_GUID'),hidden:true}, - {field:"narrative",label:PWM_ADMIN.showString('Field_Audit_Narrative'),hidden:true} + {field:"timestamp",label:await PWM_ADMIN.getDisplayString('Field_Audit_Timestamp')}, + {field:"eventCode",label:await PWM_ADMIN.getDisplayString('Field_Audit_EventCode')}, + {field:"message",label:await PWM_ADMIN.getDisplayString('Field_Audit_Message')}, + {field:"instance",label:await PWM_ADMIN.getDisplayString('Field_Audit_Instance'),hidden:true}, + {field:"guid",label:await PWM_ADMIN.getDisplayString('Field_Audit_GUID'),hidden:true}, + {field:"narrative",label:await PWM_ADMIN.getDisplayString('Field_Audit_Narrative'),hidden:true} ]; }; -PWM_ADMIN.logHeaders = function() { +PWM_ADMIN.logHeaders = async function() { return [ - {field:"d",label:PWM_ADMIN.showString('Field_Logs_Timestamp')}, - {field:"l",label:PWM_ADMIN.showString('Field_Logs_Level')}, - {field:"s",label:PWM_ADMIN.showString('Field_Logs_Source'),hidden:true}, - {field:"b",label:PWM_ADMIN.showString('Field_Logs_Label')}, - {field:"a",label:PWM_ADMIN.showString('Field_Logs_User'),hidden:true}, - {field:"t",label:PWM_ADMIN.showString('Field_Logs_Component'),hidden:true}, - {field:"m",label:PWM_ADMIN.showString('Field_Logs_Detail')}, - {field:"e",label:PWM_ADMIN.showString('Field_Logs_Error'),hidden:true} + {field:"d",label:await PWM_ADMIN.getDisplayString('Field_Logs_Timestamp')}, + {field:"l",label:await PWM_ADMIN.getDisplayString('Field_Logs_Level')}, + {field:"s",label:await PWM_ADMIN.getDisplayString('Field_Logs_Source'),hidden:true}, + {field:"b",label:await PWM_ADMIN.getDisplayString('Field_Logs_Label')}, + {field:"a",label:await PWM_ADMIN.getDisplayString('Field_Logs_User'),hidden:true}, + {field:"t",label:await PWM_ADMIN.getDisplayString('Field_Logs_Component'),hidden:true}, + {field:"m",label:await PWM_ADMIN.getDisplayString('Field_Logs_Detail')}, + {field:"e",label:await PWM_ADMIN.getDisplayString('Field_Logs_Error'),hidden:true} ]; }; -PWM_ADMIN.initLogGrid=function() { +PWM_ADMIN.initLogGrid=async function() { + const logHeaders = await PWM_ADMIN.logHeaders(); require(["dojo","dojo/_base/declare", "dgrid/Grid", "dgrid/Keyboard", "dgrid/Selection", "dgrid/extensions/ColumnResizer", "dgrid/extensions/ColumnReorder", "dgrid/extensions/ColumnHider", "dgrid/extensions/DijitRegistry"], function(dojo, declare, Grid, Keyboard, Selection, ColumnResizer, ColumnReorder, ColumnHider, DijitRegistry){ // Create a new constructor by mixing in the components const CustomGrid = declare([Grid, Keyboard, Selection, ColumnResizer, ColumnReorder, ColumnHider, DijitRegistry]); // Now, create an instance of our custom userGrid - PWM_VAR['logViewerGrid'] = new CustomGrid({columns: PWM_ADMIN.logHeaders()}, "logViewerGrid"); + PWM_VAR['logViewerGrid'] = new CustomGrid({columns: logHeaders}, "logViewerGrid"); PWM_VAR['logViewerGrid'].on(".dgrid-row:click", function(evt){ - PWM_ADMIN.detailView(evt, PWM_ADMIN.logHeaders(), PWM_VAR['logViewerGrid']); + PWM_ADMIN.detailView(evt, logHeaders, PWM_VAR['logViewerGrid']); }); } ); @@ -352,16 +359,16 @@ PWM_ADMIN.initLogGrid=function() { const loadSettings = function () { const settings = PWM_MAIN.Preferences.readSessionStorage('logSettings'); if (settings) { - PWM_MAIN.getObject('username').value = settings['username']; - PWM_MAIN.getObject('text').value = settings['text']; - PWM_MAIN.getObject('count').value = settings['count']; - PWM_MAIN.getObject('maxTime').value = settings['maxTime']; - PWM_MAIN.JSLibrary.setValueOfSelectElement('type', settings['type']); - PWM_MAIN.JSLibrary.setValueOfSelectElement('level', settings['level']); - PWM_MAIN.JSLibrary.setValueOfSelectElement('displayType', settings['displayType']); - if (PWM_MAIN.getObject('form-downloadLog')) { - PWM_MAIN.JSLibrary.setValueOfSelectElement('downloadType', settings['downloadType']); - PWM_MAIN.JSLibrary.setValueOfSelectElement('compressionType', settings['compressionType']); + PWM_JSLibrary.getElement('username').value = settings['username']; + PWM_JSLibrary.getElement('text').value = settings['text']; + PWM_JSLibrary.getElement('count').value = settings['count']; + PWM_JSLibrary.getElement('maxTime').value = settings['maxTime']; + PWM_JSLibrary.setValueOfSelectElement('type', settings['type']); + PWM_JSLibrary.setValueOfSelectElement('level', settings['level']); + PWM_JSLibrary.setValueOfSelectElement('displayType', settings['displayType']); + if (PWM_JSLibrary.getElement('form-downloadLog')) { + PWM_JSLibrary.setValueOfSelectElement('downloadType', settings['downloadType']); + PWM_JSLibrary.setValueOfSelectElement('compressionType', settings['compressionType']); } } }; @@ -370,46 +377,48 @@ PWM_ADMIN.initLogGrid=function() { PWM_ADMIN.readLogFormData = function() { const settings = {}; - settings['username'] = PWM_MAIN.getObject('username').value; - settings['text'] = PWM_MAIN.getObject('text').value; - settings['count'] = PWM_MAIN.getObject('count').value; - settings['maxTime'] = PWM_MAIN.getObject('maxTime').value; - settings['type'] = PWM_MAIN.JSLibrary.readValueOfSelectElement('type'); - settings['level'] = PWM_MAIN.JSLibrary.readValueOfSelectElement('level'); - settings['displayType'] = PWM_MAIN.JSLibrary.readValueOfSelectElement('displayType'); - if (PWM_MAIN.getObject('form-downloadLog')) { - settings['downloadType'] = PWM_MAIN.JSLibrary.readValueOfSelectElement('downloadType'); - settings['compressionType'] = PWM_MAIN.JSLibrary.readValueOfSelectElement('compressionType'); + settings['username'] = PWM_JSLibrary.getElement('username').value; + settings['text'] = PWM_JSLibrary.getElement('text').value; + settings['count'] = PWM_JSLibrary.getElement('count').value; + settings['maxTime'] = PWM_JSLibrary.getElement('maxTime').value; + settings['type'] = PWM_JSLibrary.readValueOfSelectElement('type'); + settings['level'] = PWM_JSLibrary.readValueOfSelectElement('level'); + settings['displayType'] = PWM_JSLibrary.readValueOfSelectElement('displayType'); + if (PWM_JSLibrary.getElement('form-downloadLog')) { + settings['downloadType'] = PWM_JSLibrary.readValueOfSelectElement('downloadType'); + settings['compressionType'] = PWM_JSLibrary.readValueOfSelectElement('compressionType'); } return settings; }; -PWM_ADMIN.refreshLogData = function() { - PWM_MAIN.getObject('button-search').disabled = true; +PWM_ADMIN.refreshLogData = async function() { + const PWM_GLOBAL = await PWM_MAIN.getPwmGlobal(); + + PWM_JSLibrary.getElement('button-search').disabled = true; const logSettings = PWM_ADMIN.readLogFormData(); const processFunction = function (data) { console.time('someFunction'); const records = data['data']['records']; - if (PWM_MAIN.JSLibrary.isEmpty(records)) { - PWM_MAIN.removeCssClass('div-noResultsMessage', 'nodisplay'); - PWM_MAIN.addCssClass('wrapper-logViewerGrid', 'nodisplay'); - PWM_MAIN.addCssClass('wrapper-lineViewer', 'nodisplay'); + if (PWM_JSLibrary.isEmpty(records)) { + PWM_JSLibrary.removeCssClass('div-noResultsMessage', 'nodisplay'); + PWM_JSLibrary.addCssClass('wrapper-logViewerGrid', 'nodisplay'); + PWM_JSLibrary.addCssClass('wrapper-lineViewer', 'nodisplay'); } else { if (data['data']['display'] === 'grid') { - PWM_MAIN.addCssClass('div-noResultsMessage', 'nodisplay'); - PWM_MAIN.removeCssClass('wrapper-logViewerGrid', 'nodisplay'); - PWM_MAIN.addCssClass('wrapper-lineViewer', 'nodisplay'); + PWM_JSLibrary.addCssClass('div-noResultsMessage', 'nodisplay'); + PWM_JSLibrary.removeCssClass('wrapper-logViewerGrid', 'nodisplay'); + PWM_JSLibrary.addCssClass('wrapper-lineViewer', 'nodisplay'); const grid = PWM_VAR['logViewerGrid']; grid.refresh(); grid.renderArray(records); grid.set("timestamp", {attribute: 'createTime', ascending: false, descending: true}); } else { - PWM_MAIN.addCssClass('div-noResultsMessage', 'nodisplay'); - PWM_MAIN.addCssClass('wrapper-logViewerGrid', 'nodisplay'); - PWM_MAIN.removeCssClass('wrapper-lineViewer', 'nodisplay'); + PWM_JSLibrary.addCssClass('div-noResultsMessage', 'nodisplay'); + PWM_JSLibrary.addCssClass('wrapper-logViewerGrid', 'nodisplay'); + PWM_JSLibrary.removeCssClass('wrapper-lineViewer', 'nodisplay'); let textOutput = ''; for (let iterator in records) { @@ -418,16 +427,16 @@ PWM_ADMIN.refreshLogData = function() { textOutput += "\n"; }(iterator)); } - PWM_MAIN.getObject('lineViewer').textContent = textOutput; + PWM_JSLibrary.getElement('lineViewer').textContent = textOutput; } } console.timeEnd('someFunction'); - PWM_MAIN.getObject('button-search').disabled = false; + PWM_JSLibrary.getElement('button-search').disabled = false; PWM_MAIN.closeWaitDialog(); }; - const url = PWM_MAIN.addParamToUrl(PWM_GLOBAL['url-context'] + '/private/admin', 'processAction', 'readLogData'); + const url = await PWM_MAIN.addParamToUrl(PWM_GLOBAL['url-context'] + '/private/admin', 'processAction', 'readLogData'); const options = {}; options.content = logSettings; @@ -438,34 +447,38 @@ PWM_ADMIN.refreshLogData = function() { }; -PWM_ADMIN.initAuditGrid=function() { +PWM_ADMIN.initAuditGrid=async function() { + const userHeaders = await PWM_ADMIN.auditUserHeaders(); + const systemHeaders = await PWM_ADMIN.auditSystemHeaders(); + const helpdeskHeaders = await PWM_ADMIN.auditHelpdeskHeaders(); + require(["dojo","dojo/_base/declare", "dgrid/Grid", "dgrid/Keyboard", "dgrid/Selection", "dgrid/extensions/ColumnResizer", "dgrid/extensions/ColumnReorder", "dgrid/extensions/ColumnHider", "dgrid/extensions/DijitRegistry"], function(dojo, declare, Grid, Keyboard, Selection, ColumnResizer, ColumnReorder, ColumnHider, DijitRegistry){ // Create a new constructor by mixing in the components const CustomGrid = declare([Grid, Keyboard, Selection, ColumnResizer, ColumnReorder, ColumnHider, DijitRegistry]); // Now, create an instance of our custom userGrid - PWM_VAR['auditUserGrid'] = new CustomGrid({columns: PWM_ADMIN.auditUserHeaders()}, "auditUserGrid"); - PWM_VAR['auditSystemGrid'] = new CustomGrid({columns: PWM_ADMIN.auditSystemHeaders()}, "auditSystemGrid"); - PWM_VAR['auditHelpdeskGrid'] = new CustomGrid({columns: PWM_ADMIN.auditHelpdeskHeaders()}, "auditHelpdeskGrid"); + PWM_VAR['auditUserGrid'] = new CustomGrid({columns: userHeaders}, "auditUserGrid"); + PWM_VAR['auditSystemGrid'] = new CustomGrid({columns: systemHeaders}, "auditSystemGrid"); + PWM_VAR['auditHelpdeskGrid'] = new CustomGrid({columns: helpdeskHeaders}, "auditHelpdeskGrid"); PWM_ADMIN.refreshAuditGridData(undefined,'USER'); PWM_ADMIN.refreshAuditGridData(undefined,'HELPDESK'); PWM_ADMIN.refreshAuditGridData(undefined,'SYSTEM'); PWM_VAR['auditUserGrid'].on(".dgrid-row:click", function(evt){ - PWM_ADMIN.detailView(evt, PWM_ADMIN.auditUserHeaders(), PWM_VAR['auditUserGrid']); + PWM_ADMIN.detailView(evt, userHeaders, PWM_VAR['auditUserGrid']); }); PWM_VAR['auditSystemGrid'].on(".dgrid-row:click", function(evt){ - PWM_ADMIN.detailView(evt, PWM_ADMIN.auditSystemHeaders(), PWM_VAR['auditSystemGrid']); + PWM_ADMIN.detailView(evt, systemHeaders, PWM_VAR['auditSystemGrid']); }); PWM_VAR['auditHelpdeskGrid'].on(".dgrid-row:click", function(evt){ - PWM_ADMIN.detailView(evt, PWM_ADMIN.auditHelpdeskHeaders(), PWM_VAR['auditHelpdeskGrid']); + PWM_ADMIN.detailView(evt, helpdeskHeaders, PWM_VAR['auditHelpdeskGrid']); }); }); }; -PWM_ADMIN.refreshAuditGridData=function(maximum,type) { +PWM_ADMIN.refreshAuditGridData=async function(maximum,type) { switch (type) { case 'USER': var grid = PWM_VAR['auditUserGrid']; @@ -486,16 +499,18 @@ PWM_ADMIN.refreshAuditGridData=function(maximum,type) { maximum = 100; } - let url = PWM_MAIN.addParamToUrl(window.location.href, "processAction", "auditData"); - url = PWM_MAIN.addParamToUrl(url,'maximum',maximum); - url = PWM_MAIN.addParamToUrl(url,'type',type); + let url = await PWM_MAIN.addParamToUrl(window.location.href, "processAction", "auditData"); + url = await PWM_MAIN.addParamToUrl(url,'maximum',maximum); + url = await PWM_MAIN.addParamToUrl(url,'type',type); const loadFunction = function (data) { grid.renderArray(data['data']['records']); }; PWM_MAIN.ajaxRequest(url,loadFunction,{method:'GET'}); }; -PWM_ADMIN.showStatChart = function(statName,days,divName,options) { +PWM_ADMIN.showStatChart = async function(statName,days,divName,options) { + const PWM_GLOBAL = await PWM_MAIN.getPwmGlobal(); + options = options === undefined ? {} : options; const doRefresh = options['refreshTime'] ? function () { @@ -505,7 +520,7 @@ PWM_ADMIN.showStatChart = function(statName,days,divName,options) { } : function () { }; - let statsGetUrl = PWM_MAIN.addParamToUrl(PWM_GLOBAL['url-context'] + '/public/api', "processAction", "statistics"); + let statsGetUrl = await PWM_MAIN.addParamToUrl(PWM_GLOBAL['url-context'] + '/public/api', "processAction", "statistics"); const epsTypes = PWM_GLOBAL['epsTypes']; const epsDurations = PWM_GLOBAL['epsDurations']; require(["dojo", @@ -528,7 +543,7 @@ PWM_ADMIN.showStatChart = function(statName,days,divName,options) { for (let loopEpsDurationsIndex = 0; loopEpsDurationsIndex < epsDurations.length; loopEpsDurationsIndex++) { // clear all the gauges const loopEpsDuration = epsDurations[loopEpsDurationsIndex] + ''; const loopEpsID = "EPS-GAUGE-" + loopEpsName + "_" + loopEpsDuration; - if (PWM_MAIN.getObject(loopEpsID) !== null) { + if (PWM_JSLibrary.getElement(loopEpsID) !== null) { if (registry.byId(loopEpsID)) { registry.byId(loopEpsID).setAttribute('value', '0'); } @@ -555,10 +570,10 @@ PWM_ADMIN.showStatChart = function(statName,days,divName,options) { if (loopEpsDuration === "HOURLY") { activityCount += loopEpsValue; } - if (PWM_MAIN.getObject(loopFieldEpsID) !== null) { - PWM_MAIN.getObject(loopFieldEpsID).innerHTML = loopEpmValue; + if (PWM_JSLibrary.getElement(loopFieldEpsID) !== null) { + PWM_JSLibrary.getElement(loopFieldEpsID).innerHTML = loopEpmValue; } - if (PWM_MAIN.getObject(loopEpsID) !== null) { + if (PWM_JSLibrary.getElement(loopEpsID) !== null) { console.log('EpsID=' + loopEpsID + ', ' + 'Eps=' + loopEpsValue + ', ' + 'Epm=' + loopEpmValue); if (registry.byId(loopEpsID)) { registry.byId(loopEpsID).setAttribute('value', loopEpmValue); @@ -581,9 +596,8 @@ PWM_ADMIN.showStatChart = function(statName,days,divName,options) { } } } - PWM_GLOBAL['epsActivityCount'] = activityCount; } - if (divName !== null && PWM_MAIN.getObject(divName)) { // stats chart + if (divName !== null && PWM_JSLibrary.getElement(divName)) { // stats chart const values = []; for (let key in data['nameData']) { const value = data['nameData'][key]; @@ -610,10 +624,10 @@ PWM_ADMIN.showStatChart = function(statName,days,divName,options) { }); }; -PWM_ADMIN.showAppHealth = function(parentDivID, options, refreshNow) { +PWM_ADMIN.showAppHealth = async function(parentDivID, options, refreshNow) { + const PWM_GLOBAL = await PWM_MAIN.getPwmGlobal(); const inputOpts = options || PWM_GLOBAL['showPwmHealthOptions'] || {}; - PWM_GLOBAL['showPwmHealthOptions'] = options; let refreshUrl = inputOpts['sourceUrl'] || PWM_GLOBAL['url-context'] + "/public/api?processAction=health"; const showRefresh = inputOpts['showRefresh']; const showTimestamp = inputOpts['showTimestamp']; @@ -621,107 +635,116 @@ PWM_ADMIN.showAppHealth = function(parentDivID, options, refreshNow) { const finishFunction = inputOpts['finishFunction']; console.log('starting showPwmHealth: refreshTime=' + refreshTime); - require(["dojo"],function(dojo){ - const parentDiv = dojo.byId(parentDivID); - if (PWM_GLOBAL['inhibitHealthUpdate'] === true) { - try { parentDiv.innerHTML = ''; } catch (e) { console.log('unable to update health div' + e) }; - return; - } + const parentDiv = PWM_JSLibrary.getElement(parentDivID); + if (PWM_GLOBAL['inhibitHealthUpdate'] === true) { + try { parentDiv.innerHTML = ''; } catch (e) { console.log('unable to update health div' + e) }; + return; + } - if (PWM_GLOBAL['healthCheckInProgress']) { - return; - } + if (healthCheckInProgress) { + return; + } - PWM_GLOBAL['healthCheckInProgress'] = "true"; + healthCheckInProgress = "true"; - if (refreshNow) { - parentDiv.innerHTML = '
    '; - refreshUrl = PWM_MAIN.addParamToUrl(refreshUrl, 'refreshImmediate', 'true'); - } + if (refreshNow) { + parentDiv.innerHTML = '
    '; + refreshUrl = PWM_MAIN.addParamToUrl(refreshUrl, 'refreshImmediate', 'true'); + } - const loadFunction = function (data) { - if (data['error']) { - PWM_MAIN.showErrorDialog(data); - } else { - PWM_GLOBAL['pwm-health'] = data['data']['overall']; - const htmlBody = PWM_ADMIN.makeHealthHtml(data['data'], showTimestamp, showRefresh); - parentDiv.innerHTML = htmlBody; - PWM_MAIN.TimestampHandler.initElement(PWM_MAIN.getObject('healthCheckTimestamp')); + let overallHealth; - PWM_MAIN.addEventHandler('button-refreshHealth', 'click', function () { - PWM_ADMIN.showAppHealth(parentDivID, options, true); - }); + const loadFunction = function (data) { + if (data['error']) { + PWM_MAIN.showErrorDialog(data); + } else { + overallHealth = data['data']['overall']; + const htmlBody = PWM_ADMIN.makeHealthHtml(data['data'], {showTimestamp:true, showRefresh:true}); + parentDiv.innerHTML = htmlBody; + PWM_MAIN.initTimestampElement(PWM_JSLibrary.getElement('healthCheckTimestamp')); - PWM_GLOBAL['healthCheckInProgress'] = false; + PWM_MAIN.addEventHandler('button-refreshHealth', 'click', function () { + PWM_ADMIN.showAppHealth(parentDivID, options, true); + }); - if (refreshTime > 0) { - setTimeout(function () { - PWM_ADMIN.showAppHealth(parentDivID, options); - }, refreshTime); - } - if (showTimestamp) { - PWM_MAIN.TimestampHandler.initElement(PWM_MAIN.getObject('healthCheckTimestamp')); - } - if (finishFunction) { - finishFunction(); - } - } - }; + healthCheckInProgress = false; - const errorFunction = function (error) { - if (error !== null) { - console.log('error reaching server: ' + error); - } - let htmlBody = '
    '; - htmlBody += '
    unable to load health data from server
    '; - htmlBody += '
    ' + new Date().toLocaleString() + '   '; - if (showRefresh) { - htmlBody += 'retry

    '; - } - htmlBody += '
    '; - parentDiv.innerHTML = htmlBody; - PWM_GLOBAL['healthCheckInProgress'] = false; - PWM_GLOBAL['pwm-health'] = 'WARN'; if (refreshTime > 0) { setTimeout(function () { PWM_ADMIN.showAppHealth(parentDivID, options); }, refreshTime); } + if (showTimestamp) { + PWM_MAIN.initTimestampElement(PWM_JSLibrary.getElement('healthCheckTimestamp')); + } if (finishFunction) { finishFunction(); } - }; + } + }; - PWM_MAIN.ajaxRequest(refreshUrl,loadFunction,{errorFunction:errorFunction,method:'GET'}); - }); + const errorFunction = function (error) { + if (error !== null) { + console.log('error reaching server: ' + error); + } + let htmlBody = '
    '; + htmlBody += '
    unable to load health data from server
    '; + htmlBody += '
    ' + new Date().toLocaleString() + '   '; + if (showRefresh) { + htmlBody += 'retry

    '; + } + htmlBody += '
    '; + parentDiv.innerHTML = htmlBody; + healthCheckInProgress = false; + overallHealth = 'WARN'; + if (refreshTime > 0) { + setTimeout(function () { + PWM_ADMIN.showAppHealth(parentDivID, options); + }, refreshTime); + } + if (finishFunction) { + finishFunction(); + } + }; + + PWM_MAIN.ajaxRequest(refreshUrl,loadFunction,{errorFunction:errorFunction,method:'GET'}); }; -PWM_ADMIN.makeHealthHtml = function(healthData, showTimestamp, showRefresh) { +PWM_ADMIN.makeHealthHtml = function(healthData, options) { + options = options || {}; const healthRecords = healthData['records']; let htmlBody = '
    '; - htmlBody += '
    '; - for (let i = 0; i < healthRecords.length; i++) { - (function(iter){ - const loopRecord = healthRecords[iter]; - htmlBody += ''; - })(i) - } + htmlBody += '
    '; - htmlBody += loopRecord['topic']; - htmlBody += ''; - htmlBody += loopRecord['status']; - htmlBody += '
    '; - htmlBody += loopRecord['detail']; - htmlBody += '
    '; + + const domainIds = new Set(); + PWM_JSLibrary.forEachInArray(healthRecords,function(loopRecord) { + domainIds.add(loopRecord.domainID); + }); + + PWM_JSLibrary.forEachInArray(Array.from(domainIds),function(domainId){ + if ( domainIds.size > 1 ) + { + htmlBody += `` + } + PWM_JSLibrary.forEachInArray(healthRecords,function(loopRecord){ + if ( domainId === loopRecord.domainID) { + htmlBody += '' + + `` + + `` + + `` + + ''; + } + }); + }); + htmlBody += '
    ${domainId}
    ${loopRecord.topic}${loopRecord.status}${loopRecord.detail}
    '; - if (showTimestamp || showRefresh) { + if (options.showTimestamp || options.showRefresh) { htmlBody += '"; @@ -749,12 +772,12 @@ PWM_ADMIN.initPwNotifyPage = function() { PWM_MAIN.addEventHandler('button-refreshPwNotifyStatus','click',function(){ PWM_MAIN.showWaitDialog({loadFunction:function() { - PWM_MAIN.getObject('button-refreshPwNotifyStatus').disabled = true; + PWM_JSLibrary.getElement('button-refreshPwNotifyStatus').disabled = true; PWM_ADMIN.loadPwNotifyStatus(); PWM_ADMIN.loadPwNotifyLog(); setTimeout(function () { PWM_MAIN.closeWaitDialog(); - PWM_MAIN.getObject('button-refreshPwNotifyStatus').disabled = false; + PWM_JSLibrary.getElement('button-refreshPwNotifyStatus').disabled = false; },500); } }); @@ -785,18 +808,18 @@ PWM_ADMIN.loadPwNotifyStatus = function () { })(item); } - PWM_MAIN.getObject('table-pwNotifyStatus').innerHTML = htmlData; + PWM_JSLibrary.getElement('table-pwNotifyStatus').innerHTML = htmlData; for (var item in statusData) { (function (key) { const item = statusData[key]; if (item['type'] === 'timestamp') { - PWM_MAIN.TimestampHandler.initElement(PWM_MAIN.getObject('pwNotifyStatusRow-' + key)); + PWM_MAIN.initTimestampElement(PWM_JSLibrary.getElement('pwNotifyStatusRow-' + key)); } })(item); } - PWM_MAIN.getObject('button-executePwNotifyJob').disabled = !data['data']['enableStartButton']; + PWM_JSLibrary.getElement('button-executePwNotifyJob').disabled = !data['data']['enableStartButton']; }; const url = PWM_MAIN.addParamToUrl(window.location.href, 'processAction', 'readPwNotifyStatus'); PWM_MAIN.ajaxRequest(url, processData); @@ -807,10 +830,10 @@ PWM_ADMIN.loadPwNotifyLog = function () { const processData = function (data) { const debugData = data['data']; if (debugData && debugData.length > 0) { - PWM_MAIN.getObject('div-pwNotifyDebugLog').innerHTML = ''; - PWM_MAIN.getObject('div-pwNotifyDebugLog').appendChild(document.createTextNode(debugData)); + PWM_JSLibrary.getElement('div-pwNotifyDebugLog').innerHTML = ''; + PWM_JSLibrary.getElement('div-pwNotifyDebugLog').appendChild(document.createTextNode(debugData)); } else { - PWM_MAIN.getObject('div-pwNotifyDebugLog').innerHTML = 'Job has not been run on this server since startup.'; + PWM_JSLibrary.getElement('div-pwNotifyDebugLog').innerHTML = 'Job has not been run on this server since startup.'; } }; const url = PWM_MAIN.addParamToUrl(window.location.href, 'processAction', 'readPwNotifyLog'); @@ -831,15 +854,13 @@ PWM_ADMIN.detailView = function(evt, headers, grid){ text += '' + label + ''; text += ''; if (key.toLowerCase().indexOf('time') >= 0) { - PWM_MAIN.TimestampHandler.testIfStringIsTimestamp(value,function(){ - postExecuteFunctions.push(function() { - PWM_MAIN.TimestampHandler.initElement(PWM_MAIN.getObject(id)); - }); + postExecuteFunctions.push(function() { + PWM_MAIN.initTimestampElement(PWM_JSLibrary.getElement(id)); }); } text += ''; postExecuteFunctions.push(function(){ - PWM_MAIN.getObject(id).appendChild(document.createTextNode(value)); + PWM_JSLibrary.getElement(id).appendChild(document.createTextNode(value)); }); })(item); } @@ -851,13 +872,24 @@ PWM_ADMIN.detailView = function(evt, headers, grid){ }}); }; -PWM_ADMIN.showString=function (key, options) { +PWM_ADMIN.getDisplayString=async function(key, options) { options = options || {}; options['bundle'] = 'Admin'; - return PWM_MAIN.showString(key,options); + return await PWM_MAIN.getDisplayString(key,options); }; -PWM_ADMIN.initAdminPage=function(nextFunction) { - if (nextFunction) nextFunction(); -}; +PWM_ADMIN.initActivityPage=function() { + PWM_ADMIN.initAuditGrid(); + PWM_ADMIN.initActiveSessionGrid(); + PWM_ADMIN.initIntrudersGrid(); + PWM_MAIN.addEventHandler('button-refreshAuditUser','click',function(){ + PWM_ADMIN.refreshAuditGridData(PWM_JSLibrary.getElement('maxAuditUserResults').value,'USER'); + }); + PWM_MAIN.addEventHandler('button-refreshHelpdeskUser','click',function(){ + PWM_ADMIN.refreshAuditGridData(PWM_JSLibrary.getElement('maxAuditHelpdeskResults').value,'HELPDESK'); + }); + PWM_MAIN.addEventHandler('button-refreshSystemAudit','click',function(){ + PWM_ADMIN.refreshAuditGridData(PWM_JSLibrary.getElement('maxAuditSystemResults').value,'SYSTEM'); + }); +} \ No newline at end of file diff --git a/webapp/src/main/webapp/public/resources/js/changepassword.js b/webapp/src/main/webapp/public/resources/js/changepassword.js index 42c306a50b..4ba7287c2f 100644 --- a/webapp/src/main/webapp/public/resources/js/changepassword.js +++ b/webapp/src/main/webapp/public/resources/js/changepassword.js @@ -22,9 +22,12 @@ // PWM Change Password JavaScript. // -"use strict"; +import {PWM_JSLibrary} from "./jslibrary.js"; +import {PWM_MAIN} from './main.js'; -var PWM_CHANGEPW = PWM_CHANGEPW || {}; +const PWM_CHANGEPW = {}; + +export { PWM_CHANGEPW }; // takes password values in the password fields, sends an http request to the servlet // and then parses (and displays) the response from the servlet. @@ -32,26 +35,33 @@ var PWM_CHANGEPW = PWM_CHANGEPW || {}; PWM_CHANGEPW.passwordField = "password1"; PWM_CHANGEPW.passwordConfirmField = "password2"; -PWM_CHANGEPW.validatePasswords = function(userDN, nextFunction) +const RANDOM_PW_WAIT_HTML = ' '; + +let previousP1 = null; + +PWM_CHANGEPW.validatePasswords = async function(userDN, nextFunction, extraRequestData) { // if p1 is changing, then clear out p2. - if (PWM_GLOBAL['previousP1'] !== PWM_MAIN.getObject(PWM_CHANGEPW.passwordField).value) { - PWM_MAIN.getObject(PWM_CHANGEPW.passwordConfirmField ).value = ""; - PWM_GLOBAL['previousP1'] = PWM_MAIN.getObject(PWM_CHANGEPW.passwordField).value; + if (previousP1 !== PWM_JSLibrary.getElement(PWM_CHANGEPW.passwordField).value) { + PWM_JSLibrary.getElement(PWM_CHANGEPW.passwordConfirmField ).value = ""; + previousP1 = PWM_JSLibrary.getElement(PWM_CHANGEPW.passwordField).value; } const validationProps = {}; validationProps['completeFunction'] = nextFunction ? nextFunction : function () {}; - validationProps['messageWorking'] = PWM_MAIN.showString('Display_CheckingPassword'); - validationProps['serviceURL'] = PWM_MAIN.addParamToUrl(window.location.pathname, 'processAction','checkPassword'); + validationProps['messageWorking'] = await PWM_MAIN.getDisplayString('Display_CheckingPassword'); + validationProps['serviceURL'] = await PWM_MAIN.addParamToUrl(window.location.pathname, 'processAction','checkPassword'); validationProps['readDataFunction'] = function(){ const returnObj = {}; - returnObj[PWM_CHANGEPW.passwordField ] = PWM_MAIN.getObject(PWM_CHANGEPW.passwordField ).value; - returnObj[PWM_CHANGEPW.passwordConfirmField ] = PWM_MAIN.getObject(PWM_CHANGEPW.passwordConfirmField ).value; + returnObj[PWM_CHANGEPW.passwordField ] = PWM_JSLibrary.getElement(PWM_CHANGEPW.passwordField ).value; + returnObj[PWM_CHANGEPW.passwordConfirmField ] = PWM_JSLibrary.getElement(PWM_CHANGEPW.passwordConfirmField ).value; if (userDN) { returnObj['username'] = userDN; } + if (extraRequestData) { + return Object.assign(returnObj, extraRequestData); + } return returnObj; }; validationProps['processResultsFunction'] = function(data){ @@ -66,9 +76,10 @@ PWM_CHANGEPW.validatePasswords = function(userDN, nextFunction) }; -PWM_CHANGEPW.updateDisplay = function(resultInfo) { +PWM_CHANGEPW.updateDisplay = async function(resultInfo) { if (!resultInfo) { - PWM_MAIN.showSuccess(PWM_MAIN.showString('Display_PasswordPrompt')); + const displayPrompt = await PWM_MAIN.getDisplayString('Display_PasswordPrompt'); + PWM_MAIN.showSuccess(displayPrompt); PWM_CHANGEPW.markStrength(0); PWM_CHANGEPW.markConfirmationCheck(null); return; @@ -84,7 +95,7 @@ PWM_CHANGEPW.updateDisplay = function(resultInfo) { if (resultInfo["passed"] === true) { if (resultInfo["match"] === "MATCH") { PWM_MAIN.showSuccess(message); - PWM_MAIN.getObject("password_button").disabled = false; + PWM_JSLibrary.getElement("password_button").disabled = false; } else { PWM_MAIN.showInfo(message); } @@ -106,29 +117,29 @@ PWM_CHANGEPW.updateDisplay = function(resultInfo) { }; PWM_CHANGEPW.markConfirmationCheck = function(matchStatus) { - if (PWM_MAIN.getObject("confirmCheckMark") && PWM_MAIN.getObject("confirmCrossMark")) { + if (PWM_JSLibrary.getElement("confirmCheckMark") && PWM_JSLibrary.getElement("confirmCrossMark")) { if (matchStatus === "MATCH") { - PWM_MAIN.removeCssClass("confirmCheckMark","nodisplay") - PWM_MAIN.addCssClass("confirmCrossMark","nodisplay") + PWM_JSLibrary.removeCssClass("confirmCheckMark","nodisplay") + PWM_JSLibrary.addCssClass("confirmCrossMark","nodisplay") } else if (matchStatus === "NO_MATCH") { - PWM_MAIN.addCssClass("confirmCheckMark","nodisplay") - PWM_MAIN.removeCssClass("confirmCrossMark","nodisplay") + PWM_JSLibrary.addCssClass("confirmCheckMark","nodisplay") + PWM_JSLibrary.removeCssClass("confirmCrossMark","nodisplay") } else { - PWM_MAIN.addCssClass("confirmCheckMark","nodisplay") - PWM_MAIN.addCssClass("confirmCrossMark","nodisplay") + PWM_JSLibrary.addCssClass("confirmCheckMark","nodisplay") + PWM_JSLibrary.addCssClass("confirmCrossMark","nodisplay") } } }; -PWM_CHANGEPW.markStrength = function(strength) { //strength meter - const passwordStrengthProgressElement = PWM_MAIN.getObject("passwordStrengthProgress"); +PWM_CHANGEPW.markStrength = async function(strength) { //strength meter + const passwordStrengthProgressElement = PWM_JSLibrary.getElement("passwordStrengthProgress"); if (!passwordStrengthProgressElement) { return; } - if (PWM_MAIN.getObject(PWM_CHANGEPW.passwordField).value.length > 0) { - PWM_MAIN.removeCssClass("strengthBox","noopacity"); + if (PWM_JSLibrary.getElement(PWM_CHANGEPW.passwordField).value.length > 0) { + PWM_JSLibrary.removeCssClass("strengthBox","noopacity"); } let strengthDescr; @@ -145,19 +156,19 @@ PWM_CHANGEPW.markStrength = function(strength) { //strength meter strengthDescr = "VeryLow"; } - let strengthLabel = PWM_MAIN.showString('Display_PasswordStrength' + strengthDescr); + let strengthLabel = await PWM_MAIN.getDisplayString('Display_PasswordStrength' + strengthDescr); passwordStrengthProgressElement.value = strength; passwordStrengthProgressElement.setAttribute("data-strength", strengthDescr); - const labelObject = PWM_MAIN.getObject("strengthLabelText"); + const labelObject = PWM_JSLibrary.getElement("strengthLabelText"); if (labelObject !== null) { labelObject.innerHTML = strengthLabel === null ? "" : strengthLabel; } }; -PWM_CHANGEPW.copyToPasswordFields = function(text) { // used to copy auto-generated passwords to password field +PWM_CHANGEPW.copyToPasswordFields = async function(text) { // used to copy auto-generated passwords to password field if (text.length > 255) { text = text.substring(0,255); } @@ -166,46 +177,47 @@ PWM_CHANGEPW.copyToPasswordFields = function(text) { // used to copy auto-genera PWM_MAIN.closeWaitDialog(); - PWM_MAIN.getObject(PWM_CHANGEPW.passwordField).value = text; - PWM_CHANGEPW.validatePasswords(); - PWM_MAIN.getObject(PWM_CHANGEPW.passwordConfirmField).focus(); + PWM_JSLibrary.getElement(PWM_CHANGEPW.passwordField).value = text; + await PWM_CHANGEPW.validatePasswords(); + PWM_JSLibrary.getElement(PWM_CHANGEPW.passwordConfirmField).focus(); ShowHidePasswordHandler.show('password1'); }; -PWM_CHANGEPW.showPasswordGuide=function() { +PWM_CHANGEPW.showPasswordGuide=async function() { + const PWM_GLOBAL = await PWM_MAIN.getPwmGlobal(); PWM_MAIN.showDialog({ showClose:true, - title: PWM_MAIN.showString('Title_PasswordGuide'), + title: await PWM_MAIN.getDisplayString('Title_PasswordGuide'), text: '
    ' + PWM_GLOBAL['passwordGuideText'] + '
    ' }); }; -PWM_CHANGEPW.handleChangePasswordSubmit=function(event) { +PWM_CHANGEPW.handleChangePasswordSubmit=async function(event,userDn) { + const title = await PWM_MAIN.getDisplayString('Title_ChangePassword'); console.log('intercepted change password submit'); - PWM_MAIN.cancelEvent(event); + PWM_JSLibrary.cancelEvent(event); const nextFunction = function (data) { console.log('post change password submit handler'); if (!data || data['data']['passed'] && 'MATCH' === data['data']['match']) { console.log('submitting password form'); - PWM_MAIN.getObject("changePasswordForm").submit(); + PWM_JSLibrary.getElement("changePasswordForm").submit(); } else { PWM_MAIN.closeWaitDialog(); const match = data['data']['match']; if ('MATCH' !== match) { - PWM_MAIN.getObject(PWM_CHANGEPW.passwordConfirmField).value = ''; + PWM_JSLibrary.getElement(PWM_CHANGEPW.passwordConfirmField).value = ''; } const okFunction = function () { if ('MATCH' === match || 'EMPTY' === match) { - PWM_MAIN.getObject(PWM_CHANGEPW.passwordField).focus(); + PWM_JSLibrary.getElement(PWM_CHANGEPW.passwordField).focus(); } else { - PWM_MAIN.getObject(PWM_CHANGEPW.passwordConfirmField).focus(); + PWM_JSLibrary.getElement(PWM_CHANGEPW.passwordConfirmField).focus(); } PWM_CHANGEPW.validatePasswords(); }; - const title = PWM_MAIN.showString('Title_ChangePassword'); const message = '
    ' + data['data']['message'] + '
    '; PWM_MAIN.showDialog({text: message, title: title, okAction: okFunction}); } @@ -215,24 +227,28 @@ PWM_CHANGEPW.handleChangePasswordSubmit=function(event) { loadFunction:function(){ PWM_MAIN.showInfo('\xa0'); setTimeout(function(){ - PWM_CHANGEPW.validatePasswords(null, nextFunction); + PWM_CHANGEPW.validatePasswords(userDn, nextFunction); },500); }} ); }; -PWM_CHANGEPW.doRandomGeneration=function(randomConfig) { +PWM_CHANGEPW.doRandomGeneration=async function(randomConfig) { randomConfig = randomConfig === undefined ? {} : randomConfig; const finishAction = 'finishAction' in randomConfig ? randomConfig['finishAction'] : function (password) { PWM_CHANGEPW.copyToPasswordFields(password) }; + const displayPwGen = await PWM_MAIN.getDisplayString('Display_PasswordGeneration'); + const displayButtonMore = await PWM_MAIN.getDisplayString('Button_More'); + const displayButtonCancel = await PWM_MAIN.getDisplayString('Button_Cancel'); + const eventHandlers = []; let dialogBody = ""; if (randomConfig['dialog'] && randomConfig['dialog'].length > 0) { dialogBody += randomConfig['dialog']; } else { - dialogBody += PWM_MAIN.showString('Display_PasswordGeneration'); + dialogBody += displayPwGen; } dialogBody += "

    "; dialogBody += ''; @@ -241,10 +257,11 @@ PWM_CHANGEPW.doRandomGeneration=function(randomConfig) { (function(index) { const elementID = "randomGen" + index; dialogBody += ''; - dialogBody += ''; + dialogBody += '


    "; dialogBody += ''; - dialogBody += ''; - dialogBody += ''; + dialogBody += ''; + dialogBody += ''; dialogBody += "
    "; randomConfig['dialogBody'] = dialogBody; @@ -273,7 +290,8 @@ PWM_CHANGEPW.doRandomGeneration=function(randomConfig) { }); - const titleString = randomConfig['title'] ? randomConfig['title'] : PWM_MAIN.showString('Title_RandomPasswords'); + const titleString = randomConfig['title'] ? randomConfig['title'] : await PWM_MAIN.getDisplayString('Title_RandomPasswords'); + PWM_MAIN.showDialog({ title:titleString, text:dialogBody, @@ -289,9 +307,9 @@ PWM_CHANGEPW.doRandomGeneration=function(randomConfig) { }; PWM_CHANGEPW.beginFetchRandoms=function(randomConfig) { - PWM_MAIN.getObject('moreRandomsButton').disabled = true; + PWM_JSLibrary.getElement('moreRandomsButton').disabled = true; const fetchList = new Array(); - for (let counter = 0; counter < 20; counter++) { + for (let counter = 0; counter < 10; counter++) { fetchList[counter] = 'randomGen' + counter; } fetchList.sort(function() {return 0.5 - Math.random()}); @@ -300,47 +318,51 @@ PWM_CHANGEPW.beginFetchRandoms=function(randomConfig) { PWM_CHANGEPW.fetchRandoms(randomConfig); }; -PWM_CHANGEPW.fetchRandoms=function(randomConfig) { - if (randomConfig['fetchList'].length < 1) { - const moreButton = PWM_MAIN.getObject('moreRandomsButton'); - if (moreButton !== null) { - moreButton.disabled = false; - moreButton.focus(); - } - return; - } +PWM_CHANGEPW.fetchRandoms=async function(randomConfig) { if (randomConfig['fetchList'].length > 0) { + const elementID = randomConfig['fetchList'].pop(); + const element = PWM_JSLibrary.getElement(elementID); + if (element !== null) { + element.innerHTML = RANDOM_PW_WAIT_HTML; + } + + PWM_CHANGEPW.fetchRandoms(randomConfig); + const successFunction = function (resultInfo) { const password = resultInfo['data']["password"]; - const elementID = randomConfig['fetchList'].pop(); - const element = PWM_MAIN.getObject(elementID); if (element !== null) { - element.innerHTML = password; + element.innerText = password; + } + + if (randomConfig['fetchList'].length < 1) { + const moreButton = PWM_JSLibrary.getElement('moreRandomsButton'); + if (moreButton !== null) { + moreButton.disabled = false; + moreButton.focus(); + } } - PWM_CHANGEPW.fetchRandoms(randomConfig); }; - const url = PWM_MAIN.addParamToUrl(window.location.pathname, 'processAction', 'randomPassword'); + const url = PWM_MAIN.addParamToUrl(null, 'processAction', 'randomPassword'); const content = randomConfig['dataInput'] === null ? {} : randomConfig['dataInput']; - PWM_MAIN.ajaxRequest(url,successFunction,{content:content}); } }; -PWM_CHANGEPW.startupChangePasswordPage=function() { +PWM_CHANGEPW.startupChangePasswordPage=async function() { + const PWM_GLOBAL = await PWM_MAIN.getPwmGlobal(); - //PWM_MAIN.getObject('password2').disabled = true; + //PWM_JSLibrary.getElement('password2').disabled = true; PWM_CHANGEPW.markStrength(0); // add handlers for main form - const changePasswordForm = PWM_MAIN.getObject('changePasswordForm'); + const changePasswordForm = PWM_JSLibrary.getElement('changePasswordForm'); PWM_MAIN.addEventHandler(changePasswordForm,"keyup, change",function(){ PWM_CHANGEPW.validatePasswords(null); }); PWM_MAIN.addEventHandler(changePasswordForm,"submit",function(event){ PWM_CHANGEPW.handleChangePasswordSubmit(event); - //PWM_MAIN.handleFormSubmit(changePasswordForm, event); return false; }); PWM_MAIN.addEventHandler(changePasswordForm,"reset",function(){ @@ -350,64 +372,66 @@ PWM_CHANGEPW.startupChangePasswordPage=function() { }); // show the auto generate password panel - const autoGenPasswordElement = PWM_MAIN.getObject("autogenerate-icon"); + const autoGenPasswordElement = PWM_JSLibrary.getElement("autogenerate-icon"); if (autoGenPasswordElement !== null) { - PWM_MAIN.removeCssClass(autoGenPasswordElement, "nodisplay"); + PWM_JSLibrary.removeCssClass(autoGenPasswordElement, "nodisplay"); PWM_MAIN.addEventHandler(autoGenPasswordElement,'click',function(){ PWM_CHANGEPW.doRandomGeneration(); }); } - PWM_MAIN.addEventHandler('button-reset','click',function(event){ + PWM_MAIN.addEventHandler('button-reset','click',async function(event){ console.log('intercepted reset button'); - const p1Value = PWM_MAIN.getObject(PWM_CHANGEPW.passwordField).value; - const p2Value = PWM_MAIN.getObject(PWM_CHANGEPW.passwordConfirmField).value; + const p1Value = PWM_JSLibrary.getElement(PWM_CHANGEPW.passwordField).value; + const p2Value = PWM_JSLibrary.getElement(PWM_CHANGEPW.passwordConfirmField).value; const submitForm = function () { - const resetForm = PWM_MAIN.getObject('form-reset'); + const resetForm = PWM_JSLibrary.getElement('form-reset'); PWM_MAIN.handleFormSubmit(resetForm); }; + const confirmDialogOptions = { + text:await PWM_MAIN.getDisplayString('Display_LeaveDirtyPasswordPage'), + okLabel:await PWM_MAIN.getDisplayString('Button_Continue'), + okAction:function(){ + submitForm(); + } + } + if ( p1Value.length > 0 || p2Value.length > 0 ) { - PWM_MAIN.cancelEvent(event); - PWM_MAIN.showConfirmDialog({ - text:PWM_MAIN.showString('Display_LeaveDirtyPasswordPage'), - okLabel:PWM_MAIN.showString('Button_Continue'), - okAction:function(){ - submitForm(); - } - }); + PWM_JSLibrary.cancelEvent(event); + PWM_MAIN.showConfirmDialog(confirmDialogOptions); } else { submitForm(); } }); - const messageElement = PWM_MAIN.getObject("message"); + const messageElement = PWM_JSLibrary.getElement("message"); if (messageElement.firstChild.nodeValue.length < 2) { - setTimeout(function(){ - PWM_MAIN.showInfo(PWM_MAIN.showString('Display_PasswordPrompt')); + setTimeout(async function(){ + PWM_MAIN.showInfo(await PWM_MAIN.getDisplayString('Display_PasswordPrompt')); },100); } if (PWM_GLOBAL['passwordGuideText'] && PWM_GLOBAL['passwordGuideText'].length > 0) { - const iconElement = PWM_MAIN.getObject('password-guide-icon'); + const iconElement = PWM_JSLibrary.getElement('password-guide-icon'); if (iconElement) { - PWM_MAIN.removeCssClass(iconElement,'nodisplay'); + PWM_JSLibrary.removeCssClass(iconElement,'nodisplay'); PWM_MAIN.addEventHandler('password-guide-icon','click',function(){ PWM_CHANGEPW.showPasswordGuide(); }); } } - const tooltipText = PWM_MAIN.showString('Tooltip_PasswordStrength'); + const tooltipText = await PWM_MAIN.getDisplayString('Tooltip_PasswordStrength'); if (tooltipText) { - const strengthHelpIcon = PWM_MAIN.getObject('strength-tooltip-icon'); + const strengthHelpIcon = PWM_JSLibrary.getElement('strength-tooltip-icon'); if (strengthHelpIcon) { - PWM_MAIN.addEventHandler('strength-tooltip-icon','click',function(){ + PWM_MAIN.addEventHandler('strength-tooltip-icon','click',async function(){ PWM_MAIN.showDialog({ showClose:true, - title: PWM_MAIN.showString('Title_PasswordGuide'), + title: await PWM_MAIN.getDisplayString('Title_PasswordGuide'), text: '
    ' + tooltipText + '
    ' }); }) @@ -420,21 +444,26 @@ PWM_CHANGEPW.startupChangePasswordPage=function() { }; PWM_CHANGEPW.setInputFocus=function() { - const currentPassword = PWM_MAIN.getObject('currentPassword'); + const currentPassword = PWM_JSLibrary.getElement('currentPassword'); if (currentPassword !== null) { setTimeout(function() { currentPassword.focus(); },10); } else { - const password1 = PWM_MAIN.getObject('password1'); + const password1 = PWM_JSLibrary.getElement('password1'); setTimeout(function() { password1.focus(); },10); } }; -PWM_CHANGEPW.refreshCreateStatus=function(refreshInterval) { +PWM_CHANGEPW.refreshCreateStatus=async function(refreshInterval) { const displayStringsUrl = PWM_MAIN.addParamToUrl(window.location.href, "processAction", "checkProgress"); - const completedUrl = PWM_MAIN.addPwmFormIDtoURL(PWM_MAIN.addParamToUrl(window.location.href, "processAction", "complete")); + const completedUrl = await PWM_MAIN.addPwmFormIDtoURL(PWM_MAIN.addParamToUrl(window.location.href, "processAction", "complete")); + + const displayProgComplete = await PWM_MAIN.getDisplayString('Value_ProgressComplete') + const displayInProg = await PWM_MAIN.getDisplayString('Value_ProgressInProgress'); + + const loadFunction = function (data) { console.log('beginning html5 progress refresh'); - const html5passwordProgressBar = PWM_MAIN.getObject('html5ProgressBar'); + const html5passwordProgressBar = PWM_JSLibrary.getElement('html5ProgressBar'); const percentComplete = data['data']['percentComplete']; html5passwordProgressBar.setAttribute("value", percentComplete ); @@ -445,20 +474,20 @@ PWM_CHANGEPW.refreshCreateStatus=function(refreshInterval) { (function (message) { if (message['show']) { tableBody += '' + message['label'] + ''; - tableBody += message['complete'] ? PWM_MAIN.showString('Value_ProgressComplete') : PWM_MAIN.showString('Value_ProgressInProgress'); + tableBody += message['complete'] ? displayProgComplete : displayInProg tableBody += ''; } }(data['data']['messages'][msgItem])); } } - if (PWM_MAIN.getObject('progressMessageTable')) { - PWM_MAIN.getObject('progressMessageTable').innerHTML = tableBody; + if (PWM_JSLibrary.getElement('progressMessageTable')) { + PWM_JSLibrary.getElement('progressMessageTable').innerHTML = tableBody; } - if (PWM_MAIN.getObject('estimatedRemainingSeconds')) { - PWM_MAIN.getObject('estimatedRemainingSeconds').innerHTML = data['data']['estimatedRemainingSeconds']; + if (PWM_JSLibrary.getElement('estimatedRemainingSeconds')) { + PWM_JSLibrary.getElement('estimatedRemainingSeconds').innerHTML = data['data']['estimatedRemainingSeconds']; } - if (PWM_MAIN.getObject('elapsedSeconds')) { - PWM_MAIN.getObject('elapsedSeconds').innerHTML = data['data']['elapsedSeconds']; + if (PWM_JSLibrary.getElement('elapsedSeconds')) { + PWM_JSLibrary.getElement('elapsedSeconds').innerHTML = data['data']['elapsedSeconds']; } } catch (e) { console.log('unable to update progressMessageTable, error: ' + e); @@ -480,3 +509,4 @@ PWM_CHANGEPW.refreshCreateStatus=function(refreshInterval) { }; PWM_MAIN.ajaxRequest(displayStringsUrl, loadFunction, {errorFunction:errorFunction}); }; + diff --git a/webapp/src/main/webapp/public/resources/js/configeditor-settings-action.js b/webapp/src/main/webapp/public/resources/js/configeditor-settings-action.js index 1de14c1c09..e69de29bb2 100644 --- a/webapp/src/main/webapp/public/resources/js/configeditor-settings-action.js +++ b/webapp/src/main/webapp/public/resources/js/configeditor-settings-action.js @@ -1,620 +0,0 @@ -/* - * Password Management Servlets (PWM) - * http://www.pwm-project.org - * - * Copyright (c) 2006-2009 Novell, Inc. - * Copyright (c) 2009-2021 The PWM Project - * - * 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. - */ - - -var ActionHandler = {}; -ActionHandler.defaultValue = { - name:"", - description:"", - webActions:[], - ldapActions:[] -}; - -ActionHandler.defaultWebValue = { - method:"get", - headers:{}, - url:"", - body:"", - successStatus:[200], - username:"", - password:"" -}; - -ActionHandler.defaultLdapValue = { - ldapMethod:"replace", - attributeName:"", - attributeValue:"" -}; - -ActionHandler.httpMethodOptions = [ - { label: "Delete", value: "delete" }, - { label: "Get", value: "get" }, - { label: "Post", value: "post" }, - { label: "Put", value: "put" }, - { label: "Patch", value: "patch" } -]; -ActionHandler.ldapMethodOptions = [ - { label: "Replace (Remove all existing values)", value: "replace" }, - { label: "Add (Append new value)", value: "add" }, - { label: "Remove (Remove specified value)", value: "remove" } -]; - -ActionHandler.init = function(keyName, postInitFunction) { - console.log('ActionHandler init for ' + keyName); - const parentDiv = 'table_setting_' + keyName; - PWM_CFGEDIT.clearDivElements(parentDiv, true); - PWM_CFGEDIT.readSetting(keyName, function(resultValue) { - PWM_VAR['clientSettingCache'][keyName] = resultValue; - ActionHandler.redraw(keyName); - if ( postInitFunction ) { - postInitFunction(); - } - }); -}; - -ActionHandler.redraw = function(keyName) { - console.log('ActionHandler redraw for ' + keyName); - const resultValue = PWM_VAR['clientSettingCache'][keyName]; - const parentDiv = 'table_setting_' + keyName; - PWM_CFGEDIT.clearDivElements(parentDiv, false); - const parentDivElement = PWM_MAIN.getObject(parentDiv); - - let html = ''; - if (!PWM_MAIN.JSLibrary.isEmpty(resultValue)) { - html += ''; - html += ''; - - for (const i in resultValue) { - html += ActionHandler.drawRow(keyName, i, resultValue[i]); - } - - html += '
    NameDescription
    '; - } - - html += '
    '; - parentDivElement.innerHTML = html; - - for (const i in resultValue) { - html += ActionHandler.addRowHandlers(keyName, i, resultValue[i]); - } - - PWM_MAIN.addEventHandler('button-' + keyName + '-addValue','click',function(){ - ActionHandler.addRow(keyName); - }); -}; - -ActionHandler.drawRow = function(settingKey, iteration, value) { - const inputID = 'value_' + settingKey + '_' + iteration + "_"; - const optionList = ["webservice","ldap"]; - - const newTableRow = document.createElement("tr"); - newTableRow.setAttribute("style", "border-width: 0"); - - let htmlRow = ''; - htmlRow += ''; - htmlRow += '
    '; - htmlRow += ''; - htmlRow += ''; - htmlRow += '
    '; - htmlRow += ''; - htmlRow += ''; - htmlRow += ''; - htmlRow += ''; - htmlRow += ''; - return htmlRow; -}; - -ActionHandler.addRowHandlers = function(settingKey, iteration, value) { - const inputID = 'value_' + settingKey + '_' + iteration + "_"; - UILibrary.addTextValueToElement('display-' + inputID + '-name',value['name']); - UILibrary.addTextValueToElement('display-' + inputID + '-description',value['description']); - - PWM_MAIN.addEventHandler('button-' + inputID + '-options','click',function(){ - ActionHandler.showActionsDialog(settingKey, iteration); - }); - - const descriptionEditFunction = function() { - UILibrary.stringEditorDialog({ - value: value['description'], - textarea: true, - title: 'Edit Description', - completeFunction: function (newValue) { - PWM_VAR['clientSettingCache'][settingKey][iteration]['description'] = newValue; - ActionHandler.write(settingKey,function(){ - ActionHandler.init(settingKey); - }); - } - }); - }; - - PWM_MAIN.addEventHandler('icon-editDescription-' + inputID,'click',function(){ - descriptionEditFunction(); - }); - PWM_MAIN.addEventHandler('border-editDescription-' + inputID,'click',function(){ - descriptionEditFunction(); - }); - PWM_MAIN.addEventHandler('display-' + inputID + '-description','click',function(){ - descriptionEditFunction(); - }); - PWM_MAIN.addEventHandler('button-' + inputID + '-deleteRow','click',function(){ - ActionHandler.removeRow(settingKey, iteration); - }); -}; - -ActionHandler.write = function(settingKey, finishFunction) { - const cachedSetting = PWM_VAR['clientSettingCache'][settingKey]; - PWM_CFGEDIT.writeSetting(settingKey, cachedSetting, finishFunction); -}; - -ActionHandler.removeRow = function(keyName, iteration) { - PWM_MAIN.showConfirmDialog({ - text:'Are you sure you wish to delete this item?', - okAction:function(){ - delete PWM_VAR['clientSettingCache'][keyName][iteration]; - console.log("removed iteration " + iteration + " from " + keyName + ", cached keyValue=" + PWM_VAR['clientSettingCache'][keyName]); - ActionHandler.write(keyName,function(){ - ActionHandler.init(keyName); - }); - } - }) -}; - -ActionHandler.addRow = function(keyName) { - UILibrary.stringEditorDialog({ - title:'New Action', - regex:'^[0-9a-zA-Z]+$', - instructions: 'Please enter a descriptive name for the action.', - placeholder:'Name', - completeFunction:function(newName){ - const value = PWM_VAR['clientSettingCache'][keyName]; - const currentSize = PWM_MAIN.JSLibrary.itemCount(value); - value[currentSize] = ActionHandler.defaultValue; - value[currentSize].name = newName; - ActionHandler.write(keyName,function(){ - ActionHandler.init(keyName, function(){ - ActionHandler.showActionsDialog(keyName, currentSize); - }); - }); - } - }); -}; - -ActionHandler.showActionsDialog = function(keyName, iteration) { - const value = PWM_VAR['clientSettingCache'][keyName][iteration]; - const titleText = value['name'] + ' actions'; - let bodyText = ''; - - if (!PWM_MAIN.JSLibrary.isEmpty(value['ldapActions'])) { - bodyText += ''; - } - for (const iter in value['ldapActions']) { - (function (ldapActionsIter) { - const inputID = keyName + '_' + iteration + "_ldapActions_" + ldapActionsIter; - bodyText += '' - + '' - + '' - + '' - + '' - + ''; - }(iter)); - } - bodyText += ''; - - if (!PWM_MAIN.JSLibrary.isEmpty(value['webActions'])) { - bodyText += ''; - } - for (const iter in value['webActions']) { - (function (webActionIter) { - const inputID = keyName + '_' + iteration + "_webActions_" + webActionIter; - bodyText += '' - + '' - + '' - + '' - + '' - + ''; - }(iter)); - } - - bodyText += '
    LDAP Attribute
    LDAP Action
     
    URL
    Web Service Action

    '; - - const inputID = keyName + '_' + iteration + "_"; - bodyText += '
    '; - bodyText += '
    '; - - PWM_MAIN.showDialog({ - title: titleText, - text: bodyText, - loadFunction: function(){ - for (const iter in value['ldapActions']) { - (function (ldapActionsIter) { - const inputID = keyName + '_' + iteration + "_ldapActions_" + ldapActionsIter; - PWM_MAIN.addEventHandler('tableRow-' + inputID ,'click',function(){ - ActionHandler.addOrEditLdapAction(keyName,iteration,ldapActionsIter); - }); - PWM_MAIN.addEventHandler('button-deleteRow-' + inputID ,'click',function(){ - PWM_MAIN.showConfirmDialog({okAction:function () { - PWM_VAR['clientSettingCache'][keyName][iteration]['ldapActions'].splice(ldapActionsIter,1); - ActionHandler.write(keyName,function(){ - ActionHandler.showActionsDialog(keyName,iteration); - }); - }, cancelAction:function(){ - ActionHandler.showActionsDialog(keyName,iteration); - } - }); - }); - const value = PWM_VAR['clientSettingCache'][keyName][iteration]['ldapActions'][ldapActionsIter]; - PWM_MAIN.getObject('value-' + inputID).value = value['attributeName']; - }(iter)); - } - for (const iter in value['webActions']) { - (function (webActionIter) { - const inputID = keyName + '_' + iteration + "_webActions_" + webActionIter; - PWM_MAIN.addEventHandler('tableRow-' + inputID ,'click',function(){ - ActionHandler.addOrEditWebAction(keyName,iteration,webActionIter); - }); - PWM_MAIN.addEventHandler('button-deleteRow-' + inputID ,'click',function(){ - PWM_MAIN.showConfirmDialog({okAction:function () { - PWM_VAR['clientSettingCache'][keyName][iteration]['webActions'].splice(webActionIter,1); - ActionHandler.write(keyName,function(){ - ActionHandler.showActionsDialog(keyName,iteration); - }); - }, cancelAction:function(){ - ActionHandler.showActionsDialog(keyName,iteration); - } - }); - }); - const value = PWM_VAR['clientSettingCache'][keyName][iteration]['webActions'][webActionIter]; - PWM_MAIN.getObject('value-' + inputID).value = value['url']; - }(iter)); - } - - PWM_MAIN.addEventHandler('button-addLdap-' + inputID,'click',function(){ - UILibrary.stringEditorDialog({ - textarea: false, - title: 'Attribute Name', - completeFunction: function (newValue) { - const currentSize = PWM_MAIN.JSLibrary.itemCount(value['ldapActions']); - value['ldapActions'].push(JSON.parse(JSON.stringify(ActionHandler.defaultLdapValue))); - value['ldapActions'][currentSize]['attributeName'] = newValue; - ActionHandler.write(keyName,function(){ - ActionHandler.addOrEditLdapAction(keyName,iteration,currentSize); - }); - } - }); - }); - PWM_MAIN.addEventHandler('button-addWebService-' + inputID,'click',function(){ - UILibrary.stringEditorDialog({ - textarea: false, - title: 'URL', - completeFunction: function (newValue) { - const currentSize = PWM_MAIN.JSLibrary.itemCount(value['webActions']); - value['webActions'].push(JSON.parse(JSON.stringify(ActionHandler.defaultWebValue))); - value['webActions'][currentSize]['url'] = newValue; - ActionHandler.write(keyName,function(){ - ActionHandler.addOrEditWebAction(keyName,iteration,currentSize); - }); - } - }); - }); - } - }); -}; - -ActionHandler.addOrEditLdapAction = function(keyName, iteration, ldapActionIter) { - const inputID = 'value_' + keyName + '_' + iteration + "_" + ldapActionIter; - const value = PWM_VAR['clientSettingCache'][keyName][iteration]['ldapActions'][ldapActionIter]; - const titleText = 'LDAP options'; - - let bodyText = ''; - bodyText += ''; - bodyText += ''; - bodyText += ''; - bodyText += ''; - bodyText += ''; - bodyText += ''; - bodyText += ''; - bodyText += '
    Attribute Name
    Attribute Value
    Operation Type
    '; - - PWM_MAIN.showDialog({ - title: titleText, - text: bodyText, - okAction: function(){ - ActionHandler.showActionsDialog(keyName, iteration); - }, - loadFunction: function(){ - PWM_MAIN.addEventHandler('input-' + inputID + '-attributeName','input',function(){ - value['attributeName'] = PWM_MAIN.getObject('input-' + inputID + '-attributeName').value; - ActionHandler.write(keyName); - }); - PWM_MAIN.addEventHandler('input-' + inputID + '-attributeValue','input',function(){ - value['attributeValue'] = PWM_MAIN.getObject('input-' + inputID + '-attributeValue').value; - ActionHandler.write(keyName); - }); - PWM_MAIN.addEventHandler('select-' + inputID + '-ldapMethod','change',function(){ - value['ldapMethod'] = PWM_MAIN.getObject('select-' + inputID + '-ldapMethod').value; - ActionHandler.write(keyName); - }); - } - }); -}; - - -ActionHandler.addOrEditWebAction = function(keyName, iteration, webActionIter) { - const inputID = 'value_' + keyName + '_' + iteration + "_" + webActionIter; - const value = PWM_VAR['clientSettingCache'][keyName][iteration]['webActions'][webActionIter]; - const titleText = 'Web Service Options'; - const showBody = value['method'] !== 'get' && value['method'] !== 'delete'; - - let bodyText = ''; - bodyText += ''; - bodyText += ''; - bodyText += ''; - bodyText += ''; - bodyText += ''; - bodyText += ''; - bodyText += ''; - bodyText += ''; - bodyText += ''; - bodyText += ''; - bodyText += ''; - if (showBody) { - bodyText += ''; - } - bodyText += ''; - if (value['certificates']) { - bodyText += ''; - bodyText += ''; - } else { - bodyText += ''; - bodyText += ''; - } - bodyText += ''; - bodyText += '
    HTTP Method
    HTTP Headers
    URL
    Basic Auth Username
    Basic Auth Password
    Body
    Success Status Codes'; - bodyText += ''; - bodyText += '
    CertificatesView Certificates
    CertificatesNone
    '; - - if (value['certificates']) { - bodyText += '' - } else { - bodyText += '' - } - - PWM_MAIN.showDialog({ - title: titleText, - text: bodyText, - okAction: function(){ - ActionHandler.showActionsDialog(keyName, iteration); - }, - loadFunction: function(){ - PWM_MAIN.addEventHandler('button-' + inputID + '-headers','click',function(){ - ActionHandler.showHeadersDialog(keyName,iteration, webActionIter); - }); - PWM_MAIN.addEventHandler('select-' + inputID + '-method','change',function(){ - const methodValue = PWM_MAIN.getObject('select-' + inputID + '-method').value; - if (methodValue === 'get') { - value['body'] = ''; - } - value['method'] = methodValue; - ActionHandler.write(keyName, function(){ ActionHandler.addOrEditWebAction(keyName,iteration,webActionIter)}); - }); - - PWM_MAIN.getObject('input-' + inputID + '-url').value = value['url']; - PWM_MAIN.getObject('input-' + inputID + '-url').disabled = false; - PWM_MAIN.addEventHandler('input-' + inputID + '-url','input',function(){ - value['url'] = PWM_MAIN.getObject('input-' + inputID + '-url').value; - ActionHandler.write(keyName); - }); - - PWM_MAIN.getObject('input-' + inputID + '-username').value = value['username'] ? value['username'] : ''; - PWM_MAIN.getObject('input-' + inputID + '-username').disabled = false; - PWM_MAIN.addEventHandler('input-' + inputID + '-username','input',function(){ - value['username'] = PWM_MAIN.getObject('input-' + inputID + '-username').value; - ActionHandler.write(keyName); - }); - - PWM_MAIN.getObject('input-' + inputID + '-password').value = value['password'] ? value['password'] : ''; - PWM_MAIN.getObject('input-' + inputID + '-password').disabled = false; - PWM_MAIN.addEventHandler('input-' + inputID + '-password','input',function(){ - value['password'] = PWM_MAIN.getObject('input-' + inputID + '-password').value; - ActionHandler.write(keyName); - }); - - PWM_MAIN.getObject('input-' + inputID + '-successStatus').value = value['successStatus'] ? value['successStatus'].join() : ''; - PWM_MAIN.addEventHandler('button-' + inputID + '-successStatus', 'click', function(){ - const options = {}; - options['regex'] = '[0-9]{3}'; - options['title'] = 'Success Status Codes'; - options['instructions'] = 'Enter the three digit HTTP status codes that will be considered a success if returned by the remote web service.'; - options['completeFunction'] = function(values){ - values.sort(); - value['successStatus'] = values; - ActionHandler.write(keyName); - ActionHandler.addOrEditWebAction(keyName, iteration, webActionIter) - }; - const values = 'successStatus' in value ? value['successStatus'] : []; - values.sort(); - options['value'] = values; - UILibrary.stringArrayEditorDialog(options); - }); - - if (showBody) { - UILibrary.addTextValueToElement('input-' + inputID + '-body', value['body']); - PWM_MAIN.getObject('input-' + inputID + '-body').disabled = false; - PWM_MAIN.addEventHandler('input-' + inputID + '-body', 'input', function () { - value['body'] = PWM_MAIN.getObject('input-' + inputID + '-body').value; - ActionHandler.write(keyName); - }); - } - if (value['certificates']) { - PWM_MAIN.addEventHandler('button-' + inputID + '-certDetail','click',function(){ - let extraData = JSON.stringify({iteration:iteration, webActionIter: webActionIter, keyName:keyName}); - PWM_CFGEDIT.executeSettingFunction(keyName, 'password.pwm.http.servlet.configeditor.function.ActionCertViewerFunction', - ActionHandler.showCertificateViewerDialog, extraData) - }); - PWM_MAIN.addEventHandler('button-' + inputID + '-clearCertificates','click',function() { - PWM_MAIN.showConfirmDialog({okAction:function(){ - delete value['certificates']; - ActionHandler.write(keyName, function(){ ActionHandler.addOrEditWebAction(keyName,iteration,webActionIter)}); - },cancelAction:function(){ - ActionHandler.showActionsDialog(keyName,iteration); - }}); - }); - } else { - PWM_MAIN.addEventHandler('button-' + inputID + '-importCertificates','click',function() { - const dataHandler = function(data) { - const msgBody = '
    ' + data['successMessage'] + '
    '; - PWM_MAIN.showDialog({width:700,title: 'Results', text: msgBody, okAction: function () { - PWM_CFGEDIT.readSetting(keyName, function(resultValue) { - PWM_VAR['clientSettingCache'][keyName] = resultValue; - ActionHandler.addOrEditWebAction(keyName,iteration,webActionIter) - }); - }}); - }; - const extraData = {}; - extraData.iteration = iteration; - extraData.webActionIter = webActionIter; - PWM_CFGEDIT.executeSettingFunction(keyName, 'password.pwm.http.servlet.configeditor.function.ActionCertImportFunction', dataHandler, JSON.stringify(extraData)); - }); - } - - } - }); -}; - -ActionHandler.showHeadersDialog = function(keyName, iteration, webActionIter) { - const settingValue = PWM_VAR['clientSettingCache'][keyName][iteration]['webActions'][webActionIter]; - - const inputID = 'value_' + keyName + '_' + iteration + "_webAction_" + webActionIter + "_headers_"; - - let bodyText = ''; - bodyText += ''; - bodyText += ''; - for (const iter in settingValue['headers']) { - (function(headerName) { - const value = settingValue['headers'][headerName]; - const optionID = inputID + headerName; - bodyText += ''; - bodyText += ''; - bodyText += ''; - }(iter)); - } - bodyText += '
    NameValue
    ' + headerName + '' + value + '
    '; - - PWM_MAIN.showDialog({ - title: 'HTTP Headers', - text: bodyText, - buttonHtml:'', - okAction: function() { - ActionHandler.addOrEditWebAction(keyName,iteration,webActionIter); - }, - loadFunction: function() { - for (const iter in settingValue['headers']) { - (function(headerName) { - const headerID = inputID + headerName; - PWM_MAIN.addEventHandler('button-' + headerID + '-deleteRow', 'click', function () { - delete settingValue['headers'][headerName]; - ActionHandler.write(keyName); - ActionHandler.showHeadersDialog(keyName, iteration, webActionIter); - }); - }(iter)); - } - PWM_MAIN.addEventHandler('button-' + inputID + '-addHeader','click',function(){ - ActionHandler.addHeader(keyName, iteration, webActionIter); - }); - } - }); -}; - -ActionHandler.addHeader = function(keyName, iteration, webActionIter) { - let body = ''; - body += ''; - body += ''; - body += '
    Name
    Value
    '; - - const updateFunction = function(){ - PWM_MAIN.getObject('dialog_ok_button').disabled = true; - PWM_VAR['newHeaderName'] = PWM_MAIN.getObject('newHeaderName').value; - PWM_VAR['newHeaderValue'] = PWM_MAIN.getObject('newHeaderValue').value; - if (PWM_VAR['newHeaderName'].length > 0 && PWM_VAR['newHeaderValue'].length > 0) { - PWM_MAIN.getObject('dialog_ok_button').disabled = false; - } - }; - - PWM_MAIN.showConfirmDialog({ - title:'New Header', - text:body, - showClose:true, - loadFunction:function(){ - PWM_MAIN.addEventHandler('newHeaderName','input',function(){ - updateFunction(); - }); - PWM_MAIN.addEventHandler('newHeaderValue','input',function(){ - updateFunction(); - }); - updateFunction(); - },okAction:function(){ - const headers = PWM_VAR['clientSettingCache'][keyName][iteration]['webActions'][webActionIter]['headers']; - headers[PWM_VAR['newHeaderName']] = PWM_VAR['newHeaderValue']; - ActionHandler.write(keyName); - ActionHandler.showHeadersDialog(keyName, iteration, webActionIter); - },cancelAction:function(){ - ActionHandler.showHeadersDialog(keyName, iteration, webActionIter); - } - }); -}; - -ActionHandler.showCertificateViewerDialog = function(data,extraDataJson) { - let extraData = JSON.parse(extraDataJson) - let keyName = extraData['keyName']; - let certInfos = data['data']; - let bodyText = ''; - for (const i in certInfos) { - bodyText += X509CertificateHandler.certificateToHtml(certInfos[i],keyName,i); - } - let cancelFunction = function(){ ActionHandler.addOrEditWebAction(keyName, extraData['iteration'], extraData['webActionIter']); }; - let loadFunction = function(){ - for (let i in certInfos) { - X509CertificateHandler.certHtmlActions(certInfos[i],keyName,i); - } - }; - PWM_MAIN.showDialog({ - title:'Certificate Detail', - dialogClass: 'wide', - text:bodyText, - okAction:cancelFunction, - loadFunction:loadFunction - }); -}; diff --git a/webapp/src/main/webapp/public/resources/js/configeditor-settings-challenges.js b/webapp/src/main/webapp/public/resources/js/configeditor-settings-challenges.js index 13bbc6beee..e69de29bb2 100644 --- a/webapp/src/main/webapp/public/resources/js/configeditor-settings-challenges.js +++ b/webapp/src/main/webapp/public/resources/js/configeditor-settings-challenges.js @@ -1,388 +0,0 @@ -/* - * Password Management Servlets (PWM) - * http://www.pwm-project.org - * - * Copyright (c) 2006-2009 Novell, Inc. - * Copyright (c) 2009-2021 The PWM Project - * - * 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. - */ - -var ChallengeSettingHandler = {}; -ChallengeSettingHandler.defaultItem = {text:'Question',minLength:4,maxLength:200,adminDefined:true,enforceWordlist:true,maxQuestionCharsInAnswer:3}; - -ChallengeSettingHandler.init = function(settingKey) { - const parentDiv = "table_setting_" + settingKey; - console.log('ChallengeSettingHandler init for ' + settingKey); - PWM_CFGEDIT.clearDivElements(parentDiv, true); - PWM_CFGEDIT.readSetting(settingKey, function(resultValue) { - PWM_VAR['clientSettingCache'][settingKey] = resultValue; - if (PWM_MAIN.JSLibrary.isEmpty(resultValue)) { - let htmlBody = ''; - - const parentDivElement = PWM_MAIN.getObject(parentDiv); - parentDivElement.innerHTML = htmlBody; - - PWM_MAIN.addEventHandler('button-addValue-' + settingKey,'click',function(){ - PWM_VAR['clientSettingCache'][settingKey] = {}; - PWM_VAR['clientSettingCache'][settingKey][''] = []; - PWM_VAR['clientSettingCache'][settingKey][''].push(ChallengeSettingHandler.defaultItem); - ChallengeSettingHandler.write(settingKey,function(){ - ChallengeSettingHandler.init(settingKey); - }); - }); - } else { - ChallengeSettingHandler.draw(settingKey); - } - }); -}; - -ChallengeSettingHandler.draw = function(settingKey) { - const parentDiv = "table_setting_" + settingKey; - const resultValue = PWM_VAR['clientSettingCache'][settingKey]; - const parentDivElement = PWM_MAIN.getObject(parentDiv); - let bodyText = '
    Click on challenge questions to edit questions and policy settings.
    '; - PWM_CFGEDIT.clearDivElements(parentDiv, false); - for (const localeName in resultValue) { - (function(localeKey) { - const multiValues = resultValue[localeKey]; - const rowCount = PWM_MAIN.JSLibrary.itemCount(multiValues); - - bodyText += '
    '; - bodyText += ''; - const localeLabel = localeName === '' ? 'Default Locale' : PWM_GLOBAL['localeInfo'][localeName] + " (" + localeName + ")"; - if (PWM_MAIN.JSLibrary.itemCount(PWM_VAR['clientSettingCache'][settingKey]) > 1) { - bodyText += ''; - } - - bodyText += ''; - bodyText += ''; - - bodyText += '
    ' + localeLabel + '
    '; - if (rowCount > 0) { - for (const iteration in multiValues) { - const id = 'panel-value-' + settingKey + '-' + localeKey + '-' + iteration; - bodyText += '
    text
    '; - bodyText += '
    ' - + 'Min Length: ' - + 'Max Length: ' - + 'Max Question Chars: ' - + 'Apply Wordlist: ' - + '
    '; - } - } else { - bodyText += '[No Questions]'; - } - bodyText += '
    '; - if (localeName !== '' || PWM_MAIN.JSLibrary.itemCount(PWM_VAR['clientSettingCache'][settingKey]) < 2) { // add remove locale x - bodyText += '
    '; - } - bodyText += '

    '; - }(localeName)); - } - parentDivElement.innerHTML = bodyText; - - const addLocaleFunction = function(localeValue) { - if (localeValue in PWM_VAR['clientSettingCache'][settingKey]) { - PWM_MAIN.showDialog({title:PWM_MAIN.showString('Title_Error'),text:'Locale ' + localeValue + ' is already present.'}); - } else { - PWM_VAR['clientSettingCache'][settingKey][localeValue] = []; - PWM_VAR['clientSettingCache'][settingKey][localeValue][0] = ChallengeSettingHandler.defaultItem; - ChallengeSettingHandler.write(settingKey, function(){ - ChallengeSettingHandler.init(settingKey); - }); - } - }; - const tableElement = document.createElement("div"); - parentDivElement.appendChild(tableElement); - - UILibrary.addAddLocaleButtonRow(tableElement, settingKey, addLocaleFunction, Object.keys(resultValue)); - - for (const localeName in resultValue) { - (function(localeKey) { - PWM_MAIN.addEventHandler('button-edit-' + settingKey + '-' + localeKey,'click',function(){ - ChallengeSettingHandler.editLocale(settingKey,localeKey); - }); - - const multiValues = resultValue[localeKey]; - const rowCount = PWM_MAIN.JSLibrary.itemCount(multiValues); - if (rowCount > 0) { - for (const iteration in multiValues) { - (function (rowKey) { - const id = 'panel-value-' + settingKey + '-' + localeKey + '-' + iteration; - const questionText = multiValues[rowKey]['text']; - const adminDefined = multiValues[rowKey]['adminDefined']; - const output = (adminDefined ? questionText : '[User Defined]'); - UILibrary.addTextValueToElement(id,output); - UILibrary.addTextValueToElement(id + '-minLength', multiValues[rowKey]['minLength']); - UILibrary.addTextValueToElement(id + '-maxLength', multiValues[rowKey]['maxLength']); - UILibrary.addTextValueToElement(id + '-maxQuestions', multiValues[rowKey]['maxQuestionCharsInAnswer']); - UILibrary.addTextValueToElement(id + '-wordlist', multiValues[rowKey]['enforceWordlist']); - }(iteration)); - } - } - - PWM_MAIN.addEventHandler('button-deleteRow-' + settingKey + '-' + localeKey,'click',function(){ - ChallengeSettingHandler.deleteLocale(settingKey, localeKey) - }); - }(localeName)); - } - -}; - -ChallengeSettingHandler.editLocale = function(keyName, localeKey) { - const localeDisplay = localeKey === "" ? "Default" : localeKey; - - const localeName = localeKey; - - const resultValue = PWM_VAR['clientSettingCache'][keyName]; - - const multiValues = resultValue[localeName]; - - let dialogBody = ''; - dialogBody += '
    ' - + 'Toggle All: ' - + 'Change All: ' - + '' - + '' - + '
    '; - dialogBody += '
    '; - - for (const iteration in multiValues) { - (function(rowKey) { - - - dialogBody += ''; - dialogBody += '
    ' + (parseInt(iteration) + 1) + ''; - dialogBody += ''; - dialogBody += ''; - dialogBody += ''; - - dialogBody += ''; - dialogBody += '
    '; - - const inputID = "value-" + keyName + "-" + localeName + "-" + rowKey; - PWM_CFGEDIT.clearDijitWidget(inputID); - - dialogBody += ''; - - dialogBody += '
    '; - - dialogBody += ''; - - dialogBody += ''; - - dialogBody += ''; - dialogBody += '
    Min Length'; - - dialogBody += '
    '; - dialogBody += ''; - dialogBody += '
    Max Length'; - - dialogBody += '
    '; - dialogBody += ''; - dialogBody += '
    Max Question Characters'; - - dialogBody += '
    '; - dialogBody += ''; - - dialogBody += '
    '; - if (PWM_MAIN.JSLibrary.itemCount(PWM_VAR['clientSettingCache'][keyName][localeKey]) > 1) { // add remove locale x - - dialogBody += '
    '; - } - - dialogBody += '
    '; - dialogBody += '
    '; - - }(iteration)); - } - - dialogBody += '
    '; - dialogBody += '
    '; - - const dialogTitle = PWM_SETTINGS['settings'][keyName]['label'] + ' - ' + localeDisplay + ' Locale'; - PWM_MAIN.showDialog({ - title:dialogTitle, - buttonHtml:'', - text:dialogBody, - showClose:false, - dialogClass:'wide', - loadFunction:function(){ - PWM_MAIN.addEventHandler('button-addValue','click',function(){ - ChallengeSettingHandler.addRow(keyName,localeKey); - }); - - const switchAllValues = function(settingType, settingValue) { - for (const iteration in multiValues) { - (function(rowKey) { - multiValues[rowKey][settingType] = settingValue; - }(iteration)); - } - }; - - const switchAllNumericValue = function(settingType, defaultValue, min, max) { - const dialogText = '
    New Value
    '; - PWM_VAR['tempValue'] = defaultValue; - const loadFunction = function(){ - PWM_MAIN.addEventHandler('newValue','change',function(){ - PWM_VAR['tempValue'] = PWM_MAIN.getObject('newValue').value; - }) - }; - const okAction = function() { - switchAllValues(settingType,PWM_VAR['tempValue']); - PWM_MAIN.JSLibrary.removeFromArray(PWM_VAR, 'tempValue'); - ChallengeSettingHandler.editLocale(keyName, localeKey); - }; - const cancelAction = function() { - ChallengeSettingHandler.editLocale(keyName, localeKey); - } - PWM_MAIN.showConfirmDialog({text:dialogText,loadFunction:loadFunction, okAction:okAction, cancelAction:cancelAction}); - }; - - PWM_MAIN.addEventHandler('button-toggleWordlist-' + keyName + '-' + localeKey,'click',function(){ - PWM_MAIN.showConfirmDialog({okAction:function(){ - const row0value = multiValues[0]['enforceWordlist']; - switchAllValues('enforceWordlist',!row0value); - ChallengeSettingHandler.editLocale(keyName, localeKey); - } - }); - }); - - PWM_MAIN.addEventHandler('button-changeAll-minLength-' + keyName + '-' + localeKey,'click',function(){ - switchAllNumericValue('minLength',4,1,255); - }); - PWM_MAIN.addEventHandler('button-changeAll-maxLength-' + keyName + '-' + localeKey,'click',function(){ - switchAllNumericValue('maxLength',200,1,255); - }); - PWM_MAIN.addEventHandler('button-changeAll-maxQuestionCharsInAnswer-' + keyName + '-' + localeKey,'click',function(){ - switchAllNumericValue('maxQuestionCharsInAnswer',3,0,100); - }); - - - - - for (const iteration in multiValues) { - (function(rowKey) { - const inputID = "value-" + keyName + "-" + localeName + "-" + rowKey; - UILibrary.manageNumericInput('button-minLength-' + inputID, function(value){ - PWM_VAR['clientSettingCache'][keyName][localeKey][rowKey]['minLength'] = value; - }); - UILibrary.manageNumericInput('button-maxLength-' + inputID, function(value){ - PWM_VAR['clientSettingCache'][keyName][localeKey][rowKey]['maxLength'] = value; - }); - UILibrary.manageNumericInput('button-maxQuestionCharsInAnswer-' + inputID, function(value){ - PWM_VAR['clientSettingCache'][keyName][localeKey][rowKey]['maxQuestionCharsInAnswer'] = value; - }); - - // question text - const processQuestion = function() { - const isAdminDefined = multiValues[rowKey]['adminDefined']; - PWM_MAIN.getObject(inputID).value = isAdminDefined ? multiValues[rowKey]['text'] : '[User Defined]'; - PWM_MAIN.getObject(inputID).disabled = !isAdminDefined; - }; - processQuestion(); - PWM_MAIN.addEventHandler(inputID, 'input', function () { - if (multiValues[rowKey]['adminDefined']) { - PWM_VAR['clientSettingCache'][keyName][localeKey][rowKey]['text'] = PWM_MAIN.getObject(inputID).value; - } - }); - - // admin defined select - PWM_MAIN.getObject('value-adminDefined-' + inputID).disabled = false; - PWM_MAIN.JSLibrary.setValueOfSelectElement('value-adminDefined-' + inputID, multiValues[rowKey]['adminDefined'] ? 'ADMIN' : 'USER'); - - PWM_MAIN.addEventHandler('value-adminDefined-' + inputID,'change',function(){ - const checked = PWM_MAIN.JSLibrary.readValueOfSelectElement('value-adminDefined-' + inputID) === 'ADMIN'; - multiValues[rowKey]['adminDefined'] = checked; - processQuestion(); - }); - - // wordlist checkbox - PWM_MAIN.getObject('value-wordlist-' + inputID).disabled = false; - PWM_MAIN.getObject('value-wordlist-' + inputID).checked = multiValues[rowKey]['enforceWordlist']; - PWM_MAIN.addEventHandler('value-wordlist-' + inputID,'change',function(){ - const checked = PWM_MAIN.getObject('value-wordlist-' + inputID).checked; - multiValues[rowKey]['enforceWordlist'] = checked; - }); - - // delete row - PWM_MAIN.addEventHandler('button-deleteRow-' + inputID, 'click', function () { - ChallengeSettingHandler.deleteRow(keyName, localeKey, rowKey); - }); - - }(iteration)); - } - - },okAction:function(){ - ChallengeSettingHandler.write(keyName); - ChallengeSettingHandler.draw(keyName); - }}); - - -}; - -ChallengeSettingHandler.deleteLocale = function(keyName,localeKey) { - PWM_MAIN.showConfirmDialog({ - text: 'Are you sure you want to remove all the questions for the ' + localeKey + ' locale?', - okAction:function(){ - PWM_MAIN.showWaitDialog({loadFunction:function(){ - delete PWM_VAR['clientSettingCache'][keyName][localeKey]; - PWM_CFGEDIT.writeSetting(keyName, PWM_VAR['clientSettingCache'][keyName],function(){ - PWM_MAIN.closeWaitDialog(); - ChallengeSettingHandler.init(keyName); - }); - }}); - } - }); -}; - -ChallengeSettingHandler.deleteRow = function(keyName, localeKey, rowName) { - const questionText = PWM_VAR['clientSettingCache'][keyName][localeKey][rowName]['text']; - const adminDefined = PWM_VAR['clientSettingCache'][keyName][localeKey][rowName]['adminDefined']; - const output = (adminDefined ? 'the question "' + questionText + '"': 'the [User Defined] question?'); - PWM_MAIN.showConfirmDialog({ - text: 'Are you sure you want to remove ' + output, - okAction:function(){ - PWM_MAIN.showWaitDialog({loadFunction:function(){ - delete PWM_VAR['clientSettingCache'][keyName][localeKey][rowName]; - ChallengeSettingHandler.write(keyName,function(){ - ChallengeSettingHandler.editLocale(keyName, localeKey); - }); - }}) - } - }); -}; - -ChallengeSettingHandler.addRow = function(keyName, localeKey) { - PWM_MAIN.showWaitDialog({ - loadFunction:function(){ - const newValues = PWM_MAIN.copyObject(ChallengeSettingHandler.defaultItem); - PWM_VAR['clientSettingCache'][keyName][localeKey].push(newValues); - ChallengeSettingHandler.write(keyName,function(){ - PWM_MAIN.showDialog({title:PWM_MAIN.showString('Title_Success'),text:'Added new item to end of existing question list.',okAction:function(){ - ChallengeSettingHandler.editLocale(keyName, localeKey); - }} - ); - }); - } - }); -}; - -ChallengeSettingHandler.write = function(keyName, nextFunction) { - PWM_CFGEDIT.writeSetting(keyName, PWM_VAR['clientSettingCache'][keyName], nextFunction); -}; diff --git a/webapp/src/main/webapp/public/resources/js/configeditor-settings-customlink.js b/webapp/src/main/webapp/public/resources/js/configeditor-settings-customlink.js index f68738a5c3..a6d30f98bd 100644 --- a/webapp/src/main/webapp/public/resources/js/configeditor-settings-customlink.js +++ b/webapp/src/main/webapp/public/resources/js/configeditor-settings-customlink.js @@ -20,7 +20,17 @@ // -------------------------- Custom link handler ------------------------------------ -var CustomLinkHandler = {}; +const CustomLinkHandler = {}; + +import {PWM_CFGEDIT} from "./configeditor.js"; +import {PWM_MAIN} from "./main.js"; +import {PWM_UILibrary} from "./uilibrary.js"; + +export {CustomLinkHandler}; + +const ClientSettingCache = {}; + + CustomLinkHandler.newRowValue = { name:'', labels:{'':''}, @@ -32,22 +42,22 @@ CustomLinkHandler.init = function(keyName) { const parentDiv = 'table_setting_' + keyName; PWM_CFGEDIT.clearDivElements(parentDiv, true); PWM_CFGEDIT.readSetting(keyName, function(resultValue) { - PWM_VAR['clientSettingCache'][keyName] = resultValue; + ClientSettingCache[keyName] = resultValue; CustomLinkHandler.redraw(keyName); }); }; CustomLinkHandler.redraw = function(keyName) { - const resultValue = PWM_VAR['clientSettingCache'][keyName]; + const resultValue = ClientSettingCache[keyName]; { const parentDiv = 'table_setting_' + keyName; - const parentDivElement = PWM_MAIN.getObject(parentDiv); + const parentDivElement = PWM_JSLibrary.getElement(parentDiv); parentDivElement.innerHTML = '
    '; } const parentDiv = 'table-top-' + keyName; - const parentDivElement = PWM_MAIN.getObject(parentDiv); + const parentDivElement = PWM_JSLibrary.getElement(parentDiv); - if (!PWM_MAIN.JSLibrary.isEmpty(resultValue)) { + if (!PWM_JSLibrary.isEmpty(resultValue)) { const headerRow = document.createElement("tr"); const rowHtml = 'NameLabel'; headerRow.innerHTML = rowHtml; @@ -70,11 +80,13 @@ CustomLinkHandler.redraw = function(keyName) { }; -CustomLinkHandler.drawRow = function(parentDiv, settingKey, iteration, value) { - const itemCount = PWM_MAIN.JSLibrary.itemCount(PWM_VAR['clientSettingCache'][settingKey]); +CustomLinkHandler.drawRow = async function(parentDiv, settingKey, iteration, value) { + const settingData = await PWM_CFGEDIT.getConfigSettingData(); + + const itemCount = PWM_JSLibrary.itemCount(ClientSettingCache[settingKey]); const inputID = 'value_' + settingKey + '_' + iteration + "_"; - const options = PWM_SETTINGS['settings'][settingKey]['options']; - const properties = PWM_SETTINGS['settings'][settingKey]['properties']; + const options = settingData['settings'][settingKey]['options']; + const properties = settingData['settings'][settingKey]['properties']; const newTableRow = document.createElement("tr"); newTableRow.setAttribute("style", "border-width: 0"); @@ -100,10 +112,10 @@ CustomLinkHandler.drawRow = function(parentDiv, settingKey, iteration, value) { htmlRow += ''; newTableRow.innerHTML = htmlRow; - const parentDivElement = PWM_MAIN.getObject(parentDiv); + const parentDivElement = PWM_JSLibrary.getElement(parentDiv); parentDivElement.appendChild(newTableRow); - UILibrary.addTextValueToElement("panel-name-" + inputID,value['name']); + PWM_UILibrary.addTextValueToElement("panel-name-" + inputID,value['name']); PWM_MAIN.addEventHandler(inputID + "-moveUp", 'click', function () { CustomLinkHandler.move(settingKey, true, iteration); @@ -124,17 +136,17 @@ CustomLinkHandler.drawRow = function(parentDiv, settingKey, iteration, value) { CustomLinkHandler.showOptionsDialog(settingKey, iteration); }); PWM_MAIN.addEventHandler(inputID + "name", 'input', function () { - PWM_VAR['clientSettingCache'][settingKey][iteration]['name'] = PWM_MAIN.getObject(inputID + "name").value; + ClientSettingCache[settingKey][iteration]['name'] = PWM_JSLibrary.getElement(inputID + "name").value; CustomLinkHandler.write(settingKey); }); PWM_MAIN.addEventHandler(inputID + "type", 'click', function () { - PWM_VAR['clientSettingCache'][settingKey][iteration]['type'] = PWM_MAIN.getObject(inputID + "type").value; + ClientSettingCache[settingKey][iteration]['type'] = PWM_JSLibrary.getElement(inputID + "type").value; CustomLinkHandler.write(settingKey); }); }; CustomLinkHandler.write = function(settingKey, finishFunction) { - const cachedSetting = PWM_VAR['clientSettingCache'][settingKey]; + const cachedSetting = ClientSettingCache[settingKey]; PWM_CFGEDIT.writeSetting(settingKey, cachedSetting, finishFunction); }; @@ -142,7 +154,7 @@ CustomLinkHandler.removeRow = function(keyName, iteration) { PWM_MAIN.showConfirmDialog({ text:'Are you sure you wish to delete this item?', okAction:function(){ - const currentValues = PWM_VAR['clientSettingCache'][keyName]; + const currentValues = ClientSettingCache[keyName]; currentValues.splice(iteration,1); CustomLinkHandler.write(keyName,function(){ CustomLinkHandler.init(keyName); @@ -152,7 +164,7 @@ CustomLinkHandler.removeRow = function(keyName, iteration) { }; CustomLinkHandler.move = function(settingKey, moveUp, iteration) { - const currentValues = PWM_VAR['clientSettingCache'][settingKey]; + const currentValues = ClientSettingCache[settingKey]; if (moveUp) { CustomLinkHandler.arrayMoveUtil(currentValues, iteration, iteration - 1); } else { @@ -169,23 +181,25 @@ CustomLinkHandler.arrayMoveUtil = function(arr, fromIndex, toIndex) { }; -CustomLinkHandler.addRow = function(keyName) { - UILibrary.stringEditorDialog({ - title:PWM_SETTINGS['settings'][keyName]['label'] + ' - New Custom Link Key Name', +CustomLinkHandler.addRow = async function(keyName) { + const settingData = await PWM_CFGEDIT.getConfigSettingData(); + + PWM_UILibrary.stringEditorDialog({ + title:settingData['settings'][keyName]['label'] + ' - New Custom Link Key Name', instructions: 'Acceptable characters, a-z,A-Z,0-9', regex:'^[a-zA-Z][a-zA-Z0-9-]*$', placeholder:'KeyName', completeFunction:function(value){ - for (const i in PWM_VAR['clientSettingCache'][keyName]) { - if (PWM_VAR['clientSettingCache'][keyName][i]['name'] === value) { + for (const i in ClientSettingCache[keyName]) { + if (ClientSettingCache[keyName][i]['name'] === value) { alert('key name already exists'); return; } } - const currentSize = PWM_MAIN.JSLibrary.itemCount(PWM_VAR['clientSettingCache'][keyName]); - PWM_VAR['clientSettingCache'][keyName][currentSize + 1] = CustomLinkHandler.newRowValue; - PWM_VAR['clientSettingCache'][keyName][currentSize + 1].name = value; - PWM_VAR['clientSettingCache'][keyName][currentSize + 1].labels = {'':value}; + const currentSize = PWM_JSLibrary.itemCount(ClientSettingCache[keyName]); + ClientSettingCache[keyName][currentSize + 1] = CustomLinkHandler.newRowValue; + ClientSettingCache[keyName][currentSize + 1].name = value; + ClientSettingCache[keyName][currentSize + 1].labels = {'':value}; CustomLinkHandler.write(keyName,function(){ CustomLinkHandler.init(keyName); }); @@ -193,29 +207,31 @@ CustomLinkHandler.addRow = function(keyName) { }); }; -CustomLinkHandler.showOptionsDialog = function(keyName, iteration) { - const type = PWM_VAR['clientSettingCache'][keyName][iteration]['type']; - const settings = PWM_SETTINGS['settings'][keyName]; - const options = 'options' in PWM_SETTINGS['settings'][keyName] ? PWM_SETTINGS['settings'][keyName]['options'] : {}; +CustomLinkHandler.showOptionsDialog = async function(keyName, iteration) { + const settingData = await PWM_CFGEDIT.getConfigSettingData(); + + const type = ClientSettingCache[keyName][iteration]['type']; + const settings = settingData['settings'][keyName]; + const options = 'options' in settingData['settings'][keyName] ? settingData['settings'][keyName]['options'] : {}; const inputID = 'value_' + keyName + '_' + iteration + '_'; let bodyText = '
    '; bodyText += ''; - const descriptionValue = PWM_VAR['clientSettingCache'][keyName][iteration]['description']['']; + const descriptionValue = ClientSettingCache[keyName][iteration]['description']['']; bodyText += ''; bodyText += ''; - const customLinkUrl = PWM_VAR['clientSettingCache'][keyName][iteration]['customLinkUrl']; + const customLinkUrl = ClientSettingCache[keyName][iteration]['customLinkUrl']; bodyText += ''; bodyText += ''; - const checkedValue = PWM_VAR['clientSettingCache'][keyName][iteration]['customLinkNewWindow']; - bodyText += ''; - for (const localeName in PWM_VAR['clientSettingCache'][keyName][iteration][settingType]) { - const value = PWM_VAR['clientSettingCache'][keyName][iteration][settingType][localeName]; + for (const localeName in ClientSettingCache[keyName][iteration][settingType]) { + const value = ClientSettingCache[keyName][iteration][settingType][localeName]; const localeID = inputID + localeName; bodyText += ''; bodyText += ''; @@ -287,31 +303,31 @@ CustomLinkHandler.multiLocaleStringDialog = function(keyName, iteration, setting finishAction(); }, loadFunction:function(){ - for (const iter in PWM_VAR['clientSettingCache'][keyName][iteration][settingType]) { + for (const iter in ClientSettingCache[keyName][iteration][settingType]) { (function(localeName) { const localeID = inputID + localeName; PWM_MAIN.addEventHandler(localeID + '-input', 'input', function () { - const inputElement = PWM_MAIN.getObject(localeID + '-input'); + const inputElement = PWM_JSLibrary.getElement(localeID + '-input'); const value = inputElement.value; - PWM_VAR['clientSettingCache'][keyName][iteration][settingType][localeName] = value; + ClientSettingCache[keyName][iteration][settingType][localeName] = value; CustomLinkHandler.write(keyName); }); PWM_MAIN.addEventHandler(localeID + '-removeLocaleButton', 'click', function () { - delete PWM_VAR['clientSettingCache'][keyName][iteration][settingType][localeName]; + delete ClientSettingCache[keyName][iteration][settingType][localeName]; CustomLinkHandler.write(keyName); CustomLinkHandler.multiLocaleStringDialog(keyName, iteration, settingType, finishAction, titleText); }); }(iter)); } - UILibrary.addAddLocaleButtonRow(inputID + 'table', inputID, function(localeName){ - if (localeName in PWM_VAR['clientSettingCache'][keyName][iteration][settingType]) { + PWM_UILibrary.addAddLocaleButtonRow(inputID + 'table', inputID, function(localeName){ + if (localeName in ClientSettingCache[keyName][iteration][settingType]) { alert('Locale is already present'); } else { - PWM_VAR['clientSettingCache'][keyName][iteration][settingType][localeName] = ''; + ClientSettingCache[keyName][iteration][settingType][localeName] = ''; CustomLinkHandler.write(keyName); CustomLinkHandler.multiLocaleStringDialog(keyName, iteration, settingType, finishAction, titleText); } - }, Object.keys(PWM_VAR['clientSettingCache'][keyName][iteration][settingType])); + }, Object.keys(ClientSettingCache[keyName][iteration][settingType])); } }); }); @@ -320,7 +336,7 @@ CustomLinkHandler.multiLocaleStringDialog = function(keyName, iteration, setting CustomLinkHandler.showDescriptionDialog = function(keyName, iteration) { const finishAction = function(){ CustomLinkHandler.showOptionsDialog(keyName, iteration); }; - const title = 'Description for ' + PWM_VAR['clientSettingCache'][keyName][iteration]['name']; + const title = 'Description for ' + ClientSettingCache[keyName][iteration]['name']; CustomLinkHandler.multiLocaleStringDialog(keyName, iteration, 'description', finishAction, title); }; diff --git a/webapp/src/main/webapp/public/resources/js/configeditor-settings-email.js b/webapp/src/main/webapp/public/resources/js/configeditor-settings-email.js index 448135cc1f..e69de29bb2 100644 --- a/webapp/src/main/webapp/public/resources/js/configeditor-settings-email.js +++ b/webapp/src/main/webapp/public/resources/js/configeditor-settings-email.js @@ -1,164 +0,0 @@ -/* - * Password Management Servlets (PWM) - * http://www.pwm-project.org - * - * Copyright (c) 2006-2009 Novell, Inc. - * Copyright (c) 2009-2021 The PWM Project - * - * 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. - */ - -// -------------------------- email table handler ------------------------------------ - -var EmailTableHandler = {}; -EmailTableHandler.defaultValue = { - to:"@User:Email@", - from:"@DefaultEmailFromAddress@", - subject:"Subject", - bodyPlain:"Body", - bodyHtml:"Body" -}; - -EmailTableHandler.init = function(keyName) { - console.log('EmailTableHandler init for ' + keyName); - PWM_CFGEDIT.readSetting(keyName, function(resultValue) { - PWM_VAR['clientSettingCache'][keyName] = resultValue; - EmailTableHandler.draw(keyName); - }); -}; - -EmailTableHandler.draw = function(settingKey) { - const resultValue = PWM_VAR['clientSettingCache'][settingKey]; - const parentDiv = 'table_setting_' + settingKey; - PWM_CFGEDIT.clearDivElements(parentDiv, true); - PWM_CFGEDIT.clearDivElements(parentDiv, false); - - let htmlBody = ''; - for (const localeName in resultValue) { - htmlBody += EmailTableHandler.drawRowHtml(settingKey,localeName) - } - const parentDivElement = PWM_MAIN.getObject(parentDiv); - parentDivElement.innerHTML = htmlBody; - - for (const localeName in resultValue) { - EmailTableHandler.instrumentRow(settingKey,localeName) - } - - if (PWM_MAIN.JSLibrary.isEmpty(resultValue)) { - let htmlBody = ''; - - const parentDivElement = PWM_MAIN.getObject(parentDiv); - parentDivElement.innerHTML = htmlBody; - - PWM_MAIN.addEventHandler('button-addValue-' + settingKey,'click',function(){ - PWM_CFGEDIT.resetSetting(settingKey,function(){PWM_CFGEDIT.loadMainPageBody()}); - }); - - } else { - const addLocaleFunction = function(localeValue) { - if (!PWM_VAR['clientSettingCache'][settingKey][localeValue]) { - PWM_VAR['clientSettingCache'][settingKey][localeValue] = EmailTableHandler.defaultValue; - EmailTableHandler.writeSetting(settingKey,true); - } - }; - UILibrary.addAddLocaleButtonRow(parentDiv, settingKey, addLocaleFunction, Object.keys(PWM_VAR['clientSettingCache'][settingKey])); - } -}; - -EmailTableHandler.drawRowHtml = function(settingKey, localeName) { - const localeLabel = localeName === '' ? 'Default Locale' : PWM_GLOBAL['localeInfo'][localeName] + " (" + localeName + ")"; - const idPrefix = "setting-" + localeName + "-" + settingKey; - let htmlBody = ''; - htmlBody += '
    Description'; bodyText += '
    ' + descriptionValue + '...
    '; bodyText += '
    Link URL' + '
    Open link in new windowOpen link in new window'; bodyText += '
    ' + localeName + '
    '; - htmlBody += ''; - if (PWM_MAIN.JSLibrary.itemCount(PWM_VAR['clientSettingCache'][settingKey]) > 1) { - htmlBody += ''; - } - const outputFunction = function (labelText, typeText) { - htmlBody += ''; - htmlBody += ''; - htmlBody += ''; - }; - outputFunction('To', 'to'); - outputFunction('From', 'from'); - outputFunction('Subject', 'subject'); - outputFunction('Plain Body', 'bodyPlain'); - outputFunction('HTML Body', 'bodyHtml'); - - htmlBody += '
    ' + localeLabel + '
    ' + labelText + ''; - htmlBody += '
    '; - if (localeName !== '' || PWM_MAIN.JSLibrary.itemCount(PWM_VAR['clientSettingCache'][settingKey]) < 2) { // add remove locale x - htmlBody += '
    '; - } - htmlBody += '

    '; - return htmlBody; -}; - - -EmailTableHandler.instrumentRow = function(settingKey, localeName) { - const idPrefix = "setting-" + localeName + "-" + settingKey; - - UILibrary.addTextValueToElement('panel-to-' + idPrefix,PWM_VAR['clientSettingCache'][settingKey][localeName]['to']); - PWM_MAIN.addEventHandler('button-to-' + idPrefix,'click',function(){ EmailTableHandler.editor(settingKey,localeName,false,'to',PWM_CONFIG.showString('Instructions_Edit_Email')); }); - PWM_MAIN.addEventHandler('panel-to-' + idPrefix,'click',function(){ EmailTableHandler.editor(settingKey,localeName,false,'to',PWM_CONFIG.showString('Instructions_Edit_Email')); }); - - UILibrary.addTextValueToElement('panel-from-' + idPrefix,PWM_VAR['clientSettingCache'][settingKey][localeName]['from']); - PWM_MAIN.addEventHandler('button-from-' + idPrefix,'click',function(){ EmailTableHandler.editor(settingKey,localeName,false,'from'); }); - PWM_MAIN.addEventHandler('panel-from-' + idPrefix,'click',function(){ EmailTableHandler.editor(settingKey,localeName,false,'from'); }); - - UILibrary.addTextValueToElement('panel-subject-' + idPrefix,PWM_VAR['clientSettingCache'][settingKey][localeName]['subject']); - PWM_MAIN.addEventHandler('button-subject-' + idPrefix,'click',function(){ EmailTableHandler.editor(settingKey,localeName,false,'subject'); }); - PWM_MAIN.addEventHandler('panel-subject-' + idPrefix,'click',function(){ EmailTableHandler.editor(settingKey,localeName,false,'subject'); }); - - UILibrary.addTextValueToElement('panel-bodyPlain-' + idPrefix,PWM_VAR['clientSettingCache'][settingKey][localeName]['bodyPlain']); - PWM_MAIN.addEventHandler('button-bodyPlain-' + idPrefix,'click',function(){ EmailTableHandler.editor(settingKey,localeName,true,'bodyPlain'); }); - PWM_MAIN.addEventHandler('panel-bodyPlain-' + idPrefix,'click',function(){ EmailTableHandler.editor(settingKey,localeName,true,'bodyPlain'); }); - - UILibrary.addTextValueToElement('panel-bodyHtml-' + idPrefix,PWM_VAR['clientSettingCache'][settingKey][localeName]['bodyHtml']); - PWM_MAIN.addEventHandler('button-bodyHtml-' + idPrefix,'click',function(){ EmailTableHandler.editor(settingKey,localeName,true,'bodyHtml'); }); - PWM_MAIN.addEventHandler('panel-bodyHtml-' + idPrefix,'click',function(){ EmailTableHandler.editor(settingKey,localeName,true,'bodyHtml'); }); - - PWM_MAIN.addEventHandler("button-deleteRow-" + idPrefix,"click",function(){ - PWM_MAIN.showConfirmDialog({okAction:function(){ - delete PWM_VAR['clientSettingCache'][settingKey][localeName]; - EmailTableHandler.writeSetting(settingKey,true); - }}); - }); -}; - -EmailTableHandler.editor = function(settingKey, localeName, drawTextArea, type, instructions){ - const settingData = PWM_SETTINGS['settings'][settingKey]; - UILibrary.stringEditorDialog({ - title:'Edit Value - ' + settingData['label'], - instructions: instructions ? instructions : '', - textarea:drawTextArea, - value:PWM_VAR['clientSettingCache'][settingKey][localeName][type], - completeFunction:function(value){ - PWM_VAR['clientSettingCache'][settingKey][localeName][type] = value; - PWM_CFGEDIT.writeSetting(settingKey,PWM_VAR['clientSettingCache'][settingKey],function(){ - EmailTableHandler.init(settingKey); - }); - } - }); -}; - -EmailTableHandler.writeSetting = function(settingKey, redraw) { - const currentValues = PWM_VAR['clientSettingCache'][settingKey]; - PWM_CFGEDIT.writeSetting(settingKey, currentValues, function(){ - if (redraw) { - EmailTableHandler.init(settingKey); - } - }); -}; diff --git a/webapp/src/main/webapp/public/resources/js/configeditor-settings-form.js b/webapp/src/main/webapp/public/resources/js/configeditor-settings-form.js index 5bf3dedb7c..5b6e9540b0 100644 --- a/webapp/src/main/webapp/public/resources/js/configeditor-settings-form.js +++ b/webapp/src/main/webapp/public/resources/js/configeditor-settings-form.js @@ -18,8 +18,19 @@ * limitations under the License. */ +const FormTableHandler = {}; + +import {PWM_CFGEDIT} from "./configeditor.js"; +import {PWM_CONFIG} from "./configmanager.js"; +import {PWM_MAIN} from "./main.js"; +import {PWM_JSLibrary} from "./jslibrary.js"; +import {PWM_UILibrary} from "./uilibrary.js"; + +export {FormTableHandler}; + +const ClientSettingCache = {}; + -var FormTableHandler = {}; FormTableHandler.newRowValue = { name:'', minimumLength:0, @@ -41,37 +52,38 @@ FormTableHandler.init = function(keyName) { const parentDiv = 'table_setting_' + keyName; PWM_CFGEDIT.clearDivElements(parentDiv, true); PWM_CFGEDIT.readSetting(keyName, function(resultValue) { - PWM_VAR['clientSettingCache'][keyName] = resultValue; + ClientSettingCache[keyName] = resultValue; FormTableHandler.redraw(keyName); }); }; -FormTableHandler.redraw = function(keyName) { - const resultValue = PWM_VAR['clientSettingCache'][keyName]; +FormTableHandler.redraw = async function(keyName) { + const settingData = await PWM_CFGEDIT.getConfigSettingData(); + const resultValue = ClientSettingCache[keyName]; { const parentDiv = 'table_setting_' + keyName; - const parentDivElement = PWM_MAIN.getObject(parentDiv); + const parentDivElement = PWM_JSLibrary.getElement(parentDiv); parentDivElement.innerHTML = '
    '; } const parentDiv = 'table-top-' + keyName; - const parentDivElement = PWM_MAIN.getObject(parentDiv); + const parentDivElement = PWM_JSLibrary.getElement(parentDiv); + - if (!PWM_MAIN.JSLibrary.isEmpty(resultValue)) { + if (!PWM_JSLibrary.isEmpty(resultValue)) { const headerRow = document.createElement("tr"); const rowHtml = 'NameLabel'; headerRow.innerHTML = rowHtml; parentDivElement.appendChild(headerRow); } - for (const i in resultValue) { - FormTableHandler.drawRow(parentDiv, keyName, i, resultValue[i]); - } - - const buttonRow = document.createElement("tr"); - buttonRow.setAttribute("colspan","5"); - buttonRow.innerHTML = ''; + PWM_JSLibrary.forEachInObject(resultValue,function (i,value){ + FormTableHandler.drawRow(parentDiv, keyName, i, value, settingData); + }); - parentDivElement.appendChild(buttonRow); + const buttonRowHtml = `` + + `` + + ``; + parentDivElement.insertAdjacentHTML("beforeend", buttonRowHtml ); PWM_MAIN.addEventHandler('button-' + keyName + '-addRow','click',function(){ FormTableHandler.addRow(keyName); @@ -79,58 +91,57 @@ FormTableHandler.redraw = function(keyName) { }; -FormTableHandler.drawRow = function(parentDiv, settingKey, iteration, value) { - const itemCount = PWM_MAIN.JSLibrary.itemCount(PWM_VAR['clientSettingCache'][settingKey]); +FormTableHandler.drawRow = function(parentDiv, settingKey, iteration, value, settingData) { + const itemCount = PWM_JSLibrary.itemCount(ClientSettingCache[settingKey]); const inputID = 'value_' + settingKey + '_' + iteration + "_"; - const options = PWM_SETTINGS['settings'][settingKey]['options']; - const properties = PWM_SETTINGS['settings'][settingKey]['properties']; + const options = settingData['settings'][settingKey]['options']; + const properties = settingData['settings'][settingKey]['properties']; const newTableRow = document.createElement("tr"); newTableRow.setAttribute("style", "border-width: 0"); - let htmlRow = ''; - htmlRow += '
    '; - htmlRow += ''; - htmlRow += '
    ' + value['labels'][''] + '
    '; + let htmlRow = `
    ` + + `` + + `
    ${value['labels']['']}
    `; const userDNtypeAllowed = options['type-userDN'] === 'show'; - if (!PWM_MAIN.JSLibrary.isEmpty(options)) { + if (!PWM_JSLibrary.isEmpty(options)) { htmlRow += ''; htmlRow += ''; htmlRow += ''; } - const hideOptions = PWM_MAIN.JSLibrary.arrayContains(PWM_SETTINGS['settings'][settingKey]['flags'], 'Form_HideOptions'); + const hideOptions = PWM_JSLibrary.arrayContains(settingData['settings'][settingKey]['flags'], 'Form_HideOptions'); if (!hideOptions) { htmlRow += ''; } htmlRow += ''; - if (itemCount > 1 && iteration !== (itemCount -1)) { + if (itemCount > 1 && iteration != (itemCount -1)) { htmlRow += ''; } htmlRow += ''; htmlRow += ''; - if (itemCount > 1 && iteration !== 0) { + if (itemCount > 1 && iteration != 0) { htmlRow += ''; } htmlRow += ''; htmlRow += ''; newTableRow.innerHTML = htmlRow; - const parentDivElement = PWM_MAIN.getObject(parentDiv); + const parentDivElement = PWM_JSLibrary.getElement(parentDiv); parentDivElement.appendChild(newTableRow); - UILibrary.addTextValueToElement("panel-name-" + inputID,value['name']); + PWM_UILibrary.addTextValueToElement("panel-name-" + inputID,value['name']); PWM_MAIN.addEventHandler(inputID + "-moveUp", 'click', function () { FormTableHandler.move(settingKey, true, iteration); @@ -151,17 +162,17 @@ FormTableHandler.drawRow = function(parentDiv, settingKey, iteration, value) { FormTableHandler.showOptionsDialog(settingKey, iteration); }); PWM_MAIN.addEventHandler(inputID + "name", 'input', function () { - PWM_VAR['clientSettingCache'][settingKey][iteration]['name'] = PWM_MAIN.getObject(inputID + "name").value; + ClientSettingCache[settingKey][iteration]['name'] = PWM_JSLibrary.getElement(inputID + "name").value; FormTableHandler.write(settingKey); }); PWM_MAIN.addEventHandler(inputID + "type", 'click', function () { - PWM_VAR['clientSettingCache'][settingKey][iteration]['type'] = PWM_MAIN.getObject(inputID + "type").value; + ClientSettingCache[settingKey][iteration]['type'] = PWM_JSLibrary.getElement(inputID + "type").value; FormTableHandler.write(settingKey); }); }; FormTableHandler.write = function(settingKey, finishFunction) { - const cachedSetting = PWM_VAR['clientSettingCache'][settingKey]; + const cachedSetting = ClientSettingCache[settingKey]; PWM_CFGEDIT.writeSetting(settingKey, cachedSetting, finishFunction); }; @@ -169,7 +180,7 @@ FormTableHandler.removeRow = function(keyName, iteration) { PWM_MAIN.showConfirmDialog({ text:'Are you sure you wish to delete this item?', okAction:function(){ - const currentValues = PWM_VAR['clientSettingCache'][keyName]; + const currentValues = ClientSettingCache[keyName]; currentValues.splice(iteration,1); FormTableHandler.write(keyName,function(){ FormTableHandler.init(keyName); @@ -179,7 +190,7 @@ FormTableHandler.removeRow = function(keyName, iteration) { }; FormTableHandler.move = function(settingKey, moveUp, iteration) { - const currentValues = PWM_VAR['clientSettingCache'][settingKey]; + const currentValues = ClientSettingCache[settingKey]; if (moveUp) { FormTableHandler.arrayMoveUtil(currentValues, iteration, iteration - 1); } else { @@ -196,22 +207,24 @@ FormTableHandler.arrayMoveUtil = function(arr, fromIndex, toIndex) { }; -FormTableHandler.addRow = function(keyName) { - UILibrary.stringEditorDialog({ - title:PWM_SETTINGS['settings'][keyName]['label'] + ' - New Form Field', +FormTableHandler.addRow = async function(keyName) { + const settingData = await PWM_CFGEDIT.getConfigSettingData(); + + PWM_UILibrary.stringEditorDialog({ + title:settingData['settings'][keyName]['label'] + ' - New Form Field', regex:'^[a-zA-Z][a-zA-Z0-9-]*$', placeholder:'FieldName', completeFunction:function(value){ - for (const i in PWM_VAR['clientSettingCache'][keyName]) { - if (PWM_VAR['clientSettingCache'][keyName][i]['name'] === value) { + for (const i in ClientSettingCache[keyName]) { + if (ClientSettingCache[keyName][i]['name'] === value) { alert('field already exists'); return; } } - const currentSize = PWM_MAIN.JSLibrary.itemCount(PWM_VAR['clientSettingCache'][keyName]); - PWM_VAR['clientSettingCache'][keyName][currentSize + 1] = FormTableHandler.newRowValue; - PWM_VAR['clientSettingCache'][keyName][currentSize + 1].name = value; - PWM_VAR['clientSettingCache'][keyName][currentSize + 1].labels = {'':value}; + const currentSize = PWM_JSLibrary.itemCount(ClientSettingCache[keyName]); + ClientSettingCache[keyName][currentSize + 1] = FormTableHandler.newRowValue; + ClientSettingCache[keyName][currentSize + 1].name = value; + ClientSettingCache[keyName][currentSize + 1].labels = {'':value}; FormTableHandler.write(keyName,function(){ FormTableHandler.init(keyName); }); @@ -219,50 +232,51 @@ FormTableHandler.addRow = function(keyName) { }); }; -FormTableHandler.showOptionsDialog = function(keyName, iteration) { - const type = PWM_VAR['clientSettingCache'][keyName][iteration]['type']; - const settings = PWM_SETTINGS['settings'][keyName]; - const currentValue = PWM_VAR['clientSettingCache'][keyName][iteration]; - const options = PWM_SETTINGS['settings'][keyName]['options']; +FormTableHandler.showOptionsDialog = async function(keyName, iteration) { + const settingData = await PWM_CFGEDIT.getConfigSettingData(); + const type = ClientSettingCache[keyName][iteration]['type']; + const settings = settingData['settings'][keyName]; + const currentValue = ClientSettingCache[keyName][iteration]; + const options = settingData['settings'][keyName]['options']; - const hideStandardOptions = PWM_MAIN.JSLibrary.arrayContains(settings['flags'],'Form_HideStandardOptions') || type === 'photo'; - const showRequired = PWM_MAIN.JSLibrary.arrayContains(settings['flags'],'Form_ShowRequiredOption'); - const showUnique = PWM_MAIN.JSLibrary.arrayContains(settings['flags'],'Form_ShowUniqueOption') && type !== 'photo'; - const showReadOnly = PWM_MAIN.JSLibrary.arrayContains(settings['flags'],'Form_ShowReadOnlyOption'); - const showMultiValue = PWM_MAIN.JSLibrary.arrayContains(settings['flags'],'Form_ShowMultiValueOption'); + const hideStandardOptions = PWM_JSLibrary.arrayContains(settings['flags'],'Form_HideStandardOptions') || type === 'photo'; + const showRequired = PWM_JSLibrary.arrayContains(settings['flags'],'Form_ShowRequiredOption'); + const showUnique = PWM_JSLibrary.arrayContains(settings['flags'],'Form_ShowUniqueOption') && type !== 'photo'; + const showReadOnly = PWM_JSLibrary.arrayContains(settings['flags'],'Form_ShowReadOnlyOption'); + const showMultiValue = PWM_JSLibrary.arrayContains(settings['flags'],'Form_ShowMultiValueOption'); const showConfirmation = type !== 'checkbox' && type !== 'select' && type != 'photo' && !hideStandardOptions; - const showSource = PWM_MAIN.JSLibrary.arrayContains(settings['flags'],'Form_ShowSource'); + const showSource = PWM_JSLibrary.arrayContains(settings['flags'],'Form_ShowSource'); const inputID = 'value_' + keyName + '_' + iteration + "_"; let bodyText = '
    '; if (!hideStandardOptions || type === 'photo') { bodyText += ''; - bodyText += ''; } bodyText += ''; if (showRequired) { - bodyText += ''; + bodyText += ''; bodyText += ''; } if (showConfirmation) { - bodyText += ''; + bodyText += ''; bodyText += ''; } if (showReadOnly) { - bodyText += ''; + bodyText += ''; bodyText += ''; } if (showUnique) { - bodyText += ''; + bodyText += ''; bodyText += ''; } if (showMultiValue) { - bodyText += ''; + bodyText += ''; bodyText += ''; } @@ -273,18 +287,18 @@ FormTableHandler.showOptionsDialog = function(keyName, iteration) { bodyText += ''; { // regex - bodyText += ''; + bodyText += ''; bodyText += ''; const regexErrorValue = currentValue['regexErrors']['']; - bodyText += ''; bodyText += ''; } - bodyText += ''; + bodyText += ''; bodyText += ''; - bodyText += ''; + bodyText += ''; bodyText += ''; } @@ -309,7 +323,7 @@ FormTableHandler.showOptionsDialog = function(keyName, iteration) { bodyText += '
    Description'; + bodyText += 'Description'; bodyText += '
    '; bodyText += '
    RequiredRequired
    ConfirmConfirm
    Read OnlyRead Only
    UniqueUnique
    MultiValueMultiValue
    Regular ExpressionRegular Expression
    Regular Expression
    Error Message
    '; + bodyText += 'Regular Expression
    Error Message
    '; bodyText += '
    ' + regexErrorValue + '...
    '; bodyText += '
    PlaceholderPlaceholder
    JavaScript (Depreciated)JavaScript (Depreciated)
    '; const initFormElements = function() { - const currentValue = PWM_VAR['clientSettingCache'][keyName][iteration]; + const currentValue = ClientSettingCache[keyName][iteration]; PWM_MAIN.addEventHandler(inputID + 'editOptionsButton', 'click', function(){ @@ -321,72 +335,72 @@ FormTableHandler.showOptionsDialog = function(keyName, iteration) { }); const descriptionValue = currentValue['description']['']; - UILibrary.addTextValueToElement(inputID + '-value', descriptionValue ? descriptionValue : "Edit"); + PWM_UILibrary.addTextValueToElement(inputID + '-value', descriptionValue ? descriptionValue : "Edit"); if (showRequired) { - PWM_MAIN.getObject(inputID + "required").checked = currentValue['required']; + PWM_JSLibrary.getElement(inputID + "required").checked = currentValue['required']; PWM_MAIN.addEventHandler(inputID + "required", "change", function(){ - currentValue['required'] = PWM_MAIN.getObject(inputID + "required").checked; + currentValue['required'] = PWM_JSLibrary.getElement(inputID + "required").checked; FormTableHandler.write(keyName) }); } - if (PWM_MAIN.getObject(inputID + "confirmationRequired") != null) { - PWM_MAIN.getObject(inputID + "confirmationRequired").checked = currentValue['confirmationRequired']; + if (PWM_JSLibrary.getElement(inputID + "confirmationRequired") != null) { + PWM_JSLibrary.getElement(inputID + "confirmationRequired").checked = currentValue['confirmationRequired']; PWM_MAIN.addEventHandler(inputID + "confirmationRequired", "change", function () { - currentValue['confirmationRequired'] = PWM_MAIN.getObject(inputID + "confirmationRequired").checked; + currentValue['confirmationRequired'] = PWM_JSLibrary.getElement(inputID + "confirmationRequired").checked; FormTableHandler.write(keyName) }); } if (showReadOnly) { - PWM_MAIN.getObject(inputID + "readonly").checked = currentValue['readonly']; + PWM_JSLibrary.getElement(inputID + "readonly").checked = currentValue['readonly']; PWM_MAIN.addEventHandler(inputID + "readonly", "change", function () { - currentValue['readonly'] = PWM_MAIN.getObject(inputID + "readonly").checked; + currentValue['readonly'] = PWM_JSLibrary.getElement(inputID + "readonly").checked; FormTableHandler.write(keyName) }); } if (showUnique) { - PWM_MAIN.getObject(inputID + "unique").checked = currentValue['unique']; + PWM_JSLibrary.getElement(inputID + "unique").checked = currentValue['unique']; PWM_MAIN.addEventHandler(inputID + "unique", "change", function () { - currentValue['unique'] = PWM_MAIN.getObject(inputID + "unique").checked; + currentValue['unique'] = PWM_JSLibrary.getElement(inputID + "unique").checked; FormTableHandler.write(keyName) }); } if (showMultiValue) { - PWM_MAIN.getObject(inputID + "multivalue").checked = currentValue['multivalue']; + PWM_JSLibrary.getElement(inputID + "multivalue").checked = currentValue['multivalue']; PWM_MAIN.addEventHandler(inputID + "multivalue", "change", function () { - currentValue['multivalue'] = PWM_MAIN.getObject(inputID + "multivalue").checked; + currentValue['multivalue'] = PWM_JSLibrary.getElement(inputID + "multivalue").checked; FormTableHandler.write(keyName) }); } - if (PWM_MAIN.getObject(inputID + "maximumSize")) { - PWM_MAIN.getObject(inputID + "maximumSize").value = currentValue['maximumSize']; + if (PWM_JSLibrary.getElement(inputID + "maximumSize")) { + PWM_JSLibrary.getElement(inputID + "maximumSize").value = currentValue['maximumSize']; PWM_MAIN.addEventHandler(inputID + "maximumSize", "change", function(){ - currentValue['maximumSize'] = PWM_MAIN.getObject(inputID + "maximumSize").value; + currentValue['maximumSize'] = PWM_JSLibrary.getElement(inputID + "maximumSize").value; FormTableHandler.write(keyName) }); } if (!hideStandardOptions) { - PWM_MAIN.getObject(inputID + "minimumLength").value = currentValue['minimumLength']; + PWM_JSLibrary.getElement(inputID + "minimumLength").value = currentValue['minimumLength']; PWM_MAIN.addEventHandler(inputID + "minimumLength", "change", function(){ - currentValue['minimumLength'] = PWM_MAIN.getObject(inputID + "minimumLength").value; + currentValue['minimumLength'] = PWM_JSLibrary.getElement(inputID + "minimumLength").value; FormTableHandler.write(keyName) }); - PWM_MAIN.getObject(inputID + "maximumLength").value = currentValue['maximumLength']; + PWM_JSLibrary.getElement(inputID + "maximumLength").value = currentValue['maximumLength']; PWM_MAIN.addEventHandler(inputID + "maximumLength", "change", function(){ - currentValue['maximumLength'] = PWM_MAIN.getObject(inputID + "maximumLength").value; + currentValue['maximumLength'] = PWM_JSLibrary.getElement(inputID + "maximumLength").value; FormTableHandler.write(keyName) }); - PWM_MAIN.getObject(inputID + "regex").value = currentValue['regex'] ? currentValue['regex'] : ''; + PWM_JSLibrary.getElement(inputID + "regex").value = currentValue['regex'] ? currentValue['regex'] : ''; PWM_MAIN.addEventHandler(inputID + "regex", "change", function(){ - currentValue['regex'] = PWM_MAIN.getObject(inputID + "regex").value; + currentValue['regex'] = PWM_JSLibrary.getElement(inputID + "regex").value; FormTableHandler.write(keyName) }); @@ -394,23 +408,23 @@ FormTableHandler.showOptionsDialog = function(keyName, iteration) { FormTableHandler.showRegexErrorsDialog(keyName, iteration); }); - PWM_MAIN.getObject(inputID + "placeholder").value = currentValue['placeholder'] ? currentValue['placeholder'] : ''; + PWM_JSLibrary.getElement(inputID + "placeholder").value = currentValue['placeholder'] ? currentValue['placeholder'] : ''; PWM_MAIN.addEventHandler(inputID + "placeholder", "change", function(){ - currentValue['placeholder'] = PWM_MAIN.getObject(inputID + "placeholder").value; + currentValue['placeholder'] = PWM_JSLibrary.getElement(inputID + "placeholder").value; FormTableHandler.write(keyName) }); - PWM_MAIN.getObject(inputID + "javascript").value = currentValue['javascript'] ? currentValue['javascript'] : ''; + PWM_JSLibrary.getElement(inputID + "javascript").value = currentValue['javascript'] ? currentValue['javascript'] : ''; PWM_MAIN.addEventHandler(inputID + "javascript", "change", function(){ - currentValue['javascript'] = PWM_MAIN.getObject(inputID + "javascript").value; + currentValue['javascript'] = PWM_JSLibrary.getElement(inputID + "javascript").value; FormTableHandler.write(keyName) }); } if (showSource) { const nodeID = inputID + 'source'; - PWM_MAIN.JSLibrary.setValueOfSelectElement(nodeID,currentValue['source']); + PWM_JSLibrary.setValueOfSelectElement(nodeID,currentValue['source']); PWM_MAIN.addEventHandler(nodeID,'change',function(){ - const newValue = PWM_MAIN.JSLibrary.readValueOfSelectElement(nodeID); + const newValue = PWM_JSLibrary.readValueOfSelectElement(nodeID); currentValue['source'] = newValue; FormTableHandler.write(keyName); }); @@ -418,7 +432,7 @@ FormTableHandler.showOptionsDialog = function(keyName, iteration) { }; PWM_MAIN.showDialog({ - title: PWM_SETTINGS['settings'][keyName]['label'] + ' - ' + currentValue['name'], + title: settingData['settings'][keyName]['label'] + ' - ' + currentValue['name'], text:bodyText, allowMove:true, loadFunction:initFormElements, @@ -430,7 +444,7 @@ FormTableHandler.showOptionsDialog = function(keyName, iteration) { FormTableHandler.showLabelDialog = function(keyName, iteration) { const finishAction = function(){ FormTableHandler.redraw(keyName); }; - const title = 'Label for ' + PWM_VAR['clientSettingCache'][keyName][iteration]['name']; + const title = 'Label for ' + ClientSettingCache[keyName][iteration]['name']; FormTableHandler.multiLocaleStringDialog(keyName, iteration, 'labels', finishAction, title); }; @@ -438,7 +452,7 @@ FormTableHandler.multiLocaleStringDialog = function(keyName, iteration, settingT const inputID = 'value_' + keyName + '_' + iteration + "_" + "label_"; let bodyText = ''; bodyText += ''; - for (const localeName in PWM_VAR['clientSettingCache'][keyName][iteration][settingType]) { + for (const localeName in ClientSettingCache[keyName][iteration][settingType]) { const localeID = inputID + localeName; bodyText += ''; bodyText += ''; @@ -456,33 +470,33 @@ FormTableHandler.multiLocaleStringDialog = function(keyName, iteration, settingT finishAction(); }, loadFunction:function(){ - for (const iter in PWM_VAR['clientSettingCache'][keyName][iteration][settingType]) { + for (const iter in ClientSettingCache[keyName][iteration][settingType]) { (function(localeName) { - const value = PWM_VAR['clientSettingCache'][keyName][iteration][settingType][localeName]; + const value = ClientSettingCache[keyName][iteration][settingType][localeName]; const localeID = inputID + localeName; - PWM_MAIN.getObject(localeID + '-input').value = value; + PWM_JSLibrary.getElement(localeID + '-input').value = value; PWM_MAIN.addEventHandler(localeID + '-input', 'input', function () { - const inputElement = PWM_MAIN.getObject(localeID + '-input'); + const inputElement = PWM_JSLibrary.getElement(localeID + '-input'); const value = inputElement.value; - PWM_VAR['clientSettingCache'][keyName][iteration][settingType][localeName] = value; + ClientSettingCache[keyName][iteration][settingType][localeName] = value; FormTableHandler.write(keyName); }); PWM_MAIN.addEventHandler(localeID + '-removeLocaleButton', 'click', function () { - delete PWM_VAR['clientSettingCache'][keyName][iteration][settingType][localeName]; + delete ClientSettingCache[keyName][iteration][settingType][localeName]; FormTableHandler.write(keyName); FormTableHandler.multiLocaleStringDialog(keyName, iteration, settingType, finishAction, titleText); }); }(iter)); } - UILibrary.addAddLocaleButtonRow(inputID + 'table', inputID, function(localeName){ - if (localeName in PWM_VAR['clientSettingCache'][keyName][iteration][settingType]) { + PWM_UILibrary.addAddLocaleButtonRow(inputID + 'table', inputID, function(localeName){ + if (localeName in ClientSettingCache[keyName][iteration][settingType]) { alert('Locale is already present'); } else { - PWM_VAR['clientSettingCache'][keyName][iteration][settingType][localeName] = ''; + ClientSettingCache[keyName][iteration][settingType][localeName] = ''; FormTableHandler.write(keyName); FormTableHandler.multiLocaleStringDialog(keyName, iteration, settingType, finishAction, titleText); } - }, Object.keys(PWM_VAR['clientSettingCache'][keyName][iteration][settingType])); + }, Object.keys(ClientSettingCache[keyName][iteration][settingType])); } }); }; @@ -490,7 +504,7 @@ FormTableHandler.multiLocaleStringDialog = function(keyName, iteration, settingT FormTableHandler.showRegexErrorsDialog = function(keyName, iteration) { const finishAction = function(){ FormTableHandler.showOptionsDialog(keyName, iteration); }; - const title = 'Regular Expression Error Message for ' + PWM_VAR['clientSettingCache'][keyName][iteration]['name']; + const title = 'Regular Expression Error Message for ' + ClientSettingCache[keyName][iteration]['name']; FormTableHandler.multiLocaleStringDialog(keyName, iteration, 'regexErrors', finishAction, title); }; @@ -502,9 +516,9 @@ FormTableHandler.showSelectOptionsDialog = function(keyName, iteration) { bodyText += ''; bodyText += ''; bodyText += ''; - for (const optionName in PWM_VAR['clientSettingCache'][keyName][iteration]['selectOptions']) { + for (const optionName in ClientSettingCache[keyName][iteration]['selectOptions']) { (function(counter) { - const value = PWM_VAR['clientSettingCache'][keyName][iteration]['selectOptions'][counter]; + const value = ClientSettingCache[keyName][iteration]['selectOptions'][counter]; const optionID = inputID + counter; bodyText += ''; bodyText += ''; - htmlRow += ''; - htmlRow += ''; - htmlRow += ''; - return htmlRow; -}; - -RemoteWebServiceHandler.addRowHandlers = function(settingKey, iteration, value) { - const inputID = 'value_' + settingKey + '_' + iteration + "_"; - UILibrary.addTextValueToElement('display-' + inputID + '-name',value['name']); - UILibrary.addTextValueToElement('display-' + inputID + '-url',value['url']); - UILibrary.addTextValueToElement('display-' + inputID + '-description',value['description']); - PWM_MAIN.addEventHandler('button-' + inputID + '-options','click',function(){ - RemoteWebServiceHandler.showOptionsDialog(settingKey, iteration); - }); - - PWM_MAIN.addEventHandler('button-' + inputID + '-deleteRow','click',function(){ - RemoteWebServiceHandler.removeRow(settingKey, iteration); - }); -}; - -RemoteWebServiceHandler.write = function(settingKey, finishFunction) { - const cachedSetting = PWM_VAR['clientSettingCache'][settingKey]; - PWM_CFGEDIT.writeSetting(settingKey, cachedSetting, finishFunction); -}; - -RemoteWebServiceHandler.removeRow = function(keyName, iteration) { - PWM_MAIN.showConfirmDialog({ - text:'Are you sure you wish to delete this item?', - okAction:function(){ - delete PWM_VAR['clientSettingCache'][keyName][iteration]; - console.log("removed iteration " + iteration + " from " + keyName + ", cached keyValue=" + PWM_VAR['clientSettingCache'][keyName]); - RemoteWebServiceHandler.write(keyName,function(){ - RemoteWebServiceHandler.init(keyName); - }); - } - }) -}; - -RemoteWebServiceHandler.addRow = function(keyName) { - UILibrary.stringEditorDialog({ - title:'New Remote Web Service', - regex:'^[0-9a-zA-Z]+$', - instructions:'Please enter a descriptive name for the web service.', - placeholder:'Name', - completeFunction:function(value){ - const currentSize = PWM_MAIN.JSLibrary.itemCount(PWM_VAR['clientSettingCache'][keyName]); - PWM_VAR['clientSettingCache'][keyName][currentSize + 1] = RemoteWebServiceHandler.defaultValue; - PWM_VAR['clientSettingCache'][keyName][currentSize + 1].name = value; - - if (PWM_SETTINGS['settings'][keyName]['properties']['MethodType']) { - PWM_VAR['clientSettingCache'][keyName][currentSize + 1].method = PWM_SETTINGS['settings'][keyName]['properties']['MethodType']; - } - - RemoteWebServiceHandler.write(keyName,function(){ - RemoteWebServiceHandler.init(keyName); - }); - - } - }); -}; - -RemoteWebServiceHandler.showOptionsDialog = function(keyName, iteration) { - const inputID = 'value_' + keyName + '_' + iteration + "_"; - const value = PWM_VAR['clientSettingCache'][keyName][iteration]; - const titleText = 'Web Service options for ' + value['name']; - let bodyText = '
    ' + localeName + '
    ValueDisplay Name
    ' + counter + '' + value + ''; @@ -520,7 +534,7 @@ FormTableHandler.showSelectOptionsDialog = function(keyName, iteration) { bodyText += ''; const initFormFields = function() { - for (const optionName in PWM_VAR['clientSettingCache'][keyName][iteration]['selectOptions']) { + for (const optionName in ClientSettingCache[keyName][iteration]['selectOptions']) { (function(counter) { const optionID = inputID + counter; PWM_MAIN.addEventHandler(optionID + '-removeButton','click',function(){ @@ -530,14 +544,14 @@ FormTableHandler.showSelectOptionsDialog = function(keyName, iteration) { } PWM_MAIN.addEventHandler('addSelectOptionButton','click',function(){ - const value = PWM_MAIN.getObject('addSelectOptionName').value; - const display = PWM_MAIN.getObject('addSelectOptionValue').value; + const value = PWM_JSLibrary.getElement('addSelectOptionName').value; + const display = PWM_JSLibrary.getElement('addSelectOptionValue').value; FormTableHandler.addSelectOptionsOption(keyName, iteration, value, display); }); }; PWM_MAIN.showDialog({ - title: 'Select Options for ' + PWM_VAR['clientSettingCache'][keyName][iteration]['name'], + title: 'Select Options for ' + ClientSettingCache[keyName][iteration]['name'], text: bodyText, loadFunction: initFormFields, okAction: function(){ @@ -558,19 +572,19 @@ FormTableHandler.addSelectOptionsOption = function(keyName, iteration, optionNam return; } - PWM_VAR['clientSettingCache'][keyName][iteration]['selectOptions'][optionName] = optionValue; + ClientSettingCache[keyName][iteration]['selectOptions'][optionName] = optionValue; FormTableHandler.write(keyName); FormTableHandler.showSelectOptionsDialog(keyName, iteration); }; FormTableHandler.removeSelectOptionsOption = function(keyName, iteration, optionName) { - delete PWM_VAR['clientSettingCache'][keyName][iteration]['selectOptions'][optionName]; + delete ClientSettingCache[keyName][iteration]['selectOptions'][optionName]; FormTableHandler.write(keyName); FormTableHandler.showSelectOptionsDialog(keyName, iteration); }; FormTableHandler.showDescriptionDialog = function(keyName, iteration) { const finishAction = function(){ FormTableHandler.showOptionsDialog(keyName, iteration); }; - const title = 'Description for ' + PWM_VAR['clientSettingCache'][keyName][iteration]['name']; + const title = 'Description for ' + ClientSettingCache[keyName][iteration]['name']; FormTableHandler.multiLocaleStringDialog(keyName, iteration, 'description', finishAction, title); }; diff --git a/webapp/src/main/webapp/public/resources/js/configeditor-settings-permissions.js b/webapp/src/main/webapp/public/resources/js/configeditor-settings-permissions.js index ec89478cbb..e69de29bb2 100644 --- a/webapp/src/main/webapp/public/resources/js/configeditor-settings-permissions.js +++ b/webapp/src/main/webapp/public/resources/js/configeditor-settings-permissions.js @@ -1,304 +0,0 @@ -/* - * Password Management Servlets (PWM) - * http://www.pwm-project.org - * - * Copyright (c) 2006-2009 Novell, Inc. - * Copyright (c) 2009-2021 The PWM Project - * - * 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. - */ - -// -------------------------- user permission handler ------------------------------------ - -var UserPermissionHandler = {}; -UserPermissionHandler.defaultFilterValue = {type:'ldapFilter',ldapQuery:"(objectClass=*)",ldapBase:""}; -UserPermissionHandler.defaultGroupValue = {type:'ldapGroup',ldapBase:""}; -UserPermissionHandler.defaultUserValue = {type:'ldapUser',ldapBase:""}; -UserPermissionHandler.defaultAllUserValue = {type:'ldapAllUsers',ldapProfileID:'all'}; - -UserPermissionHandler.init = function(keyName) { - console.log('UserPermissionHandler init for ' + keyName); - PWM_CFGEDIT.readSetting(keyName, function(resultValue) { - PWM_VAR['clientSettingCache'][keyName] = resultValue; - UserPermissionHandler.draw(keyName); - }); -}; - -UserPermissionHandler.draw = function(keyName) { - const resultValue = PWM_VAR['clientSettingCache'][keyName]; - const parentDiv = 'table_setting_' + keyName; - const parentDivElement = PWM_MAIN.getObject(parentDiv); - - while (parentDivElement.firstChild) { - parentDivElement.removeChild(parentDivElement.firstChild); - } - - let htmlBody = ''; - if (resultValue.length > 0) { - for (const iteration in resultValue) { - (function (rowKey) { - if (htmlBody.length > 0) { - htmlBody += '

    OR
    ' - } - htmlBody += UserPermissionHandler.drawRow(keyName, resultValue, rowKey) - }(iteration)); - } - } else { - htmlBody += '

    No users are added.

    '; - } - - htmlBody += ''; - - const hideMatch = PWM_MAIN.JSLibrary.arrayContains(PWM_SETTINGS['settings'][keyName]['flags'], 'Permission_HideMatch') - || resultValue.length === 0; - if (!hideMatch) { - htmlBody += ''; - } - - parentDivElement.innerHTML = htmlBody; - - for (const iteration in resultValue) { - (function(rowKey) { - UserPermissionHandler.addRowHandlers(resultValue, keyName, rowKey); - }(iteration)); - } - - PWM_MAIN.addEventHandler('button-' + keyName + '-viewMatches','click',function(){ - const dataHandler = function(data) { - const html = PWM_CONFIG.convertListOfIdentitiesToHtml(data['data']); - PWM_MAIN.showDialog({title:'Matches',text:html,dialogClass:'wide',showOk:false,showClose:true}); - }; - PWM_CFGEDIT.executeSettingFunction(keyName, 'password.pwm.http.servlet.configeditor.function.UserMatchViewerFunction', dataHandler, null) - }); - - PWM_MAIN.addEventHandler('button-' + keyName + '-addPermission','click',function(){ - UserPermissionHandler.addPermission(keyName); - }); -}; - -UserPermissionHandler.drawRow = function(keyName, resultValue, rowKey) { - const inputID = "value-" + keyName + "-" + rowKey; - const type = resultValue[rowKey]['type']; - - let htmlBody = '
    '; - const profileLabelKey = (type === 'ldapAllUsers') ? 'Setting_Permission_Profile_AllUsers' : 'Setting_Permission_Profile'; - - htmlBody += '' - + '' - + '' - + '' - + ''; - - if (type !== 'ldapAllUsers') { - if (type !== 'ldapGroup' && type !== 'ldapUser') { - htmlBody += '' - + '' - + '' - + '' - + ''; - } - - const rowLabelKey = (type === 'ldapGroup') ? 'Setting_Permission_Base_Group' : - (type === 'ldapUser') ? 'Setting_Permission_Base_User' : 'Setting_Permission_Base' - htmlBody += '' - + '' - + '' - + '' - + ''; - } - - htmlBody += '
    ' + PWM_CONFIG.showString(profileLabelKey) + '
    ' - + '
    ' - + '
    ' - + '' - + '
    ' - + '' - + '
    ' - + '
    ' + PWM_CONFIG.showString('Setting_Permission_Filter') + '
    ' - + PWM_CONFIG.showString(rowLabelKey) - + '
     
    '; - htmlBody += '
    '; - - return htmlBody; -}; - -UserPermissionHandler.addRowHandlers = function( resultValue, keyName, rowKey) { - const inputID = "value-" + keyName + "-" + rowKey; - - const profileDataList = PWM_MAIN.getObject(inputID + "-datalist"); - const profileIdList = PWM_SETTINGS['var']['ldapProfileIds']; - for (const i in profileIdList) { - const option = document.createElement('option'); - option.value = profileIdList[i]; - profileDataList.appendChild(option); - } - - let currentProfile = PWM_VAR['clientSettingCache'][keyName][rowKey]['ldapProfileID']; - currentProfile = currentProfile === undefined ? '' : currentProfile; - const profileSelectNewValueFunction = function(newValue, writeNewValue) { - const allProfilesEnabled = !newValue || newValue === 'all' || newValue === ''; - if (allProfilesEnabled) { - PWM_MAIN.JSLibrary.setValueOfSelectElement(inputID + '-profileSelect', 'all'); - PWM_MAIN.addCssClass( inputID + '-profileWrapper', 'hidden'); - } else { - PWM_MAIN.JSLibrary.setValueOfSelectElement(inputID + '-profileSelect', 'specified'); - PWM_MAIN.removeCssClass( inputID + '-profileWrapper', 'hidden'); - } - - if ( writeNewValue ) { - if (allProfilesEnabled) { - PWM_VAR['clientSettingCache'][keyName][rowKey]['ldapProfileID'] = ''; - } - UserPermissionHandler.write(keyName); - } - } - profileSelectNewValueFunction(currentProfile, false); - PWM_MAIN.getObject(inputID+'-profile').value = currentProfile; - - PWM_MAIN.addEventHandler(inputID + '-profileSelect','change',function(){ - profileSelectNewValueFunction(this.value, true); - }); - - PWM_MAIN.addEventHandler(inputID + '-profile','input',function(){ - PWM_VAR['clientSettingCache'][keyName][rowKey]['ldapProfileID'] = this.value; - UserPermissionHandler.write(keyName); - }); - - if (resultValue[rowKey]['type'] !== 'ldapGroup') { - UILibrary.addTextValueToElement(inputID + '-query', PWM_VAR['clientSettingCache'][keyName][rowKey]['ldapQuery']); - const queryEditor = function(){ - UILibrary.stringEditorDialog({ - value:PWM_VAR['clientSettingCache'][keyName][rowKey]['ldapQuery'], - completeFunction:function(value) { - PWM_VAR['clientSettingCache'][keyName][rowKey]['ldapQuery'] = value; - UserPermissionHandler.write(keyName,true); - } - }); - }; - - PWM_MAIN.addEventHandler(inputID + "-query",'click',function(){ - queryEditor(); - }); - PWM_MAIN.addEventHandler('icon-edit-query-' + inputID,'click',function(){ - queryEditor(); - }); - } - - const currentBaseValue = ('ldapBase' in resultValue[rowKey]) ? resultValue[rowKey]['ldapBase'] : ""; - const baseEditor = function(){ - UILibrary.editLdapDN(function(value, ldapProfileID) { - PWM_VAR['clientSettingCache'][keyName][rowKey]['ldapProfileID'] = ldapProfileID; - PWM_VAR['clientSettingCache'][keyName][rowKey]['ldapBase'] = value; - UserPermissionHandler.write(keyName,true); - }, {currentDN: currentBaseValue, profile: currentProfile}); - }; - if (currentBaseValue && currentBaseValue.length > 0) { - UILibrary.addTextValueToElement(inputID + '-base', currentBaseValue); - } - PWM_MAIN.addEventHandler(inputID + '-base','click',function(){ - baseEditor(); - }); - PWM_MAIN.addEventHandler('icon-edit-base-' + inputID,'click',function(){ - baseEditor(); - }); - - const deleteButtonID = 'button-' + inputID + '-deleteRow'; - const hasID = PWM_MAIN.getObject(deleteButtonID) ? "YES" : "NO"; - console.log("addEventHandler row: " + deleteButtonID + " rowKey=" + rowKey + " hasID="+hasID); - PWM_MAIN.addEventHandler(deleteButtonID,'click',function(){ - console.log("delete row: " + inputID + " rowKey=" + rowKey + " hasID="+hasID); - delete PWM_VAR['clientSettingCache'][keyName][rowKey]; - UserPermissionHandler.write(keyName,true); - }); - - PWM_MAIN.showTooltip({ - id:inputID +'_profileHeader', - width: 300, - text:PWM_CONFIG.showString('Tooltip_Setting_Permission_Profile') - }); - PWM_MAIN.showTooltip({ - id:inputID +'_FilterHeader', - width: 300, - text:PWM_CONFIG.showString('Tooltip_Setting_Permission_Filter') - }); - PWM_MAIN.showTooltip({ - id: inputID + '_BaseHeader', - width: 300, - text: PWM_CONFIG.showString('Tooltip_Setting_Permission_Base') - }); -} - -UserPermissionHandler.write = function(settingKey,redraw) { - const nextFunction = function(){ - if (redraw) { - UserPermissionHandler.draw(settingKey); - } - }; - PWM_CFGEDIT.writeSetting(settingKey, PWM_VAR['clientSettingCache'][settingKey], nextFunction); -}; - -UserPermissionHandler.addPermission = function(keyName) { - let bodyHtml = '

    ' + PWM_CONFIG.showString('MenuDisplay_Permissions') + '

    ' - const hideGroup = PWM_MAIN.JSLibrary.arrayContains(PWM_SETTINGS['settings'][keyName]['flags'], 'Permission_HideGroups'); - bodyHtml += ''; - bodyHtml += ''; - if (!hideGroup) { - bodyHtml += ''; - } - bodyHtml += ''; - - bodyHtml += '
    ' + PWM_CONFIG.showString('MenuDisplay_Permission_AllUsers') + '
    ' + PWM_CONFIG.showString('MenuDisplay_Permission_LdapUser') + '
    ' + PWM_CONFIG.showString('MenuDisplay_Permission_LdapGroup') + '
    ' + PWM_CONFIG.showString('MenuDisplay_Permission_LdapFilter') + '
    '; - - const completeAddPermission = function(template) { - PWM_VAR['clientSettingCache'][keyName].push(PWM_MAIN.copyObject(template)); - PWM_MAIN.closeWaitDialog(); - UserPermissionHandler.write(keyName, true); - } - - const dialogOptions = {}; - dialogOptions.title = PWM_CONFIG.showString('Button_AddPermission'); - dialogOptions.text = bodyHtml; - dialogOptions.showCancel = false; - dialogOptions.showClose = true; - dialogOptions.showOk = false; - dialogOptions.loadFunction = function() { - PWM_MAIN.addEventHandler('button-' + keyName + '-addFilterValue','click',function(){ - completeAddPermission(UserPermissionHandler.defaultFilterValue); - }); - - PWM_MAIN.addEventHandler('button-' + keyName + '-addGroupValue','click',function(){ - completeAddPermission(UserPermissionHandler.defaultGroupValue); - }); - - PWM_MAIN.addEventHandler('button-' + keyName + '-addUserValue','click',function(){ - completeAddPermission(UserPermissionHandler.defaultUserValue); - }); - - PWM_MAIN.addEventHandler('button-' + keyName + '-addAllUsersValue','click',function(){ - completeAddPermission(UserPermissionHandler.defaultAllUserValue); - }); - }; - PWM_MAIN.showDialog(dialogOptions); -} diff --git a/webapp/src/main/webapp/public/resources/js/configeditor-settings-remotewebservices.js b/webapp/src/main/webapp/public/resources/js/configeditor-settings-remotewebservices.js index f6ab709a2f..e69de29bb2 100644 --- a/webapp/src/main/webapp/public/resources/js/configeditor-settings-remotewebservices.js +++ b/webapp/src/main/webapp/public/resources/js/configeditor-settings-remotewebservices.js @@ -1,395 +0,0 @@ -/* - * Password Management Servlets (PWM) - * http://www.pwm-project.org - * - * Copyright (c) 2006-2009 Novell, Inc. - * Copyright (c) 2009-2021 The PWM Project - * - * 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. - */ - -var RemoteWebServiceHandler = {}; -RemoteWebServiceHandler.defaultValue = { - name:"", - method:"get", - url:"", - body:"", - username:"", - password:"", - headers:{} -}; -RemoteWebServiceHandler.httpMethodOptions = [ - { label: "Delete", value: "delete" }, - { label: "Get", value: "get" }, - { label: "Post", value: "post" }, - { label: "Put", value: "put" }, - { label: "Patch", value: "patch" } -]; - -RemoteWebServiceHandler.init = function(keyName) { - console.log('RemoteWebServiceHandler init for ' + keyName); - const parentDiv = 'table_setting_' + keyName; - PWM_CFGEDIT.clearDivElements(parentDiv, true); - PWM_CFGEDIT.readSetting(keyName, function(resultValue) { - PWM_VAR['clientSettingCache'][keyName] = resultValue; - RemoteWebServiceHandler.redraw(keyName); - }); -}; - -RemoteWebServiceHandler.redraw = function(keyName) { - console.log('RemoteWebServiceHandler redraw for ' + keyName); - const resultValue = PWM_VAR['clientSettingCache'][keyName]; - const parentDiv = 'table_setting_' + keyName; - PWM_CFGEDIT.clearDivElements(parentDiv, false); - const parentDivElement = PWM_MAIN.getObject(parentDiv); - - let html = ''; - if (!PWM_MAIN.JSLibrary.isEmpty(resultValue)) { - html += ''; - html += ''; - - for (const loop in resultValue) { - (function (loop) { - html += RemoteWebServiceHandler.drawRow(keyName, loop, resultValue[loop]); - })(loop); - } - - html += '
    NameURL
    '; - } - - const rowCount = PWM_MAIN.JSLibrary.itemCount(resultValue); - const maxRowCount = PWM_SETTINGS['settings'][keyName]['properties']['Maximum']; - if (maxRowCount > 0 && rowCount < maxRowCount) { - html += '
    '; - } - - parentDivElement.innerHTML = html; - - for (const i in resultValue) { - html += RemoteWebServiceHandler.addRowHandlers(keyName, i, resultValue[i]); - } - - PWM_MAIN.addEventHandler('button-' + keyName + '-addValue','click',function(){ - RemoteWebServiceHandler.addRow(keyName); - }); -}; - -RemoteWebServiceHandler.drawRow = function(settingKey, iteration) { - const inputID = 'value_' + settingKey + '_' + iteration + "_"; - - const newTableRow = document.createElement("tr"); - newTableRow.setAttribute("style", "border-width: 0"); - - let htmlRow = '
    '; - htmlRow += '
    '; - htmlRow += '
    '; - htmlRow += '
    '; - htmlRow += '
    '; - htmlRow += ''; - htmlRow += '
    '; - - const hasMethodType = 'MethodType' in PWM_SETTINGS['settings'][keyName]['properties']; - const showBody = value['method'] !== 'get' && !(PWM_MAIN.JSLibrary.arrayContains(PWM_SETTINGS['settings'][keyName]['flags'], 'WebService_NoBody')); - - bodyText += ''; - bodyText += ''; - bodyText += ''; - bodyText += ''; - bodyText += ''; - bodyText += ''; - bodyText += ''; - bodyText += ''; - bodyText += ''; - bodyText += ''; - bodyText += ''; - if (showBody) { - bodyText += ''; - } - if (value['certificates']) { - bodyText += ''; - bodyText += ''; - } else { - bodyText += ''; - bodyText += ''; - } - bodyText += ''; - - bodyText += '
    HTTP Method
    HTTP Headers
    URL
    Basic Auth Username
    Basic Auth Password
    Body
    CertificatesView Certificates
    CertificatesNone
    '; - - if (value['certificates']) { - bodyText += '' - } else { - bodyText += '' - } - - PWM_MAIN.showDialog({ - title: titleText, - text: bodyText, - okAction: function(){ - RemoteWebServiceHandler.init(keyName); - }, - loadFunction: function(){ - PWM_MAIN.addEventHandler('button-' + inputID + '-headers','click',function(){ - RemoteWebServiceHandler.showHeadersDialog(keyName,iteration); - }); - - PWM_MAIN.addEventHandler('select-' + inputID + '-method','change',function(){ - const methodValue = PWM_MAIN.getObject('select-' + inputID + '-method').value; - if (methodValue === 'get') { - value['body'] = ''; - } - value['method'] = methodValue; - RemoteWebServiceHandler.write(keyName, function(){ RemoteWebServiceHandler.showOptionsDialog(keyName,iteration)}); - }); - PWM_MAIN.getObject('input-' + inputID + '-url').value = value['url'] ? value['url'] : ''; - PWM_MAIN.getObject('input-' + inputID + '-url').disabled = false; - PWM_MAIN.addEventHandler('input-' + inputID + '-url','input',function(){ - value['url'] = PWM_MAIN.getObject('input-' + inputID + '-url').value; - RemoteWebServiceHandler.write(keyName); - }); - - PWM_MAIN.getObject('input-' + inputID + '-username').value = value['username'] ? value['username'] : ''; - PWM_MAIN.getObject('input-' + inputID + '-username').disabled = false; - PWM_MAIN.addEventHandler('input-' + inputID + '-username','input',function(){ - value['username'] = PWM_MAIN.getObject('input-' + inputID + '-username').value; - ActionHandler.write(keyName); - }); - - PWM_MAIN.getObject('input-' + inputID + '-password').value = value['password'] ? value['password'] : ''; - PWM_MAIN.getObject('input-' + inputID + '-password').disabled = false; - PWM_MAIN.addEventHandler('input-' + inputID + '-password','input',function(){ - value['password'] = PWM_MAIN.getObject('input-' + inputID + '-password').value; - ActionHandler.write(keyName); - }); - - PWM_MAIN.addEventHandler('input-' + inputID + '-body','input',function(){ - value['body'] = PWM_MAIN.getObject('input-' + inputID + '-body').value; - RemoteWebServiceHandler.write(keyName); - }); - if (value['certificates']) { - PWM_MAIN.addEventHandler('button-' + inputID + '-certDetail','click',function(){ - let extraData = JSON.stringify({iteration:iteration,keyName:keyName}); - debugger; - PWM_CFGEDIT.executeSettingFunction(keyName, 'password.pwm.http.servlet.configeditor.function.RemoteWebServiceCertViewerFunction', - RemoteWebServiceHandler.showCertificateViewerDialog, extraData) - - }); - PWM_MAIN.addEventHandler('button-' + inputID + '-clearCertificates','click',function() { - PWM_MAIN.showConfirmDialog({okAction:function(){ - delete value['certificates']; - delete value['certificateInfos']; - RemoteWebServiceHandler.write(keyName, function(){ RemoteWebServiceHandler.showOptionsDialog(keyName,iteration)}); - },cancelAction:function(){ - RemoteWebServiceHandler.showOptionsDialog(keyName,iteration); - }}); - }); - } else { - PWM_MAIN.addEventHandler('button-' + inputID + '-importCertificates','click',function() { - const dataHandler = function(data) { - const msgBody = '
    ' + data['successMessage'] + '
    '; - PWM_MAIN.showDialog({width:700,title: 'Results', text: msgBody, okAction: function () { - PWM_CFGEDIT.readSetting(keyName, function(resultValue) { - PWM_VAR['clientSettingCache'][keyName] = resultValue; - RemoteWebServiceHandler.showOptionsDialog(keyName, iteration); - }); - }}); - }; - PWM_CFGEDIT.executeSettingFunction(keyName, 'password.pwm.http.servlet.configeditor.function.RemoteWebServiceCertImportFunction', dataHandler, value['name']) - }); - } - - } - }); -}; - - - -RemoteWebServiceHandler.showHeadersDialog = function(keyName, iteration) { - const settingValue = PWM_VAR['clientSettingCache'][keyName][iteration]; - const inputID = 'value_' + keyName + '_' + iteration + "_" + "headers_"; - - let bodyText = ''; - bodyText += ''; - bodyText += ''; - for (const iter in settingValue['headers']) { - (function(headerName) { - const value = settingValue['headers'][headerName]; - const optionID = inputID + headerName; - bodyText += ''; - bodyText += ''; - bodyText += ''; - }(iter)); - } - bodyText += '
    NameValue
    ' + headerName + '' + value + '
    '; - - PWM_MAIN.showDialog({ - title: 'HTTP Headers for webservice ' + settingValue['name'], - text: bodyText, - buttonHtml:'', - okAction: function() { - RemoteWebServiceHandler.showOptionsDialog(keyName,iteration); - }, - loadFunction: function() { - for (const iter in settingValue['headers']) { - (function(headerName) { - const headerID = inputID + headerName; - PWM_MAIN.addEventHandler('button-' + headerID + '-deleteRow', 'click', function () { - delete settingValue['headers'][headerName]; - RemoteWebServiceHandler.write(keyName); - RemoteWebServiceHandler.showHeadersDialog(keyName, iteration); - }); - }(iter)); - } - PWM_MAIN.addEventHandler('button-' + inputID + '-addHeader','click',function(){ - RemoteWebServiceHandler.addHeader(keyName, iteration); - }); - } - }); -}; - -RemoteWebServiceHandler.addHeader = function(keyName, iteration) { - let body = ''; - body += ''; - body += ''; - body += '
    Name
    Value
    '; - - const updateFunction = function(){ - PWM_MAIN.getObject('dialog_ok_button').disabled = true; - PWM_VAR['newHeaderName'] = PWM_MAIN.getObject('newHeaderName').value; - PWM_VAR['newHeaderValue'] = PWM_MAIN.getObject('newHeaderValue').value; - if (PWM_VAR['newHeaderName'].length > 0 && PWM_VAR['newHeaderValue'].length > 0) { - PWM_MAIN.getObject('dialog_ok_button').disabled = false; - } - }; - - PWM_MAIN.showConfirmDialog({ - title:'New Header', - text:body, - showClose:true, - loadFunction:function(){ - PWM_MAIN.addEventHandler('newHeaderName','input',function(){ - updateFunction(); - }); - PWM_MAIN.addEventHandler('newHeaderValue','input',function(){ - updateFunction(); - }); - updateFunction(); - },okAction:function(){ - const headers = PWM_VAR['clientSettingCache'][keyName][iteration]['headers']; - headers[PWM_VAR['newHeaderName']] = PWM_VAR['newHeaderValue']; - RemoteWebServiceHandler.write(keyName); - RemoteWebServiceHandler.showHeadersDialog(keyName, iteration); - },cancelAction:function(){ - RemoteWebServiceHandler.showHeadersDialog(keyName, iteration); - } - }); -}; - -RemoteWebServiceHandler.showCertificateViewerDialog = function(data,extraDataJson) { - let extraData = JSON.parse(extraDataJson) - let keyName = extraData['keyName']; - let certInfos = data['data']; - let bodyText = ''; - for (let i in certInfos) { - bodyText += X509CertificateHandler.certificateToHtml(certInfos[i],keyName,i); - } - let cancelFunction = function(){ RemoteWebServiceHandler.showOptionsDialog(keyName, extraData['iteration'])}; - let loadFunction = function(){ - for (let i in certInfos) { - X509CertificateHandler.certHtmlActions(certInfos[i],keyName,i); - } - }; - PWM_MAIN.showDialog({ - title:'Certificate Detail', - dialogClass: 'wide', - text:bodyText, - okAction:cancelFunction, - loadFunction:loadFunction - }); -}; diff --git a/webapp/src/main/webapp/public/resources/js/configeditor-settings-stringarray.js b/webapp/src/main/webapp/public/resources/js/configeditor-settings-stringarray.js index ee2c3e595f..e69de29bb2 100644 --- a/webapp/src/main/webapp/public/resources/js/configeditor-settings-stringarray.js +++ b/webapp/src/main/webapp/public/resources/js/configeditor-settings-stringarray.js @@ -1,297 +0,0 @@ -/* - * Password Management Servlets (PWM) - * http://www.pwm-project.org - * - * Copyright (c) 2006-2009 Novell, Inc. - * Copyright (c) 2009-2021 The PWM Project - * - * 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. - */ - -var StringArrayValueHandler = {}; - -StringArrayValueHandler.init = function(keyName) { - console.log('StringArrayValueHandler init for ' + keyName); - - { - const parentDiv = 'table_setting_' + keyName; - PWM_MAIN.getObject(parentDiv).innerHTML = '
    '; - } - const parentDiv = PWM_MAIN.getObject('tableTop_' + keyName); - - PWM_VAR['clientSettingCache'][keyName + "_options"] = PWM_VAR['clientSettingCache'][keyName + "_options"] || {}; - PWM_VAR['clientSettingCache'][keyName + "_options"]['parentDiv'] = parentDiv; - PWM_CFGEDIT.clearDivElements(parentDiv, true); - PWM_CFGEDIT.readSetting(keyName, function(resultValue) { - PWM_VAR['clientSettingCache'][keyName] = resultValue; - StringArrayValueHandler.draw(keyName); - - const syntax = PWM_SETTINGS['settings'][keyName]['syntax']; - if (syntax === 'PROFILE') { - PWM_MAIN.getObject("resetButton-" + keyName).style.display = 'none'; - PWM_MAIN.getObject("helpButton-" + keyName).style.display = 'none'; - PWM_MAIN.getObject("modifiedNoticeIcon-" + keyName).style.display = 'none'; - } - }); -}; - - -StringArrayValueHandler.draw = function(settingKey) { - const parentDiv = PWM_VAR['clientSettingCache'][settingKey + "_options"]['parentDiv']; - const parentDivElement = PWM_MAIN.getObject(parentDiv); - - PWM_CFGEDIT.clearDivElements(parentDiv, false); - const resultValue = PWM_VAR['clientSettingCache'][settingKey]; - - const tableElement = document.createElement("table"); - tableElement.setAttribute("style", "border-width: 0;"); - - const syntax = PWM_SETTINGS['settings'][settingKey]['syntax']; - if (syntax === 'PROFILE') { - const divDescriptionElement = document.createElement("div"); - let text = PWM_SETTINGS['settings'][settingKey]['description']; - text += '
    ' + PWM_CONFIG.showString('Display_ProfileNamingRules'); - divDescriptionElement.innerHTML = text; - parentDivElement.appendChild(divDescriptionElement); - - const defaultProfileRow = document.createElement("tr"); - defaultProfileRow.setAttribute("colspan", "5"); - } - - let counter = 0; - const itemCount = PWM_MAIN.JSLibrary.itemCount(PWM_VAR['clientSettingCache'][settingKey]); - parentDivElement.appendChild(tableElement); - - for (const i in resultValue) { - (function(iteration) { - StringArrayValueHandler.drawRow(settingKey, iteration, resultValue[iteration], itemCount, tableElement); - counter++; - })(i); - } - - const settingProperties = PWM_SETTINGS['settings'][settingKey]['properties']; - if (settingProperties && 'Maximum' in settingProperties && itemCount >= settingProperties['Maximum']) { - // item count is already maxed out - } else { - const addItemButton = document.createElement("button"); - addItemButton.setAttribute("type", "button"); - addItemButton.setAttribute("class", "btn"); - addItemButton.setAttribute("id", "button-" + settingKey + "-addItem"); - addItemButton.innerHTML = '' + (syntax === 'PROFILE' ? "Add Profile" : "Add Value"); - parentDivElement.appendChild(addItemButton); - - PWM_MAIN.addEventHandler('button-' + settingKey + '-addItem', 'click', function () { - StringArrayValueHandler.valueHandler(settingKey, -1); - }); - } -}; - -StringArrayValueHandler.drawRow = function(settingKey, iteration, value, itemCount, parentDivElement) { - const settingInfo = PWM_SETTINGS['settings'][settingKey]; - const settingProperties = PWM_SETTINGS['settings'][settingKey]['properties']; - const syntax = settingInfo['syntax']; - - const inputID = 'value-' + settingKey + '-' + iteration; - - const valueRow = document.createElement("tr"); - valueRow.setAttribute("style", "border-width: 0"); - valueRow.setAttribute("id",inputID + "_row"); - - let rowHtml = ''; - if (syntax !== 'PROFILE') { - rowHtml = ''; - } - rowHtml += '
    '; - - const copyButtonID = 'button-' + settingKey + '-' + iteration + '-copy'; - if (syntax === 'PROFILE') { - rowHtml += ''; - rowHtml += ''; - rowHtml += ''; - } else if (syntax === 'DOMAIN') { - rowHtml += ''; - rowHtml += ''; - rowHtml += ''; - } - - const showMoveButtons = syntax !== 'DOMAIN'; - const downButtonID = 'button-' + settingKey + '-' + iteration + '-moveDown'; - const upButtonID = 'button-' + settingKey + '-' + iteration + '-moveUp'; - if ( showMoveButtons ) { - rowHtml += ''; - if (itemCount > 1 && iteration !== (itemCount - 1)) { - rowHtml += ''; - } - rowHtml += ''; - - rowHtml += ''; - if (itemCount > 1 && iteration !== 0) { - rowHtml += ''; - } - rowHtml += ''; - } - - const minValuesRequired = settingProperties['Minimum'] ? settingProperties['Minimum'] : settingInfo['required'] ? 1 : 0; - const showDeleteButtons = itemCount > minValuesRequired; - const deleteButtonID = 'button-' + settingKey + '-' + iteration + '-delete'; - if (showDeleteButtons) { - rowHtml += ''; - rowHtml += ''; - rowHtml += ''; - } - - valueRow.innerHTML = rowHtml; - parentDivElement.appendChild(valueRow); - - let allowEditValue = true; - if (syntax === 'PROFILE' || syntax === 'DOMAIN') { - allowEditValue = false; - PWM_MAIN.addEventHandler(copyButtonID, 'click', function () { - const editorOptions = {}; - editorOptions['title'] = syntax === 'PROFILE' ? 'Copy Profile' : 'Copy Domain'; - editorOptions['instructions'] = syntax === 'PROFILE' ? 'Copy profile and all profile settings from existing "' + value + '" profile to a new profile.' : - 'Copy domain and all domain settings from existing "' + value + '" domain to a new domain.' - editorOptions['regex'] = PWM_SETTINGS['settings'][settingKey]['pattern']; - editorOptions['placeholder'] = PWM_SETTINGS['settings'][settingKey]['placeholder']; - editorOptions['completeFunction'] = function (newValue) { - const options = {}; - options['setting'] = settingKey; - options['sourceID'] = value; - options['destinationID'] = newValue; - const resultFunction = function (data) { - if (data['error']) { - PWM_MAIN.showErrorDialog(data); - } else { - PWM_MAIN.gotoUrl('editor'); - } - }; - - const actionName = syntax === 'PROFILE' ? 'copyProfile' : 'copyDomain'; - PWM_MAIN.showWaitDialog({ - loadFunction: function () { - const url = PWM_MAIN.addParamToUrl(window.location.pathname, 'processAction',actionName); - PWM_MAIN.ajaxRequest(url, resultFunction, {content: options}); - } - }); - }; - UILibrary.stringEditorDialog(editorOptions); - }); - } - - UILibrary.addTextValueToElement(inputID, value); - - if (allowEditValue) { - PWM_MAIN.addEventHandler(inputID, 'click', function () { - StringArrayValueHandler.valueHandler(settingKey, iteration); - }); - PWM_MAIN.addEventHandler('button-' + inputID, 'click', function () { - StringArrayValueHandler.valueHandler(settingKey, iteration); - }); - } - - if (itemCount > 1 && iteration !== (itemCount -1)) { - PWM_MAIN.addEventHandler(downButtonID,'click',function(){StringArrayValueHandler.move(settingKey,false,iteration)}); - } - - if (itemCount > 1 && iteration !== 0) { - PWM_MAIN.addEventHandler(upButtonID,'click',function(){StringArrayValueHandler.move(settingKey,true,iteration)}); - } - - if (itemCount > 1 || !PWM_SETTINGS['settings'][settingKey]['required']) { - PWM_MAIN.addEventHandler(deleteButtonID,'click',function(){StringArrayValueHandler.removeValue(settingKey,iteration)}); - } -}; - -StringArrayValueHandler.valueHandler = function(settingKey, iteration) { - const okAction = function(value) { - if (iteration > -1) { - PWM_VAR['clientSettingCache'][settingKey][iteration] = value; - } else { - PWM_VAR['clientSettingCache'][settingKey].push(value); - } - StringArrayValueHandler.writeSetting(settingKey) - }; - - const editorOptions = {}; - editorOptions['title'] = PWM_SETTINGS['settings'][settingKey]['label'] + " - " + (iteration > -1 ? "Edit" : "Add") + " Value"; - editorOptions['regex'] = PWM_SETTINGS['settings'][settingKey]['pattern']; - editorOptions['placeholder'] = PWM_SETTINGS['settings'][settingKey]['placeholder']; - editorOptions['completeFunction'] = okAction; - editorOptions['value'] = iteration > -1 ? PWM_VAR['clientSettingCache'][settingKey][iteration] : ''; - - const isLdapDN = PWM_MAIN.JSLibrary.arrayContains(PWM_SETTINGS['settings'][settingKey]['flags'],'ldapDnSyntax'); - if (isLdapDN) { - UILibrary.editLdapDN(okAction,{currentDN: editorOptions['value']}); - } else { - UILibrary.stringEditorDialog(editorOptions); - } -}; - -StringArrayValueHandler.move = function(settingKey, moveUp, iteration) { - const currentValues = PWM_VAR['clientSettingCache'][settingKey]; - if (moveUp) { - StringArrayValueHandler.arrayMoveUtil(currentValues, iteration, iteration - 1); - } else { - StringArrayValueHandler.arrayMoveUtil(currentValues, iteration, iteration + 1); - } - StringArrayValueHandler.writeSetting(settingKey) -}; - -StringArrayValueHandler.arrayMoveUtil = function(arr, fromIndex, toIndex) { - const element = arr[fromIndex]; - arr.splice(fromIndex, 1); - arr.splice(toIndex, 0, element); -}; - -StringArrayValueHandler.removeValue = function(settingKey, iteration) { - const syntax = PWM_SETTINGS['settings'][settingKey]['syntax']; - const profileName = PWM_VAR['clientSettingCache'][settingKey][iteration]; - const deleteFunction = function() { - const currentValues = PWM_VAR['clientSettingCache'][settingKey]; - currentValues.splice(iteration,1); - StringArrayValueHandler.writeSetting(settingKey,false); - }; - if (syntax === 'PROFILE') { - PWM_MAIN.showConfirmDialog({ - text:PWM_CONFIG.showString('Confirm_RemoveProfile',{value1:profileName}), - okAction:function(){ - deleteFunction(); - } - }); - } else if (syntax === 'DOMAIN') { - PWM_MAIN.showConfirmDialog({ - text:PWM_CONFIG.showString('Confirm_RemoveDomain',{value1:profileName}), - okAction:function(){ - deleteFunction(); - } - }); - } else { - deleteFunction(); - } -}; - -StringArrayValueHandler.writeSetting = function(settingKey, reload) { - const syntax = PWM_SETTINGS['settings'][settingKey]['syntax']; - const nextFunction = function() { - if (syntax === 'PROFILE') { - PWM_MAIN.gotoUrl('editor'); - } - if (reload) { - StringArrayValueHandler.init(settingKey); - } else { - StringArrayValueHandler.draw(settingKey); - } - }; - const currentValues = PWM_VAR['clientSettingCache'][settingKey]; - PWM_CFGEDIT.writeSetting(settingKey, currentValues, nextFunction); -}; \ No newline at end of file diff --git a/webapp/src/main/webapp/public/resources/js/configeditor-settings.js b/webapp/src/main/webapp/public/resources/js/configeditor-settings.js index bfaccd792b..e69de29bb2 100644 --- a/webapp/src/main/webapp/public/resources/js/configeditor-settings.js +++ b/webapp/src/main/webapp/public/resources/js/configeditor-settings.js @@ -1,1514 +0,0 @@ -/* - * Password Management Servlets (PWM) - * http://www.pwm-project.org - * - * Copyright (c) 2006-2009 Novell, Inc. - * Copyright (c) 2009-2021 The PWM Project - * - * 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. - */ - -"use strict"; - -var PWM_CFGEDIT = PWM_CFGEDIT || {}; -var PWM_CONFIG = PWM_CONFIG || {}; -var PWM_MAIN = PWM_MAIN || {}; -var PWM_VAR = PWM_VAR || {}; -var PWM_SETTINGS = PWM_SETTINGS || {}; - -PWM_VAR['clientSettingCache'] = { }; - -// -------------------------- locale table handler ------------------------------------ -var LocalizedStringValueHandler = {}; - -PWM_VAR['LocalizedStringValueHandler-settingData'] = {}; -LocalizedStringValueHandler.init = function(settingKey, settingData) { - console.log('LocalizedStringValueHandler init for ' + settingKey); - - if (settingData) { - PWM_VAR['LocalizedStringValueHandler-settingData'][settingKey] = settingData; - } else { - PWM_VAR['LocalizedStringValueHandler-settingData'][settingKey] = PWM_SETTINGS['settings'][settingKey]; - } - - let parentDiv = 'table_setting_' + settingKey; - PWM_MAIN.getObject(parentDiv).innerHTML = ''; - parentDiv = PWM_MAIN.getObject('tableTop_' + settingKey); - - PWM_VAR['clientSettingCache'][settingKey + "_parentDiv"] = parentDiv; - PWM_CFGEDIT.clearDivElements(parentDiv, true); - PWM_CFGEDIT.readSetting(settingKey, function(resultValue) { - PWM_VAR['clientSettingCache'][settingKey] = resultValue; - LocalizedStringValueHandler.draw(settingKey); - }); -}; - -LocalizedStringValueHandler.draw = function(settingKey) { - const parentDiv = PWM_VAR['clientSettingCache'][settingKey + "_parentDiv"]; - const settingData = PWM_VAR['LocalizedStringValueHandler-settingData'][settingKey]; - - const resultValue = PWM_VAR['clientSettingCache'][settingKey]; - PWM_CFGEDIT.clearDivElements(parentDiv, false); - if (PWM_MAIN.JSLibrary.isEmpty(resultValue)) { - parentDiv.innerHTML = ''; - PWM_MAIN.addEventHandler('button-' + settingKey + '-addValue','click',function(){ - UILibrary.stringEditorDialog({ - title:'Add Value', - textarea:('LOCALIZED_TEXT_AREA' === settingData['syntax']), - regex:'pattern' in settingData ? settingData['pattern'] : '.+', - placeholder:settingData['placeholder'], - value:'', - completeFunction:function(value){ - LocalizedStringValueHandler.writeLocaleSetting(settingKey,'',value); - } - }); - }) - } else { - for (const localeKey in resultValue) { - LocalizedStringValueHandler.drawRow(parentDiv, settingKey, localeKey, resultValue[localeKey]) - } - UILibrary.addAddLocaleButtonRow(parentDiv, settingKey, function(localeKey) { - LocalizedStringValueHandler.addLocaleSetting(settingKey, localeKey); - }, Object.keys(resultValue)); - } - - PWM_VAR['clientSettingCache'][settingKey] = resultValue; -}; - -LocalizedStringValueHandler.drawRow = function(parentDiv, settingKey, localeString, value) { - const settingData = PWM_VAR['LocalizedStringValueHandler-settingData'][settingKey]; - const inputID = 'value-' + settingKey + '-' + localeString; - - const newTableRow = document.createElement("tr"); - newTableRow.setAttribute("style", "border-width: 0"); - - let tableHtml = ''; - - tableHtml += ''; - - const defaultLocale = (localeString === null || localeString.length < 1); - const required = settingData['required']; - const hasNonDefaultValues = PWM_MAIN.JSLibrary.itemCount(PWM_VAR['clientSettingCache'][settingKey]) > 1 ; - - if (!defaultLocale || !required && !hasNonDefaultValues) { - tableHtml += '
    '; - } - - newTableRow.innerHTML = tableHtml; - const parentDivElement = PWM_MAIN.getObject(parentDiv); - parentDivElement.appendChild(newTableRow); - - PWM_MAIN.addEventHandler("button-" + settingKey + '-' + localeString + "-deleteRow","click",function(){ - LocalizedStringValueHandler.removeLocaleSetting(settingKey, localeString); - }); - UILibrary.addTextValueToElement('value-' + inputID, (value !== null && value.length > 0) ? value : ' '); - - const editFunction = function() { - UILibrary.stringEditorDialog({ - title:'Edit Value', - textarea:('LOCALIZED_TEXT_AREA' === settingData['syntax']), - regex:'pattern' in settingData ? settingData['pattern'] : '.+', - placeholder:settingData['placeholder'], - value:value, - completeFunction:function(value){ - LocalizedStringValueHandler.writeLocaleSetting(settingKey,localeString,value); - } - }); - }; - - PWM_MAIN.addEventHandler("panel-" + inputID,'click',function(){ editFunction(); }); - PWM_MAIN.addEventHandler("button-" + inputID,'click',function(){ editFunction(); }); -}; - -LocalizedStringValueHandler.writeLocaleSetting = function(settingKey, locale, value) { - const existingValues = PWM_VAR['clientSettingCache'][settingKey]; - existingValues[locale] = value; - PWM_CFGEDIT.writeSetting(settingKey, existingValues); - LocalizedStringValueHandler.draw(settingKey); -}; - -LocalizedStringValueHandler.removeLocaleSetting = function(settingKey, locale) { - const existingValues = PWM_VAR['clientSettingCache'][settingKey]; - delete existingValues[locale]; - PWM_CFGEDIT.writeSetting(settingKey, existingValues); - LocalizedStringValueHandler.draw(settingKey); -}; - -LocalizedStringValueHandler.addLocaleSetting = function(settingKey, localeKey) { - const existingValues = PWM_VAR['clientSettingCache'][settingKey]; - const settingData = PWM_VAR['LocalizedStringValueHandler-settingData'][settingKey]; - if (localeKey in existingValues) { - PWM_MAIN.showErrorDialog('Locale ' + localeKey + ' is already present.'); - } else { - UILibrary.stringEditorDialog({ - title:'Add Value - ' + localeKey, - textarea:('LOCALIZED_TEXT_AREA' === settingData['syntax']), - regex:'pattern' in settingData ? settingData['pattern'] : '.+', - placeholder:settingData['placeholder'], - value:'', - completeFunction:function(value){ - LocalizedStringValueHandler.writeLocaleSetting(settingKey,localeKey,value); - } - }); - } -}; - - -// -------------------------- multi locale table handler ------------------------------------ - -const MultiLocaleTableHandler = {}; - -MultiLocaleTableHandler.initMultiLocaleTable = function(keyName) { - console.log('MultiLocaleTableHandler init for ' + keyName); - const parentDiv = 'table_setting_' + keyName; - - PWM_CFGEDIT.clearDivElements(parentDiv, true); - PWM_CFGEDIT.readSetting(keyName, function(resultValue) { - PWM_VAR['clientSettingCache'][keyName] = resultValue; - MultiLocaleTableHandler.draw(keyName); - }); -}; - -MultiLocaleTableHandler.draw = function(keyName) { - const parentDiv = 'table_setting_' + keyName; - const regExPattern = PWM_SETTINGS['settings'][keyName]['pattern']; - - const resultValue = PWM_VAR['clientSettingCache'][keyName]; - PWM_CFGEDIT.clearDivElements(parentDiv, false); - for (const localeName in resultValue) { - const localeTableRow = document.createElement("tr"); - localeTableRow.setAttribute("style", "border-width: 0;"); - - const localeTdName = document.createElement("td"); - localeTdName.setAttribute("style", "border-width: 0; width:15px"); - localeTdName.innerHTML = localeName; - localeTableRow.appendChild(localeTdName); - - const localeTdContent = document.createElement("td"); - localeTdContent.setAttribute("style", "border-width: 0; width: 525px"); - localeTableRow.appendChild(localeTdContent); - - const localeTableElement = document.createElement("table"); - localeTableElement.setAttribute("style", "border-width: 0px; width:525px; margin:0"); - localeTdContent.appendChild(localeTableElement); - - const multiValues = resultValue[localeName]; - - for (const iteration in multiValues) { - - const valueTableRow = document.createElement("tr"); - - const valueTd1 = document.createElement("td"); - valueTd1.setAttribute("style", "border-width: 0;"); - - const inputID = "value-" + keyName + "-" + localeName + "-" + iteration; - - const inputElement = document.createElement("input"); - inputElement.setAttribute("id", inputID); - inputElement.setAttribute("value", multiValues[iteration]); - inputElement.setAttribute("onchange", "MultiLocaleTableHandler.writeMultiLocaleSetting('" + keyName + "','" + localeName + "','" + iteration + "',this.value,'" + regExPattern + "')"); - inputElement.setAttribute("style", "width: 480px; padding: 5px;"); - inputElement.setAttribute("regExp", regExPattern); - inputElement.setAttribute("invalidMessage", "The value does not have the correct format."); - valueTd1.appendChild(inputElement); - valueTableRow.appendChild(valueTd1); - localeTableElement.appendChild(valueTableRow); - - // add remove button - const imgElement = document.createElement("div"); - imgElement.setAttribute("style", "width: 10px; height: 10px;"); - imgElement.setAttribute("class", "delete-row-icon action-icon pwm-icon pwm-icon-times"); - imgElement.setAttribute("id", inputID + "-remove"); - valueTd1.appendChild(imgElement); - } - - { // add row button for this locale group - const newTableRow = document.createElement("tr"); - newTableRow.setAttribute("style", "border-width: 0"); - newTableRow.setAttribute("colspan", "5"); - - const newTableData = document.createElement("td"); - newTableData.setAttribute("style", "border-width: 0;"); - - const addItemButton = document.createElement("button"); - addItemButton.setAttribute("type", "button"); - addItemButton.setAttribute("onclick", "PWM_VAR['clientSettingCache']['" + keyName + "']['" + localeName + "'].push('');MultiLocaleTableHandler.writeMultiLocaleSetting('" + keyName + "',null,null,null,'" + regExPattern + "')"); - addItemButton.innerHTML = "Add Value"; - newTableData.appendChild(addItemButton); - - newTableRow.appendChild(newTableData); - localeTableElement.appendChild(newTableRow); - } - - if (localeName !== '') { // add remove locale x - const imgElement2 = document.createElement("div"); - imgElement2.setAttribute("id", "div-" + keyName + "-" + localeName + "-remove"); - imgElement2.setAttribute("class", "delete-row-icon action-icon pwm-icon pwm-icon-times"); - const tdElement = document.createElement("td"); - tdElement.setAttribute("style", "border-width: 0; text-align: left; vertical-align: top;width 10px"); - - localeTableRow.appendChild(tdElement); - tdElement.appendChild(imgElement2); - } - - const parentDivElement = PWM_MAIN.getObject(parentDiv); - parentDivElement.appendChild(localeTableRow); - - { // add a spacer row - const spacerTableRow = document.createElement("tr"); - spacerTableRow.setAttribute("style", "border-width: 0"); - parentDivElement.appendChild(spacerTableRow); - - const spacerTableData = document.createElement("td"); - spacerTableData.setAttribute("style", "border-width: 0"); - spacerTableData.innerHTML = " "; - spacerTableRow.appendChild(spacerTableData); - } - } - - const addLocaleFunction = function(value) { - MultiLocaleTableHandler.writeMultiLocaleSetting(keyName, value, 0, '', regExPattern); - }; - - UILibrary.addAddLocaleButtonRow(parentDiv, keyName, addLocaleFunction, Object.keys(resultValue)); - PWM_VAR['clientSettingCache'][keyName] = resultValue; - - for (const localeName in resultValue) { - const multiValues = resultValue[localeName]; - for (const iteration in multiValues) { - const inputID = "value-" + keyName + "-" + localeName + "-" + iteration; - { - const removeID = inputID + "-remove"; - PWM_MAIN.addEventHandler(removeID, 'click', function () { - MultiLocaleTableHandler.writeMultiLocaleSetting(keyName, localeName, iteration, null, regExPattern); - }); - } - { - const removeID = "div-" + keyName + "-" + localeName + "-remove"; - PWM_MAIN.addEventHandler(removeID, 'click', function () { - MultiLocaleTableHandler.writeMultiLocaleSetting(keyName, localeName, null, null, regExPattern); - }); - } - } - } -}; - -MultiLocaleTableHandler.writeMultiLocaleSetting = function(settingKey, locale, iteration, value) { - if (locale !== null) { - if (PWM_VAR['clientSettingCache'][settingKey][locale] === null) { - PWM_VAR['clientSettingCache'][settingKey][locale] = [ "" ]; - } - - if (iteration === null) { - delete PWM_VAR['clientSettingCache'][settingKey][locale]; - } else { - if (value === null) { - PWM_VAR['clientSettingCache'][settingKey][locale].splice(iteration,1); - } else { - PWM_VAR['clientSettingCache'][settingKey][locale][iteration] = value; - } - } - } - - PWM_CFGEDIT.writeSetting(settingKey, PWM_VAR['clientSettingCache'][settingKey]); - MultiLocaleTableHandler.draw(settingKey); -}; - - - -// -------------------------- change password handler ------------------------------------ - -const ChangePasswordHandler = {}; - -ChangePasswordHandler.init = function(settingKey) { - const parentDiv = 'table_setting_' + settingKey; - const parentDivElement = PWM_MAIN.getObject(parentDiv); - - if (parentDivElement) { - PWM_CFGEDIT.readSetting(settingKey,function(data){ - const hasPassword = !data['isDefault']; - let htmlBody = ''; - if (hasPassword) { - htmlBody += '
    '; - if (localeString !== null && localeString.length > 0) { - tableHtml += localeString; - } - tableHtml += ''; - - tableHtml += ''; - tableHtml += '
    '; - tableHtml += '
    Value stored.
    '; - htmlBody += ''; - } else { - htmlBody += ''; - } - - parentDivElement.innerHTML = htmlBody; - - PWM_MAIN.addEventHandler('button-changePassword-' + settingKey,'click',function(){ - ChangePasswordHandler.popup(settingKey,PWM_SETTINGS['settings'][settingKey]['label']); - }); - - PWM_MAIN.addEventHandler('button-clearPassword-' + settingKey,'click',function(){ - PWM_MAIN.showConfirmDialog({ - text:'Clear password for setting ' + PWM_SETTINGS['settings'][settingKey]['label'] + '?', - okAction:function() { - PWM_CFGEDIT.resetSetting(settingKey,function(){ - ChangePasswordHandler.init(settingKey); - }); - } - }); - }); - }); - } - -}; - -ChangePasswordHandler.popup = function(settingKey,settingName,writeFunction) { - if (!PWM_VAR['clientSettingCache'][settingKey]) { - PWM_VAR['clientSettingCache'][settingKey] = {}; - } - if (!PWM_VAR['clientSettingCache'][settingKey]['settings']) { - PWM_VAR['clientSettingCache'][settingKey]['settings'] = {}; - } - PWM_VAR['clientSettingCache'][settingKey]['settings']['name'] = settingName; - if (writeFunction) { - PWM_VAR['clientSettingCache'][settingKey]['settings']['writeFunction'] = writeFunction; - } else { - PWM_VAR['clientSettingCache'][settingKey]['settings']['writeFunction'] = function(passwordValue){ - ChangePasswordHandler.doChange(settingKey,passwordValue); - } - } - PWM_VAR['clientSettingCache'][settingKey]['settings']['showFields'] = false; - ChangePasswordHandler.clear(settingKey); - ChangePasswordHandler.changePasswordPopup(settingKey); -}; - -ChangePasswordHandler.validatePasswordPopupFields = function(settingKey) { - const password1 = PWM_MAIN.getObject('password1').value; - const password2 = PWM_MAIN.getObject('password2').value; - - let matchStatus = ""; - - const properties = settingKey === undefined || PWM_SETTINGS['settings'][settingKey] === undefined ? {} : PWM_SETTINGS['settings'][settingKey]['properties']; - const minLength = properties && 'Minimum' in properties ? properties['Minimum'] : 1; - - PWM_MAIN.getObject('field-password-length').innerHTML = password1.length; - PWM_MAIN.getObject('button-storePassword').disabled = true; - - if (minLength > 1 && password1.length < minLength) { - PWM_MAIN.addCssClass('field-password-length','invalid-value'); - } else { - PWM_MAIN.removeCssClass('field-password-length','invalid-value'); - if (password2.length > 0) { - - if (password1 === password2) { - matchStatus = "MATCH"; - PWM_MAIN.getObject('button-storePassword').disabled = false; - } else { - matchStatus = "NO_MATCH"; - } - } - } - - ChangePasswordHandler.markConfirmationCheck(matchStatus); -}; - -ChangePasswordHandler.markConfirmationCheck = function(matchStatus) { - if (matchStatus === "MATCH") { - PWM_MAIN.getObject("confirmCheckMark").style.visibility = 'visible'; - PWM_MAIN.getObject("confirmCrossMark").style.visibility = 'hidden'; - PWM_MAIN.getObject("confirmCheckMark").width = '15'; - PWM_MAIN.getObject("confirmCrossMark").width = '0'; - } else if (matchStatus === "NO_MATCH") { - PWM_MAIN.getObject("confirmCheckMark").style.visibility = 'hidden'; - PWM_MAIN.getObject("confirmCrossMark").style.visibility = 'visible'; - PWM_MAIN.getObject("confirmCheckMark").width = '0'; - PWM_MAIN.getObject("confirmCrossMark").width = '15'; - } else { - PWM_MAIN.getObject("confirmCheckMark").style.visibility = 'hidden'; - PWM_MAIN.getObject("confirmCrossMark").style.visibility = 'hidden'; - PWM_MAIN.getObject("confirmCheckMark").width = '0'; - PWM_MAIN.getObject("confirmCrossMark").width = '0'; - } -}; - -ChangePasswordHandler.doChange = function(settingKey, passwordValue) { - PWM_MAIN.showWaitDialog({loadFunction:function(){ - PWM_CFGEDIT.writeSetting(settingKey,passwordValue,function(){ - ChangePasswordHandler.clear(settingKey); - ChangePasswordHandler.init(settingKey); - PWM_MAIN.closeWaitDialog(); - }); - - }}) -}; - -ChangePasswordHandler.clear = function(settingKey) { - PWM_VAR['clientSettingCache'][settingKey]['settings']['p1'] = ''; - PWM_VAR['clientSettingCache'][settingKey]['settings']['p2'] = ''; -}; - -ChangePasswordHandler.generateRandom = function(settingKey) { - const length = PWM_VAR['passwordDialog-randomLength']; - const special = PWM_VAR['passwordDialog-special']; - - if (!PWM_VAR['clientSettingCache'][settingKey]['settings']['showFields']) { - PWM_VAR['clientSettingCache'][settingKey]['settings']['showFields'] = true; - } - - let charMap = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; - if (special) { - charMap += '~`!@#$%^&*()_-+=;:,.[]{}'; - } - const postData = { }; - postData.maxLength = length; - postData.minLength = length; - postData.chars = charMap; - postData.noUser = true; - PWM_MAIN.getObject('button-storePassword').disabled = true; - - const url = PWM_MAIN.addParamToUrl(window.location.href,'processAction','randomPassword'); - const loadFunction = function(data) { - ChangePasswordHandler.changePasswordPopup(settingKey); - PWM_MAIN.getObject('password1').value = data['data']['password']; - PWM_MAIN.getObject('password2').value = ''; - PWM_MAIN.getObject('button-storePassword').disabled = false; - }; - - PWM_MAIN.showWaitDialog({loadFunction:function(){ - PWM_MAIN.ajaxRequest(url,loadFunction,{content:postData}); - }}); -}; - -ChangePasswordHandler.changePasswordPopup = function(settingKey) { - const writeFunction = PWM_VAR['clientSettingCache'][settingKey]['settings']['writeFunction']; - const showFields = PWM_VAR['clientSettingCache'][settingKey]['settings']['showFields']; - const p1 = PWM_VAR['clientSettingCache'][settingKey]['settings']['p1']; - const p2 = PWM_VAR['clientSettingCache'][settingKey]['settings']['p2']; - const properties = settingKey === undefined || PWM_SETTINGS['settings'][settingKey] === undefined ? {} : PWM_SETTINGS['settings'][settingKey]['properties']; - const minLength = properties && 'Minimum' in properties ? properties['Minimum'] : 1; - let randomLength = 'passwordDialog-randomLength' in PWM_VAR ? PWM_VAR['passwordDialog-randomLength'] : 25; - randomLength = randomLength < minLength ? minLength : randomLength; - const special = 'passwordDialog-special' in PWM_VAR ? PWM_VAR['passwordDialog-special'] : false; - - let bodyText = ''; - if (minLength > 1) { - bodyText += 'Minimum Length: ' + minLength + '


    ' - } - bodyText += '' - + '' - + '' - + '' - + '' - + '' - + '
    ' + PWM_MAIN.showString('Field_NewPassword') + '
    '; - - if (showFields) { - bodyText += ''; - } else { - bodyText += ''; - } - - bodyText += '
    ' + PWM_MAIN.showString('Field_ConfirmPassword') + '
    '; - - if (showFields) { - bodyText += ''; - } else { - bodyText += ''; - } - - bodyText += '
    ' - + '' - + '' - + '
    '; - - bodyText += '
    Length: -'; - - bodyText += '

    Generate Random Password
    ' - + '' - + '    Length' - + '    ' - + '


    ' - + '  ' - + '' - + '


    '; - - PWM_MAIN.showDialog({ - title: 'Store Password - ' + PWM_VAR['clientSettingCache'][settingKey]['settings']['name'], - text: bodyText, - showOk: false, - showClose: true, - loadFunction:function(){ - PWM_MAIN.addEventHandler('button-storePassword','click',function() { - const passwordValue = PWM_MAIN.getObject('password1').value; - PWM_MAIN.closeWaitDialog(); - writeFunction(passwordValue); - }); - PWM_MAIN.addEventHandler('button-generateRandom','click',function() { - PWM_VAR['passwordDialog-randomLength'] = PWM_MAIN.getObject('input-randomLength').value; - PWM_VAR['passwordDialog-special'] = PWM_MAIN.getObject('input-special').checked; - ChangePasswordHandler.generateRandom(settingKey); - }); - PWM_MAIN.addEventHandler('password1','input',function(){ - PWM_VAR['clientSettingCache'][settingKey]['settings']['p1'] = PWM_MAIN.getObject('password1').value; - ChangePasswordHandler.validatePasswordPopupFields(settingKey); - PWM_MAIN.getObject('password2').value = ''; - }); - PWM_MAIN.addEventHandler('password2','input',function(){ - PWM_VAR['clientSettingCache'][settingKey]['settings']['p2'] = PWM_MAIN.getObject('password2').value; - ChangePasswordHandler.validatePasswordPopupFields(settingKey); - }); - PWM_MAIN.addEventHandler('show','change',function(){ - PWM_VAR['clientSettingCache'][settingKey]['settings']['showFields'] = PWM_MAIN.getObject('show').checked; - ChangePasswordHandler.changePasswordPopup(settingKey); - }); - PWM_MAIN.getObject('password1').focus(); - ChangePasswordHandler.validatePasswordPopupFields(settingKey); - } - }); -}; - - - -// -------------------------- boolean handler ------------------------------------ - -const BooleanHandler = {}; - -BooleanHandler.init = function(keyName) { - console.log('BooleanHandler init for ' + keyName); - - const parentDiv = 'table_setting_' + keyName; - const parentDivElement = PWM_MAIN.getObject(parentDiv); - - parentDivElement.innerHTML = ''; - - PWM_CFGEDIT.readSetting(keyName,function(data){ - const checkElement = PWM_MAIN.getObject("value_" + keyName); - checkElement.checked = data; - checkElement.disabled = false; - PWM_MAIN.addEventHandler("value_" + keyName, 'change', function(){ - PWM_CFGEDIT.writeSetting(keyName,checkElement.checked); - }); - }); -}; - -BooleanHandler.toggle = function(keyName,widget) { - PWM_CFGEDIT.writeSetting(keyName,widget.checked); -}; - - - -// -------------------------- option list handler ------------------------------------ - -const OptionListHandler = {}; -OptionListHandler.defaultItem = []; - -OptionListHandler.init = function(keyName) { - console.log('OptionListHandler init for ' + keyName); - - const parentDiv = 'table_setting_' + keyName; - const parentDivElement = PWM_MAIN.getObject(parentDiv); - - let htmlBody = ''; - const options = PWM_SETTINGS['settings'][keyName]['options']; - for (const key in options) { - (function (optionKey) { - const buttonID = keyName + "_button_" + optionKey; - htmlBody += ''; - })(key); - } - parentDivElement.innerHTML = htmlBody; - - - PWM_CFGEDIT.readSetting(keyName, function(resultValue) { - PWM_VAR['clientSettingCache'][keyName] = resultValue; - OptionListHandler.draw(keyName); - }); -}; - -OptionListHandler.draw = function(keyName) { - const resultValue = PWM_VAR['clientSettingCache'][keyName]; - const options = PWM_SETTINGS['settings'][keyName]['options']; - for (const key in options) { - (function (optionKey) { - const buttonID = keyName + "_button_" + optionKey; - const checked = PWM_MAIN.JSLibrary.arrayContains(resultValue,optionKey) - PWM_MAIN.getObject(buttonID).checked = checked; - PWM_MAIN.getObject(buttonID).disabled = false; - PWM_MAIN.addEventHandler(buttonID,'change',function(){ - OptionListHandler.toggle(keyName,optionKey); - }); - })(key); - } -}; - -OptionListHandler.toggle = function(keyName,optionKey) { - const resultValue = PWM_VAR['clientSettingCache'][keyName]; - const checked = PWM_MAIN.JSLibrary.arrayContains(resultValue,optionKey) - if (checked) { - PWM_MAIN.JSLibrary.removeFromArray(resultValue, optionKey); - } else { - resultValue.push(optionKey); - } - PWM_CFGEDIT.writeSetting(keyName, resultValue); -}; - -// -------------------------- numeric value handler ------------------------------------ - -const NumericValueHandler = {}; -NumericValueHandler.init = function(settingKey) { - NumericValueHandler.impl(settingKey, 'number', 0, 100); -}; - -NumericValueHandler.impl = function(settingKey, type, defaultMin, defaultMax) { - const parentDiv = 'table_setting_' + settingKey; - const parentDivElement = PWM_MAIN.getObject(parentDiv); - const properties = PWM_SETTINGS['settings'][settingKey]['properties']; - const min = 'Minimum' in properties ? parseInt(properties['Minimum']) : defaultMin; - const max = 'Maximum' in properties ? parseInt(properties['Maximum']) : defaultMax; - - let htmlBody = ''; - if (type === 'number') { - htmlBody += '' + min + ' - ' + max + ''; - } else if (type === 'duration') { - htmlBody += '' + PWM_MAIN.showString('Display_Seconds') + '' - htmlBody += ''; - } - - parentDivElement.innerHTML = htmlBody; - - PWM_CFGEDIT.readSetting(settingKey,function(data){ - PWM_MAIN.getObject('value_' + settingKey).value = data; - UILibrary.manageNumericInput('value_' + settingKey,function(value){ - PWM_VAR['clientSettingCache'][settingKey] = value; - PWM_CFGEDIT.writeSetting(settingKey, value); - NumericValueHandler.updateDurationDisplay(settingKey,value); - }); - - PWM_MAIN.addEventHandler('value_' + settingKey,'mousewheel',function(e){ e.blur(); }); - NumericValueHandler.updateDurationDisplay(settingKey,data); - }); -}; - -NumericValueHandler.updateDurationDisplay = function(settingKey, numberValue) { - numberValue = parseInt(numberValue); - const displayElement = PWM_MAIN.getObject('display-' + settingKey + '-duration'); - if (displayElement) { - displayElement.innerHTML = (numberValue && numberValue !== 0) - ? PWM_MAIN.convertSecondsToDisplayTimeDuration(numberValue, true) - : ''; - } -}; - - - -// -------------------------- duration value --------------------------- - -const DurationValueHandler = {}; -DurationValueHandler.init = function(settingKey) { - NumericValueHandler.impl(settingKey, 'duration', -1, 365 * 24 * 60 * 60, 1 ); -}; - -// -------------------------- numeric array value handler ------------------------------------ - -const NumericArrayValueHandler = {}; -NumericArrayValueHandler.init = function(settingKey) { - NumericArrayValueHandler.impl(settingKey, 'number', 0, 100); -}; - -NumericArrayValueHandler.impl = function(settingKey, type) { - PWM_CFGEDIT.readSetting(settingKey,function(data){ - PWM_VAR['clientSettingCache'][settingKey] = data; - NumericArrayValueHandler.draw(settingKey, type); - }); -}; - -NumericArrayValueHandler.draw = function(settingKey, type) { - const resultValue = PWM_VAR['clientSettingCache'][settingKey]; - - const parentDiv = 'table_setting_' + settingKey; - const parentDivElement = PWM_MAIN.getObject(parentDiv); - const properties = PWM_SETTINGS['settings'][settingKey]['properties']; - const min = 'Minimum' in properties ? parseInt(properties['Minimum']) : 1; - const max = 'Maximum' in properties ? parseInt(properties['Maximum']) : 365 * 24 * 60 * 60; - const minValues = 'Minimum_Values' in properties ? parseInt(properties['Minimum_Values']) : 1; - const maxValues = 'Maximum_Values' in properties ? parseInt(properties['Maximum_Values']) : 10; - - let htmlBody = ''; - for (const iteration in resultValue) { - (function(rowKey) { - const id = settingKey+ "-" + rowKey; - - htmlBody += ''; - - }(iteration)); - } - - htmlBody += '
    '; - if (type === 'number') { - htmlBody += '' + min + ' - ' + max + ''; - } else if (type === 'duration') { - htmlBody += '' + PWM_MAIN.showString('Display_Seconds') + '' - htmlBody += ''; - } - htmlBody += ''; - if ( resultValue.length > minValues ) { - htmlBody += ''; - } - htmlBody += '
    '; - if ( resultValue.length < maxValues ) { - htmlBody += '
    '; - } - - parentDivElement.innerHTML = htmlBody; - - const addListeners = function() { - for (const iteration in resultValue) { - (function(rowKey) { - const id = settingKey+ "-" + rowKey; - const readValue = resultValue[rowKey]; - PWM_MAIN.getObject('value-' + id).value = readValue; - PWM_MAIN.getObject('value-' + id).disabled = false; - - UILibrary.manageNumericInput('value-' + id,function(value){ - PWM_VAR['clientSettingCache'][settingKey][rowKey] = value; - PWM_CFGEDIT.writeSetting(settingKey, PWM_VAR['clientSettingCache'][settingKey]); - NumericValueHandler.updateDurationDisplay(id, value); - }); - - PWM_MAIN.addEventHandler('value-' + settingKey,'mousewheel',function(e){ e.blur(); }); - NumericValueHandler.updateDurationDisplay(id, readValue); - - PWM_MAIN.addEventHandler('button-' + id + '-delete','click',function(){ - PWM_MAIN.showConfirmDialog({okAction:function(){ - PWM_VAR['clientSettingCache'][settingKey].splice(rowKey, 1); - PWM_CFGEDIT.writeSetting(settingKey, PWM_VAR['clientSettingCache'][settingKey],function(){ - NumericArrayValueHandler.draw(settingKey, type); - }); - }}); - }); - - }(iteration)); - } - - PWM_MAIN.addEventHandler('button-addValue-' + settingKey,'click',function () { - PWM_VAR['clientSettingCache'][settingKey].push(86400); - PWM_CFGEDIT.writeSetting(settingKey, PWM_VAR['clientSettingCache'][settingKey],function(){ - NumericArrayValueHandler.draw(settingKey, type); - }); - }); - }; - - addListeners(); -}; - - -// -------------------------- duration array value --------------------------- - -const DurationArrayValueHandler = {}; -DurationArrayValueHandler.init = function(settingKey) { - NumericArrayValueHandler.impl(settingKey, 'duration'); -}; - - -// -------------------------- string value handler ------------------------------------ - -const StringValueHandler = {}; - -StringValueHandler.init = function(settingKey) { - const parentDiv = 'table_setting_' + settingKey; - const parentDivElement = PWM_MAIN.getObject(parentDiv); - const settingData = PWM_SETTINGS['settings'][settingKey]; - const textAreaMode = 'TEXT_AREA' === settingData['syntax']; - PWM_CFGEDIT.readSetting(settingKey,function(data) { - const inputID = settingKey; - let bodyHtml = ''; - const value = data; - const cssClass = textAreaMode ? 'eulaText' : 'configStringPanel'; - if (value && value.length > 0) { - bodyHtml += ''; - bodyHtml += ''; - if (!settingData['required']) { - bodyHtml += ''; - } - - bodyHtml += '
    '; - bodyHtml += '
    '; - } else { - bodyHtml += ''; - } - - parentDivElement.innerHTML = bodyHtml; - UILibrary.addTextValueToElement('panel-' + inputID, value); - - PWM_MAIN.addEventHandler('button-' + inputID + '-delete','click',function(){ - PWM_CFGEDIT.writeSetting(settingKey,'',function(){ - StringValueHandler.init(settingKey); - }); - }); - - const isLdapDN = PWM_MAIN.JSLibrary.arrayContains(PWM_SETTINGS['settings'][settingKey]['flags'], 'ldapDnSyntax'); - const editor = function(){ - const writeBackFunc = function(value){ - PWM_CFGEDIT.writeSetting(settingKey,value,function(){ - StringValueHandler.init(settingKey); - }); - }; - if (isLdapDN) { - const ldapProfile = PWM_CFGEDIT.readCurrentProfile(); - UILibrary.editLdapDN(writeBackFunc,{currentDN: value, profile: ldapProfile}); - } else { - UILibrary.stringEditorDialog({ - title:'Edit Value - ' + settingData['label'], - textarea:(textAreaMode), - regex:'pattern' in settingData ? settingData['pattern'] : '.+', - placeholder:settingData['placeholder'], - value:value, - completeFunction:writeBackFunc - }); - } - }; - - PWM_MAIN.addEventHandler('button-' + settingKey,'click',function(){ - editor(); - }); - PWM_MAIN.addEventHandler('button-add-' + settingKey,'click',function(){ - editor(); - }); - PWM_MAIN.addEventHandler('panel-' + settingKey,'click',function(){ - editor(); - }); - }); -}; - - -// -------------------------- text area handler ------------------------------------ - -const TextAreaValueHandler = {}; -TextAreaValueHandler.init = function(settingKey) { - StringValueHandler.init(settingKey); -}; - - -// -------------------------- select value handler ------------------------------------ - -const SelectValueHandler = {}; -SelectValueHandler.init = function(settingKey) { - const parentDiv = 'table_setting_' + settingKey; - const parentDivElement = PWM_MAIN.getObject(parentDiv); - const allowUserInput = PWM_MAIN.JSLibrary.arrayContains(PWM_SETTINGS['settings'][settingKey]['flags'],'Select_AllowUserInput'); - - let htmlBody = ''; - - if (allowUserInput) { - htmlBody += ''; - - } - - parentDivElement.innerHTML = htmlBody; - - PWM_MAIN.addEventHandler('setting_' + settingKey,'change',function(){ - const settingElement = PWM_MAIN.getObject('setting_' + settingKey); - const changeFunction = function(){ - const selectedValue = settingElement.options[settingElement.selectedIndex].value; - PWM_CFGEDIT.writeSetting(settingKey,selectedValue); - settingElement.setAttribute('previousValue',settingElement.selectedIndex); - }; - if ('ModificationWarning' in PWM_SETTINGS['settings'][settingKey]['properties']) { - PWM_MAIN.showConfirmDialog({ - text:PWM_SETTINGS['settings'][settingKey]['properties']['ModificationWarning'], - cancelAction: function(){ - settingElement.selectedIndex = settingElement.getAttribute('previousValue'); - PWM_MAIN.closeWaitDialog(); - }, - okAction:changeFunction - }); - } else { - changeFunction(); - } - }); - - PWM_MAIN.addEventHandler('button_selectOverride_' + settingKey,'click',function() { - const changeFunction = function(value){ - PWM_CFGEDIT.writeSetting(settingKey,value,function(){ - SelectValueHandler.init(settingKey); - }); - }; - UILibrary.stringEditorDialog({ - title:'Set Value', - value:'', - completeFunction:function(value){ - changeFunction(value); - } - }); - }); - - - PWM_CFGEDIT.readSetting(settingKey, function(dataValue) { - const settingElement = PWM_MAIN.getObject('setting_' + settingKey); - - let optionsHtml = ''; - const options = PWM_SETTINGS['settings'][settingKey]['options']; - - if (dataValue && dataValue.length > 0 && !(dataValue in options)) { - optionsHtml += '' - } - for (const option in options) { - const optionValue = options[option]; - optionsHtml += '' - } - settingElement.innerHTML = optionsHtml; - settingElement.value = dataValue; - settingElement.disabled = false; - settingElement.setAttribute('previousValue',settingElement.selectedIndex); - }); -}; - - -// -------------------------- x509 setting handler ------------------------------------ - -const X509CertificateHandler = {}; - -X509CertificateHandler.init = function(keyName) { - PWM_CFGEDIT.readSetting(keyName, function(resultValue) { - PWM_VAR['clientSettingCache'][keyName] = resultValue; - X509CertificateHandler.draw(keyName); - }); -}; - -X509CertificateHandler.certificateToHtml = function(certificate, keyName, id) { - let htmlBody = ''; - htmlBody += '
    '; - htmlBody += ''; - htmlBody += ''; - htmlBody += ''; - htmlBody += ''; - htmlBody += ''; - htmlBody += ''; - htmlBody += ''; - htmlBody += ''; - htmlBody += ''; - htmlBody += '
    Certificate ' + id + ' (detail)
    Subject
    ' + certificate['subject'] + '
    Issuer
    ' + certificate['issuer'] + '
    Serial
    ' + certificate['serial'] + '
    Issue Date' + certificate['issueDate'] + '
    Expire Date' + certificate['expireDate'] + '
    MD5 Hash
    ' + certificate['md5Hash'] + '
    SHA1 Hash
    ' + certificate['sha1Hash'] + '
    SHA512 Hash
    ' + certificate['sha512Hash'] + '
    '; - return htmlBody; -}; - -X509CertificateHandler.certHtmlActions = function(certificate, keyName, id) { - PWM_MAIN.TimestampHandler.initElement(PWM_MAIN.getObject('certTimestamp-issue-' + keyName + '-' + id)); - PWM_MAIN.TimestampHandler.initElement(PWM_MAIN.getObject('certTimestamp-expire-' + keyName + '-' + id)); - PWM_MAIN.addEventHandler('certTimestamp-detail-' + keyName + '-' + id,'click',function(){ - PWM_MAIN.showDialog({ - title: 'Detail - ' + PWM_SETTINGS['settings'][keyName]['label'] + ' - Certificate ' + id, - text: '
    ' + certificate['detail'] + '
    ', - dialogClass: 'wide', - showClose: true, - showOk: false - }); - }); -}; - -X509CertificateHandler.draw = function(keyName) { - const parentDiv = 'table_setting_' + keyName; - const parentDivElement = PWM_MAIN.getObject(parentDiv); - - const resultValue = PWM_VAR['clientSettingCache'][keyName]; - - let htmlBody = '
    '; - for (const certCounter in resultValue) { - (function (counter) { - const certificate = resultValue[counter]; - htmlBody += X509CertificateHandler.certificateToHtml(certificate, keyName, counter); - })(certCounter); - } - htmlBody += '
    '; - - if (!PWM_MAIN.JSLibrary.isEmpty(resultValue)) { - htmlBody += '' - } - htmlBody += '' - parentDivElement.innerHTML = htmlBody; - - for (const certCounter in resultValue) { - (function (counter) { - const certificate = resultValue[counter]; - X509CertificateHandler.certHtmlActions(certificate, keyName, counter) - })(certCounter); - } - - if (!PWM_MAIN.JSLibrary.isEmpty(resultValue)) { - PWM_MAIN.addEventHandler(keyName + '_ClearButton','click',function(){ - handleResetClick(keyName); - }); - } - const importClassname = PWM_SETTINGS['settings'][keyName]['properties']['Cert_ImportHandler']; - PWM_MAIN.addEventHandler(keyName + '_AutoImportButton','click',function(){ - PWM_CFGEDIT.executeSettingFunction(keyName,importClassname); - }); -}; - - -// -------------------------- verification method handler ------------------------------------ - -const VerificationMethodHandler = {}; -VerificationMethodHandler.init = function(settingKey) { - PWM_CFGEDIT.readSetting(settingKey, function(resultValue) { - PWM_VAR['clientSettingCache'][settingKey] = resultValue; - VerificationMethodHandler.draw(settingKey); - }); -}; - -VerificationMethodHandler.draw = function(settingKey) { - const settingOptions = PWM_SETTINGS['settings'][settingKey]['options']; - const parentDiv = 'table_setting_' + settingKey; - const parentDivElement = PWM_MAIN.getObject(parentDiv); - - const showMinOptional = !PWM_MAIN.JSLibrary.arrayContains(PWM_SETTINGS['settings'][settingKey]['flags'],'Verification_HideMinimumOptional'); - - let htmlBody = ''; - for (const method in settingOptions) { - const id = settingKey + '-' + method; - const label = settingOptions[method]; - const title = PWM_CONFIG.showString('VerificationMethodDetail_' + method); - htmlBody += ''; - htmlBody += ''; - } - htmlBody += '
    ' + label + '
    '; - - if (showMinOptional) { - htmlBody += '
    '; - } - parentDivElement.innerHTML = htmlBody; - for (const method in settingOptions) { - const id = settingKey + '-' + method; - PWM_MAIN.addEventHandler('input-range-' + id,'change',function(){ - VerificationMethodHandler.updateLabels(settingKey); - VerificationMethodHandler.write(settingKey); - }); - - const enabledState = PWM_VAR['clientSettingCache'][settingKey]['methodSettings'][method] - && PWM_VAR['clientSettingCache'][settingKey]['methodSettings'][method]['enabledState']; - - let numberValue = 0; - if (enabledState) { - switch (enabledState) { - case 'disabled': - numberValue = 0; - break; - case 'optional': - numberValue = 1; - break; - case 'required': - numberValue = 2; - break; - default: - alert('unknown value = VerificationMethodHandler.draw = ' + method); - } - } - PWM_MAIN.getObject('input-range-' + id).value = numberValue; - } - if (showMinOptional) { - PWM_MAIN.getObject('input-minOptional-' + settingKey).value = PWM_VAR['clientSettingCache'][settingKey]['minOptionalRequired']; - PWM_MAIN.addEventHandler('input-minOptional-' + settingKey, 'input', function () { - VerificationMethodHandler.updateLabels(settingKey); - VerificationMethodHandler.write(settingKey); - }); - } - - VerificationMethodHandler.updateLabels(settingKey); -}; - -VerificationMethodHandler.write = function(settingKey) { - const showMinOptional = !PWM_MAIN.JSLibrary.arrayContains(PWM_SETTINGS['settings'][settingKey]['flags'],'Verification_HideMinimumOptional'); - - const settingOptions = PWM_SETTINGS['settings'][settingKey]['options']; - const values = {}; - values['minOptionalRequired'] = showMinOptional ? Number(PWM_MAIN.getObject('input-minOptional-' + settingKey).value) : 0; - values['methodSettings'] = {}; - for (const method in settingOptions) { - const id = settingKey + '-' + method; - const value = Number(PWM_MAIN.getObject('input-range-' + id).value); - - let enabledState = 'disabled'; - switch (value) { - case 0: - enabledState = 'disabled'; - break; - case 1: - enabledState = 'optional'; - break; - case 2: - enabledState = 'required'; - break; - } - values['methodSettings'][method] = {}; - values['methodSettings'][method]['enabledState'] = enabledState; - } - PWM_CFGEDIT.writeSetting(settingKey, values); -}; - -VerificationMethodHandler.updateLabels = function(settingKey) { - const settingOptions = PWM_SETTINGS['settings'][settingKey]['options']; - let optionalCount = 0; - for (const method in settingOptions) { - const id = settingKey + '-' + method; - const value = Number(PWM_MAIN.getObject('input-range-' + id).value); - let label = ''; - switch (value) { - case 0: - label = 'Not Used'; - break; - case 1: - label = 'Optional'; - optionalCount++; - break; - case 2: - label = 'Required'; - break; - default: - alert('unknown value = VerificationMethodHandler.updateLabels'); - } - PWM_MAIN.getObject('label-' + id).innerHTML = label; - } - const showMinOptional = !PWM_MAIN.JSLibrary.arrayContains(PWM_SETTINGS['settings'][settingKey]['flags'],'Verification_HideMinimumOptional'); - if (showMinOptional) { - const minOptionalInput = PWM_MAIN.getObject('input-minOptional-' + settingKey); - minOptionalInput.max = optionalCount; - const currentMax = Number(minOptionalInput.value); - if (currentMax > optionalCount) { - minOptionalInput.value = optionalCount.toString(); - } - } -}; - - -// -------------------------- file setting handler ------------------------------------ - -const FileValueHandler = {}; - -FileValueHandler.init = function(keyName) { - PWM_CFGEDIT.readSetting(keyName, function(resultValue) { - PWM_VAR['clientSettingCache'][keyName] = resultValue; - FileValueHandler.draw(keyName); - }); -}; - -FileValueHandler.draw = function(keyName) { - const parentDiv = 'table_setting_' + keyName; - const parentDivElement = PWM_MAIN.getObject(parentDiv); - - const resultValue = PWM_VAR['clientSettingCache'][keyName]; - - let htmlBody = ''; - - if (PWM_MAIN.JSLibrary.isEmpty(resultValue)) { - htmlBody = '

    No File Present

    '; - } else { - for (const fileCounter in resultValue) { - (function (counter) { - const fileInfo = resultValue[counter]; - htmlBody += ''; - htmlBody += ''; - htmlBody += ''; - htmlBody += ''; - htmlBody += ''; - htmlBody += '
    File' + '
    Name' + fileInfo['name'] + '
    Size' + fileInfo['size'] + '
    SHA512 checksum' + fileInfo['sha512sum'] + '
    ' - })(fileCounter); - } - } - - if (PWM_MAIN.JSLibrary.isEmpty(resultValue)) { - htmlBody += ''; - } else { - htmlBody += ''; - } - - parentDivElement.innerHTML = htmlBody; - - PWM_MAIN.addEventHandler('button-uploadFile-' + keyName,'click',function(){ - FileValueHandler.uploadFile(keyName); - }); - - PWM_MAIN.addEventHandler('button-removeFile-' + keyName,'click',function(){ - PWM_MAIN.showConfirmDialog({text:'Are you sure you want to remove the currently stored file?',okAction:function(){ - PWM_MAIN.showWaitDialog({loadFunction:function(){ - PWM_CFGEDIT.resetSetting(keyName,function(){ - FileValueHandler.init(keyName); - PWM_MAIN.closeWaitDialog(); - }); - }}); - }}); - }); -}; - -FileValueHandler.uploadFile = function(keyName) { - let url = PWM_MAIN.addParamToUrl(window.location.pathname, 'processAction','uploadFile'); - url = PWM_MAIN.addParamToUrl(url, 'key',keyName); - - const options = {}; - options['url'] = url; - options['nextFunction'] = function() { - PWM_MAIN.showWaitDialog({loadFunction:function(){ - FileValueHandler.init(keyName); - PWM_MAIN.closeWaitDialog(); - }}); - }; - UILibrary.uploadFileDialog(options); -}; - - -// -------------------------- x509 setting handler ------------------------------------ - -const PrivateKeyHandler = {}; - -PrivateKeyHandler.init = function(keyName) { - PWM_CFGEDIT.readSetting(keyName, function(resultValue) { - PWM_VAR['clientSettingCache'][keyName] = resultValue; - PrivateKeyHandler.draw(keyName); - }); -}; - -PrivateKeyHandler.draw = function(keyName) { - const parentDiv = 'table_setting_' + keyName; - const parentDivElement = PWM_MAIN.getObject(parentDiv); - - const resultValue = PWM_VAR['clientSettingCache'][keyName]; - - let htmlBody = '
    '; - - const hasValue = resultValue !== undefined && 'key' in resultValue; - - if (hasValue) { - const certificates = resultValue['certificates']; - for (const certCounter in certificates) { - (function (counter) { - const certificate = certificates[counter]; - htmlBody += X509CertificateHandler.certificateToHtml(certificate, keyName, counter); - })(certCounter); - } - htmlBody += '
    '; - - const key = resultValue['key']; - htmlBody += '
    '; - htmlBody += ''; - htmlBody += ''; - htmlBody += ''; - htmlBody += '
    Key
    Format
    ' + key['format'] + '
    Algorithm
    ' + key['algorithm'] + '
    '; - htmlBody += '' - } else { - htmlBody += '
    No Key Present

    '; - } - - if (!hasValue) { - htmlBody += '' - } - parentDivElement.innerHTML = htmlBody; - - if (hasValue) { - const certificates = resultValue['certificates']; - for (const certCounter in certificates) { - (function (counter) { - const certificate = certificates[counter]; - X509CertificateHandler.certHtmlActions(certificate, keyName, counter); - })(certCounter); - } - } - - if (!PWM_MAIN.JSLibrary.isEmpty(resultValue)) { - PWM_MAIN.addEventHandler(keyName + '_ClearButton','click',function(){ - handleResetClick(keyName); - }); - } - PWM_MAIN.addEventHandler(keyName + '_UploadButton','click',function(){ - const options = {}; - let url = PWM_MAIN.addParamToUrl(window.location.href,'processAction','uploadFile'); - url = PWM_MAIN.addParamToUrl(url, 'key', keyName); - options['url'] = url; - - let text = '
    '; - text += ''; - text += ''; - text += ''; - text += '
    File Format
    Password
    Alias
    Alias only required if file has multiple aliases
    '; - options['text'] = text; - - const urlUpdateFunction = function(url) { - const formatSelect = PWM_MAIN.getObject('input-certificateUpload-format'); - url = PWM_MAIN.addParamToUrl(url,'format',formatSelect.options[formatSelect.selectedIndex].value); - url = PWM_MAIN.addParamToUrl(url,'password',PWM_MAIN.getObject('input-certificateUpload-password').value); - url = PWM_MAIN.addParamToUrl(url,'alias',PWM_MAIN.getObject('input-certificateUpload-alias').value); - return url; - }; - options['title'] = 'Import Private Key & Certificate'; - options['urlUpdateFunction'] = urlUpdateFunction; - UILibrary.uploadFileDialog(options); - }); -}; - - -//--------- named secret handler --- -const NamedSecretHandler = {}; - -NamedSecretHandler.init = function(settingKey) { - const parentDiv = 'table_setting_' + settingKey; - const parentDivElement = PWM_MAIN.getObject(parentDiv); - - if (parentDivElement) { - PWM_CFGEDIT.readSetting(settingKey,function(data){ - PWM_VAR['clientSettingCache'][settingKey] = data; - let htmlBody = ''; - htmlBody += ''; - let rowCounter = 0; - for (const key in data) { - const id = settingKey + '_' + key; - htmlBody += ''; - htmlBody += ''; - htmlBody += ''; - htmlBody += ''; - rowCounter++; - } - - if (rowCounter < 1) { - htmlBody += ''; - } - - htmlBody += '
    ' + key + 'Stored Value
    No values.
    '; - - htmlBody += ''; - parentDivElement.innerHTML = htmlBody; - - PWM_MAIN.addEventHandler('button-addPassword-' + settingKey,'click',function(){ - NamedSecretHandler.addPassword(settingKey); - }); - - for (const key in data) { - (function (loopKey) { - const id = settingKey + '_' + loopKey; - PWM_MAIN.addEventHandler('button-deleteRow-' + id,'click',function(){ - NamedSecretHandler.deletePassword(settingKey, loopKey); - }); - PWM_MAIN.addEventHandler('button-usage-' + id,'click',function(){ - NamedSecretHandler.usagePopup(settingKey, loopKey); - }); - })(key); - } - }); - } -}; - -NamedSecretHandler.usagePopup = function(settingKey, key) { - const titleText = PWM_SETTINGS['settings'][settingKey]['label'] + ' - Usage - ' + key ; - const options = PWM_SETTINGS['settings'][settingKey]['options']; - const currentValues = PWM_VAR['clientSettingCache'][settingKey]; - let html = ''; - for (const loopKey in options) { - (function (optionKey) { - html += ''; - })(loopKey); - } - html += '
    '; - const buttonID = key + "_usage_button_" + optionKey; - html += ''; - html += '
    '; - const loadFunction = function () { - for (const loopKey in options) { - (function (optionKey) { - const buttonID = key + "_usage_button_" + optionKey; - const checked = PWM_MAIN.JSLibrary.arrayContains(currentValues[key]['usage'],optionKey); - PWM_MAIN.getObject(buttonID).checked = checked; - PWM_MAIN.addEventHandler(buttonID,'click',function(){ - const nowChecked = PWM_MAIN.getObject(buttonID).checked; - if (nowChecked) { - currentValues[key]['usage'].push(optionKey); - } else { - PWM_MAIN.JSLibrary.removeFromArray(currentValues[key]['usage'], optionKey); - } - }); - })(loopKey); - } - }; - const okFunction = function() { - const postWriteFunction = function() { - NamedSecretHandler.init(settingKey); - }; - PWM_CFGEDIT.writeSetting(settingKey, currentValues, postWriteFunction); - }; - PWM_MAIN.showDialog({title:titleText,text:html,loadFunction:loadFunction,okAction:okFunction}); -}; - -NamedSecretHandler.addPassword = function(settingKey) { - const titleText = PWM_SETTINGS['settings'][settingKey]['label'] + ' - Name'; - const stringEditorFinishFunc = function(nameValue) { - const currentValues = PWM_VAR['clientSettingCache'][settingKey]; - if (nameValue in currentValues) {; - const errorTxt = '"' + nameValue + '" already exists.'; - PWM_MAIN.showErrorDialog(errorTxt); - return; - } - const pwDialogOptions = {}; - pwDialogOptions['title'] = PWM_SETTINGS['settings'][settingKey]['label'] + ' - Password'; - pwDialogOptions['showRandomGenerator'] = true; - pwDialogOptions['showValues'] = true; - pwDialogOptions['writeFunction'] = function(pwValue) { - currentValues[nameValue] = {}; - currentValues[nameValue]['password'] = pwValue; - currentValues[nameValue]['usage'] = []; - - const postWriteFunction = function() { - NamedSecretHandler.init(settingKey); - }; - - PWM_CFGEDIT.writeSetting(settingKey, currentValues, postWriteFunction); - }; - UILibrary.passwordDialogPopup(pwDialogOptions) - }; - const instructions = 'Please enter the name for the new password.'; - UILibrary.stringEditorDialog({ - title: titleText, - regex: '[a-zA-Z]{2,20}', - instructions: instructions, - completeFunction: stringEditorFinishFunc - }); -}; - - -NamedSecretHandler.deletePassword = function(settingKey, key) { - PWM_MAIN.showConfirmDialog({ - text:'Delete named password ' + key + '?', - okAction:function() { - const currentValues = PWM_VAR['clientSettingCache'][settingKey]; - delete currentValues[key]; - - const postWriteFunction = function() { - NamedSecretHandler.init(settingKey); - }; - PWM_CFGEDIT.writeSetting(settingKey, currentValues, postWriteFunction); - } - }); - -}; - - diff --git a/webapp/src/main/webapp/public/resources/js/configeditor.js b/webapp/src/main/webapp/public/resources/js/configeditor.js index eef3754d20..e69de29bb2 100644 --- a/webapp/src/main/webapp/public/resources/js/configeditor.js +++ b/webapp/src/main/webapp/public/resources/js/configeditor.js @@ -1,1391 +0,0 @@ -/* - * Password Management Servlets (PWM) - * http://www.pwm-project.org - * - * Copyright (c) 2006-2009 Novell, Inc. - * Copyright (c) 2009-2021 The PWM Project - * - * 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. - */ - -"use strict"; - -var PWM_CFGEDIT = PWM_CFGEDIT || {}; -var PWM_CONFIG = PWM_CONFIG || {}; -var PWM_MAIN = PWM_MAIN || {}; -var PWM_VAR = PWM_VAR || {}; -var PWM_SETTINGS = PWM_SETTINGS || {}; - -PWM_VAR['outstandingOperations'] = 0; -PWM_VAR['skippedSettingCount'] = 0; - -PWM_CFGEDIT.syntaxFunctionMap = { - FORM: function (settingKey) { - FormTableHandler.init(settingKey, {}); - }, - OPTIONLIST: function (settingKey) { - OptionListHandler.init(settingKey); - }, - CUSTOMLINKS: function (settingKey) { - CustomLinkHandler.init(settingKey, {}); - }, - EMAIL: function (settingKey) { - EmailTableHandler.init(settingKey); - }, - ACTION: function (settingKey) { - ActionHandler.init(settingKey); - }, - PASSWORD: function (settingKey) { - ChangePasswordHandler.init(settingKey); - }, - NAMED_SECRET: function (settingKey) { - NamedSecretHandler.init(settingKey); - }, - NUMERIC: function (settingKey) { - NumericValueHandler.init(settingKey); - }, - DURATION: function (settingKey) { - DurationValueHandler.init(settingKey); - }, - DURATION_ARRAY: function (settingKey) { - DurationArrayValueHandler.init(settingKey); - }, - STRING: function (settingKey) { - StringValueHandler.init(settingKey); - }, - TEXT_AREA: function (settingKey) { - TextAreaValueHandler.init(settingKey) - }, - SELECT: function (settingKey) { - SelectValueHandler.init(settingKey); - }, - BOOLEAN: function (settingKey) { - BooleanHandler.init(settingKey); - }, - LOCALIZED_STRING_ARRAY: function (settingKey) { - MultiLocaleTableHandler.initMultiLocaleTable(settingKey); - }, - STRING_ARRAY: function (settingKey) { - StringArrayValueHandler.init(settingKey); - }, - PROFILE: function (settingKey) { - StringArrayValueHandler.init(settingKey); - }, - DOMAIN: function (settingKey) { - StringArrayValueHandler.init(settingKey); - }, - LOCALIZED_STRING: function (settingKey) { - LocalizedStringValueHandler.init(settingKey); - }, - LOCALIZED_TEXT_AREA: function (settingKey) { - LocalizedStringValueHandler.init(settingKey); - }, - CHALLENGE: function (settingKey) { - ChallengeSettingHandler.init(settingKey); - }, - X509CERT: function (settingKey) { - X509CertificateHandler.init(settingKey); - }, - PRIVATE_KEY: function (settingKey) { - PrivateKeyHandler.init(settingKey); - }, - FILE: function (settingKey) { - FileValueHandler.init(settingKey); - }, - VERIFICATION_METHOD: function (settingKey) { - VerificationMethodHandler.init(settingKey); - }, - REMOTE_WEB_SERVICE: function (settingKey) { - RemoteWebServiceHandler.init(settingKey); - }, - USER_PERMISSION: function (settingKey) { - UserPermissionHandler.init(settingKey); - } -}; - - -PWM_CFGEDIT.readSetting = function(keyName, valueWriter) { - const modifiedOnly = PWM_CFGEDIT.readNavigationFilters()['modifiedSettingsOnly']; - const maxLevel = parseInt(PWM_CFGEDIT.readNavigationFilters()['level']); - PWM_VAR['outstandingOperations']++; - PWM_CFGEDIT.handleWorkingIcon(); - let url = PWM_MAIN.addParamToUrl(window.location.pathname, 'processAction','readSetting'); - url = PWM_MAIN.addParamToUrl(url, 'key', keyName); - if (PWM_CFGEDIT.readCurrentProfile()) { - url = PWM_MAIN.addParamToUrl(url, 'profile', PWM_CFGEDIT.readCurrentProfile()); - } - const loadFunction = function(data) { - PWM_VAR['outstandingOperations']--; - PWM_CFGEDIT.handleWorkingIcon(); - console.log('read data for setting ' + keyName); - const resultValue = data['data']['value']; - const isDefault = data['data']['isDefault']; - const settingLevel = (PWM_SETTINGS['settings'][keyName] && PWM_SETTINGS['settings'][keyName]['level']) - ? PWM_SETTINGS['settings'][keyName]['level'] - : 0; - - const showSetting = (PWM_SETTINGS['settings'][keyName] && PWM_SETTINGS['settings'][keyName]['syntax'] === 'PROFILE') || (!modifiedOnly || !isDefault) && (maxLevel < 0 || settingLevel <= maxLevel ); - if (showSetting) { - valueWriter(resultValue); - PWM_MAIN.removeCssClass('outline_' + keyName,'nodisplay'); - PWM_CFGEDIT.updateSettingDisplay(keyName, isDefault); - PWM_CFGEDIT.updateLastModifiedInfo(keyName, data); - } else { - PWM_MAIN.addCssClass('outline_' + keyName,'nodisplay'); - PWM_VAR['skippedSettingCount']++; - if (PWM_VAR['skippedSettingCount'] > 0 && PWM_MAIN.getObject('panel-skippedSettingInfo')) { - PWM_MAIN.getObject('panel-skippedSettingInfo').innerHTML = "" + PWM_VAR['skippedSettingCount'] + " items are not shown due to filter settings." - } - } - }; - const errorFunction = function(error) { - PWM_VAR['outstandingOperations']--; - PWM_CFGEDIT.handleWorkingIcon(); - PWM_MAIN.showDialog({title:PWM_MAIN.showString('Title_Error'),text:"Unable to communicate with server. Please refresh page."}); - console.log("error loading " + keyName + ", reason: " + error); - }; - PWM_MAIN.ajaxRequest(url,loadFunction,{errorFunction:errorFunction}); -}; - -PWM_CFGEDIT.updateLastModifiedInfo = function(keyName, data) { - if (PWM_MAIN.getObject('panel-' + keyName + '-modifyTime')) { - if (data['data']['modifyTime']) { - PWM_MAIN.getObject('panel-' + keyName + '-modifyTime').innerHTML = 'Last Modified ' - + '' + data['data']['modifyTime'] + ''; - PWM_MAIN.TimestampHandler.initElement(PWM_MAIN.getObject('panel-' + keyName + '-modifyTimestamp')); - } else { - PWM_MAIN.getObject('panel-' + keyName + '-modifyTime').innerHTML = ''; - } - } - if (PWM_MAIN.getObject('panel-' + keyName + '-modifyUser')) { - if (data['data']['modifyUser']) { - let output = 'Modified by ' + data['data']['modifyUser']['userDN']; - if (data['data']['modifyUser']['ldapProfile'] && data['data']['modifyUser']['ldapProfile'] !== "default") { - output += ' [' + data['data']['modifyUser']['ldapProfile'] + ']'; - } - PWM_MAIN.getObject('panel-' + keyName + '-modifyUser').innerHTML = output; - } else { - PWM_MAIN.getObject('panel-' + keyName + '-modifyUser').innerHTML = ''; - } - } -}; - -PWM_CFGEDIT.writeSetting = function(keyName, valueData, nextAction) { - PWM_VAR['outstandingOperations']++; - PWM_CFGEDIT.handleWorkingIcon(); - let url = PWM_MAIN.addParamToUrl(window.location.pathname, 'processAction','writeSetting'); - url = PWM_MAIN.addParamToUrl(url, 'key', keyName); - if (PWM_CFGEDIT.readCurrentProfile()) { - url = PWM_MAIN.addParamToUrl(url,'profile',PWM_CFGEDIT.readCurrentProfile()); - } - const loadFunction = function(data) { - PWM_VAR['outstandingOperations']--; - PWM_CFGEDIT.handleWorkingIcon(); - console.log('wrote data for setting ' + keyName); - const isDefault = data['data']['isDefault']; - PWM_CFGEDIT.updateSettingDisplay(keyName, isDefault); - if (data['errorMessage']) { - PWM_MAIN.showError(data['data']['errorMessage']); - } else { - PWM_MAIN.clearError(); - } - const restartEditor = PWM_MAIN.JSLibrary.arrayContains(PWM_SETTINGS['settings'][keyName]['flags'],'ReloadEditorOnModify'); - if ( restartEditor ) - { - PWM_MAIN.gotoUrl(window.location.href); - return; - } - if (nextAction !== undefined) { - nextAction(); - } - }; - const errorFunction = function(error) { - PWM_VAR['outstandingOperations']--; - PWM_CFGEDIT.handleWorkingIcon(); - PWM_MAIN.showErrorDialog(error); - console.log("error writing setting " + keyName + ", reason: " + error) - }; - PWM_MAIN.ajaxRequest(url,loadFunction,{errorFunction:errorFunction,content:valueData}); -}; - -PWM_CFGEDIT.resetSetting=function(keyName, nextAction) { - let url = PWM_MAIN.addParamToUrl(window.location.pathname, 'processAction', 'resetSetting'); - url = PWM_MAIN.addParamToUrl(url, 'key', keyName); - if (PWM_CFGEDIT.readCurrentProfile()) { - url = PWM_MAIN.addParamToUrl(url,'profile',PWM_CFGEDIT.readCurrentProfile()); - } - const loadFunction = function() { - console.log('reset data for ' + keyName); - if (nextAction !== undefined) { - nextAction(); - } - }; - PWM_MAIN.ajaxRequest(url,loadFunction); -}; - - -PWM_CFGEDIT.handleWorkingIcon = function() { - const iconElement = PWM_MAIN.getObject('working_icon'); - if (iconElement) { - if (PWM_VAR['outstandingOperations'] > 0) { - iconElement.style.visibility = 'visible'; - } else { - iconElement.style.visibility = 'hidden'; - } - } -}; - - -PWM_CFGEDIT.updateSettingDisplay = function(keyName, isDefault) { - const resetImageButton = PWM_MAIN.getObject('resetButton-' + keyName); - const modifiedIcon = PWM_MAIN.getObject('modifiedNoticeIcon-' + keyName); - let settingSyntax = ''; - try { - settingSyntax = PWM_SETTINGS['settings'][keyName]['syntax']; - } catch (e) { /* noop */ } //setting keys may not be loaded - - if (PWM_SETTINGS['settings'][keyName]) { - if (PWM_MAIN.JSLibrary.arrayContains(PWM_SETTINGS['settings'][keyName]['flags'],'NoDefault')) { - isDefault = true; - } - } - - if (!isDefault) { - if (resetImageButton) { - resetImageButton.style.visibility = 'visible'; - } - if (modifiedIcon) { - modifiedIcon.style.display = 'inline'; - } - try { - document.getElementById('title_' + keyName).classList.add('modified'); - document.getElementById('titlePane_' + keyName).classList.add('modified'); - } catch (e) { /* noop */ } - } else { - if (resetImageButton) { - resetImageButton.style.visibility = 'hidden'; - } - if (modifiedIcon) { - modifiedIcon.style.display = 'none'; - } - try { - document.getElementById('title_' + keyName).classList.remove('modified'); - document.getElementById('titlePane_' + keyName).classList.remove('modified'); - } catch (e) { /* noop */ } - } -}; - -PWM_CFGEDIT.getSettingValueElement = function(settingKey) { - const parentDiv = 'table_setting_' + settingKey; - return PWM_MAIN.getObject(parentDiv); -}; - -PWM_CFGEDIT.clearDivElements = function(parentDiv) { - const parentDivElement = PWM_MAIN.getObject(parentDiv); - if (parentDivElement !== null) { - if (parentDivElement.hasChildNodes()) { - while (parentDivElement.childNodes.length >= 1) { - const firstChild = parentDivElement.firstChild; - parentDivElement.removeChild(firstChild); - } - } - } -}; - -PWM_CFGEDIT.addValueButtonRow = function(parentDiv, keyName, addFunction) { - const buttonId = keyName + '-addValueButton'; - const newTableRow = document.createElement("tr"); - newTableRow.setAttribute("style", "border-width: 0"); - newTableRow.setAttribute("colspan", "5"); - - const newTableData = document.createElement("td"); - newTableData.setAttribute("style", "border-width: 0;"); - - const addItemButton = document.createElement("button"); - addItemButton.setAttribute("type", "button"); - addItemButton.setAttribute("id", buttonId); - addItemButton.setAttribute("class", "btn"); - addItemButton.onclick = addFunction; - addItemButton.innerHTML = "Add Value"; - newTableData.appendChild(addItemButton); - - const parentDivElement = PWM_MAIN.getObject(parentDiv); - parentDivElement.appendChild(newTableRow); - newTableRow.appendChild(newTableData); -}; - -PWM_CFGEDIT.saveConfiguration = function() { - PWM_VAR['cancelHeartbeatCheck'] = true; - PWM_MAIN.preloadAll(function(){ - const confirmFunction = function(){ - const url = PWM_MAIN.addParamToUrl(window.location.pathname, 'processAction', 'finishEditing'); - const saveFunction = function(data) { - if (data['error'] === true) { - PWM_MAIN.showErrorDialog(data); - } else { - console.log('save completed'); - PWM_MAIN.showWaitDialog({title:'Save complete, applying configuration...',loadFunction:function(){ - PWM_CONFIG.waitForRestart({location:'/'}); - }}); - } - }; - PWM_MAIN.showWaitDialog({title:'Saving...',loadFunction:function(){ - PWM_MAIN.ajaxRequest(url,saveFunction); - }}); - }; - PWM_CFGEDIT.showChangeLog(confirmFunction); - }); -}; - -PWM_CFGEDIT.showChangeLog=function(nextFunction) { - const jasonFunction = function() { - PWM_CFGEDIT.showHealthWarnings(nextFunction); - } - const url = PWM_MAIN.addParamToUrl(window.location.pathname, 'processAction', 'readChangeLog'); - const loadFunction = function(data) { - PWM_MAIN.closeWaitDialog(); - const bodyText = PWM_CFGEDIT.makeChangeLogHtml(data['data']); - const titleText = 'Changes'; - const okLabel = PWM_MAIN.showString('Button_Continue'); - PWM_MAIN.showConfirmDialog({title: titleText, okLabel:okLabel, text: bodyText, dialogClass:'wide', showClose: true, okAction:jasonFunction}); - }; - PWM_MAIN.showWaitDialog({loadFunction: function () { - PWM_MAIN.ajaxRequest(url, loadFunction); - } - }); -}; - -PWM_CFGEDIT.showHealthWarnings=function( nextFunction ) { - const url = PWM_MAIN.addParamToUrl(window.location.pathname, 'processAction', 'readWarnings'); - const loadFunction = function(data) { - PWM_MAIN.closeWaitDialog(); - let bodyText = PWM_CFGEDIT.makeConfigWarningsHtml(data['data']) - bodyText += '
    ' + PWM_CONFIG.showString('MenuDisplay_SaveConfig') + '
    '; - const titleText = 'Configuration Concerns'; - PWM_MAIN.showConfirmDialog({title: titleText, text: bodyText, dialogClass:'wide', showClose: true, okAction:nextFunction}); - }; - PWM_MAIN.showWaitDialog({loadFunction: function () { - PWM_MAIN.ajaxRequest(url, loadFunction); - } - }); -}; - -PWM_CFGEDIT.setConfigurationPassword = function(password) { - if (password) { - const url = PWM_MAIN.addParamToUrl(window.location.pathname, 'processAction', 'setConfigurationPassword'); - const loadFunction = function(data) { - if (data['error']) { - PWM_MAIN.closeWaitDialog(); - PWM_MAIN.showDialog({title: PWM_MAIN.showString('Title_Error'), text: data['errorMessage']}); - } else { - PWM_MAIN.closeWaitDialog(); - PWM_MAIN.showDialog({title: PWM_MAIN.showString('Title_Success'), text: data['successMessage']}); - } - }; - const errorFunction = function(errorObj) { - PWM_MAIN.closeWaitDialog(); - PWM_MAIN.showDialog ({title:PWM_MAIN.showString('Title_Error'),text:"error saving configuration password: " + errorObj}); - }; - PWM_CFGEDIT.clearDijitWidget('dialogPopup'); - PWM_MAIN.showWaitDialog({loadFunction:function(){ - PWM_MAIN.ajaxRequest(url,loadFunction,{errorFunction:errorFunction,content:{password:password}}); - }}); - return; - } - - const writeFunction = function(passwordValue) { - PWM_CFGEDIT.setConfigurationPassword(passwordValue); - }; - ChangePasswordHandler.popup('configPw','Configuration Password',writeFunction); -}; - - -function handleResetClick(settingKey) { - const label = PWM_SETTINGS['settings'][settingKey] ? PWM_SETTINGS['settings'][settingKey]['label'] : ' '; - const dialogText = PWM_CONFIG.showString('Warning_ResetSetting',{value1:label}); - const titleText = 'Reset ' + label ? label : ''; - - PWM_MAIN.showConfirmDialog({title:titleText,text:dialogText,okAction:function(){ - PWM_CFGEDIT.resetSetting(settingKey,function(){ - PWM_CFGEDIT.loadMainPageBody(); - }); - }}); -} - -PWM_CFGEDIT.initConfigEditor = function(nextFunction) { - PWM_CFGEDIT.applyStoredSettingFilterPrefs(); - - PWM_MAIN.addEventHandler('homeSettingSearch',['input','focus'],function(){PWM_CFGEDIT.processSettingSearch(PWM_MAIN.getObject('searchResults'));}); - PWM_MAIN.addEventHandler('button-navigationExpandAll','click',function(){PWM_VAR['navigationTree'].expandAll()}); - PWM_MAIN.addEventHandler('button-navigationCollapseAll','click',function(){PWM_VAR['navigationTree'].collapseAll()}); - - PWM_MAIN.addEventHandler('cancelButton_icon','click',function(){PWM_CFGEDIT.cancelEditing()}); - PWM_MAIN.addEventHandler('saveButton_icon','click',function(){PWM_CFGEDIT.saveConfiguration()}); - PWM_MAIN.addEventHandler('setPassword_icon','click',function(){PWM_CFGEDIT.setConfigurationPassword()}); - PWM_MAIN.addEventHandler('referenceDoc_icon','click',function(){ - PWM_MAIN.newWindowOpen(PWM_GLOBAL['url-context'] + '/public/reference/','referencedoc'); - }); - PWM_MAIN.addEventHandler('macroDoc_icon','click',function(){ PWM_CFGEDIT.showMacroHelp(); }); - - PWM_MAIN.addEventHandler('button-closeMenu','click',function(){ - PWM_CFGEDIT.closeMenuPanel(); - }); - PWM_MAIN.addEventHandler('button-openMenu','click',function(){ - PWM_CFGEDIT.openMenuPanel(); - }); - PWM_MAIN.addEventHandler('radio-setting-level','change',function(){ - PWM_CFGEDIT.handleSettingsFilterLevelRadioClick(); - }); - PWM_MAIN.addEventHandler('radio-modified-only','change',function(){ - PWM_CFGEDIT.handleModifiedSettingsRadioClick(); - }); - - PWM_CFGEDIT.initDomainMenu(); - - PWM_CONFIG.heartbeatCheck(); - - PWM_CFGEDIT.loadMainPageBody(); - - console.log('completed initConfigEditor'); - if (nextFunction) { - nextFunction(); - } -}; - -PWM_CFGEDIT.initDomainMenu = function() { - const domainList = PWM_VAR['domainIds']; - if ( domainList && domainList.length > 1 ) - { - const domainMenuElement = PWM_MAIN.getObject('domainMenu'); - let html = ''; - domainMenuElement.innerHTML = html; - PWM_MAIN.addEventHandler('domainMenuSelect','change',function(){ - const selectedDomain = PWM_MAIN.JSLibrary.readValueOfSelectElement('domainMenuSelect'); - PWM_MAIN.Preferences.writeSessionStorage('configEditor-lastSelected', null); - PWM_MAIN.gotoUrl(PWM_GLOBAL['url-context'] + '/private/config/editor/' + selectedDomain ); - }); - } -} - -PWM_CFGEDIT.executeSettingFunction = function (setting, name, resultHandler, extraData) { - const jsonSendData = {}; - jsonSendData['setting'] = setting; - jsonSendData['function'] = name; - jsonSendData['extraData'] = extraData; - - resultHandler = resultHandler !== undefined ? resultHandler : function(data) { - const msgBody = '
    ' + data['successMessage'] + '
    '; - PWM_MAIN.showDialog({width:700,title: 'Results', text: msgBody, okAction: function () { - PWM_CFGEDIT.loadMainPageBody(); - }}); - }; - - let requestUrl = PWM_MAIN.addParamToUrl(window.location.pathname, 'processAction', 'executeSettingFunction'); - if (PWM_CFGEDIT.readCurrentProfile()) { - requestUrl = PWM_MAIN.addParamToUrl(requestUrl,'profile',PWM_CFGEDIT.readCurrentProfile()); - } - - PWM_MAIN.showWaitDialog({loadFunction:function() { - const loadFunction = function(data) { - if (data['error']) { - PWM_MAIN.showErrorDialog(data); - } else { - resultHandler(data,extraData); - } - }; - PWM_MAIN.ajaxRequest(requestUrl, loadFunction, {content:jsonSendData}); - }}); -}; - -PWM_CFGEDIT.processSettingSearch = function(destinationDiv) { - const iteration = 'settingSearchIteration' in PWM_VAR ? PWM_VAR['settingSearchIteration'] + 1 : 0; - const startTime = new Date().getTime(); - PWM_VAR['settingSearchIteration'] = iteration; - - const resetDisplay = function() { - PWM_MAIN.addCssClass('indicator-noResults',"hidden"); - PWM_MAIN.addCssClass('indicator-searching',"hidden"); - PWM_MAIN.addCssClass(destinationDiv.id,"hidden"); - destinationDiv.innerHTML = ''; - }; - - const readSearchTerm = function() { - if (!PWM_MAIN.getObject('homeSettingSearch') || !PWM_MAIN.getObject('homeSettingSearch') || PWM_MAIN.getObject('homeSettingSearch').value.length < 1) { - return null; - } - return PWM_MAIN.getObject('homeSettingSearch').value; - }; - - console.log('beginning search #' + iteration); - const url = PWM_MAIN.addParamToUrl(window.location.pathname, 'processAction', 'search'); - - const loadFunction = function(data) { - resetDisplay(); - - if (!readSearchTerm()) { - resetDisplay(); - return; - } - - if (!data) { - console.log('search #' + iteration + ", no data returned"); - return; - } - - if (data['error']) { - console.log('search #' + iteration + ", error returned: " + data); - PWM_MAIN.showErrorDialog(data); - } else { - let bodyText = ''; - let resultCount = 0; - const elapsedTime = (new Date().getTime()) - startTime; - if (PWM_MAIN.JSLibrary.isEmpty(data['data'])) { - PWM_MAIN.removeCssClass('indicator-noResults','hidden') - console.log('search #' + iteration + ', 0 results, ' + elapsedTime + 'ms'); - } else { - PWM_MAIN.addCssClass('indicator-noResults','hidden') - PWM_MAIN.JSLibrary.forEachInObject(data['data'], function (categoryIter, category) { - bodyText += '
    ' + categoryIter + '
    '; - PWM_MAIN.JSLibrary.forEachInObject(category, function (settingIter, setting) { - const profileID = setting['profile']; - const linkID = 'link-' + setting['category'] + '-' + settingIter + (profileID ? profileID : ''); - const settingID = "search_" + (profileID ? profileID + '_' : '') + settingIter; - bodyText += '
    '; - bodyText += PWM_SETTINGS['settings'][settingIter]['label']; - bodyText += ' '; - if (!setting['default']) { - bodyText += ' '; - } - bodyText += '
    '; - resultCount++; - }); - }); - console.log('search #' + iteration + ', ' + resultCount + ' results, ' + elapsedTime + 'ms'); - PWM_MAIN.removeCssClass(destinationDiv.id, "hidden"); - destinationDiv.innerHTML = bodyText; - PWM_MAIN.JSLibrary.forEachInObject(data['data'], function (categoryIter, category) { - PWM_MAIN.JSLibrary.forEachInObject(category, function (settingKey, setting) { - const profileID = setting['profile']; - const settingID = "search_" + (profileID ? profileID + '_' : '') + settingKey; - const value = setting['value']; - let toolBody = 'Setting'; - toolBody += '
    ' + PWM_SETTINGS['settings'][settingKey]['label'] + '

    '; - toolBody += 'Description'; - toolBody += '
    ' + PWM_SETTINGS['settings'][settingKey]['description'] + '

    '; - toolBody += 'Value'; - toolBody += '
    ' + value.replace('\n', '
    ') + '
    '; - PWM_MAIN.showTooltip({ - id: settingID + '_popup', - text: toolBody, - width: 500 - }); - const linkID = 'link-' + setting['category'] + '-' + settingKey + (profileID ? profileID : ''); - PWM_MAIN.addEventHandler(linkID, 'click', function () { - resetDisplay(); - PWM_MAIN.Preferences.writeSessionStorage('configEditor-lastSelected', { - type: 'category', - category: setting['category'], - setting: settingKey, - profile: profileID - }); - PWM_CFGEDIT.gotoSetting(setting['category'], settingKey, profileID); - }); - }); - }); - } - } - }; - const validationProps = {}; - validationProps['serviceURL'] = url; - validationProps['readDataFunction'] = function(){ - resetDisplay(); - PWM_MAIN.removeCssClass('indicator-searching','hidden'); - - const value = readSearchTerm(); - return {search:value,key:value}; - }; - validationProps['completeFunction'] = function() { - PWM_MAIN.addCssClass('indicator-searching','hidden'); - }; - validationProps['processResultsFunction'] = loadFunction; - PWM_MAIN.pwmFormValidator(validationProps); -}; - - -PWM_CFGEDIT.gotoSetting = function(category,settingKey,profile) { - console.log('going to setting... category=' + category + " settingKey=" + settingKey + " profile=" + profile); - - if (!category) { - if (settingKey) { - const settingInfo = PWM_SETTINGS['settings'][settingKey]; - if (settingInfo) { - category = settingInfo['category']; - } - } - } - - if (!settingKey && !category) { - console.log('unable to gotoUrl setting: settingKey and category parameter are not specified'); - return; - } - - if (settingKey && !(settingKey in PWM_SETTINGS['settings'])) { - console.log('unable to gotoUrl setting: settingKey parameter "' + settingKey + '" is not valid'); - return; - } - - if (!(category in PWM_SETTINGS['categories'])) { - console.log('unable to gotoUrl setting: category parameter "' + category + '" is not valid'); - return; - } - - PWM_CFGEDIT.setCurrentProfile(profile); - PWM_CFGEDIT.displaySettingsCategory(category); - - if (PWM_SETTINGS['categories'][category]['menuLocation']) { - let text = PWM_SETTINGS['categories'][category]['menuLocation']; - if (PWM_SETTINGS['categories'][category]['profiles']) { - text = text.replace('PROFILE',profile); - } - PWM_CFGEDIT.setBreadcrumbText(text); - } - - const item = {}; - item['id'] = category; - item['type'] = 'category'; - - if (settingKey) { - setTimeout(function(){ - const settingElement = PWM_CFGEDIT.getSettingValueElement(settingKey); - console.log('navigating and highlighting setting ' + settingKey); - //location.href = "#setting-" + settingKey; - settingElement.scrollIntoView(true); - if (settingElement.getBoundingClientRect().top < 100) { - window.scrollBy(0, -100); - } - PWM_MAIN.flashDomElement('red','title_' + settingKey, 5000); - },1000); - } -}; - -PWM_CFGEDIT.setBreadcrumbText = function(text) { - PWM_MAIN.getObject('currentPageDisplay').innerHTML = text; - -}; - -PWM_CFGEDIT.makeChangeLogHtml = function(changeData) { - let bodyText = '
    '; - if (PWM_MAIN.JSLibrary.isEmpty(changeData)) { - bodyText += '
    No changes.
    '; - } else { - PWM_MAIN.JSLibrary.forEachInObject(changeData, function (key) { - bodyText += '
    ' + key + '
    '; - bodyText += '
    ' + changeData[key] + '
    '; - }); - } - bodyText += '
    '; - return bodyText; -}; - -PWM_CFGEDIT.makeConfigWarningsHtml = function(configWarnings) { - let bodyText = '
    '; - PWM_MAIN.JSLibrary.forEachInObject(configWarnings,function(key){ - bodyText += '
    ' + key + '
    '; - const values = configWarnings[key]; - PWM_MAIN.JSLibrary.forEachInArray(values,function(value){ - bodyText += '
    ' + value + '
    '; - }); - }); - bodyText += '
    '; - return bodyText; -}; - -PWM_CFGEDIT.cancelEditing = function() { - const nextUrl = PWM_GLOBAL['url-context'] + '/private/config/manager'; - const url = PWM_MAIN.addParamToUrl(window.location.pathname, 'processAction', 'readChangeLog'); - PWM_MAIN.showWaitDialog({loadFunction:function(){ - const loadFunction = function(data) { - if (data['error']) { - PWM_MAIN.showDialog({title: PWM_MAIN.showString("Title_Error"), text: data['errorMessage']}); - } else { - if (!PWM_MAIN.JSLibrary.isEmpty(data['data'])) { - let bodyText = PWM_CFGEDIT.makeChangeLogHtml(data['data']); - bodyText += '
    '; - bodyText += PWM_CONFIG.showString('MenuDisplay_CancelConfig'); - bodyText += '
    '; - PWM_MAIN.closeWaitDialog(); - PWM_MAIN.showConfirmDialog({dialogClass:'wide',showClose:true,allowMove:true,text:bodyText,okAction: - function () { - PWM_MAIN.showWaitDialog({loadFunction: function () { - const url = PWM_MAIN.addParamToUrl(window.location.pathname, 'processAction', 'cancelEditing'); - PWM_MAIN.ajaxRequest(url,function(){ - PWM_MAIN.gotoUrl(nextUrl, {addFormID: true}); - }); - }}); - } - }); - } else { - PWM_MAIN.gotoUrl(nextUrl, {addFormID: true}); - } - } - }; - PWM_MAIN.ajaxRequest(url, loadFunction); - }}); -}; - -PWM_CFGEDIT.showMacroHelp = function() { - const processExampleFunction = function() { - PWM_MAIN.getObject('panel-testMacroOutput').innerHTML = PWM_MAIN.showString('Display_PleaseWait'); - const sendData = {}; - sendData['input'] = PWM_MAIN.getObject('input-testMacroInput').value; - const url = PWM_MAIN.addParamToUrl(window.location.pathname, 'processAction', 'testMacro'); - const loadFunction = function(data) { - PWM_MAIN.getObject('panel-testMacroOutput').innerHTML = data['data']; - }; - PWM_MAIN.ajaxRequest(url,loadFunction,{content:sendData}); - }; - - const loadFunction = function() { - if (PWM_MAIN.getObject('input-testMacroInput')) { - console.log('connected to macroHelpDiv'); - setTimeout(function(){ - PWM_MAIN.addEventHandler('input-testMacroInput','input',processExampleFunction); - processExampleFunction(); - PWM_MAIN.getObject('input-testMacroInput').focus(); - PWM_MAIN.getObject('input-testMacroInput').setSelectionRange(-1, -1); - },500); - } else { - if (attempts < 50) { - attempts++; - setTimeout(loadFunction,100); - } - } - }; - - const options = {}; - options['title'] = 'Macro Help' - options['id'] = 'id-dialog-macroHelp' - options['dialogClass'] = 'wide'; - options['showClose'] = true; - options['href'] = PWM_GLOBAL['url-resources'] + "/text/macroHelp.html" - options['loadFunction'] = loadFunction; - PWM_MAIN.showDialog( options ); -}; - -PWM_CFGEDIT.showTimezoneList = function() { - const options = {}; - options['title'] = 'Timezones' - options['id'] = 'id-dialog-timeZoneHelp' - options['dialogClass'] = 'wide'; - options['showClose'] = true; - options['href'] = PWM_GLOBAL['url-context'] + "/public/reference/timezones.jsp" - PWM_MAIN.showDialog( options ); -}; - -PWM_CFGEDIT.showDateTimeFormatHelp = function() { - const options = {}; - options['title'] = 'Date & Time Formatting' - options['id'] = 'id-dialog-dateTimePopup' - options['dialogClass'] = 'wide'; - options['showClose'] = true; - options['href'] = PWM_GLOBAL['url-resources'] + "/text/datetimeFormatHelp.html" - PWM_MAIN.showDialog( options ); -}; - -PWM_CFGEDIT.ldapHealthCheck = function() { - PWM_MAIN.showWaitDialog({loadFunction:function() { - let url = PWM_MAIN.addParamToUrl(window.location.pathname, 'processAction', 'ldapHealthCheck'); - url = PWM_MAIN.addParamToUrl(url,'profile',PWM_CFGEDIT.readCurrentProfile()); - const loadFunction = function(data) { - PWM_MAIN.closeWaitDialog(); - if (data['error']) { - PWM_MAIN.showDialog({title: PWM_MAIN.showString("Title_Error"), text: data['errorMessage']}); - } else { - const bodyText = PWM_ADMIN.makeHealthHtml(data['data'],false,false); - const profileName = PWM_CFGEDIT.readCurrentProfile(); - const titleText = PWM_MAIN.showString('Field_LdapProfile') + ": " + profileName; - PWM_MAIN.showDialog({text:bodyText,title:titleText}); - } - }; - PWM_MAIN.ajaxRequest(url,loadFunction); - }}); -}; - -PWM_CFGEDIT.databaseHealthCheck = function() { - PWM_MAIN.showWaitDialog({title:'Checking database connection...',loadFunction:function(){ - const url = PWM_MAIN.addParamToUrl(window.location.pathname, 'processAction', 'databaseHealthCheck'); - const loadFunction = function(data) { - PWM_MAIN.closeWaitDialog(); - if (data['error']) { - PWM_MAIN.showDialog({title: PWM_MAIN.showString("Title_Error"), text: data['errorMessage']}); - } else { - const bodyText = PWM_ADMIN.makeHealthHtml(data['data'],false,false); - const titleText = 'Database Connection Status'; - PWM_MAIN.showDialog({text:bodyText,title:titleText}); - } - }; - PWM_MAIN.ajaxRequest(url,loadFunction); - }}); -}; - -PWM_CFGEDIT.httpsCertificateView = function() { - PWM_MAIN.showWaitDialog({title:'Parsing...',loadFunction:function(){ - const url = PWM_MAIN.addParamToUrl(window.location.pathname, 'processAction', 'httpsCertificateView'); - const loadFunction = function(data) { - PWM_MAIN.closeWaitDialog(); - if (data['error']) { - PWM_MAIN.showErrorDialog(data); - } else { - const bodyText = '
    ' + data['data'] + '
    '; - const titleText = 'HTTPS Certificate'; - PWM_MAIN.showDialog({text:bodyText,title:titleText}); - } - }; - PWM_MAIN.ajaxRequest(url,loadFunction); - }}); -}; - -PWM_CFGEDIT.smsHealthCheck = function() { - const title = 'Test SMS Settings' - - const dialogFormRows = '

    ' + PWM_CONFIG.showString('Warning_SmsTestData') +'

    ' - + 'To' - + 'Message'; - - const actionParam = 'smsHealthCheck'; - - PWM_CFGEDIT.healthCheckImpl(dialogFormRows,title,actionParam); -}; - -PWM_CFGEDIT.emailHealthCheck = function() { - const title = PWM_CONFIG.showString('Warning_EmailTestData'); - - const dialogFormRows = 'To' - + 'From' - + 'Subject' - + 'Body'; - - const actionParam = 'emailHealthCheck'; - - PWM_CFGEDIT.healthCheckImpl(dialogFormRows,title,actionParam); -}; - -PWM_CFGEDIT.healthCheckImpl = function(dialogFormRows, title, actionParam) { - const formBody = '
    ' + dialogFormRows + '
    '; - PWM_MAIN.showDialog({text:formBody,showCancel:true,title:title,closeOnOk:false,okAction:function(){ - const formElement = PWM_MAIN.getObject("parametersForm"); - const formData = PWM_MAIN.JSLibrary.formToValueMap(formElement); - let url = PWM_MAIN.addParamToUrl(window.location.pathname, 'processAction', actionParam); - url = PWM_MAIN.addParamToUrl(url,'profile',PWM_CFGEDIT.readCurrentProfile()); - PWM_MAIN.showWaitDialog({loadFunction:function(){ - const loadFunction = function(data) { - if (data['error']) { - PWM_MAIN.showErrorDialog(data); - } else { - const bodyText = '
    ' + data['data'] + '
    '; - PWM_MAIN.showDialog({text:bodyText,title:title,showCancel:true,dialogClass:'wide'}); - } - }; - PWM_MAIN.ajaxRequest(url,loadFunction,{content:formData}); - }}); - }}); -}; - -PWM_CFGEDIT.selectTemplate = function(newTemplate) { - PWM_MAIN.showConfirmDialog({ - text: PWM_CONFIG.showString('Warning_ChangeTemplate'), - okAction: function () { - PWM_MAIN.showWaitDialog({loadFunction: function () { - let url = PWM_MAIN.addParamToUrl(window.location.pathname, 'processAction', 'setOption'); - url = PWM_MAIN.addParamToUrl(url, 'template',newTemplate); - PWM_MAIN.ajaxRequest(url, function(){ PWM_MAIN.gotoUrl('editor'); }); - }}); - } - }); -}; - -PWM_CFGEDIT.loadMainPageBody = function() { - - const drawSettingsFunction = function () { - let dispatched = false; - const lastSelected = PWM_MAIN.Preferences.readSessionStorage('configEditor-lastSelected', null); - if (lastSelected) { - PWM_CFGEDIT.dispatchNavigationItem(lastSelected); - dispatched = true; - } - - if (!dispatched) - { - const systemSelected = PWM_VAR['selectedDomainId'] === 'system'; - if (systemSelected) { - PWM_CFGEDIT.dispatchNavigationItem({id: 'DOMAINS', type: 'category', category: 'DOMAINS'}); - } else { - PWM_CFGEDIT.dispatchNavigationItem({id: 'TEMPLATES', type: 'category', category: 'TEMPLATES'}); - } - } - - { - const processActionParam = PWM_MAIN.JSLibrary.getParameterByName('processAction'); - if (processActionParam === 'gotoSetting') { - PWM_CFGEDIT.gotoSetting( - PWM_MAIN.JSLibrary.getParameterByName('category'), - PWM_MAIN.JSLibrary.getParameterByName('settingKey'), - PWM_MAIN.JSLibrary.getParameterByName('profile') ); - return; - } - } - } - - PWM_CFGEDIT.drawNavigationMenu( drawSettingsFunction ); -}; - -PWM_CFGEDIT.displaySettingsCategory = function(category) { - const settingsPanel = PWM_MAIN.getObject('settingsPanel'); - settingsPanel.innerHTML = ''; - console.log('loadingSettingsCategory: ' + category); - - if (!category) { - settingsPanel.innerHTML = ''; - console.log('no selected category'); - return; - } - let htmlSettingBody = ''; - - if (category === 'LDAP_BASE') { - htmlSettingBody += '
    ' - + '' - + '
    '; - } else if (category === 'DATABASE_SETTINGS') { - htmlSettingBody += '
    ' - + '' - + '
    '; - } else if (category === 'SMS_GATEWAY') { - htmlSettingBody += '
    ' - + '' - + '
    '; - } else if (category === 'EMAIL_SERVERS') { - htmlSettingBody += '
    ' - + '' - + '
    '; - } - - PWM_VAR['skippedSettingCount'] = 0; - PWM_MAIN.JSLibrary.forEachInObject(PWM_SETTINGS['settings'],function(key,settingInfo){ - if (settingInfo['category'] === category && !settingInfo['hidden']) { - htmlSettingBody += PWM_CFGEDIT.drawHtmlOutlineForSetting(settingInfo); - } - }); - - htmlSettingBody += '
    '; - settingsPanel.innerHTML = htmlSettingBody; - - PWM_MAIN.JSLibrary.forEachInObject(PWM_SETTINGS['settings'],function(key,settingInfo) { - if (settingInfo['category'] === category && !settingInfo['hidden']) { - PWM_CFGEDIT.initSettingDisplay(settingInfo); - } - }); - - if (category === 'LDAP_BASE') { - PWM_MAIN.addEventHandler('button-test-LDAP_BASE', 'click', function(){PWM_CFGEDIT.ldapHealthCheck();}); - } else if (category === 'DATABASE_SETTINGS') { - PWM_MAIN.addEventHandler('button-test-DATABASE_SETTINGS', 'click', function(){PWM_CFGEDIT.databaseHealthCheck();}); - } else if (category === 'SMS_GATEWAY') { - PWM_MAIN.addEventHandler('button-test-SMS', 'click', function(){PWM_CFGEDIT.smsHealthCheck();}); - } else if (category === 'EMAIL_SERVERS') { - PWM_MAIN.addEventHandler('button-test-EMAIL', 'click', function(){PWM_CFGEDIT.emailHealthCheck();}); - } else if (category === 'HTTPS_SERVER') { - PWM_MAIN.addEventHandler('button-test-HTTPS_SERVER', 'click', function(){PWM_CFGEDIT.httpsCertificateView();}); - } - PWM_CFGEDIT.applyGotoSettingHandlers(); -}; - -PWM_CFGEDIT.drawProfileEditorPage = function(settingKey) { - const settingsPanel = PWM_MAIN.getObject('settingsPanel'); - settingsPanel.innerHTML = ''; - const settingInfo = PWM_SETTINGS['settings'][settingKey]; - console.log('drawing profile-editor for setting-' + settingKey); - - settingsPanel.innerHTML = PWM_CFGEDIT.drawHtmlOutlineForSetting(settingInfo); - PWM_CFGEDIT.initSettingDisplay(settingInfo); -}; - -PWM_CFGEDIT.drawHtmlOutlineForSetting = function(settingInfo, options) { - options = options === undefined ? {} : options; - const settingKey = settingInfo['key']; - const settingLabel = settingInfo['label']; - let htmlBody = '
    ' - + '
    ' - + '' + settingLabel + '' - + ''; - - if (settingInfo['description']) { - htmlBody += '
    '; - } - - htmlBody += '' - + '
    ' // close title - + '
    '; - - if (settingInfo['description']) { - const prefs = PWM_MAIN.Preferences.readSessionStorage('helpExpanded',{}); - const expandHelp = settingKey in prefs; - htmlBody += '
    ' - + settingInfo['description']; - if (settingInfo['placeholder']) { - htmlBody += '

    Example: ' + settingInfo['placeholder'] + '

    '; - } - htmlBody += '
    '; - } - - - htmlBody += '
    ' - + '
    ' // close setting; - + '
    ' // close body - + '
    ' - + '
    ' - + '
    '; // close outline - - return htmlBody; -}; - -PWM_CFGEDIT.initSettingDisplay = function(setting, options) { - const settingKey = setting['key']; - options = options === undefined ? {} : options; - - PWM_MAIN.addEventHandler('helpButton-' + settingKey, 'click', function () { - PWM_CFGEDIT.displaySettingHelp(settingKey); - }); - PWM_MAIN.addEventHandler('setting-' + settingKey, 'click', function () { - PWM_CFGEDIT.displaySettingHelp(settingKey); - }); - PWM_MAIN.addEventHandler('resetButton-' + settingKey, 'click', function () { - handleResetClick(settingKey); - }); - - const syntax = setting['syntax']; - const syntaxFunction = PWM_CFGEDIT.syntaxFunctionMap[syntax]; - if ( syntaxFunction ) { - syntaxFunction(settingKey); - } -}; - -PWM_CFGEDIT.drawNavigationMenu = function(nextFunction) { - console.log('drawNavigationMenu') - PWM_MAIN.getObject('navigationTree').innerHTML = ''; - //PWM_MAIN.setStyle('navigationTreeWrapper','display','none'); - - const makeTreeFunction = function(menuTreeData) { - require(["dojo","dojo/_base/window", "dojo/store/Memory", "dijit/tree/ObjectStoreModel", "dijit/Tree","dijit","dojo/domReady!"], - function(dojo, win, Memory, ObjectStoreModel, Tree) - { - PWM_CFGEDIT.clearDijitWidget('navigationTree'); - // Create test store, adding the getChildren() method required by ObjectStoreModel - const myStore = new Memory({ - data: menuTreeData, - getChildren: function(object){ - return this.query({parent: object.id}); - } - }); - - // Create the model - const model = new ObjectStoreModel({ - store: myStore, - query: {id: 'ROOT'}, - mayHaveChildren: function(object){ - return object.type === 'navigation'; - } - }); - - // Create the Tree. - const tree = new Tree({ - model: model, - persist: false, - getIconClass: function(/*dojo.store.Item*/ item, /*Boolean*/ opened){ - return 'tree-noicon'; - }, - showRoot: false, - openOnClick: true, - id: 'navigationTree', - onClick: function(item){ - PWM_MAIN.Preferences.writeSessionStorage('configEditor-lastSelected',item); - const path = tree.get('paths'); - PWM_MAIN.Preferences.writeSessionStorage('configEditor-path',JSON.stringify(path)); - PWM_CFGEDIT.dispatchNavigationItem(item); - } - }); - - const storedPath = PWM_MAIN.Preferences.readSessionStorage('configEditor-path'); - if (storedPath) { - const path = JSON.parse(storedPath); - tree.set('paths', path); - } - - PWM_MAIN.getObject('navigationTree').innerHTML = ''; - tree.placeAt(PWM_MAIN.getObject('navigationTree')); - tree.startup(); - //PWM_MAIN.setStyle('navigationTreeWrapper','display','inherit'); - PWM_VAR['navigationTree'] = tree; // used for expand/collapse button events; - console.log('completed menu tree drawing'); - } - ); - }; - - const url = PWM_MAIN.addParamToUrl(window.location.pathname, 'processAction', 'menuTreeData'); - const filterParams = PWM_CFGEDIT.readNavigationFilters(); - - PWM_MAIN.ajaxRequest(url,function(data){ - const menuTreeData = data['data']; - makeTreeFunction(menuTreeData); - if (nextFunction) { - nextFunction(); - } - },{content:filterParams,preventCache:true}); -}; - -PWM_CFGEDIT.readNavigationFilters = function() { - const result = {}; - result['modifiedSettingsOnly'] = 'settingFilter_modifiedSettingsOnly' in PWM_VAR ? PWM_VAR['settingFilter_modifiedSettingsOnly'] : false; - result['level'] = 'settingFilter_level' in PWM_VAR ? PWM_VAR['settingFilter_level'] : 2; - result['text'] = 'settingFilter_text' in PWM_VAR ? PWM_VAR['settingFilter_text'] : ''; - return result; -}; - -PWM_CFGEDIT.dispatchNavigationItem = function(item) { - const currentID = item['id']; - const type = item['type']; - if (type === 'navigation') { - /* not used, nav tree set to auto-expand */ - } else if (type === 'category') { - const category = item['category']; - if (item['profile']) { - PWM_CFGEDIT.gotoSetting(category,null,item['profile']); - } else { - PWM_CFGEDIT.gotoSetting(category); - } - } else if (type === 'displayText') { - const keys = item['keys']; - PWM_CFGEDIT.setBreadcrumbText('Display Text - ' + item['name']); - PWM_CFGEDIT.drawDisplayTextPage(currentID,keys); - } else if (type === 'profile') { - const category = item['category']; - PWM_CFGEDIT.gotoSetting(category,null,currentID); - } else if (type === 'profileDefinition') { - const profileSettingKey = item['profileSetting']; - PWM_CFGEDIT.drawProfileEditorPage(profileSettingKey); - } -}; - -PWM_CFGEDIT.drawDisplayTextPage = function(settingKey, keys) { - const settingsPanel = PWM_MAIN.getObject('settingsPanel'); - let remainingLoads = keys.length; - settingsPanel.innerHTML = '
    ' - + PWM_MAIN.showString('Display_PleaseWait') + ' 
    '; - console.log('drawing displaytext-editor for setting-' + settingKey); - let htmlBody = '