Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,10 @@
* {@link UserOptions} are those of {@link net.sf.jsqlparser.statement.create.user.CreateUser}.
* {@code ALTER USER acct DEFAULT ROLE ...} keeps its roles in
* {@link UserOptions#getDefaultRoles()}. {@code ALTER USER USER() ...} names the current user with
* the unquoted account name {@code USER()}, see {@link #isUserFunction()}.
* the unquoted account name {@code USER()}, see {@link #isUserFunction()}. Multi-factor
* authentication changes ({@code ADD}, {@code MODIFY}, {@code DROP} of {@code 2 FACTOR} or
* {@code 3 FACTOR}) and the registration steps of one account are the
* {@link UserAccount#getFactorOperations()} of the account.
*/
public class AlterUser extends ASTNodeAccessImpl implements Statement {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,27 @@
package net.sf.jsqlparser.statement.create.user;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import net.sf.jsqlparser.expression.StringValue;

/**
* A user and optional host together with its authentication clause. The current user may be named
* {@code CURRENT_USER} or {@code CURRENT_USER()}.
* {@code CURRENT_USER} or {@code CURRENT_USER()}. MySQL multi-factor authentication adds the second
* and third factor of {@code CREATE USER ... IDENTIFIED ... AND IDENTIFIED ...} in
* {@link #getAdditionalAuthentications()}, and the factor changes and registration steps of
* {@code ALTER USER} in {@link #getFactorOperations()}.
*/
public class UserAccount implements Serializable {

private StringValue user;
private StringValue host;
private UserAuthentication authentication;
private boolean currentUserParentheses;
private final List<UserAuthentication> additionalAuthentications = new ArrayList<>();
private final List<UserFactorOperation> factorOperations = new ArrayList<>();

public StringValue getUser() {
return user;
Expand Down Expand Up @@ -56,6 +65,38 @@ public void setAuthentication(UserAuthentication authentication) {
this.authentication = authentication;
}

/**
* The authentication of the second and third factor, each written {@code AND IDENTIFIED ...}
* after the first one in {@code CREATE USER}; empty, never null, when none are given.
*/
public List<UserAuthentication> getAdditionalAuthentications() {
return Collections.unmodifiableList(additionalAuthentications);
}

public void setAdditionalAuthentications(
Collection<? extends UserAuthentication> additionalAuthentications) {
this.additionalAuthentications.clear();
if (additionalAuthentications != null) {
this.additionalAuthentications.addAll(additionalAuthentications);
}
}

/**
* The {@code ADD}, {@code MODIFY} and {@code DROP} factor changes, or the single registration
* step, of an {@code ALTER USER} account in source order; empty, never null, when none are
* given.
*/
public List<UserFactorOperation> getFactorOperations() {
return Collections.unmodifiableList(factorOperations);
}

public void setFactorOperations(Collection<? extends UserFactorOperation> factorOperations) {
this.factorOperations.clear();
if (factorOperations != null) {
this.factorOperations.addAll(factorOperations);
}
}

@Override
public String toString() {
StringBuilder builder = new StringBuilder().append(user);
Expand All @@ -68,6 +109,12 @@ public String toString() {
if (authentication != null) {
builder.append(" ").append(authentication);
}
for (UserAuthentication additional : additionalAuthentications) {
builder.append(" AND ").append(additional);
}
for (UserFactorOperation operation : factorOperations) {
builder.append(" ").append(operation);
}
return builder.toString();
}

Expand All @@ -90,4 +137,27 @@ public UserAccount withAuthentication(UserAuthentication authentication) {
setAuthentication(authentication);
return this;
}

public UserAccount withAdditionalAuthentications(
Collection<? extends UserAuthentication> additionalAuthentications) {
setAdditionalAuthentications(additionalAuthentications);
return this;
}

public UserAccount addAdditionalAuthentications(
UserAuthentication... additionalAuthentications) {
Collections.addAll(this.additionalAuthentications, additionalAuthentications);
return this;
}

public UserAccount withFactorOperations(
Collection<? extends UserFactorOperation> factorOperations) {
setFactorOperations(factorOperations);
return this;
}

public UserAccount addFactorOperations(UserFactorOperation... factorOperations) {
Collections.addAll(this.factorOperations, factorOperations);
return this;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@
* {@code REPLACE 'current'} and {@code RETAIN CURRENT PASSWORD} to a password assignment, and
* {@code DISCARD OLD PASSWORD} as a mode of its own. The plugin name keeps its quote character, as
* in {@code IDENTIFIED WITH 'caching_sha2_password'}, and the hash of {@code AS} may be a
* hexadecimal literal, as SHOW CREATE USER prints it.
* hexadecimal literal, as SHOW CREATE USER prints it. {@code CREATE USER} may give a passwordless
* plugin a temporary first factor, {@code IDENTIFIED WITH plugin INITIAL AUTHENTICATION IDENTIFIED
* ...}, kept in {@link #getInitialAuthentication()}.
*/
public class UserAuthentication implements Serializable {

Expand All @@ -38,6 +40,7 @@ public boolean isPasswordAssignment() {
private Expression credential;
private StringValue currentCredential;
private boolean retainCurrentPassword;
private UserAuthentication initialAuthentication;

public Mode getMode() {
return mode;
Expand Down Expand Up @@ -111,9 +114,26 @@ public void setRetainCurrentPassword(boolean retainCurrentPassword) {
this.retainCurrentPassword = retainCurrentPassword;
}

/**
* The authentication after {@code INITIAL AUTHENTICATION}, which registers the device of a
* passwordless plugin, or null when omitted. MySQL writes it as {@code IDENTIFIED BY 'auth'},
* {@code IDENTIFIED BY RANDOM PASSWORD} or {@code IDENTIFIED WITH plugin AS 'hash'}, after
* {@code IDENTIFIED WITH plugin} only.
*/
public UserAuthentication getInitialAuthentication() {
return initialAuthentication;
}

public void setInitialAuthentication(UserAuthentication initialAuthentication) {
this.initialAuthentication = initialAuthentication;
}

@Override
public String toString() {
StringBuilder builder = new StringBuilder(modeToString());
if (initialAuthentication != null) {
builder.append(" INITIAL AUTHENTICATION ").append(initialAuthentication);
}
if (currentCredential != null) {
builder.append(" REPLACE ").append(currentCredential);
}
Expand Down Expand Up @@ -178,4 +198,10 @@ public UserAuthentication withRetainCurrentPassword(boolean retainCurrentPasswor
setRetainCurrentPassword(retainCurrentPassword);
return this;
}

public UserAuthentication withInitialAuthentication(
UserAuthentication initialAuthentication) {
setInitialAuthentication(initialAuthentication);
return this;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
/*-
* #%L
* JSQLParser library
* %%
* Copyright (C) 2004 - 2026 JSQLParser
* %%
* Dual licensed under GNU LGPL 2.1 or Apache License 2.0
* #L%
*/
package net.sf.jsqlparser.statement.create.user;

import java.io.Serializable;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.expression.HexValue;
import net.sf.jsqlparser.expression.StringValue;

/**
* One multi-factor authentication change of a MySQL {@code ALTER USER} account:
* {@code ADD factor IDENTIFIED ...}, {@code MODIFY factor IDENTIFIED ...}, {@code DROP factor}, or
* a registration step of a passwordless device, {@code factor INITIATE REGISTRATION},
* {@code factor FINISH REGISTRATION SET CHALLENGE_RESPONSE AS '...'} and {@code factor UNREGISTER}.
* The factor is written {@code 2 FACTOR} or {@code 3 FACTOR}; the server checks the number when it
* executes the statement.
*/
public class UserFactorOperation implements Serializable {

public enum Operation {
ADD, MODIFY, DROP, INITIATE_REGISTRATION, FINISH_REGISTRATION, UNREGISTER;

/** Whether an {@code IDENTIFIED} clause follows the factor, as for ADD and MODIFY. */
public boolean hasAuthentication() {
return this == ADD || this == MODIFY;
}

/** Whether this is a registration step, written after the factor. */
public boolean isRegistration() {
return this == INITIATE_REGISTRATION || this == FINISH_REGISTRATION
|| this == UNREGISTER;
}
}

private Operation operation;
private int factor;
private UserAuthentication authentication;
private Expression challengeResponse;

public UserFactorOperation() {
// for the fluent setters
}

public UserFactorOperation(Operation operation, int factor) {
this.operation = operation;
this.factor = factor;
}

public Operation getOperation() {
return operation;
}

public void setOperation(Operation operation) {
this.operation = operation;
}

/** The factor number of {@code n FACTOR}: 2 or 3 in a statement the server executes. */
public int getFactor() {
return factor;
}

public void setFactor(int factor) {
this.factor = factor;
}

/** The authentication of the added or modified factor, or null for the other operations. */
public UserAuthentication getAuthentication() {
return authentication;
}

public void setAuthentication(UserAuthentication authentication) {
this.authentication = authentication;
}

/**
* The value after {@code SET CHALLENGE_RESPONSE AS} of FINISH REGISTRATION: a
* {@link StringValue} or a {@link HexValue}, or null for the other operations.
*/
public Expression getChallengeResponse() {
return challengeResponse;
}

public void setChallengeResponse(Expression challengeResponse) {
if (challengeResponse != null && !(challengeResponse instanceof StringValue)
&& !(challengeResponse instanceof HexValue)) {
throw new IllegalArgumentException(
"A challenge response is a string or hexadecimal literal: "
+ challengeResponse);
}
this.challengeResponse = challengeResponse;
}

@Override
public String toString() {
String factorClause = factor + " FACTOR";
switch (operation) {
case ADD:
case MODIFY:
return operation + " " + factorClause + " " + authentication;
case DROP:
return "DROP " + factorClause;
case INITIATE_REGISTRATION:
return factorClause + " INITIATE REGISTRATION";
case FINISH_REGISTRATION:
return factorClause + " FINISH REGISTRATION SET CHALLENGE_RESPONSE AS "
+ challengeResponse;
case UNREGISTER:
return factorClause + " UNREGISTER";
default:
throw new IllegalStateException("Unsupported factor operation: " + operation);
}
}

public UserFactorOperation withOperation(Operation operation) {
setOperation(operation);
return this;
}

public UserFactorOperation withFactor(int factor) {
setFactor(factor);
return this;
}

public UserFactorOperation withAuthentication(UserAuthentication authentication) {
setAuthentication(authentication);
return this;
}

public UserFactorOperation withChallengeResponse(Expression challengeResponse) {
setChallengeResponse(challengeResponse);
return this;
}
}
Loading
Loading