Przeglądaj źródła

WIP. change password functionality

tags/v0.5.0
Jonathan Cobb 4 lat temu
rodzic
commit
f996f3697b
9 zmienionych plików z 127 dodań i 13 usunięć
  1. +14
    -1
      bubble-server/src/main/java/bubble/model/account/Account.java
  2. +7
    -0
      bubble-server/src/main/java/bubble/model/account/AccountPolicy.java
  3. +53
    -0
      bubble-server/src/main/java/bubble/resources/account/AccountsResource.java
  4. +7
    -3
      bubble-server/src/main/java/bubble/resources/account/AuthResource.java
  5. +32
    -3
      bubble-server/src/main/java/bubble/resources/account/MeResource.java
  6. +1
    -3
      bubble-server/src/main/java/bubble/service/account/StandardAccountMessageService.java
  7. +11
    -1
      bubble-server/src/main/resources/message_templates/en_US/server/post_auth/ResourceMessages.properties
  8. +1
    -1
      bubble-web
  9. +1
    -1
      utils/cobbzilla-wizard

+ 14
- 1
bubble-server/src/main/java/bubble/model/account/Account.java Wyświetl plik

@@ -237,7 +237,20 @@ public class Account extends IdentifiableBaseParentEntity implements TokenPrinci
public boolean sendWelcomeEmail() { return sendWelcomeEmail != null && sendWelcomeEmail; }

@Transient @Getter @Setter private transient String loginRequest;
@Transient @Getter @Setter private transient AccountContact[] multifactorAuth;
@Transient @Getter private transient AccountContact[] multifactorAuth;
public Account setMultifactorAuth(AccountContact[] mfa) {
if (!empty(mfa)) {
final AccountContact[] masked = new AccountContact[mfa.length];
for (int i=0; i<mfa.length; i++) masked[i] = mfa[i].mask();
this.multifactorAuth = masked;
} else {
this.multifactorAuth = null;
}
return this;
}
public Account setMultifactorAuthList (List<AccountContact> mfa) {
return setMultifactorAuth(empty(mfa) ? null : mfa.stream().map(AccountContact::mask).toArray(AccountContact[]::new));
}

@Transient @Getter @Setter private transient String remoteHost;
@Transient @JsonIgnore @Getter @Setter private transient Boolean verifyContact;


+ 7
- 0
bubble-server/src/main/java/bubble/model/account/AccountPolicy.java Wyświetl plik

@@ -141,6 +141,13 @@ public class AccountPolicy extends IdentifiableBase implements HasAccount {
}
}

public List<AccountContact> getRequiredExternalApprovals(AccountMessage message) {
final List<AccountContact> required = getRequiredApprovals(message);
return required.isEmpty() ? required : required.stream()
.filter(AccountContact::isNotAuthenticator)
.collect(Collectors.toList());
}

public List<AccountContact> requiredAuthFactors() {
return Arrays.stream(getAccountContacts())
.filter(AccountContact::requiredAuthFactor)


+ 53
- 0
bubble-server/src/main/java/bubble/resources/account/AccountsResource.java Wyświetl plik

@@ -1,6 +1,7 @@
package bubble.resources.account;

import bubble.cloud.CloudServiceType;
import bubble.dao.SessionDAO;
import bubble.dao.account.AccountDAO;
import bubble.dao.account.AccountPolicyDAO;
import bubble.dao.account.message.AccountMessageDAO;
@@ -26,6 +27,8 @@ import bubble.service.account.download.AccountDownloadService;
import bubble.service.boot.SelfNodeService;
import bubble.service.cloud.StandardNetworkService;
import lombok.extern.slf4j.Slf4j;
import org.cobbzilla.wizard.auth.ChangePasswordRequest;
import org.cobbzilla.wizard.model.HashedPassword;
import org.cobbzilla.wizard.validation.ConstraintViolationBean;
import org.cobbzilla.wizard.validation.ValidationResult;
import org.glassfish.grizzly.http.server.Request;
@@ -43,6 +46,7 @@ import java.util.Map;
import static bubble.ApiConstants.*;
import static bubble.model.account.Account.ADMIN_UPDATE_FIELDS;
import static bubble.model.account.Account.validatePassword;
import static bubble.resources.account.AuthResource.forgotPasswordMessage;
import static org.cobbzilla.util.http.HttpContentTypes.APPLICATION_JSON;
import static org.cobbzilla.wizard.resources.ResourceUtil.*;

@@ -60,6 +64,7 @@ public class AccountsResource {
@Autowired private AccountDownloadService downloadService;
@Autowired private AuthenticatorService authenticatorService;
@Autowired private SelfNodeService selfNodeService;
@Autowired private SessionDAO sessionDAO;

@GET
public Response list(@Context ContainerRequest ctx) {
@@ -310,6 +315,54 @@ public class AccountsResource {
.setRemoteHost(getRemoteHost(req))));
}


@POST @Path("/{id}"+EP_CHANGE_PASSWORD)
public Response rootChangePassword(@Context Request req,
@Context ContainerRequest ctx,
@PathParam("id") String id,
ChangePasswordRequest request) {
final AccountContext c = new AccountContext(ctx, id);
if (!c.caller.admin()) return forbidden();

final AccountPolicy policy = policyDAO.findSingleByAccount(c.account.getUuid());
if (policy != null && request.hasTotpToken()) {
authenticatorService.authenticate(c.account, policy, new AuthenticatorRequest()
.setAccount(c.account.getUuid())
.setAuthenticate(true)
.setToken(request.getTotpToken()));
}

if (c.caller.getUuid().equals(c.account.getUuid()) || c.account.admin()) {
if (policy != null) authenticatorService.ensureAuthenticated(ctx, policy, ActionTarget.account);
if (!c.account.getHashedPassword().isCorrectPassword(request.getOldPassword())) {
return invalid("err.currentPassword.invalid", "current password was invalid", "");
}
}

final ConstraintViolationBean passwordViolation = validatePassword(request.getNewPassword());
if (passwordViolation != null) return invalid(passwordViolation);

if (policy != null) {
final AccountMessage forgotPasswordMessage = forgotPasswordMessage(req, c.account, configuration);
final List<AccountContact> requiredApprovals = policy.getRequiredExternalApprovals(forgotPasswordMessage);
if (!requiredApprovals.isEmpty()) {
messageDAO.create(forgotPasswordMessage);
return ok(c.account.setMultifactorAuthList(requiredApprovals));
}
}

c.account.setHashedPassword(new HashedPassword(request.getNewPassword()));

// Update account
final Account updated = accountDAO.update(c.account);
if (c.caller.getUuid().equals(c.account.getUuid())) {
sessionDAO.update(c.caller.getApiToken(), updated);
} else {
sessionDAO.invalidateAllSessions(c.account.getUuid());
}
return ok(updated);
}

@DELETE @Path("/{id}")
public Response rootDeleteUser(@Context ContainerRequest ctx,
@PathParam("id") String id) {


+ 7
- 3
bubble-server/src/main/java/bubble/resources/account/AuthResource.java Wyświetl plik

@@ -262,15 +262,19 @@ public class AuthResource {
final Account account = accountDAO.findById(request.getName());
if (account == null) return ok();

accountMessageDAO.create(new AccountMessage()
accountMessageDAO.create(forgotPasswordMessage(req, account, configuration));
return ok();
}

public static AccountMessage forgotPasswordMessage(Request req, Account account, BubbleConfiguration configuration) {
return new AccountMessage()
.setAccount(account.getUuid())
.setNetwork(configuration.getThisNetwork().getUuid())
.setName(account.getUuid())
.setMessageType(AccountMessageType.request)
.setAction(AccountAction.password)
.setTarget(ActionTarget.account)
.setRemoteHost(getRemoteHost(req)));
return ok();
.setRemoteHost(getRemoteHost(req));
}

@POST @Path(EP_APPROVE+"/{token}")


+ 32
- 3
bubble-server/src/main/java/bubble/resources/account/MeResource.java Wyświetl plik

@@ -3,8 +3,11 @@ package bubble.resources.account;
import bubble.dao.SessionDAO;
import bubble.dao.account.AccountDAO;
import bubble.dao.account.AccountPolicyDAO;
import bubble.dao.account.message.AccountMessageDAO;
import bubble.model.account.Account;
import bubble.model.account.AccountContact;
import bubble.model.account.AccountPolicy;
import bubble.model.account.AuthenticatorRequest;
import bubble.model.account.message.AccountMessage;
import bubble.model.account.message.AccountMessageType;
import bubble.model.account.message.ActionTarget;
@@ -36,6 +39,7 @@ import org.cobbzilla.wizard.client.script.ApiRunnerListener;
import org.cobbzilla.wizard.client.script.ApiRunnerListenerStreamLogger;
import org.cobbzilla.wizard.client.script.ApiScript;
import org.cobbzilla.wizard.model.HashedPassword;
import org.cobbzilla.wizard.validation.ConstraintViolationBean;
import org.glassfish.grizzly.http.server.Request;
import org.glassfish.jersey.media.multipart.FormDataParam;
import org.glassfish.jersey.server.ContainerRequest;
@@ -49,9 +53,12 @@ import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringWriter;
import java.util.List;
import java.util.Locale;

import static bubble.ApiConstants.*;
import static bubble.model.account.Account.validatePassword;
import static bubble.resources.account.AuthResource.forgotPasswordMessage;
import static org.cobbzilla.util.daemon.ZillaRuntime.empty;
import static org.cobbzilla.util.daemon.ZillaRuntime.errorString;
import static org.cobbzilla.util.http.HttpContentTypes.*;
@@ -70,6 +77,7 @@ public class MeResource {
@Autowired private AccountDownloadService downloadService;
@Autowired private BubbleConfiguration configuration;
@Autowired private AuthenticatorService authenticatorService;
@Autowired private AccountMessageDAO messageDAO;

@GET
public Response me(@Context ContainerRequest ctx) {
@@ -104,13 +112,34 @@ public class MeResource {
}

@POST @Path(EP_CHANGE_PASSWORD)
public Response changePassword(@Context ContainerRequest ctx,
public Response changePassword(@Context Request req,
@Context ContainerRequest ctx,
ChangePasswordRequest request) {
final Account caller = userPrincipal(ctx);
authenticatorService.ensureAuthenticated(ctx, ActionTarget.account);

final AccountPolicy policy = policyDAO.findSingleByAccount(caller.getUuid());
if (policy != null && request.hasTotpToken()) {
authenticatorService.authenticate(caller, policy, new AuthenticatorRequest()
.setAccount(caller.getUuid())
.setAuthenticate(true)
.setToken(request.getTotpToken()));
}
if (policy != null) authenticatorService.ensureAuthenticated(ctx, ActionTarget.account);
if (!caller.getHashedPassword().isCorrectPassword(request.getOldPassword())) {
return invalid("err.oldPassword.invalid", "old password was invalid");
return invalid("err.currentPassword.invalid", "current password was invalid", "");
}
final ConstraintViolationBean passwordViolation = validatePassword(request.getNewPassword());
if (passwordViolation != null) return invalid(passwordViolation);

if (policy != null) {
final AccountMessage forgotPasswordMessage = forgotPasswordMessage(req, caller, configuration);
final List<AccountContact> requiredApprovals = policy.getRequiredExternalApprovals(forgotPasswordMessage);
if (!requiredApprovals.isEmpty()) {
messageDAO.create(forgotPasswordMessage);
return ok(caller.setMultifactorAuthList(requiredApprovals));
}
}

caller.setHashedPassword(new HashedPassword(request.getNewPassword()));

// Update account, and write back to session


+ 1
- 3
bubble-server/src/main/java/bubble/service/account/StandardAccountMessageService.java Wyświetl plik

@@ -259,8 +259,6 @@ public class StandardAccountMessageService implements AccountMessageService {
.collect(Collectors.toList());

// return masked list of contacts remaining to approve
return new Account().setMultifactorAuth(remainingApprovals.stream()
.map(AccountContact::mask)
.toArray(AccountContact[]::new));
return new Account().setMultifactorAuthList(remainingApprovals);
}
}

+ 11
- 1
bubble-server/src/main/resources/message_templates/en_US/server/post_auth/ResourceMessages.properties Wyświetl plik

@@ -156,6 +156,16 @@ button_label_create_account=Create Account
button_label_delete_account=Delete
button_label_force_delete_account=Force Delete

# Change Password page
form_title_change_password=Change Password
field_label_current_password=Current Password
field_label_new_password=New Password
field_label_new_password_confirm=Confirm New Password
button_label_change_password=Set New Password
button_label_request_password_reset=Request Password Reset
message_change_password_external_auth=Changing account password requires approval from these contacts on file:
message_change_password_authenticator_auth=Changing account password requires Authenticator password

# Networks table
loading_networks=Loading bubbles...
table_title_networks=Bubbles
@@ -548,7 +558,7 @@ err.node.notInitialized=Node is not initialized
err.node.running=Node must be stopped before deleting
err.node.shutdownFailed=Node shutdown failed
err.node.stop.error=Error stopping node
err.oldPassword.invalid=Old password was invalid
err.currentPassword.invalid=Current password was invalid
err.paymentInfo.invalid=Payment information is invalid
err.paymentInfo.required=Payment information is required
err.paymentInfo.processingError=Processing payment information failed


+ 1
- 1
bubble-web

@@ -1 +1 @@
Subproject commit 7b0bc74bd5923465c6d99ea27ed7904c3078a9e5
Subproject commit 689734e6c9fa7a51cbe19b47105520d0801c47f0

+ 1
- 1
utils/cobbzilla-wizard

@@ -1 +1 @@
Subproject commit 661578b96b67dd02c0e7540843ac8313bc6e8777
Subproject commit 1c4d29aef889254e046e781378e35012853d32a6

Ładowanie…
Anuluj
Zapisz