Control login.

This commit is contained in:
Michael Lipp 2025-02-27 14:46:13 +01:00
parent 59bf4937ef
commit b4cb3b8694
9 changed files with 349 additions and 29 deletions

View file

@ -33,6 +33,10 @@ import org.jdrupes.vmoperator.runner.qemu.commands.QmpSetPasswordExpiry;
import org.jdrupes.vmoperator.runner.qemu.events.ConfigureQemu;
import org.jdrupes.vmoperator.runner.qemu.events.MonitorCommand;
import org.jdrupes.vmoperator.runner.qemu.events.RunnerStateChange.RunState;
import org.jdrupes.vmoperator.runner.qemu.events.VmopAgentConnected;
import org.jdrupes.vmoperator.runner.qemu.events.VmopAgentLogIn;
import org.jdrupes.vmoperator.runner.qemu.events.VmopAgentLogOut;
import org.jdrupes.vmoperator.runner.qemu.events.VmopAgentLoggedIn;
import org.jgrapes.core.Channel;
import org.jgrapes.core.Component;
import org.jgrapes.core.annotation.Handler;
@ -48,6 +52,8 @@ public class DisplayController extends Component {
private String currentPassword;
private String protocol;
private final Path configDir;
private boolean vmopAgentConnected;
private boolean userLoginRequested;
/**
* Instantiates a new Display controller.
@ -63,6 +69,16 @@ public class DisplayController extends Component {
fire(new WatchFile(configDir.resolve(DATA_DISPLAY_PASSWORD)));
}
/**
* On vmop agent connected.
*
* @param event the event
*/
@Handler
public void onVmopAgentConnected(VmopAgentConnected event) {
vmopAgentConnected = true;
}
/**
* On configure qemu.
*
@ -75,7 +91,7 @@ public class DisplayController extends Component {
}
protocol
= event.configuration().vm.display.spice != null ? "spice" : null;
updatePassword();
configureAccess(false);
}
/**
@ -87,12 +103,56 @@ public class DisplayController extends Component {
@SuppressWarnings("PMD.EmptyCatchBlock")
public void onFileChanged(FileChanged event) {
if (event.path().equals(configDir.resolve(DATA_DISPLAY_PASSWORD))) {
updatePassword();
configureAccess(true);
}
}
@SuppressWarnings("PMD.DataflowAnomalyAnalysis")
private void updatePassword() {
private void configureAccess(boolean passwordChange) {
var userLoginConfigured = readFromFile(DATA_DISPLAY_LOGIN)
.map(Boolean::parseBoolean).orElse(false);
if (!userLoginConfigured) {
// Check if it was configured before and there may thus be an
// active auto login
if (userLoginRequested && vmopAgentConnected) {
// Make sure to log out
fire(new VmopAgentLogOut());
}
userLoginRequested = false;
configurePassword();
return;
}
// With user login configured, we have to make sure that the
// user is logged in before we set the password and thus allow
// access to the display.
userLoginRequested = true;
if (!vmopAgentConnected) {
if (passwordChange) {
logger.warning(() -> "Request for user login before "
+ "VM operator agent has connected");
}
return;
}
var user = readFromFile(DATA_DISPLAY_USER);
if (user.isEmpty()) {
logger.warning(() -> "Login requested, but no user configured");
}
fire(new VmopAgentLogIn(user.get()).setAssociated(this, user.get()));
}
/**
* On vmop agent logged in.
*
* @param event the event
*/
@Handler
public void onVmopAgentLoggedIn(VmopAgentLoggedIn event) {
configurePassword();
}
private void configurePassword() {
if (protocol == null) {
return;
}

View file

@ -88,10 +88,12 @@ public class GuestAgentClient extends AgentConnector {
* On guest agent command.
*
* @param event the event
* @throws IOException
*/
@Handler
@SuppressWarnings("PMD.AvoidSynchronizedStatement")
public void onGuestAgentCommand(GuestAgentCommand event) {
public void onGuestAgentCommand(GuestAgentCommand event)
throws IOException {
if (qemuChannel() == null) {
return;
}
@ -106,15 +108,10 @@ public class GuestAgentClient extends AgentConnector {
return;
}
synchronized (executing) {
writer().ifPresent(writer -> {
try {
if (writer().isPresent()) {
executing.add(command);
writer.append(asText).append('\n').flush();
} catch (IOException e) {
// Cannot happen, but...
logger.log(Level.WARNING, e, e::getMessage);
sendCommand(asText);
}
});
}
}
}

View file

@ -47,8 +47,8 @@ import org.jgrapes.util.events.WatchFile;
/**
* A component that handles the communication with QEMU over a socket.
*
* If the log level for this class is set to fine, the messages
* exchanged on the socket are logged.
* Derived classes should log the messages exchanged on the socket
* if the log level is set to fine.
*/
public abstract class QemuConnector extends Component {
@ -174,6 +174,22 @@ public abstract class QemuConnector extends Component {
return qemuChannel().flatMap(c -> c.associated(Writer.class));
}
/**
* Send the given command to QEMU. A newline is appended to the
* command automatically.
*
* @param command the command
* @return true, if successful
* @throws IOException Signals that an I/O exception has occurred.
*/
protected boolean sendCommand(String command) throws IOException {
if (writer().isEmpty()) {
return false;
}
writer().get().append(command).append('\n').flush();
return true;
}
/**
* Called when the connector has been connected to the socket.
*/

View file

@ -155,11 +155,12 @@ public class QemuMonitor extends QemuConnector {
* On monitor command.
*
* @param event the event
* @throws IOException
*/
@Handler
@SuppressWarnings({ "PMD.AvoidLiteralsInIfCondition",
"PMD.AvoidSynchronizedStatement" })
public void onExecQmpCommand(MonitorCommand event) {
public void onExecQmpCommand(MonitorCommand event) throws IOException {
var command = event.command();
logger.fine(() -> "monitor(out): " + command.toString());
String asText;
@ -171,15 +172,10 @@ public class QemuMonitor extends QemuConnector {
return;
}
synchronized (executing) {
writer().ifPresent(writer -> {
try {
if (writer().isPresent()) {
executing.add(command);
writer.append(asText).append('\n').flush();
} catch (IOException e) {
// Cannot happen, but...
logger.log(Level.WARNING, e, e::getMessage);
sendCommand(asText);
}
});
}
}

View file

@ -19,8 +19,16 @@
package org.jdrupes.vmoperator.runner.qemu;
import java.io.IOException;
import java.util.Deque;
import java.util.concurrent.ConcurrentLinkedDeque;
import org.jdrupes.vmoperator.runner.qemu.events.VmopAgentConnected;
import org.jdrupes.vmoperator.runner.qemu.events.VmopAgentLogIn;
import org.jdrupes.vmoperator.runner.qemu.events.VmopAgentLogOut;
import org.jdrupes.vmoperator.runner.qemu.events.VmopAgentLoggedIn;
import org.jdrupes.vmoperator.runner.qemu.events.VmopAgentLoggedOut;
import org.jgrapes.core.Channel;
import org.jgrapes.core.Event;
import org.jgrapes.core.annotation.Handler;
/**
* A component that handles the communication over the vmop agent
@ -31,6 +39,8 @@ import org.jgrapes.core.Channel;
*/
public class VmopAgentClient extends AgentConnector {
private final Deque<Event<?>> executing = new ConcurrentLinkedDeque<>();
/**
* Instantiates a new VM operator agent client.
*
@ -41,11 +51,82 @@ public class VmopAgentClient extends AgentConnector {
super(componentChannel);
}
@Override
protected void processInput(String line) throws IOException {
if (line.startsWith("220 ")) {
rep().fire(new VmopAgentConnected());
/**
* On vmop agent login.
*
* @param event the event
* @throws IOException Signals that an I/O exception has occurred.
*/
@Handler
public void onVmopAgentLogIn(VmopAgentLogIn event) throws IOException {
logger.fine(() -> "vmop agent(out): login " + event.user());
if (writer().isPresent()) {
executing.add(event);
sendCommand("login " + event.user());
}
}
/**
* On vmop agent logout.
*
* @param event the event
* @throws IOException Signals that an I/O exception has occurred.
*/
@Handler
public void onVmopAgentLogout(VmopAgentLogOut event) throws IOException {
logger.fine(() -> "vmop agent(out): logout");
if (writer().isPresent()) {
executing.add(event);
sendCommand("logout");
}
}
@Override
@SuppressWarnings({ "PMD.UnnecessaryReturn",
"PMD.AvoidLiteralsInIfCondition" })
protected void processInput(String line) throws IOException {
logger.fine(() -> "vmop agent(in): " + line);
// Check validity
if (line.isEmpty() || !Character.isDigit(line.charAt(0))) {
logger.warning(() -> "Illegal response: " + line);
return;
}
// Check positive responses
if (line.startsWith("220 ")) {
rep().fire(new VmopAgentConnected());
return;
}
if (line.startsWith("201 ")) {
Event<?> cmd = executing.pop();
if (cmd instanceof VmopAgentLogIn login) {
rep().fire(new VmopAgentLoggedIn(login));
} else {
logger.severe(() -> "Response " + line
+ " does not match executing command " + cmd);
}
return;
}
if (line.startsWith("202 ")) {
Event<?> cmd = executing.pop();
if (cmd instanceof VmopAgentLogOut logout) {
rep().fire(new VmopAgentLoggedOut(logout));
} else {
logger.severe(() -> "Response " + line
+ "does not match executing command " + cmd);
}
return;
}
// Ignore unhandled continuations
if (line.charAt(0) == '1') {
return;
}
// Error
logger.warning(() -> "Error response: " + line);
executing.pop();
}
}

View file

@ -0,0 +1,45 @@
/*
* VM-Operator
* Copyright (C) 2025 Michael N. Lipp
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.jdrupes.vmoperator.runner.qemu.events;
import org.jgrapes.core.Event;
/**
* Sends the login command to the VM operator agent.
*/
public class VmopAgentLogIn extends Event<Void> {
private final String user;
/**
* Instantiates a new vmop agent logout.
*/
public VmopAgentLogIn(String user) {
this.user = user;
}
/**
* Returns the user.
*
* @return the user
*/
public String user() {
return user;
}
}

View file

@ -0,0 +1,27 @@
/*
* VM-Operator
* Copyright (C) 2025 Michael N. Lipp
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.jdrupes.vmoperator.runner.qemu.events;
import org.jgrapes.core.Event;
/**
* Sends the logout command to the VM operator agent.
*/
public class VmopAgentLogOut extends Event<Void> {
}

View file

@ -0,0 +1,49 @@
/*
* VM-Operator
* Copyright (C) 2025 Michael N. Lipp
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.jdrupes.vmoperator.runner.qemu.events;
import org.jgrapes.core.Event;
/**
* Signals that the logout command has been processes by the
* VM operator agent.
*/
public class VmopAgentLoggedIn extends Event<Void> {
private final VmopAgentLogIn triggering;
/**
* Instantiates a new vmop agent logged in.
*
* @param triggeringEvent the triggering event
*/
public VmopAgentLoggedIn(VmopAgentLogIn triggeringEvent) {
this.triggering = triggeringEvent;
}
/**
* Gets the triggering event.
*
* @return the triggering
*/
public VmopAgentLogIn triggering() {
return triggering;
}
}

View file

@ -0,0 +1,49 @@
/*
* VM-Operator
* Copyright (C) 2025 Michael N. Lipp
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.jdrupes.vmoperator.runner.qemu.events;
import org.jgrapes.core.Event;
/**
* Signals that the logout command has been processes by the
* VM operator agent.
*/
public class VmopAgentLoggedOut extends Event<Void> {
private final VmopAgentLogOut triggering;
/**
* Instantiates a new vmop agent logged out.
*
* @param triggeringEvent the triggering event
*/
public VmopAgentLoggedOut(VmopAgentLogOut triggeringEvent) {
this.triggering = triggeringEvent;
}
/**
* Gets the triggering event.
*
* @return the triggering
*/
public VmopAgentLogOut triggering() {
return triggering;
}
}