Skip to content
Merged
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 @@ -12,12 +12,16 @@
import java.io.Serializable;
import net.sf.jsqlparser.expression.StringValue;

/** A user and optional host together with its authentication clause. */
/**
* A user and optional host together with its authentication clause. The current user may be named
* {@code CURRENT_USER} or {@code CURRENT_USER()}.
*/
public class UserAccount implements Serializable {

private StringValue user;
private StringValue host;
private UserAuthentication authentication;
private boolean currentUserParentheses;

public StringValue getUser() {
return user;
Expand All @@ -35,6 +39,15 @@ public void setHost(StringValue host) {
this.host = host;
}

/** Whether the user is written {@code CURRENT_USER()}. */
public boolean isCurrentUserParentheses() {
return currentUserParentheses;
}

public void setCurrentUserParentheses(boolean currentUserParentheses) {
this.currentUserParentheses = currentUserParentheses;
}

public UserAuthentication getAuthentication() {
return authentication;
}
Expand All @@ -46,6 +59,9 @@ public void setAuthentication(UserAuthentication authentication) {
@Override
public String toString() {
StringBuilder builder = new StringBuilder().append(user);
if (currentUserParentheses) {
builder.append("()");
}
if (host != null) {
builder.append("@").append(host);
}
Expand All @@ -65,6 +81,11 @@ public UserAccount withHost(StringValue host) {
return this;
}

public UserAccount withCurrentUserParentheses(boolean currentUserParentheses) {
setCurrentUserParentheses(currentUserParentheses);
return this;
}

public UserAccount withAuthentication(UserAuthentication authentication) {
setAuthentication(authentication);
return this;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,16 @@
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;

/**
* Structured authentication clause of a MySQL user account. {@code ALTER USER} adds
* {@code REPLACE 'current'} and {@code RETAIN CURRENT PASSWORD} to a password assignment, and
* {@code DISCARD OLD PASSWORD} as a mode of its own.
* {@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.
*/
public class UserAuthentication implements Serializable {

Expand All @@ -30,8 +34,8 @@ public boolean isPasswordAssignment() {
}

private Mode mode;
private String plugin;
private StringValue credential;
private StringValue pluginName;
private Expression credential;
private StringValue currentCredential;
private boolean retainCurrentPassword;

Expand All @@ -43,22 +47,52 @@ public void setMode(Mode mode) {
this.mode = mode;
}

/** The plugin name without its quotes, or null without {@code IDENTIFIED WITH}. */
public String getPlugin() {
return plugin;
return pluginName == null ? null : pluginName.getValue();
}

/** Sets an unquoted plugin name. */
public void setPlugin(String plugin) {
this.plugin = plugin;
this.pluginName =
plugin == null ? null : new StringValue().withValue(plugin).setQuoteStr("");
}

/** The plugin name with its quote character, which is empty for an unquoted name. */
public StringValue getPluginName() {
return pluginName;
}

public void setPluginName(StringValue pluginName) {
this.pluginName = pluginName;
}

/**
* The password or hash as a string literal, or null when there is none or the hash of
* {@code AS} is a hexadecimal literal; {@link #getCredentialValue()} returns either.
*/
public StringValue getCredential() {
return credential;
return credential instanceof StringValue ? (StringValue) credential : null;
}

public void setCredential(StringValue credential) {
this.credential = credential;
}

/** The password or hash as written: a {@link StringValue}, or a {@link HexValue} after AS. */
public Expression getCredentialValue() {
return credential;
}

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

/** The current password of {@code REPLACE 'current'}, or null when omitted. */
public StringValue getCurrentCredential() {
return currentCredential;
Expand Down Expand Up @@ -96,13 +130,13 @@ private String modeToString() {
case BY_RANDOM_PASSWORD:
return "IDENTIFIED BY RANDOM PASSWORD";
case WITH_PLUGIN:
return "IDENTIFIED WITH " + plugin;
return "IDENTIFIED WITH " + pluginName;
case WITH_PLUGIN_BY_PASSWORD:
return "IDENTIFIED WITH " + plugin + " BY " + credential;
return "IDENTIFIED WITH " + pluginName + " BY " + credential;
case WITH_PLUGIN_BY_RANDOM_PASSWORD:
return "IDENTIFIED WITH " + plugin + " BY RANDOM PASSWORD";
return "IDENTIFIED WITH " + pluginName + " BY RANDOM PASSWORD";
case WITH_PLUGIN_AS_STRING:
return "IDENTIFIED WITH " + plugin + " AS " + credential;
return "IDENTIFIED WITH " + pluginName + " AS " + credential;
case DISCARD_OLD_PASSWORD:
return "DISCARD OLD PASSWORD";
default:
Expand All @@ -120,11 +154,21 @@ public UserAuthentication withPlugin(String plugin) {
return this;
}

public UserAuthentication withPluginName(StringValue pluginName) {
setPluginName(pluginName);
return this;
}

public UserAuthentication withCredential(StringValue credential) {
setCredential(credential);
return this;
}

public UserAuthentication withCredentialValue(Expression credential) {
setCredentialValue(credential);
return this;
}

public UserAuthentication withCurrentCredential(StringValue currentCredential) {
setCurrentCredential(currentCredential);
return this;
Expand Down
57 changes: 42 additions & 15 deletions src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt
Original file line number Diff line number Diff line change
Expand Up @@ -2761,6 +2761,14 @@ public class CCJSqlParser extends AbstractJSqlParser<CCJSqlParser> {
if (!condition) { throw new ParseException(message); }
}

/** MySQL role lists name roles with role_ident, which does not take CURRENT_USER(). */
private static List<UserAccount> requireRoleNames(List<UserAccount> roles) throws ParseException {
for (UserAccount role : roles) {
requireAccessSyntax(!role.isCurrentUserParentheses(), "A role list does not take CURRENT_USER()");
}
return roles;
}

private static <E extends Enum<E>> E accessEnum(Class<E> type, String value) throws ParseException {
try { return Enum.valueOf(type, value.toUpperCase(Locale.ROOT)); }
catch (IllegalArgumentException ex) { throw new ParseException("Invalid " + type.getSimpleName() + ": " + value); }
Expand Down Expand Up @@ -2840,14 +2848,15 @@ public class CCJSqlParser extends AbstractJSqlParser<CCJSqlParser> {

/**
* Whether ALTER USER uses MySQL account syntax rather than the PostgreSQL alias of ALTER ROLE,
* which names one unquoted role followed by an action: IF EXISTS, USER(), a quoted name, a
* host, an account list, no action at all, or a MySQL account clause.
* which names one unquoted role followed by an action: IF EXISTS, USER(), CURRENT_USER(), a
* quoted name, a host, an account list, no action at all, or a MySQL account clause.
*/
private boolean isMySqlAlterUserAhead() {
if (getToken(1).kind != K_USER) { return false; }
Token name = getToken(2);
if (name.kind == K_IF || name.kind == S_CHAR_LITERAL
|| name.kind == K_USER && "(".equals(getToken(3).image)
|| (name.kind == K_USER || "CURRENT_USER".equalsIgnoreCase(name.image))
&& "(".equals(getToken(3).image)
|| isMySqlAccountHostAhead(2, false)) {
return true;
}
Expand Down Expand Up @@ -16735,6 +16744,11 @@ UserAccount MySqlAccountName():
|
part=MySqlAccountNamePart() { account.setUser(part); }
[
// CURRENT_USER() names the current user like CURRENT_USER; a quoted part has quotes in its image
LOOKAHEAD({ "CURRENT_USER".equalsIgnoreCase(getToken(0).image)
&& "(".equals(getToken(1).image) && ")".equals(getToken(2).image) })
"(" ")" { account.setCurrentUserParentheses(true); }
|
<K_AT_SIGN> part=MySqlAccountNamePart() { account.setHost(part); }
|
// a quoted user followed by an unquoted @host
Expand Down Expand Up @@ -16778,11 +16792,11 @@ RoleSelection MySqlRoleSelection(boolean setRole):
{ selection = new RoleSelection(RoleSelection.Kind.ALL); }
[
LOOKAHEAD({ getToken(1).kind == K_EXCEPT }) <K_EXCEPT> roles=MySqlAccountNameList()
{ selection.setKind(RoleSelection.Kind.ALL_EXCEPT); selection.setRoles(roles); }
{ selection.setKind(RoleSelection.Kind.ALL_EXCEPT); selection.setRoles(requireRoleNames(roles)); }
]
|
roles=MySqlAccountNameList()
{ selection = new RoleSelection(RoleSelection.Kind.ROLES).withRoles(roles); }
{ selection = new RoleSelection(RoleSelection.Kind.ROLES).withRoles(requireRoleNames(roles)); }
)
{
requireAccessSyntax(setRole || selection.getKind() != RoleSelection.Kind.DEFAULT
Expand All @@ -16792,10 +16806,14 @@ RoleSelection MySqlRoleSelection(boolean setRole):
}
}

/**
* IDENTIFIED BY or IDENTIFIED WITH. The plugin may be quoted like an account name part, and the
* hash after AS may be a hexadecimal literal, as SHOW CREATE USER prints both.
*/
UserAuthentication MySqlUserAuthentication():
{
UserAuthentication authentication = new UserAuthentication();
String plugin = null;
StringValue plugin = null;
Token credential = null;
}
{
Expand All @@ -16813,9 +16831,9 @@ UserAuthentication MySqlUserAuthentication():
}
)
|
<K_WITH> plugin=RelObjectName() {
<K_WITH> plugin=MySqlAccountNamePart() {
authentication.setMode(UserAuthentication.Mode.WITH_PLUGIN);
authentication.setPlugin(plugin);
authentication.setPluginName(plugin);
}
[
<K_BY>
Expand All @@ -16831,10 +16849,13 @@ UserAuthentication MySqlUserAuthentication():
}
)
|
<K_AS> credential=<S_CHAR_LITERAL> {
authentication.setMode(UserAuthentication.Mode.WITH_PLUGIN_AS_STRING);
authentication.setCredential(stringValue(credential));
}
<K_AS> { authentication.setMode(UserAuthentication.Mode.WITH_PLUGIN_AS_STRING); }
(
credential=<S_CHAR_LITERAL> { authentication.setCredential(stringValue(credential)); }
|
credential=<S_HEX>
{ authentication.setCredentialValue(new HexValue(credential.image)); }
)
]
)
{ return authentication; }
Expand Down Expand Up @@ -17142,7 +17163,7 @@ CreateRole MySqlCreateRole():
{
ContextualKeyword("ROLE")
[ LOOKAHEAD(3) <K_IF> <K_NOT> <K_EXISTS> { role.setIfNotExists(true); } ]
roles=MySqlAccountNameList() { role.setRoles(roles); }
roles=MySqlAccountNameList() { role.setRoles(requireRoleNames(roles)); }
{ return role; }
}

Expand Down Expand Up @@ -17181,7 +17202,8 @@ SetDefaultRoleStatement SetDefaultRoleStatement():
{ statement.setRoles(roles); }
[
( <K_TO> | <K_FOR> { statement.setForKeyword(true); } )
accounts=MySqlAccountNameList() { statement.setAccounts(accounts); }
// MySQL reads these accounts as a role list
accounts=MySqlAccountNameList() { statement.setAccounts(requireRoleNames(accounts)); }
]
{ return statement; }
}
Expand Down Expand Up @@ -19917,6 +19939,11 @@ void DropTargets(Drop drop):
LOOKAHEAD({ drop.getObjectType().hasAccountTargets() })
account=MySqlAccountName() { drop.addAccounts(account); }
( "," account=MySqlAccountName() { drop.addAccounts(account); } )*
{
if (drop.getObjectType() == Drop.ObjectType.ROLE) {
requireRoleNames(drop.getAccounts());
}
}
|
name=DropObjectName() { drop.addNames(name); }
( "," name=DropObjectName() { drop.addNames(name); } )*
Expand Down Expand Up @@ -23059,7 +23086,7 @@ PrivilegeClause AccessClause(boolean revoke, boolean defaults):
( "," privilege=AccessPrivilege() { clause.getPrivileges().add(privilege); } )*
<K_ON> target=AccessTarget(defaults) { clause.setTarget(target); }
|
roles=MySqlAccountNameList() { clause.setRoleItems(roles); clause.setTarget(null); }
roles=MySqlAccountNameList() { clause.setRoleItems(requireRoleNames(roles)); clause.setTarget(null); }
)
( <K_TO> { requireAccessSyntax(!revoke, "REVOKE requires FROM"); } | <K_FROM> { requireAccessSyntax(revoke, "GRANT requires TO"); } )
grantees=AccessGrantees() { clause.setGranteeItems(grantees); return clause; }
Expand Down
25 changes: 25 additions & 0 deletions src/test/java/net/sf/jsqlparser/statement/alter/AlterUserTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,31 @@ void replacesCurrentPassword() throws JSQLParserException {
assertTrue(authentication.isRetainCurrentPassword());
}

@Test
void namesTheCurrentUserWithParentheses() throws JSQLParserException {
AlterUser alterUser = parse("ALTER USER CURRENT_USER() IDENTIFIED BY 'x' "
+ "RETAIN CURRENT PASSWORD");
UserAccount account = alterUser.getAccounts().get(0);

assertEquals("CURRENT_USER", account.getUser().getValue());
assertTrue(account.isCurrentUserParentheses());
assertFalse(alterUser.isUserFunction());
assertFalse(parse("ALTER USER CURRENT_USER IDENTIFIED BY 'x'").getAccounts().get(0)
.isCurrentUserParentheses());
assertEquals("CURRENT_USER()", new UserAccount()
.withUser(new StringValue("CURRENT_USER").setQuoteStr(""))
.withCurrentUserParentheses(true).toString());
}

@ParameterizedTest
@ValueSource(strings = {"ALTER USER 'CURRENT_USER'() IDENTIFIED BY 'x'",
"ALTER USER app() IDENTIFIED BY 'x'",
"ALTER USER CURRENT_USER()@localhost ACCOUNT LOCK"})
void takesParenthesesOnlyAfterAnUnquotedCurrentUser(String sql) {
assertThrows(JSQLParserException.class,
() -> CCJSqlParserUtil.parse(sql, parser -> parser.withDialect(Dialect.MYSQL)));
}

@Test
void altersTheCurrentUserWithUserFunction() throws JSQLParserException {
AlterUser alterUser = parse("ALTER USER USER() IDENTIFIED BY 'x'");
Expand Down
Loading
Loading