Add password-bootstrap SSH setup for new connections
This commit is contained in:
@@ -1,11 +1,14 @@
|
||||
package com.sshmanager.service;
|
||||
|
||||
import com.sshmanager.dto.ConnectionCreateRequest;
|
||||
import com.sshmanager.dto.ConnectionDto;
|
||||
import com.sshmanager.entity.Connection;
|
||||
import com.sshmanager.repository.ConnectionRepository;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import com.sshmanager.dto.ConnectionCreateRequest;
|
||||
import com.sshmanager.dto.ConnectionDto;
|
||||
import com.sshmanager.entity.Connection;
|
||||
import com.sshmanager.exception.AccessDeniedException;
|
||||
import com.sshmanager.exception.InvalidOperationException;
|
||||
import com.sshmanager.exception.NotFoundException;
|
||||
import com.sshmanager.repository.ConnectionRepository;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.List;
|
||||
@@ -14,17 +17,20 @@ import java.util.stream.Collectors;
|
||||
@Service
|
||||
public class ConnectionService {
|
||||
|
||||
private final ConnectionRepository connectionRepository;
|
||||
private final EncryptionService encryptionService;
|
||||
private final SshService sshService;
|
||||
|
||||
public ConnectionService(ConnectionRepository connectionRepository,
|
||||
EncryptionService encryptionService,
|
||||
SshService sshService) {
|
||||
this.connectionRepository = connectionRepository;
|
||||
this.encryptionService = encryptionService;
|
||||
this.sshService = sshService;
|
||||
}
|
||||
private final ConnectionRepository connectionRepository;
|
||||
private final EncryptionService encryptionService;
|
||||
private final SshService sshService;
|
||||
private final SshBootstrapService sshBootstrapService;
|
||||
|
||||
public ConnectionService(ConnectionRepository connectionRepository,
|
||||
EncryptionService encryptionService,
|
||||
SshService sshService,
|
||||
SshBootstrapService sshBootstrapService) {
|
||||
this.connectionRepository = connectionRepository;
|
||||
this.encryptionService = encryptionService;
|
||||
this.sshService = sshService;
|
||||
this.sshBootstrapService = sshBootstrapService;
|
||||
}
|
||||
|
||||
public List<ConnectionDto> listByUserId(Long userId) {
|
||||
return connectionRepository.findByUserIdOrderByUpdatedAtDesc(userId).stream()
|
||||
@@ -32,73 +38,69 @@ public class ConnectionService {
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public ConnectionDto getById(Long id, Long userId) {
|
||||
Connection conn = connectionRepository.findById(id).orElseThrow(
|
||||
() -> new RuntimeException("Connection not found: " + id));
|
||||
if (!conn.getUserId().equals(userId)) {
|
||||
throw new RuntimeException("Access denied");
|
||||
}
|
||||
return ConnectionDto.fromEntity(conn);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public ConnectionDto create(ConnectionCreateRequest request, Long userId) {
|
||||
Connection conn = new Connection();
|
||||
conn.setUserId(userId);
|
||||
conn.setName(request.getName());
|
||||
conn.setHost(request.getHost());
|
||||
conn.setPort(request.getPort() != null ? request.getPort() : 22);
|
||||
conn.setUsername(request.getUsername());
|
||||
conn.setAuthType(request.getAuthType() != null ? request.getAuthType() : Connection.AuthType.PASSWORD);
|
||||
public ConnectionDto getById(Long id, Long userId) {
|
||||
Connection conn = connectionRepository.findById(id).orElseThrow(
|
||||
() -> new NotFoundException("Connection not found: " + id));
|
||||
if (!conn.getUserId().equals(userId)) {
|
||||
throw new AccessDeniedException("Access denied");
|
||||
}
|
||||
return ConnectionDto.fromEntity(conn);
|
||||
}
|
||||
|
||||
if (conn.getAuthType() == Connection.AuthType.PASSWORD) {
|
||||
conn.setEncryptedPassword(encryptionService.encrypt(request.getPassword()));
|
||||
conn.setEncryptedPrivateKey(null);
|
||||
@Transactional
|
||||
public ConnectionDto create(ConnectionCreateRequest request, Long userId) {
|
||||
validateCreateRequest(request);
|
||||
|
||||
Connection conn = new Connection();
|
||||
conn.setUserId(userId);
|
||||
conn.setName(trimToNull(request.getName()));
|
||||
conn.setHost(trimToNull(request.getHost()));
|
||||
conn.setPort(request.getPort() != null ? request.getPort() : 22);
|
||||
conn.setUsername(trimToNull(request.getUsername()));
|
||||
|
||||
if (getSetupMode(request) == ConnectionCreateRequest.SetupMode.PASSWORD_BOOTSTRAP) {
|
||||
SshBootstrapService.BootstrapResult bootstrapResult = sshBootstrapService.bootstrapWithPassword(request, userId);
|
||||
conn.setAuthType(Connection.AuthType.PRIVATE_KEY);
|
||||
conn.setEncryptedPassword(null);
|
||||
conn.setEncryptedPrivateKey(encryptionService.encrypt(bootstrapResult.getPrivateKey()));
|
||||
conn.setPassphrase(null);
|
||||
} else {
|
||||
conn.setEncryptedPassword(null);
|
||||
conn.setEncryptedPrivateKey(encryptionService.encrypt(request.getPrivateKey()));
|
||||
conn.setPassphrase(encryptionService.encrypt(request.getPassphrase()));
|
||||
conn.setAuthType(resolveAuthType(request));
|
||||
applyCredentialUpdate(conn, request);
|
||||
}
|
||||
|
||||
conn = connectionRepository.save(conn);
|
||||
return ConnectionDto.fromEntity(conn);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public ConnectionDto update(Long id, ConnectionCreateRequest request, Long userId) {
|
||||
Connection conn = connectionRepository.findById(id).orElseThrow(
|
||||
() -> new RuntimeException("Connection not found: " + id));
|
||||
if (!conn.getUserId().equals(userId)) {
|
||||
throw new RuntimeException("Access denied");
|
||||
}
|
||||
|
||||
if (request.getName() != null) conn.setName(request.getName());
|
||||
if (request.getHost() != null) conn.setHost(request.getHost());
|
||||
if (request.getPort() != null) conn.setPort(request.getPort());
|
||||
if (request.getUsername() != null) conn.setUsername(request.getUsername());
|
||||
|
||||
conn = connectionRepository.save(conn);
|
||||
return ConnectionDto.fromEntity(conn);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public ConnectionDto update(Long id, ConnectionCreateRequest request, Long userId) {
|
||||
Connection conn = connectionRepository.findById(id).orElseThrow(
|
||||
() -> new NotFoundException("Connection not found: " + id));
|
||||
if (!conn.getUserId().equals(userId)) {
|
||||
throw new AccessDeniedException("Access denied");
|
||||
}
|
||||
if (getSetupMode(request) == ConnectionCreateRequest.SetupMode.PASSWORD_BOOTSTRAP) {
|
||||
throw new InvalidOperationException("编辑连接时不支持一键免密配置");
|
||||
}
|
||||
|
||||
if (request.getName() != null) conn.setName(trimToNull(request.getName()));
|
||||
if (request.getHost() != null) conn.setHost(trimToNull(request.getHost()));
|
||||
if (request.getPort() != null) {
|
||||
validatePort(request.getPort());
|
||||
conn.setPort(request.getPort());
|
||||
}
|
||||
if (request.getUsername() != null) conn.setUsername(trimToNull(request.getUsername()));
|
||||
if (request.getAuthType() != null) conn.setAuthType(request.getAuthType());
|
||||
|
||||
if (conn.getAuthType() == Connection.AuthType.PASSWORD) {
|
||||
if (request.getPassword() != null) {
|
||||
conn.setEncryptedPassword(encryptionService.encrypt(request.getPassword()));
|
||||
}
|
||||
conn.setEncryptedPrivateKey(null);
|
||||
conn.setPassphrase(null);
|
||||
} else {
|
||||
if (request.getPrivateKey() != null) {
|
||||
conn.setEncryptedPrivateKey(encryptionService.encrypt(request.getPrivateKey()));
|
||||
}
|
||||
if (request.getPassphrase() != null) {
|
||||
conn.setPassphrase(encryptionService.encrypt(request.getPassphrase()));
|
||||
}
|
||||
conn.setEncryptedPassword(null);
|
||||
}
|
||||
|
||||
conn.setUpdatedAt(Instant.now());
|
||||
conn = connectionRepository.save(conn);
|
||||
return ConnectionDto.fromEntity(conn);
|
||||
}
|
||||
validatePersistedFields(conn);
|
||||
applyCredentialUpdate(conn, request);
|
||||
validateStoredCredentials(conn);
|
||||
|
||||
conn.setUpdatedAt(Instant.now());
|
||||
conn = connectionRepository.save(conn);
|
||||
return ConnectionDto.fromEntity(conn);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void delete(Long id, Long userId) {
|
||||
@@ -107,19 +109,19 @@ public class ConnectionService {
|
||||
return;
|
||||
}
|
||||
if (!conn.getUserId().equals(userId)) {
|
||||
throw new RuntimeException("Access denied");
|
||||
throw new AccessDeniedException("Access denied");
|
||||
}
|
||||
connectionRepository.delete(conn);
|
||||
}
|
||||
|
||||
public Connection getConnectionForSsh(Long id, Long userId) {
|
||||
Connection conn = connectionRepository.findById(id).orElseThrow(
|
||||
() -> new RuntimeException("Connection not found: " + id));
|
||||
if (!conn.getUserId().equals(userId)) {
|
||||
throw new RuntimeException("Access denied");
|
||||
}
|
||||
return conn;
|
||||
}
|
||||
|
||||
public Connection getConnectionForSsh(Long id, Long userId) {
|
||||
Connection conn = connectionRepository.findById(id).orElseThrow(
|
||||
() -> new NotFoundException("Connection not found: " + id));
|
||||
if (!conn.getUserId().equals(userId)) {
|
||||
throw new AccessDeniedException("Access denied");
|
||||
}
|
||||
return conn;
|
||||
}
|
||||
|
||||
public String getDecryptedPassword(Connection conn) {
|
||||
return conn.getEncryptedPassword() != null ?
|
||||
@@ -144,9 +146,115 @@ public class ConnectionService {
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Connection test failed: " + e.getMessage(), e);
|
||||
} finally {
|
||||
if (session != null) {
|
||||
session.disconnect();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (session != null) {
|
||||
session.disconnect();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void validateCreateRequest(ConnectionCreateRequest request) {
|
||||
if (request == null) {
|
||||
throw new InvalidOperationException("连接信息不能为空");
|
||||
}
|
||||
validatePersistedFields(request);
|
||||
if (getSetupMode(request) == ConnectionCreateRequest.SetupMode.PASSWORD_BOOTSTRAP) {
|
||||
requireText(request.getBootstrapPassword(), "启用一键免密配置时必须填写初始登录密码");
|
||||
return;
|
||||
}
|
||||
|
||||
Connection.AuthType authType = resolveAuthType(request);
|
||||
if (authType == Connection.AuthType.PASSWORD) {
|
||||
if (!hasText(request.getPassword())) {
|
||||
throw new InvalidOperationException("请填写密码");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (!hasText(request.getPrivateKey())) {
|
||||
throw new InvalidOperationException("请填写私钥");
|
||||
}
|
||||
}
|
||||
|
||||
private void validatePersistedFields(ConnectionCreateRequest request) {
|
||||
requireText(request.getName(), "请填写名称");
|
||||
requireText(request.getHost(), "请填写主机");
|
||||
requireText(request.getUsername(), "请填写用户名");
|
||||
validatePort(request.getPort() != null ? request.getPort() : 22);
|
||||
}
|
||||
|
||||
private void validatePersistedFields(Connection conn) {
|
||||
requireText(conn.getName(), "请填写名称");
|
||||
requireText(conn.getHost(), "请填写主机");
|
||||
requireText(conn.getUsername(), "请填写用户名");
|
||||
validatePort(conn.getPort() != null ? conn.getPort() : 22);
|
||||
}
|
||||
|
||||
private void applyCredentialUpdate(Connection conn, ConnectionCreateRequest request) {
|
||||
if (conn.getAuthType() == Connection.AuthType.PASSWORD) {
|
||||
String password = trimToNull(request.getPassword());
|
||||
if (password != null) {
|
||||
conn.setEncryptedPassword(encryptionService.encrypt(password));
|
||||
}
|
||||
conn.setEncryptedPrivateKey(null);
|
||||
conn.setPassphrase(null);
|
||||
return;
|
||||
}
|
||||
|
||||
String privateKey = trimToNull(request.getPrivateKey());
|
||||
if (privateKey != null) {
|
||||
conn.setEncryptedPrivateKey(encryptionService.encrypt(privateKey));
|
||||
}
|
||||
if (request.getPassphrase() != null) {
|
||||
conn.setPassphrase(encryptionService.encrypt(trimToNull(request.getPassphrase())));
|
||||
}
|
||||
conn.setEncryptedPassword(null);
|
||||
}
|
||||
|
||||
private void validateStoredCredentials(Connection conn) {
|
||||
if (conn.getAuthType() == Connection.AuthType.PASSWORD) {
|
||||
if (!hasText(conn.getEncryptedPassword())) {
|
||||
throw new InvalidOperationException("请填写密码");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (!hasText(conn.getEncryptedPrivateKey())) {
|
||||
throw new InvalidOperationException("请填写私钥");
|
||||
}
|
||||
}
|
||||
|
||||
private Connection.AuthType resolveAuthType(ConnectionCreateRequest request) {
|
||||
return request.getAuthType() != null ? request.getAuthType() : Connection.AuthType.PASSWORD;
|
||||
}
|
||||
|
||||
private ConnectionCreateRequest.SetupMode getSetupMode(ConnectionCreateRequest request) {
|
||||
if (request == null || request.getSetupMode() == null) {
|
||||
return ConnectionCreateRequest.SetupMode.NONE;
|
||||
}
|
||||
return request.getSetupMode();
|
||||
}
|
||||
|
||||
private void requireText(String value, String message) {
|
||||
if (!hasText(value)) {
|
||||
throw new InvalidOperationException(message);
|
||||
}
|
||||
}
|
||||
|
||||
private void validatePort(Integer port) {
|
||||
if (port == null || port < 1 || port > 65535) {
|
||||
throw new InvalidOperationException("端口号必须在1-65535之间");
|
||||
}
|
||||
}
|
||||
|
||||
private boolean hasText(String value) {
|
||||
return value != null && !value.trim().isEmpty();
|
||||
}
|
||||
|
||||
private String trimToNull(String value) {
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
String trimmed = value.trim();
|
||||
return trimmed.isEmpty() ? null : trimmed;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user