/*
* Copyright 2016-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.glowroot.ui;
import java.security.GeneralSecurityException;
import java.util.List;
import javax.annotation.Nullable;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.base.Optional;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import com.google.common.collect.Ordering;
import io.netty.handler.codec.http.HttpResponseStatus;
import org.immutables.value.Value;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.glowroot.common.config.ImmutableUserConfig;
import org.glowroot.common.config.RoleConfig;
import org.glowroot.common.config.UserConfig;
import org.glowroot.common.repo.ConfigRepository;
import org.glowroot.common.repo.ConfigRepository.CannotDeleteLastUserException;
import org.glowroot.common.repo.ConfigRepository.DuplicateUsernameException;
import org.glowroot.common.repo.ConfigRepository.UserNotFoundException;
import org.glowroot.common.util.ObjectMappers;
import static com.google.common.base.Preconditions.checkNotNull;
import static io.netty.handler.codec.http.HttpResponseStatus.CONFLICT;
@JsonService
class UserConfigJsonService {
private static final Logger logger = LoggerFactory.getLogger(UserConfigJsonService.class);
private static final ObjectMapper mapper = ObjectMappers.create();
private static final Ordering<UserConfig> orderingByName = new Ordering<UserConfig>() {
@Override
public int compare(UserConfig left, UserConfig right) {
return left.username().compareToIgnoreCase(right.username());
}
};
private final ConfigRepository configRepository;
UserConfigJsonService(ConfigRepository configRepository) {
this.configRepository = configRepository;
}
@GET(path = "/backend/admin/users", permission = "admin:view:user")
String getUserConfig(@BindRequest UserConfigRequest request) throws Exception {
Optional<String> username = request.username();
if (username.isPresent()) {
return getUserConfigInternal(username.get());
} else {
List<UserConfigDto> responses = Lists.newArrayList();
List<UserConfig> userConfigs = configRepository.getUserConfigs();
userConfigs = orderingByName.immutableSortedCopy(userConfigs);
for (UserConfig userConfig : userConfigs) {
responses.add(UserConfigDto.create(userConfig));
}
return mapper.writeValueAsString(responses);
}
}
@GET(path = "/backend/admin/all-role-names", permission = "admin:edit:user")
String getAllRoleNames() throws Exception {
return mapper.writeValueAsString(ImmutableAllRolesResponse.builder()
.allRoles(getAllRoleNamesInternal())
.ldapAvailable(!configRepository.getLdapConfig().url().isEmpty())
.build());
}
@POST(path = "/backend/admin/users/add", permission = "admin:edit:user")
String addUser(@BindRequest UserConfigDto userConfigDto) throws Exception {
UserConfig userConfig = userConfigDto.convert(null);
try {
configRepository.insertUserConfig(userConfig);
} catch (DuplicateUsernameException e) {
// log exception at debug level
logger.debug(e.getMessage(), e);
throw new JsonServiceException(CONFLICT, "username");
}
return getUserConfigInternal(userConfig.username());
}
@POST(path = "/backend/admin/users/update", permission = "admin:edit:user")
String updateUser(@BindRequest UserConfigDto userConfigDto) throws Exception {
UserConfig existingUserConfig = configRepository.getUserConfig(userConfigDto.username());
if (existingUserConfig == null) {
throw new UserNotFoundException();
}
UserConfig userConfig = userConfigDto.convert(existingUserConfig);
String version = userConfigDto.version().get();
try {
configRepository.updateUserConfig(userConfig, version);
} catch (DuplicateUsernameException e) {
// log exception at debug level
logger.debug(e.getMessage(), e);
throw new JsonServiceException(CONFLICT, "username");
}
return getUserConfigInternal(userConfig.username());
}
@POST(path = "/backend/admin/users/remove", permission = "admin:edit:user")
String removeUser(@BindRequest UserConfigRequest request) throws Exception {
try {
configRepository.deleteUserConfig(request.username().get());
} catch (CannotDeleteLastUserException e) {
logger.debug(e.getMessage(), e);
return "{\"errorCannotDeleteLastUser\":true}";
}
return "{}";
}
private String getUserConfigInternal(String username) throws Exception {
UserConfig userConfig = configRepository.getUserConfig(username);
if (userConfig == null) {
throw new JsonServiceException(HttpResponseStatus.NOT_FOUND);
}
return mapper.writeValueAsString(ImmutableUserConfigResponse.builder()
.config(UserConfigDto.create(userConfig))
.allRoles(getAllRoleNamesInternal())
.ldapAvailable(!configRepository.getLdapConfig().url().isEmpty())
.build());
}
private List<String> getAllRoleNamesInternal() throws Exception {
List<String> roleNames = Lists.newArrayList();
for (RoleConfig roleConfig : configRepository.getRoleConfigs()) {
roleNames.add(roleConfig.name());
}
return roleNames;
}
@Value.Immutable
interface UserConfigRequest {
Optional<String> username();
}
@Value.Immutable
interface UserConfigResponse {
UserConfigDto config();
ImmutableList<String> allRoles();
boolean ldapAvailable();
}
@Value.Immutable
interface AllRolesResponse {
ImmutableList<String> allRoles();
boolean ldapAvailable();
}
@Value.Immutable
abstract static class UserConfigDto {
abstract String username();
abstract boolean ldap();
// only used in request
@Value.Default
String newPassword() {
return "";
}
abstract ImmutableList<String> roles();
abstract Optional<String> version(); // absent for insert operations
private UserConfig convert(@Nullable UserConfig existingUserConfig)
throws GeneralSecurityException {
String passwordHash;
String newPassword = newPassword();
if (ldap() || username().equalsIgnoreCase("anonymous")) {
passwordHash = "";
} else if (newPassword.isEmpty()) {
passwordHash = checkNotNull(existingUserConfig).passwordHash();
} else {
passwordHash = PasswordHash.createHash(newPassword);
}
return ImmutableUserConfig.builder()
.username(username())
.ldap(ldap())
.passwordHash(passwordHash)
.roles(roles())
.build();
}
private static UserConfigDto create(UserConfig userConfig) {
return ImmutableUserConfigDto.builder()
.username(userConfig.username())
.ldap(userConfig.ldap())
.roles(userConfig.roles())
.version(userConfig.version())
.build();
}
}
}