content: /*
   * This file is part of Cockpit.
   *
   * Copyright (C) 2020 Red Hat, Inc.
   *
   * Cockpit is free software; you can redistribute it and/or modify it
   * under the terms of the GNU Lesser General Public License as published by
   * the Free Software Foundation; either version 2.1 of the License, or
   * (at your option) any later version.
   *
   * Cockpit is distributed in the hope that it will be useful, but
   * WITHOUT ANY WARRANTY; without even the implied warranty of
   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
   * Lesser General Public License for more details.
   *
   * You should have received a copy of the GNU Lesser General Public License
   * along with Cockpit; If not, see <http://www.gnu.org/licenses/>.
   */
  
  import cockpit from 'cockpit';
  import React from 'react';
  import { superuser } from "superuser";
  import { Form, FormGroup, TextInput } from '@patternfly/react-core';
  
  import { has_errors } from "./dialog-utils.js";
  import { show_modal_dialog, apply_modal_dialog } from "cockpit-components-dialog.jsx";
  import { password_quality, PasswordFormFields } from "cockpit-components-password.jsx";
  
  const _ = cockpit.gettext;
  
  function passwd_self(old_pass, new_pass) {
      var old_exps = [
          /Current password: $/,
          /Current Password: $/,
          /.*\(current\) UNIX password: $/,
      ];
      var new_exps = [
          /.*New password: $/,
          /.*Retype new password: $/,
          /.*Enter new \w*\s?password: $/,
          /.*Retype new \w*\s?password: $/
      ];
      var bad_exps = [
          /.*BAD PASSWORD:.*/
      ];
      var too_new_exps = [
          /.*must wait longer to change.*/
      ];
  
      return new Promise((resolve, reject) => {
          var buffer = "";
          var sent_new = false;
          var failure = _("Old password not accepted");
          var i;
  
          var proc;
          var timeout = window.setTimeout(function() {
              failure = _("Prompting via passwd timed out");
              proc.close("timeout");
          }, 10 * 1000);
  
          proc = cockpit.spawn(["/usr/bin/passwd"], { pty: true, environ: ["LC_ALL=C"], err: "out" })
                  .always(function() {
                      window.clearInterval(timeout);
                  })
                  .done(function() {
                      resolve();
                  })
                  .fail(function(ex) {
                      if (ex.exit_status || ex.problem == "timeout")
                          ex = new Error(failure);
                      reject(ex);
                  })
                  .stream(function(data) {
                      buffer += data;
  
                      for (i = 0; i < too_new_exps.length; i++) {
                          if (too_new_exps[i].test(buffer)) {
                              failure = _("You must wait longer to change your password");
                          }
                      }
  
                      if (sent_new) {
                          for (i = 0; i < bad_exps.length; i++) {
                              if (bad_exps[i].test(buffer)) {
                                  failure = _("New password was not accepted");
                              }
                          }
                      }
  
                      for (i = 0; i < old_exps.length; i++) {
                          if (old_exps[i].test(buffer)) {
                              buffer = "";
                              this.input(old_pass + "\n", true);
                              return;
                          }
                      }
  
                      for (i = 0; i < new_exps.length; i++) {
                          if (new_exps[i].test(buffer)) {
                              buffer = "";
                              this.input(new_pass + "\n", true);
                              failure = _("Failed to change password");
                              sent_new = true;
                              return;
                          }
                      }
                  });
      });
  }
  
  export function passwd_change(user, new_pass) {
      return new Promise((resolve, reject) => {
          cockpit.spawn(["chpasswd"], { superuser: "require", err: "out" })
                  .input(user + ":" + new_pass)
                  .done(function() {
                      resolve();
                  })
                  .fail(function(ex, response) {
                      if (ex.exit_status) {
                          console.log(ex);
                          if (response)
                              ex = new Error(response);
                          else
                              ex = new Error(_("Failed to change password"));
                      }
                      reject(ex);
                  });
      });
  }
  
  function SetPasswordDialogBody({ state, errors, change }) {
      const {
          need_old, password_old, password, password_confirm,
          password_strength, password_message
      } = state;
  
      return (
          <Form isHorizontal onSubmit={apply_modal_dialog}>
              { need_old &&
              <FormGroup label={_("Old password")}
                         helperTextInvalid={errors && errors.password_old}
                         validated={(errors && errors.password_old) ? "error" : "default"}
                         fieldId="account-set-password-old">
                  <TextInput className="check-passwords" type="password" id="account-set-password-old"
                             value={password_old} onChange={value => change("password_old", value)} />
              </FormGroup> }
              <PasswordFormFields password={password}
                                  password_confirm={password_confirm}
                                  password_label={_("New password")}
                                  password_confirm_label={_("Confirm new password")}
                                  password_strength={password_strength}
                                  password_message={password_message}
                                  error_password={errors && errors.password}
                                  error_password_confirm={errors && errors.password_confirm}
                                  idPrefix="account-set-password"
                                  change={change} />
          </Form>
      );
  }
  
  export function set_password_dialog(account, current_user) {
      let dlg = null;
  
      const change_self = (account.name == current_user && !superuser.allowed);
  
      const state = {
          need_old: change_self,
          password_old: "",
          password: "",
          password_confirm: "",
          password_strength: "",
          confirm_weak: false,
      };
  
      let errors = { };
  
      let old_password = null;
  
      function change(field, value) {
          state[field] = value;
  
          if (state.password != old_password) {
              state.confirm_weak = false;
              old_password = state.password;
              if (state.password) {
                  password_quality(state.password)
                          .catch(ex => {
                              return { value: 0 };
                          })
                          .then(strength => {
                              state.password_strength = strength.value;
                              state.password_message = strength.message;
                              update();
                          });
              } else {
                  state.password_strength = "";
              }
          }
  
          update();
      }
  
      function validate(force) {
          errors = { };
  
          if (state.password != state.password_confirm)
              errors.password_confirm = _("The passwords do not match");
  
          return password_quality(state.password, force)
                  .catch(ex => {
                      errors.password = (ex.message || ex.toString()).replace("\n", " ");
                      errors.password += "\n" + cockpit.format(_("Click $0 again to use the password anyway."), _("Set password"));
                  })
                  .then(() => {
                      return !has_errors(errors);
                  });
      }
  
      function update() {
          const props = {
              id: "account-set-password-dialog",
              title: _("Set password"),
              body: <SetPasswordDialogBody state={state} errors={errors} change={change} />
          };
  
          const footer = {
              actions: [
                  {
                      caption: _("Set password"),
                      style: "primary",
                      clicked: () => {
                          const second_click = state.confirm_weak;
                          state.confirm_weak = !state.confirm_weak;
  
                          return validate(second_click).then(valid => {
                              if (valid) {
                                  if (change_self)
                                      return passwd_self(state.password_old, state.password);
                                  else
                                      return passwd_change(account.name, state.password);
                              } else {
                                  update();
                                  return Promise.reject();
                              }
                          });
                      }
                  }
              ]
          };
  
          if (!dlg)
              dlg = show_modal_dialog(props, footer);
          else {
              dlg.setProps(props);
              dlg.setFooterProps(footer);
          }
      }
  
      update();
  }
  
  export function reset_password_dialog(account) {
      var msg = cockpit.format(_("The account '$0' will be forced to change their password on next login"),
                               account.name);
  
      const props = {
          id: "password-reset",
          title: _("Force password change"),
          body: <p>{msg}</p>
      };
  
      const footer = {
          actions: [
              {
                  caption: _("Reset password"),
                  style: "primary",
                  clicked: () => {
                      return cockpit.spawn(["/usr/bin/passwd", "-e", account.name],
                                           { superuser : true, err: "message" });
                  }
              }
          ]
      };
  
      show_modal_dialog(props, footer);
  }
  