Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Disable API Key Access for users, accounts and domains #9741

Draft
wants to merge 15 commits into
base: main
Choose a base branch
from
Draft
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
4 changes: 4 additions & 0 deletions api/src/main/java/com/cloud/user/Account.java
Original file line number Diff line number Diff line change
Expand Up @@ -93,4 +93,8 @@ public static Type getFromValue(Integer type){

boolean isDefault();

public void setApiKeyAccess(Boolean apiKeyAccess);

public Boolean getApiKeyAccess();

}
5 changes: 5 additions & 0 deletions api/src/main/java/com/cloud/user/User.java
Original file line number Diff line number Diff line change
Expand Up @@ -94,4 +94,9 @@ public enum Source {
public boolean isUser2faEnabled();

public String getKeyFor2fa();

public void setApiKeyAccess(Boolean apiKeyAccess);

public Boolean getApiKeyAccess();

}
27 changes: 27 additions & 0 deletions api/src/main/java/org/apache/cloudstack/api/ApiConstants.java
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
public static final String ALLOW_USER_FORCE_STOP_VM = "allowuserforcestopvm";
public static final String ANNOTATION = "annotation";
public static final String API_KEY = "apikey";
public static final String API_KEY_ACCESS = "apikeyaccess";
public static final String ARCHIVED = "archived";
public static final String ARCH = "arch";
public static final String AS_NUMBER = "asnumber";
Expand Down Expand Up @@ -1238,4 +1239,30 @@
public enum DomainDetails {
all, resource, min;
}

public enum ApiKeyAccess {
DISABLED(false),
ENABLED(true),
INHERIT(null);

Boolean apiKeyAccess;

ApiKeyAccess(Boolean keyAccess) {
apiKeyAccess = keyAccess;
}

public Boolean toBoolean() {

Check warning on line 1254 in api/src/main/java/org/apache/cloudstack/api/ApiConstants.java

View check run for this annotation

Codecov / codecov/patch

api/src/main/java/org/apache/cloudstack/api/ApiConstants.java#L1254

Added line #L1254 was not covered by tests
return apiKeyAccess;
}

Check warning on line 1256 in api/src/main/java/org/apache/cloudstack/api/ApiConstants.java

View check run for this annotation

Codecov / codecov/patch

api/src/main/java/org/apache/cloudstack/api/ApiConstants.java#L1256

Added line #L1256 was not covered by tests

public static ApiKeyAccess fromBoolean(Boolean value) {

Check warning on line 1258 in api/src/main/java/org/apache/cloudstack/api/ApiConstants.java

View check run for this annotation

Codecov / codecov/patch

api/src/main/java/org/apache/cloudstack/api/ApiConstants.java#L1258

Added line #L1258 was not covered by tests
if (value == null) {
return INHERIT;

Check warning on line 1260 in api/src/main/java/org/apache/cloudstack/api/ApiConstants.java

View check run for this annotation

Codecov / codecov/patch

api/src/main/java/org/apache/cloudstack/api/ApiConstants.java#L1260

Added line #L1260 was not covered by tests
} else if (value) {
return ENABLED;

Check warning on line 1262 in api/src/main/java/org/apache/cloudstack/api/ApiConstants.java

View check run for this annotation

Codecov / codecov/patch

api/src/main/java/org/apache/cloudstack/api/ApiConstants.java#L1262

Added line #L1262 was not covered by tests
} else {
return DISABLED;

Check warning on line 1264 in api/src/main/java/org/apache/cloudstack/api/ApiConstants.java

View check run for this annotation

Codecov / codecov/patch

api/src/main/java/org/apache/cloudstack/api/ApiConstants.java#L1264

Added line #L1264 was not covered by tests
}
}

Check warning on line 1266 in api/src/main/java/org/apache/cloudstack/api/ApiConstants.java

View check run for this annotation

Codecov / codecov/patch

api/src/main/java/org/apache/cloudstack/api/ApiConstants.java#L1266

Added line #L1266 was not covered by tests
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@

import javax.inject.Inject;

import org.apache.cloudstack.acl.RoleType;
import org.apache.cloudstack.api.ApiCommandResourceType;
import org.apache.cloudstack.api.response.RoleResponse;

Expand Down Expand Up @@ -70,6 +71,9 @@
@Parameter(name = ApiConstants.ACCOUNT_DETAILS, type = CommandType.MAP, description = "Details for the account used to store specific parameters")
private Map details;

@Parameter(name = ApiConstants.API_KEY_ACCESS, type = CommandType.STRING, description = "Determines if Api key access for this user is enabled, disabled or inherits the value from its parent, the domain level setting api.key.access", since = "4.20.1.0", authorized = {RoleType.Admin})
private String apiKeyAccess;

@Inject
RegionService _regionService;

Expand Down Expand Up @@ -109,6 +113,10 @@
return params;
}

public String getApiKeyAccess() {
return apiKeyAccess;
}

Check warning on line 118 in api/src/main/java/org/apache/cloudstack/api/command/admin/account/UpdateAccountCmd.java

View check run for this annotation

Codecov / codecov/patch

api/src/main/java/org/apache/cloudstack/api/command/admin/account/UpdateAccountCmd.java#L116-L118

Added lines #L116 - L118 were not covered by tests

/////////////////////////////////////////////////////
/////////////// API Implementation///////////////////
/////////////////////////////////////////////////////
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import com.cloud.server.ResourceIcon;
import com.cloud.server.ResourceTag;
import com.cloud.user.Account;
import org.apache.cloudstack.acl.RoleType;
import org.apache.cloudstack.api.response.ResourceIconResponse;

import org.apache.cloudstack.api.APICommand;
Expand Down Expand Up @@ -53,6 +54,9 @@
@Parameter(name = ApiConstants.USERNAME, type = CommandType.STRING, description = "List user by the username")
private String username;

@Parameter(name = ApiConstants.API_KEY_ACCESS, type = CommandType.STRING, description = "List users by the Api key access value", since = "4.20.1.0", authorized = {RoleType.Admin})
private String apiKeyAccess;

@Parameter(name = ApiConstants.SHOW_RESOURCE_ICON, type = CommandType.BOOLEAN,
description = "flag to display the resource icon for users")
private Boolean showIcon;
Expand All @@ -77,6 +81,10 @@
return username;
}

public String getApiKeyAccess() {
return apiKeyAccess;
}

Check warning on line 86 in api/src/main/java/org/apache/cloudstack/api/command/admin/user/ListUsersCmd.java

View check run for this annotation

Codecov / codecov/patch

api/src/main/java/org/apache/cloudstack/api/command/admin/user/ListUsersCmd.java#L84-L86

Added lines #L84 - L86 were not covered by tests

public Boolean getShowIcon() {
return showIcon != null ? showIcon : false;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

import javax.inject.Inject;

import org.apache.cloudstack.acl.RoleType;
import org.apache.cloudstack.api.APICommand;
import org.apache.cloudstack.api.ApiCommandResourceType;
import org.apache.cloudstack.api.ApiConstants;
Expand Down Expand Up @@ -69,6 +70,9 @@
@Parameter(name = ApiConstants.USER_SECRET_KEY, type = CommandType.STRING, description = "The secret key for the user. Must be specified with userApiKey")
private String secretKey;

@Parameter(name = ApiConstants.API_KEY_ACCESS, type = CommandType.STRING, description = "Determines if Api key access for this user is enabled, disabled or inherits the value from its parent, the owning account", since = "4.20.1.0", authorized = {RoleType.Admin})
private String apiKeyAccess;

@Parameter(name = ApiConstants.TIMEZONE,
type = CommandType.STRING,
description = "Specifies a timezone for this command. For more information on the timezone parameter, see Time Zone Format.")
Expand Down Expand Up @@ -120,6 +124,10 @@
return secretKey;
}

public String getApiKeyAccess() {
return apiKeyAccess;
}

Check warning on line 129 in api/src/main/java/org/apache/cloudstack/api/command/admin/user/UpdateUserCmd.java

View check run for this annotation

Codecov / codecov/patch

api/src/main/java/org/apache/cloudstack/api/command/admin/user/UpdateUserCmd.java#L127-L129

Added lines #L127 - L129 were not covered by tests

public String getTimezone() {
return timezone;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import java.util.EnumSet;
import java.util.List;

import org.apache.cloudstack.acl.RoleType;
import org.apache.cloudstack.api.APICommand;
import org.apache.cloudstack.api.ApiCommandResourceType;
import org.apache.cloudstack.api.ApiConstants;
Expand Down Expand Up @@ -70,6 +71,9 @@
description = "comma separated list of account details requested, value can be a list of [ all, resource, min]")
private List<String> viewDetails;

@Parameter(name = ApiConstants.API_KEY_ACCESS, type = CommandType.STRING, description = "List accounts by the Api key access value", since = "4.20.1.0", authorized = {RoleType.Admin})
private String apiKeyAccess;

@Parameter(name = ApiConstants.SHOW_RESOURCE_ICON, type = CommandType.BOOLEAN,
description = "flag to display the resource icon for accounts")
private Boolean showIcon;
Expand Down Expand Up @@ -120,6 +124,10 @@
return dv;
}

public String getApiKeyAccess() {
return apiKeyAccess;
}

Check warning on line 129 in api/src/main/java/org/apache/cloudstack/api/command/user/account/ListAccountsCmd.java

View check run for this annotation

Codecov / codecov/patch

api/src/main/java/org/apache/cloudstack/api/command/user/account/ListAccountsCmd.java#L127-L129

Added lines #L127 - L129 were not covered by tests

public boolean getShowIcon() {
return showIcon != null ? showIcon : false;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,10 @@
@Param(description = "The tagged resource limit and count for the account", since = "4.20.0")
List<TaggedResourceLimitAndCountResponse> taggedResources;

@SerializedName(ApiConstants.API_KEY_ACCESS)
@Param(description = "whether api key access is Enabled, Disabled or set to Inherit (it inherits the value from the parent)", since = "4.20.1.0")
ApiConstants.ApiKeyAccess apiKeyAccess;

@Override
public String getObjectId() {
return id;
Expand Down Expand Up @@ -554,4 +558,8 @@
public void setTaggedResourceLimitsAndCounts(List<TaggedResourceLimitAndCountResponse> taggedResourceLimitsAndCounts) {
this.taggedResources = taggedResourceLimitsAndCounts;
}

public void setApiKeyAccess(Boolean apiKeyAccess) {
this.apiKeyAccess = ApiConstants.ApiKeyAccess.fromBoolean(apiKeyAccess);
}

Check warning on line 564 in api/src/main/java/org/apache/cloudstack/api/response/AccountResponse.java

View check run for this annotation

Codecov / codecov/patch

api/src/main/java/org/apache/cloudstack/api/response/AccountResponse.java#L562-L564

Added lines #L562 - L564 were not covered by tests
}
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,10 @@
@Param(description = "true if user has two factor authentication is mandated", since = "4.18.0.0")
private Boolean is2FAmandated;

@SerializedName(ApiConstants.API_KEY_ACCESS)
@Param(description = "whether api key access is Enabled, Disabled or set to Inherit (it inherits the value from the parent)", since = "4.20.1.0")
ApiConstants.ApiKeyAccess apiKeyAccess;

@Override
public String getObjectId() {
return this.getId();
Expand Down Expand Up @@ -309,4 +313,8 @@
public void set2FAmandated(Boolean is2FAmandated) {
this.is2FAmandated = is2FAmandated;
}

public void setApiKeyAccess(Boolean apiKeyAccess) {
this.apiKeyAccess = ApiConstants.ApiKeyAccess.fromBoolean(apiKeyAccess);
}

Check warning on line 319 in api/src/main/java/org/apache/cloudstack/api/response/UserResponse.java

View check run for this annotation

Codecov / codecov/patch

api/src/main/java/org/apache/cloudstack/api/response/UserResponse.java#L317-L319

Added lines #L317 - L319 were not covered by tests
}
13 changes: 13 additions & 0 deletions engine/schema/src/main/java/com/cloud/user/AccountVO.java
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,9 @@
@Column(name = "default")
boolean isDefault;

@Column(name = "api_key_access")
private Boolean apiKeyAccess;

public AccountVO() {
uuid = UUID.randomUUID().toString();
}
Expand Down Expand Up @@ -229,4 +232,14 @@
public String reflectionToString() {
return ReflectionToStringBuilderUtils.reflectOnlySelectedFields(this, "id", "uuid", "accountName", "domainId");
}

@Override
public void setApiKeyAccess(Boolean apiKeyAccess) {
this.apiKeyAccess = apiKeyAccess;
}

Check warning on line 239 in engine/schema/src/main/java/com/cloud/user/AccountVO.java

View check run for this annotation

Codecov / codecov/patch

engine/schema/src/main/java/com/cloud/user/AccountVO.java#L237-L239

Added lines #L237 - L239 were not covered by tests

@Override
public Boolean getApiKeyAccess() {

Check warning on line 242 in engine/schema/src/main/java/com/cloud/user/AccountVO.java

View check run for this annotation

Codecov / codecov/patch

engine/schema/src/main/java/com/cloud/user/AccountVO.java#L242

Added line #L242 was not covered by tests
return apiKeyAccess;
}

Check warning on line 244 in engine/schema/src/main/java/com/cloud/user/AccountVO.java

View check run for this annotation

Codecov / codecov/patch

engine/schema/src/main/java/com/cloud/user/AccountVO.java#L244

Added line #L244 was not covered by tests
}
12 changes: 12 additions & 0 deletions engine/schema/src/main/java/com/cloud/user/UserVO.java
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,9 @@
@Column(name = "key_for_2fa")
private String keyFor2fa;

@Column(name = "api_key_access")
private Boolean apiKeyAccess;

public UserVO() {
this.uuid = UUID.randomUUID().toString();
}
Expand Down Expand Up @@ -350,4 +353,13 @@
this.user2faProvider = user2faProvider;
}

@Override
public void setApiKeyAccess(Boolean apiKeyAccess) {
this.apiKeyAccess = apiKeyAccess;
}

Check warning on line 359 in engine/schema/src/main/java/com/cloud/user/UserVO.java

View check run for this annotation

Codecov / codecov/patch

engine/schema/src/main/java/com/cloud/user/UserVO.java#L357-L359

Added lines #L357 - L359 were not covered by tests

@Override
public Boolean getApiKeyAccess() {
return apiKeyAccess;
}

Check warning on line 364 in engine/schema/src/main/java/com/cloud/user/UserVO.java

View check run for this annotation

Codecov / codecov/patch

engine/schema/src/main/java/com/cloud/user/UserVO.java#L362-L364

Added lines #L362 - L364 were not covered by tests
}
30 changes: 21 additions & 9 deletions engine/schema/src/main/java/com/cloud/user/dao/AccountDaoImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,8 @@

@Component
public class AccountDaoImpl extends GenericDaoBase<AccountVO, Long> implements AccountDao {
private static final String FIND_USER_ACCOUNT_BY_API_KEY = "SELECT u.id, u.username, u.account_id, u.secret_key, u.state, "
+ "a.id, a.account_name, a.type, a.role_id, a.domain_id, a.state " + "FROM `cloud`.`user` u, `cloud`.`account` a "
private static final String FIND_USER_ACCOUNT_BY_API_KEY = "SELECT u.id, u.username, u.account_id, u.secret_key, u.state, u.api_key_access, "
+ "a.id, a.account_name, a.type, a.role_id, a.domain_id, a.state, a.api_key_access " + "FROM `cloud`.`user` u, `cloud`.`account` a "
+ "WHERE u.account_id = a.id AND u.api_key = ? and u.removed IS NULL";

protected final SearchBuilder<AccountVO> AllFieldsSearch;
Expand Down Expand Up @@ -148,13 +148,25 @@
u.setAccountId(rs.getLong(3));
u.setSecretKey(DBEncryptionUtil.decrypt(rs.getString(4)));
u.setState(State.getValueOf(rs.getString(5)));

AccountVO a = new AccountVO(rs.getLong(6));
a.setAccountName(rs.getString(7));
a.setType(Account.Type.getFromValue(rs.getInt(8)));
a.setRoleId(rs.getLong(9));
a.setDomainId(rs.getLong(10));
a.setState(State.getValueOf(rs.getString(11)));
boolean apiKeyAccess = rs.getBoolean(6);

Check warning on line 151 in engine/schema/src/main/java/com/cloud/user/dao/AccountDaoImpl.java

View check run for this annotation

Codecov / codecov/patch

engine/schema/src/main/java/com/cloud/user/dao/AccountDaoImpl.java#L151

Added line #L151 was not covered by tests
if (rs.wasNull()) {
u.setApiKeyAccess(null);

Check warning on line 153 in engine/schema/src/main/java/com/cloud/user/dao/AccountDaoImpl.java

View check run for this annotation

Codecov / codecov/patch

engine/schema/src/main/java/com/cloud/user/dao/AccountDaoImpl.java#L153

Added line #L153 was not covered by tests
} else {
u.setApiKeyAccess(apiKeyAccess);

Check warning on line 155 in engine/schema/src/main/java/com/cloud/user/dao/AccountDaoImpl.java

View check run for this annotation

Codecov / codecov/patch

engine/schema/src/main/java/com/cloud/user/dao/AccountDaoImpl.java#L155

Added line #L155 was not covered by tests
}

AccountVO a = new AccountVO(rs.getLong(7));
a.setAccountName(rs.getString(8));
a.setType(Account.Type.getFromValue(rs.getInt(9)));
a.setRoleId(rs.getLong(10));
a.setDomainId(rs.getLong(11));
a.setState(State.getValueOf(rs.getString(12)));
apiKeyAccess = rs.getBoolean(13);

Check warning on line 164 in engine/schema/src/main/java/com/cloud/user/dao/AccountDaoImpl.java

View check run for this annotation

Codecov / codecov/patch

engine/schema/src/main/java/com/cloud/user/dao/AccountDaoImpl.java#L158-L164

Added lines #L158 - L164 were not covered by tests
if (rs.wasNull()) {
a.setApiKeyAccess(null);

Check warning on line 166 in engine/schema/src/main/java/com/cloud/user/dao/AccountDaoImpl.java

View check run for this annotation

Codecov / codecov/patch

engine/schema/src/main/java/com/cloud/user/dao/AccountDaoImpl.java#L166

Added line #L166 was not covered by tests
} else {
a.setApiKeyAccess(apiKeyAccess);

Check warning on line 168 in engine/schema/src/main/java/com/cloud/user/dao/AccountDaoImpl.java

View check run for this annotation

Codecov / codecov/patch

engine/schema/src/main/java/com/cloud/user/dao/AccountDaoImpl.java#L168

Added line #L168 was not covered by tests
}

userAcctPair = new Pair<User, Account>(u, a);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -425,3 +425,6 @@ INSERT IGNORE INTO `cloud`.`guest_os_hypervisor` (uuid, hypervisor_type, hypervi

CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.vm_instance', 'delete_protection', 'boolean DEFAULT FALSE COMMENT "delete protection for vm" ');
CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.volumes', 'delete_protection', 'boolean DEFAULT FALSE COMMENT "delete protection for volumes" ');

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

targeted for 4.20.1? add these changes in engine/schema/src/main/resources/META-INF/db/schema-42000to42010.sql

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will create the new schema file once 4.20.0 is cut. Keeping this PR in draft until then

CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.user', 'api_key_access', 'boolean DEFAULT NULL COMMENT "is api key access allowed for the user" AFTER `secret_key`');
CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.account', 'api_key_access', 'boolean DEFAULT NULL COMMENT "is api key access allowed for the account" ');
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ select
`account`.`cleanup_needed` AS `cleanup_needed`,
`account`.`network_domain` AS `network_domain` ,
`account`.`default` AS `default`,
`account`.`api_key_access` AS `api_key_access`,
`domain`.`id` AS `domain_id`,
`domain`.`uuid` AS `domain_uuid`,
`domain`.`name` AS `domain_name`,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ select
user.incorrect_login_attempts,
user.source,
user.default,
user.api_key_access,
account.id account_id,
account.uuid account_uuid,
account.account_name account_name,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ public class ConfigKey<T> {
public static final String CATEGORY_ADVANCED = "Advanced";
public static final String CATEGORY_ALERT = "Alert";
public static final String CATEGORY_NETWORK = "Network";
public static final String CATEGORY_SYSTEM = "System";

public enum Scope {
Global, Zone, Cluster, StoragePool, Account, ManagementServer, ImageStore, Domain
Expand Down
33 changes: 33 additions & 0 deletions server/src/main/java/com/cloud/api/ApiServer.java
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,7 @@
import com.cloud.utils.net.NetUtils;
import com.google.gson.reflect.TypeToken;

import static com.cloud.user.AccountManagerImpl.apiKeyAccess;
import static org.apache.cloudstack.user.UserPasswordResetManager.UserPasswordResetEnabled;

@Component
Expand Down Expand Up @@ -874,6 +875,34 @@
}
}

protected boolean verifyApiKeyAccessAllowed(User user, Account account) {
Boolean apiKeyAccessEnabled = user.getApiKeyAccess();
if (apiKeyAccessEnabled != null) {
if (apiKeyAccessEnabled == true) {
return true;
} else {
logger.info("Api-Key access is disabled for the User");
return false;
}
}
apiKeyAccessEnabled = account.getApiKeyAccess();
if (apiKeyAccessEnabled != null) {
if (apiKeyAccessEnabled == true) {
return true;
} else {
logger.info("Api-Key access is disabled for the Account");
return false;
}
}
apiKeyAccessEnabled = apiKeyAccess.valueIn(account.getDomainId());
if (apiKeyAccessEnabled == true) {
return true;
} else {
logger.info("Api-Key access is disabled by the Domain level setting");

Check warning on line 901 in server/src/main/java/com/cloud/api/ApiServer.java

View check run for this annotation

Codecov / codecov/patch

server/src/main/java/com/cloud/api/ApiServer.java#L901

Added line #L901 was not covered by tests
}
return false;

Check warning on line 903 in server/src/main/java/com/cloud/api/ApiServer.java

View check run for this annotation

Codecov / codecov/patch

server/src/main/java/com/cloud/api/ApiServer.java#L903

Added line #L903 was not covered by tests
}

@Override
public boolean verifyRequest(final Map<String, Object[]> requestParameters, final Long userId, InetAddress remoteAddress) throws ServerApiException {
try {
Expand Down Expand Up @@ -990,6 +1019,10 @@
return false;
}

if (!verifyApiKeyAccessAllowed(user, account)) {
return false;

Check warning on line 1023 in server/src/main/java/com/cloud/api/ApiServer.java

View check run for this annotation

Codecov / codecov/patch

server/src/main/java/com/cloud/api/ApiServer.java#L1023

Added line #L1023 was not covered by tests
}

if (!commandAvailable(remoteAddress, commandName, user)) {
return false;
}
Expand Down
Loading
Loading