mirror of
https://github.com/scm-manager/scm-manager.git
synced 2025-11-12 08:25:44 +01:00
create own endpoints for user conversion and apply changed workflow to user form in ui
This commit is contained in:
@@ -63,10 +63,16 @@
|
|||||||
},
|
},
|
||||||
"userForm": {
|
"userForm": {
|
||||||
"subtitle": "Benutzer bearbeiten",
|
"subtitle": "Benutzer bearbeiten",
|
||||||
"button": "Speichern",
|
"userIsInternal": "Der Benutzer wird vom SCM-Manager verwaltet",
|
||||||
|
"userIsExternal": "Der Benutzer wird von einem externen System verwaltet",
|
||||||
|
"button": {
|
||||||
|
"submit": "Speichern",
|
||||||
|
"convertToExternal": "Zu externem Benutzer konvertieren",
|
||||||
|
"convertToInternal": "Zu internem Benutzer konvertieren"
|
||||||
|
},
|
||||||
"modal": {
|
"modal": {
|
||||||
"passwordRequired": "Neues Passwort für internen Benutzer setzen",
|
"passwordRequired": "Neues Passwort für internen Benutzer setzen",
|
||||||
"setPassword": "Passwort setzen"
|
"convertToInternal": "Zu internem Benutzer konvertieren"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"publicKey": {
|
"publicKey": {
|
||||||
|
|||||||
@@ -63,10 +63,16 @@
|
|||||||
},
|
},
|
||||||
"userForm": {
|
"userForm": {
|
||||||
"subtitle": "Edit User",
|
"subtitle": "Edit User",
|
||||||
"button": "Submit",
|
"userIsInternal": "This user is managed by SCM-Manager",
|
||||||
|
"userIsExternal": "This user is managed by an external system",
|
||||||
|
"button": {
|
||||||
|
"submit": "Submit",
|
||||||
|
"convertToExternal": "Convert to external",
|
||||||
|
"convertToInternal": "Convert to internal"
|
||||||
|
},
|
||||||
"modal": {
|
"modal": {
|
||||||
"setPassword": "Set password",
|
"passwordRequired": "Set new password for internal user",
|
||||||
"passwordRequired": "Set new password for internal user"
|
"convertToInternal": "Convert to internal"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"publicKey": {
|
"publicKey": {
|
||||||
|
|||||||
130
scm-ui/ui-webapp/src/users/components/UserConverter.tsx
Normal file
130
scm-ui/ui-webapp/src/users/components/UserConverter.tsx
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
/*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
import React, { FC, useState } from "react";
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
Modal,
|
||||||
|
PasswordConfirmation,
|
||||||
|
SubmitButton,
|
||||||
|
ErrorNotification,
|
||||||
|
Level
|
||||||
|
} from "@scm-manager/ui-components";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { Link, User } from "@scm-manager/ui-types";
|
||||||
|
import { convertToExternal, convertToInternal } from "./convertUser";
|
||||||
|
import styled from "styled-components";
|
||||||
|
|
||||||
|
const ExternalDescription = styled.div`
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
font-size: 1.25rem;
|
||||||
|
font-weight: 400;
|
||||||
|
`;
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
user: User;
|
||||||
|
fetchUser: (user: User) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
const UserConverter: FC<Props> = ({ user, fetchUser }) => {
|
||||||
|
const [t] = useTranslation("users");
|
||||||
|
const [showPasswordModal, setShowPasswordModal] = useState(false);
|
||||||
|
const [password, setPassword] = useState("");
|
||||||
|
const [passwordValid, setPasswordValid] = useState(false);
|
||||||
|
const [error, setError] = useState<Error | undefined>();
|
||||||
|
|
||||||
|
const toInternal = () => {
|
||||||
|
convertToInternal((user._links.convertToInternal as Link).href, password)
|
||||||
|
.then(() => fetchUser(user))
|
||||||
|
.then(() => setShowPasswordModal(false))
|
||||||
|
.catch(setError);
|
||||||
|
};
|
||||||
|
|
||||||
|
const toExternal = () => {
|
||||||
|
convertToExternal((user._links.convertToExternal as Link).href)
|
||||||
|
.then(() => fetchUser(user))
|
||||||
|
.catch(setError);
|
||||||
|
};
|
||||||
|
|
||||||
|
const changePassword = (password: string, valid: boolean) => {
|
||||||
|
setPassword(password);
|
||||||
|
setPasswordValid(valid);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getUserExternalDescription = () => {
|
||||||
|
if (user.external) {
|
||||||
|
return t("userForm.userIsExternal");
|
||||||
|
} else {
|
||||||
|
return t("userForm.userIsInternal");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getConvertButton = () => {
|
||||||
|
if (user.external) {
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
label={t("userForm.button.convertToInternal")}
|
||||||
|
action={() => setShowPasswordModal(true)}
|
||||||
|
icon="exchange-alt"
|
||||||
|
className="is-pulled-right"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return <Button label={t("userForm.button.convertToExternal")} action={() => toExternal()} icon="exchange-alt" />;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const passwordChangeField = <PasswordConfirmation passwordChanged={changePassword} />;
|
||||||
|
const passwordModal = (
|
||||||
|
<Modal
|
||||||
|
body={passwordChangeField}
|
||||||
|
closeFunction={() => setShowPasswordModal(false)}
|
||||||
|
active={showPasswordModal}
|
||||||
|
title={t("userForm.modal.passwordRequired")}
|
||||||
|
footer={
|
||||||
|
<SubmitButton
|
||||||
|
action={() => password && passwordValid && toInternal()}
|
||||||
|
disabled={!passwordValid}
|
||||||
|
scrollToTop={false}
|
||||||
|
label={t("userForm.modal.convertToInternal")}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{showPasswordModal && passwordModal}
|
||||||
|
{error && <ErrorNotification error={error} />}
|
||||||
|
<div className="columns is-multiline">
|
||||||
|
<ExternalDescription className="column is-half">{getUserExternalDescription()}</ExternalDescription>
|
||||||
|
<div className="column is-half">
|
||||||
|
<Level right={getConvertButton()} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default UserConverter;
|
||||||
@@ -29,14 +29,12 @@ import {
|
|||||||
ErrorNotification,
|
ErrorNotification,
|
||||||
InputField,
|
InputField,
|
||||||
Level,
|
Level,
|
||||||
Modal,
|
|
||||||
PasswordConfirmation,
|
PasswordConfirmation,
|
||||||
SubmitButton,
|
SubmitButton,
|
||||||
Subtitle,
|
Subtitle,
|
||||||
validation as validator
|
validation as validator
|
||||||
} from "@scm-manager/ui-components";
|
} from "@scm-manager/ui-components";
|
||||||
import * as userValidator from "./userValidation";
|
import * as userValidator from "./userValidation";
|
||||||
import { setPassword } from "./setPassword";
|
|
||||||
|
|
||||||
type Props = WithTranslation & {
|
type Props = WithTranslation & {
|
||||||
submitForm: (p: User) => void;
|
submitForm: (p: User) => void;
|
||||||
@@ -50,7 +48,6 @@ type State = {
|
|||||||
nameValidationError: boolean;
|
nameValidationError: boolean;
|
||||||
displayNameValidationError: boolean;
|
displayNameValidationError: boolean;
|
||||||
passwordValid: boolean;
|
passwordValid: boolean;
|
||||||
showPasswordModal: boolean;
|
|
||||||
error?: Error;
|
error?: Error;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -71,8 +68,7 @@ class UserForm extends React.Component<Props, State> {
|
|||||||
mailValidationError: false,
|
mailValidationError: false,
|
||||||
displayNameValidationError: false,
|
displayNameValidationError: false,
|
||||||
nameValidationError: false,
|
nameValidationError: false,
|
||||||
passwordValid: false,
|
passwordValid: false
|
||||||
showPasswordModal: false
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -119,43 +115,22 @@ class UserForm extends React.Component<Props, State> {
|
|||||||
this.state.mailValidationError ||
|
this.state.mailValidationError ||
|
||||||
this.state.displayNameValidationError ||
|
this.state.displayNameValidationError ||
|
||||||
this.state.nameValidationError ||
|
this.state.nameValidationError ||
|
||||||
!user.displayName ||
|
!user.displayName
|
||||||
(this.props.user?.external && !user.external && !user.password)
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
submit = (event: Event) => {
|
submit = (event: Event) => {
|
||||||
const { user, passwordValid } = this.state;
|
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
if (!this.isInvalid()) {
|
if (!this.isInvalid()) {
|
||||||
this.props.submitForm(this.state.user);
|
this.props.submitForm(this.state.user);
|
||||||
if (user.password && passwordValid && user._links?.password) {
|
|
||||||
setPassword((user._links.password as Link).href, user.password).catch(error => this.setState({ error }));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { loading, t } = this.props;
|
const { loading, t } = this.props;
|
||||||
const { user, showPasswordModal, passwordValid, error } = this.state;
|
const { user, error } = this.state;
|
||||||
|
|
||||||
const passwordChangeField = <PasswordConfirmation passwordChanged={this.handlePasswordChange} />;
|
const passwordChangeField = <PasswordConfirmation passwordChanged={this.handlePasswordChange} />;
|
||||||
const passwordModal = (
|
|
||||||
<Modal
|
|
||||||
body={passwordChangeField}
|
|
||||||
closeFunction={() => this.setState({ user: { ...user, external: true } }, () => this.showPasswordModal(false))}
|
|
||||||
active={showPasswordModal}
|
|
||||||
title={t("userForm.modal.passwordRequired")}
|
|
||||||
footer={
|
|
||||||
<SubmitButton
|
|
||||||
action={() => !!user.password && passwordValid && this.showPasswordModal(false)}
|
|
||||||
disabled={!this.state.passwordValid}
|
|
||||||
scrollToTop={false}
|
|
||||||
label={t("userForm.modal.setPassword")}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
let nameField = null;
|
let nameField = null;
|
||||||
let subtitle = null;
|
let subtitle = null;
|
||||||
if (!this.props.user) {
|
if (!this.props.user) {
|
||||||
@@ -180,7 +155,6 @@ class UserForm extends React.Component<Props, State> {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{subtitle}
|
{subtitle}
|
||||||
{showPasswordModal && passwordModal}
|
|
||||||
<form onSubmit={this.submit}>
|
<form onSubmit={this.submit}>
|
||||||
<div className="columns is-multiline">
|
<div className="columns is-multiline">
|
||||||
{nameField}
|
{nameField}
|
||||||
@@ -204,14 +178,6 @@ class UserForm extends React.Component<Props, State> {
|
|||||||
helpText={t("help.mailHelpText")}
|
helpText={t("help.mailHelpText")}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="column is-full">
|
|
||||||
<Checkbox
|
|
||||||
label={t("user.externalFlag")}
|
|
||||||
onChange={this.handleExternalFlagChange}
|
|
||||||
checked={!!user?.external}
|
|
||||||
helpText={t("help.externalFlagHelpText")}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
{!user.external && (
|
{!user.external && (
|
||||||
<>
|
<>
|
||||||
@@ -229,16 +195,12 @@ class UserForm extends React.Component<Props, State> {
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{error && <ErrorNotification error={error} />}
|
{error && <ErrorNotification error={error} />}
|
||||||
<Level right={<SubmitButton disabled={this.isInvalid()} loading={loading} label={t("userForm.button")} />} />
|
<Level right={<SubmitButton disabled={this.isInvalid()} loading={loading} label={t("userForm.button.submit")} />} />
|
||||||
</form>
|
</form>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
showPasswordModal = (showPasswordModal: boolean) => {
|
|
||||||
this.setState({ showPasswordModal });
|
|
||||||
};
|
|
||||||
|
|
||||||
handleUsernameChange = (name: string) => {
|
handleUsernameChange = (name: string) => {
|
||||||
this.setState({
|
this.setState({
|
||||||
nameValidationError: !validator.isNameValid(name),
|
nameValidationError: !validator.isNameValid(name),
|
||||||
@@ -287,19 +249,6 @@ class UserForm extends React.Component<Props, State> {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
handleExternalFlagChange = (external: boolean) => {
|
|
||||||
this.setState(
|
|
||||||
{
|
|
||||||
user: {
|
|
||||||
...this.state.user,
|
|
||||||
external
|
|
||||||
}
|
|
||||||
},
|
|
||||||
//Only show password modal if edit mode and external flag was changed to internal and password was not already set
|
|
||||||
() => !external && this.props.user?.external && !this.state.user.password && this.showPasswordModal(true)
|
|
||||||
);
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default withTranslation("users")(UserForm);
|
export default withTranslation("users")(UserForm);
|
||||||
|
|||||||
46
scm-ui/ui-webapp/src/users/components/convertUser.ts
Normal file
46
scm-ui/ui-webapp/src/users/components/convertUser.ts
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
/*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { apiClient } from "@scm-manager/ui-components";
|
||||||
|
import { CONTENT_TYPE_USER } from "../modules/users";
|
||||||
|
|
||||||
|
export function convertToInternal(url: string, newPassword: string) {
|
||||||
|
return apiClient
|
||||||
|
.put(
|
||||||
|
url,
|
||||||
|
{
|
||||||
|
newPassword
|
||||||
|
},
|
||||||
|
CONTENT_TYPE_USER
|
||||||
|
)
|
||||||
|
.then(response => {
|
||||||
|
return response;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function convertToExternal(url: string) {
|
||||||
|
return apiClient.put(url, {}, CONTENT_TYPE_USER).then(response => {
|
||||||
|
return response;
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -43,7 +43,7 @@ class ChangePasswordNavLink extends React.Component<Props> {
|
|||||||
|
|
||||||
hasPermissionToSetPassword = () => {
|
hasPermissionToSetPassword = () => {
|
||||||
const { user } = this.props;
|
const { user } = this.props;
|
||||||
return !user.external && user._links.password;
|
return user._links.password;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -27,10 +27,17 @@ import { withRouter } from "react-router-dom";
|
|||||||
import UserForm from "../components/UserForm";
|
import UserForm from "../components/UserForm";
|
||||||
import DeleteUser from "./DeleteUser";
|
import DeleteUser from "./DeleteUser";
|
||||||
import { User } from "@scm-manager/ui-types";
|
import { User } from "@scm-manager/ui-types";
|
||||||
import { getModifyUserFailure, isModifyUserPending, modifyUser, modifyUserReset } from "../modules/users";
|
import {
|
||||||
|
fetchUserByLink,
|
||||||
|
getModifyUserFailure,
|
||||||
|
isModifyUserPending,
|
||||||
|
modifyUser,
|
||||||
|
modifyUserReset
|
||||||
|
} from "../modules/users";
|
||||||
import { History } from "history";
|
import { History } from "history";
|
||||||
import { ErrorNotification } from "@scm-manager/ui-components";
|
import { ErrorNotification } from "@scm-manager/ui-components";
|
||||||
import { compose } from "redux";
|
import { compose } from "redux";
|
||||||
|
import UserConverter from "../components/UserConverter";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
loading: boolean;
|
loading: boolean;
|
||||||
@@ -39,6 +46,7 @@ type Props = {
|
|||||||
// dispatch functions
|
// dispatch functions
|
||||||
modifyUser: (user: User, callback?: () => void) => void;
|
modifyUser: (user: User, callback?: () => void) => void;
|
||||||
modifyUserReset: (p: User) => void;
|
modifyUserReset: (p: User) => void;
|
||||||
|
fetchUser: (user: User) => void;
|
||||||
|
|
||||||
// context objects
|
// context objects
|
||||||
user: User;
|
user: User;
|
||||||
@@ -64,7 +72,9 @@ class EditUser extends React.Component<Props> {
|
|||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<ErrorNotification error={error} />
|
<ErrorNotification error={error} />
|
||||||
<UserForm submitForm={user => this.modifyUser(user)} user={user} loading={loading} />
|
<UserForm submitForm={this.modifyUser} user={user} loading={loading} />
|
||||||
|
<hr />
|
||||||
|
<UserConverter user={user} fetchUser={this.props.fetchUser} />
|
||||||
<DeleteUser user={user} />
|
<DeleteUser user={user} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -87,6 +97,9 @@ const mapDispatchToProps = (dispatch: any) => {
|
|||||||
},
|
},
|
||||||
modifyUserReset: (user: User) => {
|
modifyUserReset: (user: User) => {
|
||||||
dispatch(modifyUserReset(user));
|
dispatch(modifyUserReset(user));
|
||||||
|
},
|
||||||
|
fetchUser: (user: User) => {
|
||||||
|
dispatch(fetchUserByLink(user));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ export const DELETE_USER_PENDING = `${DELETE_USER}_${types.PENDING_SUFFIX}`;
|
|||||||
export const DELETE_USER_SUCCESS = `${DELETE_USER}_${types.SUCCESS_SUFFIX}`;
|
export const DELETE_USER_SUCCESS = `${DELETE_USER}_${types.SUCCESS_SUFFIX}`;
|
||||||
export const DELETE_USER_FAILURE = `${DELETE_USER}_${types.FAILURE_SUFFIX}`;
|
export const DELETE_USER_FAILURE = `${DELETE_USER}_${types.FAILURE_SUFFIX}`;
|
||||||
|
|
||||||
const CONTENT_TYPE_USER = "application/vnd.scmm-user+json;v=2";
|
export const CONTENT_TYPE_USER = "application/vnd.scmm-user+json;v=2";
|
||||||
|
|
||||||
// TODO i18n for error messages
|
// TODO i18n for error messages
|
||||||
|
|
||||||
|
|||||||
@@ -123,6 +123,14 @@ class ResourceLinks {
|
|||||||
return userLinkBuilder.method("getUserResource").parameters(name).method("overwritePassword").parameters().href();
|
return userLinkBuilder.method("getUserResource").parameters(name).method("overwritePassword").parameters().href();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String toExternal(String name) {
|
||||||
|
return userLinkBuilder.method("getUserResource").parameters(name).method("toExternal").parameters().href();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String toInternal(String name) {
|
||||||
|
return userLinkBuilder.method("getUserResource").parameters(name).method("toInternal").parameters().href();
|
||||||
|
}
|
||||||
|
|
||||||
public String publicKeys(String name) {
|
public String publicKeys(String name) {
|
||||||
return publicKeyLinkBuilder.method("findAll").parameters(name).href();
|
return publicKeyLinkBuilder.method("findAll").parameters(name).href();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -186,6 +186,71 @@ public class UserResource {
|
|||||||
return Response.noContent().build();
|
return Response.noContent().build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This Endpoint is for Admin user to convert external user to internal.
|
||||||
|
* The oldPassword property of the DTO is not needed here. it will be ignored.
|
||||||
|
* The oldPassword property is needed in the MeResources when the actual user change the own password.
|
||||||
|
*
|
||||||
|
* <strong>Note:</strong> This method requires "user:modify" privilege to modify the password of other users.
|
||||||
|
*
|
||||||
|
* @param name name of the user to be modified
|
||||||
|
* @param passwordOverwrite change password object to modify password. the old password is here not required
|
||||||
|
*/
|
||||||
|
@PUT
|
||||||
|
@Path("convert-to-internal")
|
||||||
|
@Consumes(VndMediaType.USER)
|
||||||
|
@Operation(summary = "Converts an external user to internal", description = "Converts an external user to an internal one and set the new password.", tags = "User")
|
||||||
|
@ApiResponse(responseCode = "204", description = "update success")
|
||||||
|
@ApiResponse(responseCode = "400", description = "invalid body, e.g. the new password is missing")
|
||||||
|
@ApiResponse(responseCode = "401", description = "not authenticated / invalid credentials")
|
||||||
|
@ApiResponse(responseCode = "403", description = "not authorized, the current user does not have the \"user\" privilege")
|
||||||
|
@ApiResponse(
|
||||||
|
responseCode = "404",
|
||||||
|
description = "not found, no user with the specified id/name available",
|
||||||
|
content = @Content(
|
||||||
|
mediaType = VndMediaType.ERROR_TYPE,
|
||||||
|
schema = @Schema(implementation = ErrorDto.class)
|
||||||
|
))
|
||||||
|
@ApiResponse(responseCode = "500", description = "internal server error")
|
||||||
|
public Response toInternal(@PathParam("id") String name, @Valid PasswordOverwriteDto passwordOverwrite) {
|
||||||
|
UserDto dto = userToDtoMapper.map(userManager.get(name));
|
||||||
|
dto.setExternal(false);
|
||||||
|
adapter.update(name, existing -> dtoToUserMapper.map(dto, existing.getPassword()));
|
||||||
|
userManager.overwritePassword(name, passwordService.encryptPassword(passwordOverwrite.getNewPassword()));
|
||||||
|
return Response.noContent().build();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This Endpoint is for Admin user to convert internal user to external.
|
||||||
|
*
|
||||||
|
* <strong>Note:</strong> This method requires "user:modify" privilege to modify the password of other users.
|
||||||
|
*
|
||||||
|
* @param name name of the user to be modified
|
||||||
|
*/
|
||||||
|
@PUT
|
||||||
|
@Path("convert-to-external")
|
||||||
|
@Consumes(VndMediaType.USER)
|
||||||
|
@Operation(summary = "Converts an internal user to external", description = "Converts an internal user to an external one and removes the local password.", tags = "User")
|
||||||
|
@ApiResponse(responseCode = "204", description = "update success")
|
||||||
|
@ApiResponse(responseCode = "400", description = "invalid body, e.g. the new password is missing")
|
||||||
|
@ApiResponse(responseCode = "401", description = "not authenticated / invalid credentials")
|
||||||
|
@ApiResponse(responseCode = "403", description = "not authorized, the current user does not have the \"user\" privilege")
|
||||||
|
@ApiResponse(
|
||||||
|
responseCode = "404",
|
||||||
|
description = "not found, no user with the specified id/name available",
|
||||||
|
content = @Content(
|
||||||
|
mediaType = VndMediaType.ERROR_TYPE,
|
||||||
|
schema = @Schema(implementation = ErrorDto.class)
|
||||||
|
))
|
||||||
|
@ApiResponse(responseCode = "500", description = "internal server error")
|
||||||
|
public Response toExternal(@PathParam("id") String name) {
|
||||||
|
userManager.overwritePassword(name, passwordService.encryptPassword(null));
|
||||||
|
UserDto dto = userToDtoMapper.map(userManager.get(name));
|
||||||
|
dto.setExternal(true);
|
||||||
|
adapter.update(name, existing -> dtoToUserMapper.map(dto, existing.getPassword()));
|
||||||
|
return Response.noContent().build();
|
||||||
|
}
|
||||||
|
|
||||||
@Path("permissions")
|
@Path("permissions")
|
||||||
public UserPermissionResource permissions() {
|
public UserPermissionResource permissions() {
|
||||||
return userPermissionResource;
|
return userPermissionResource;
|
||||||
|
|||||||
@@ -66,7 +66,12 @@ public abstract class UserToUserDtoMapper extends BaseMapper<User, UserDto> {
|
|||||||
if (UserPermissions.modify(user).isPermitted()) {
|
if (UserPermissions.modify(user).isPermitted()) {
|
||||||
linksBuilder.single(link("update", resourceLinks.user().update(user.getName())));
|
linksBuilder.single(link("update", resourceLinks.user().update(user.getName())));
|
||||||
linksBuilder.single(link("publicKeys", resourceLinks.user().publicKeys(user.getName())));
|
linksBuilder.single(link("publicKeys", resourceLinks.user().publicKeys(user.getName())));
|
||||||
linksBuilder.single(link("password", resourceLinks.user().passwordChange(user.getName())));
|
if (user.isExternal()) {
|
||||||
|
linksBuilder.single(link("convertToInternal", resourceLinks.user().toInternal(user.getName())));
|
||||||
|
} else {
|
||||||
|
linksBuilder.single(link("password", resourceLinks.user().passwordChange(user.getName())));
|
||||||
|
linksBuilder.single(link("convertToExternal", resourceLinks.user().toExternal(user.getName())));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (PermissionPermissions.read().isPermitted()) {
|
if (PermissionPermissions.read().isPermitted()) {
|
||||||
linksBuilder.single(link("permissions", resourceLinks.userPermissions().permissions(user.getName())));
|
linksBuilder.single(link("permissions", resourceLinks.userPermissions().permissions(user.getName())));
|
||||||
|
|||||||
@@ -90,14 +90,15 @@ public class UserToUserDtoMapperTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void shouldGetPasswordLinkForAdmin() {
|
public void shouldGetInternalUserLinks() {
|
||||||
User user = createDefaultUser();
|
User user = createDefaultUser();
|
||||||
|
user.setExternal(false);
|
||||||
when(subject.isPermitted("user:modify:abc")).thenReturn(true);
|
when(subject.isPermitted("user:modify:abc")).thenReturn(true);
|
||||||
when(userManager.isTypeDefault(eq(user))).thenReturn(true);
|
|
||||||
|
|
||||||
UserDto userDto = mapper.map(user);
|
UserDto userDto = mapper.map(user);
|
||||||
|
|
||||||
assertEquals("expected password link with modify permission", expectedBaseUri.resolve("abc/password").toString(), userDto.getLinks().getLinkBy("password").get().getHref());
|
assertEquals("expected password link with modify permission", expectedBaseUri.resolve("abc/password").toString(), userDto.getLinks().getLinkBy("password").get().getHref());
|
||||||
|
assertEquals("expected convert to external link with modify permission", expectedBaseUri.resolve("abc/convert-to-external").toString(), userDto.getLinks().getLinkBy("convertToExternal").get().getHref());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
Reference in New Issue
Block a user