diff --git a/java/code/src/com/suse/manager/hub/HubManager.java b/java/code/src/com/suse/manager/hub/HubManager.java index 92dea373fa19..7b412f3b31bc 100644 --- a/java/code/src/com/suse/manager/hub/HubManager.java +++ b/java/code/src/com/suse/manager/hub/HubManager.java @@ -12,13 +12,16 @@ package com.suse.manager.hub; import com.redhat.rhn.common.conf.ConfigDefaults; +import com.redhat.rhn.common.security.PermissionException; import com.redhat.rhn.domain.credentials.CredentialsFactory; import com.redhat.rhn.domain.credentials.HubSCCCredentials; import com.redhat.rhn.domain.credentials.SCCCredentials; import com.redhat.rhn.domain.role.RoleFactory; +import com.redhat.rhn.domain.user.User; import com.redhat.rhn.manager.setup.MirrorCredentialsManager; import com.suse.manager.model.hub.HubFactory; +import com.suse.manager.model.hub.IssAccessToken; import com.suse.manager.model.hub.IssHub; import com.suse.manager.model.hub.IssPeripheral; import com.suse.manager.model.hub.IssRole; @@ -72,100 +75,76 @@ public HubManager(HubFactory hubFactoryIn, IssClientFactory clientFactoryIn, /** * Create a new access token for the given FQDN and store it in the database + * @param user the user performing the operation * @param fqdn the FQDN of the peripheral/hub * @return the serialized form of the token * @throws TokenBuildingException when an error occurs during generation * @throws TokenParsingException when the generated token cannot be parsed */ - public String issueAccessToken(String fqdn) throws TokenBuildingException, TokenParsingException { - Token token = new IssTokenBuilder(fqdn) - .usingServerSecret() - .build(); + public String issueAccessToken(User user, String fqdn) throws TokenBuildingException, TokenParsingException { + ensureSatAdmin(user); - hubFactory.saveToken(fqdn, token.getSerializedForm(), TokenType.ISSUED, token.getExpirationTime()); + Token token = createAndSaveToken(fqdn); return token.getSerializedForm(); } /** - * Returns the ISS of the specified role, if present - * @param role the role of the server - * @param serverFqdn the FQDN - * @return an {@link IssHub} or {@link IssPeripheral} depending on the specified role, null if the FQDN is unknown + * Stores in the database the access token of the given FQDN + * @param user the user performing the operation + * @param fqdn the FQDN of the peripheral/hub that generated this token + * @param token the token + * @throws TokenParsingException when it's not possible to process the token */ - public IssServer findServer(IssRole role, String serverFqdn) { - return switch (role) { - case HUB -> hubFactory.lookupIssHubByFqdn(serverFqdn).orElse(null); - case PERIPHERAL -> hubFactory.lookupIssPeripheralByFqdn(serverFqdn).orElse(null); - }; + public void storeAccessToken(User user, String fqdn, String token) throws TokenParsingException { + ensureSatAdmin(user); + + parseAndSaveToken(fqdn, token); } /** - * Save a server - * @param server the server to save + * Stores in the database the access token of the given FQDN + * @param accessToken the access token granting access and identifying the caller + * @param tokenToStore the token + * @throws TokenParsingException when it's not possible to process the token */ - public void saveServer(IssServer server) { - if (server instanceof IssHub hub) { - hubFactory.save(hub); - } - else if (server instanceof IssPeripheral peripheral) { - hubFactory.save(peripheral); - } - else { - throw new IllegalArgumentException("Unknown server class " + server.getClass().getName()); - } + public void storeAccessToken(IssAccessToken accessToken, String tokenToStore) throws TokenParsingException { + ensureValidToken(accessToken); + + parseAndSaveToken(accessToken.getServerFqdn(), tokenToStore); } /** - * Save the given remote server as hub or peripheral depending on the specified role + * Returns the ISS of the specified role, if present + * @param accessToken the access token granting access and identifying the the caller * @param role the role of the server - * @param serverFqdn the fqdn of the server - * @param rootCA the root certificate, if needed - * @return the persisted remote server + * @return an {@link IssHub} or {@link IssPeripheral} depending on the specified role, null if the FQDN is unknown */ - public IssServer saveNewServer(IssRole role, String serverFqdn, String rootCA) { + public IssServer findServer(IssAccessToken accessToken, IssRole role) { + ensureValidToken(accessToken); + return switch (role) { - case HUB -> { - IssHub hub = new IssHub(serverFqdn, rootCA); - hubFactory.save(hub); - yield hub; - } - case PERIPHERAL -> { - IssPeripheral peripheral = new IssPeripheral(serverFqdn, rootCA); - hubFactory.save(peripheral); - yield peripheral; - } + case HUB -> hubFactory.lookupIssHubByFqdn(accessToken.getServerFqdn()).orElse(null); + case PERIPHERAL -> hubFactory.lookupIssPeripheralByFqdn(accessToken.getServerFqdn()).orElse(null); }; } /** - * Stores in the database the access token of the given FQDN - * @param fqdn the FQDN of the peripheral/hub that generated this token - * @param token the token - * @throws TokenParsingException when it's not possible to process the token + * Save the given remote server as hub or peripheral depending on the specified role + * @param accessToken the access token granting access and identifying the caller + * @param role the role of the server + * @param rootCA the root certificate, if needed + * @return the persisted remote server */ - public void storeAccessToken(String fqdn, String token) throws TokenParsingException { - // We do not need to verify the signature as this token is for accessing another system. - // That system will take care of ensuring its authenticity - Token parsedToken = new TokenParser() - .skippingSignatureVerification() - .verifyingExpiration() - .verifyingNotBefore() - .parse(token); - - // Verify if this token is for this system - String targetFqdn = parsedToken.getClaim("fqdn", String.class); - String hostname = ConfigDefaults.get().getHostname(); + public IssServer saveNewServer(IssAccessToken accessToken, IssRole role, String rootCA) { + ensureValidToken(accessToken); - if (targetFqdn == null || !targetFqdn.equals(hostname)) { - throw new TokenParsingException("FQDN do not match. Expected %s got %s".formatted(hostname, targetFqdn)); - } - - hubFactory.saveToken(fqdn, token, TokenType.CONSUMED, parsedToken.getExpirationTime()); + return createServer(role, accessToken.getServerFqdn(), rootCA); } /** * Register a remote server with the specified role for ISS * + * @param user the user performing the operation * @param remoteServer the peripheral server FQDN * @param role the ISS role, either hub or peripheral * @param username the username of a {@link RoleFactory#SAT_ADMIN} of the remote server @@ -177,8 +156,10 @@ public void storeAccessToken(String fqdn, String token) throws TokenParsingExcep * @throws TokenBuildingException if an error occurs while generating the token for the server * @throws IOException when connecting to the server fails */ - public void register(String remoteServer, IssRole role, String username, String password, String rootCA) + public void register(User user, String remoteServer, IssRole role, String username, String password, String rootCA) throws CertificateException, TokenBuildingException, IOException, TokenParsingException { + ensureSatAdmin(user); + // Verify this server is not already registered as hub or peripheral ensureServerNotRegistered(remoteServer); @@ -194,6 +175,7 @@ public void register(String remoteServer, IssRole role, String username, String /** * Register a remote server with the specified role for ISS * + * @param user the user performing the operation * @param remoteServer the peripheral server FQDN * @param role the ISS role, either hub or peripheral * @param remoteToken the token used to connect to the peripheral server @@ -204,8 +186,10 @@ public void register(String remoteServer, IssRole role, String username, String * @throws TokenBuildingException if an error occurs while generating the token for the peripheral server * @throws IOException when connecting to the peripheral server fails */ - public void register(String remoteServer, IssRole role, String remoteToken, String rootCA) + public void register(User user, String remoteServer, IssRole role, String remoteToken, String rootCA) throws CertificateException, TokenBuildingException, IOException, TokenParsingException { + ensureSatAdmin(user); + // Verify this server is not already registered as hub or peripheral ensureServerNotRegistered(remoteServer); @@ -214,52 +198,39 @@ public void register(String remoteServer, IssRole role, String remoteToken, Stri /** * Generate SCC credentials for the specified peripheral - * @param peripheral the id of the peripheral server + * @param accessToken the access token granting access and identifying the caller * @return the generated {@link HubSCCCredentials} */ - public HubSCCCredentials generateSCCCredentials(IssPeripheral peripheral) { - String username = "peripheral-%06d".formatted(peripheral.getId()); - String password = RandomStringUtils.random(24, 0, 0, true, true, null, new SecureRandom()); + public HubSCCCredentials generateSCCCredentials(IssAccessToken accessToken) { + ensureValidToken(accessToken); - var hubSCCCredentials = CredentialsFactory.createHubSCCCredentials(username, password, peripheral.getFqdn()); - CredentialsFactory.storeCredentials(hubSCCCredentials); + IssPeripheral peripheral = hubFactory.lookupIssPeripheralByFqdn(accessToken.getServerFqdn()) + .orElseThrow(() -> new IllegalArgumentException("Access token does not identify a peripheral server")); - peripheral.setMirrorCredentials(hubSCCCredentials); - saveServer(peripheral); - - return hubSCCCredentials; + return generateCredentials(peripheral); } /** * Store the given SCC credentials into the credentials database - * @param hub the FQDN of the hub of this credentials + * @param accessToken the access token granting access and identifying the caller * @param username the username * @param password the password * @return the stored {@link SCCCredentials} */ - public SCCCredentials storeSCCCredentials(IssHub hub, String username, String password) { - // Delete any existing SCC Credentials - CredentialsFactory.listSCCCredentials() - .forEach(creds -> mirrorCredentialsManager.deleteMirrorCredentials(creds.getId(), null)); - - // Create the new credentials for the hub - SCCCredentials credentials = CredentialsFactory.createSCCCredentials(username, password); + public SCCCredentials storeSCCCredentials(IssAccessToken accessToken, String username, String password) { + ensureValidToken(accessToken); - // TODO Ensure the path is correct once the SCC Endoint is implemented - credentials.setUrl("https://" + hub.getFqdn()); - CredentialsFactory.storeCredentials(credentials); - - hub.setMirrorCredentials(credentials); - saveServer(hub); + IssHub hub = hubFactory.lookupIssHubByFqdn(accessToken.getServerFqdn()) + .orElseThrow(() -> new IllegalArgumentException("Access token does not identify a hub server")); - return credentials; + return saveCredentials(hub, username, password); } private void registerWithToken(String remoteServer, IssRole role, String rootCA, String remoteToken) throws TokenParsingException, CertificateException, TokenBuildingException, IOException { - storeAccessToken(remoteServer, remoteToken); + parseAndSaveToken(remoteServer, remoteToken); - IssServer registeredServer = saveNewServer(role, remoteServer, rootCA); + IssServer registeredServer = createServer(role, remoteServer, rootCA); registerToRemote(registeredServer, remoteToken, rootCA); } @@ -272,21 +243,21 @@ private void registerToRemote(IssServer remoteServer, String remoteToken, String // Register this server on the remote with the opposite role IssRole localRoleForRemote = remoteServer.getRole() == IssRole.HUB ? IssRole.PERIPHERAL : IssRole.HUB; // Issue a token for granting access to the remote server - String localAccessToken = issueAccessToken(remoteServer.getFqdn()); + Token localAccessToken = createAndSaveToken(remoteServer.getFqdn()); // Send the local trusted root, if we needed a different certificate to connect String localRootCA = rootCA != null ? CertificateUtils.loadLocalTrustedRoot() : null; - internalApi.register(localRoleForRemote, localAccessToken, localRootCA); + internalApi.register(localRoleForRemote, localAccessToken.getSerializedForm(), localRootCA); if (remoteServer instanceof IssPeripheral peripheral) { // if the remote server is a peripheral, generate the scc credentials for it - HubSCCCredentials credentials = generateSCCCredentials(peripheral); + HubSCCCredentials credentials = generateCredentials(peripheral); internalApi.storeCredentials(credentials.getUsername(), credentials.getPassword()); } else if (remoteServer instanceof IssHub hub) { // If remote server is a hub, ask for the credentials SCCCredentialsJson credentialsJson = internalApi.generateCredentials(); - storeSCCCredentials(hub, credentialsJson.getUsername(), credentialsJson.getPassword()); + saveCredentials(hub, credentialsJson.getUsername(), credentialsJson.getPassword()); } else { throw new IllegalStateException("Unknown IssServer class " + remoteServer.getClass()); @@ -304,4 +275,108 @@ private void ensureServerNotRegistered(String peripheralServer) { throw new IllegalStateException("Server " + peripheralServer + " is already registered as peripheral"); } } + + private HubSCCCredentials generateCredentials(IssPeripheral peripheral) { + String username = "peripheral-%06d".formatted(peripheral.getId()); + String password = RandomStringUtils.random(24, 0, 0, true, true, null, new SecureRandom()); + + var hubSCCCredentials = CredentialsFactory.createHubSCCCredentials(username, password, peripheral.getFqdn()); + CredentialsFactory.storeCredentials(hubSCCCredentials); + + peripheral.setMirrorCredentials(hubSCCCredentials); + saveServer(peripheral); + + return hubSCCCredentials; + } + + private SCCCredentials saveCredentials(IssHub hub, String username, String password) { + // Delete any existing SCC Credentials + CredentialsFactory.listSCCCredentials() + .forEach(creds -> mirrorCredentialsManager.deleteMirrorCredentials(creds.getId(), null)); + + // Create the new credentials for the hub + SCCCredentials credentials = CredentialsFactory.createSCCCredentials(username, password); + + // TODO Ensure the path is correct once the SCC Endoint is implemented + credentials.setUrl("https://" + hub.getFqdn()); + CredentialsFactory.storeCredentials(credentials); + + hub.setMirrorCredentials(credentials); + saveServer(hub); + + return credentials; + } + + private Token createAndSaveToken(String fqdn) throws TokenBuildingException, TokenParsingException { + Token token = new IssTokenBuilder(fqdn) + .usingServerSecret() + .build(); + + hubFactory.saveToken(fqdn, token.getSerializedForm(), TokenType.ISSUED, token.getExpirationTime()); + return token; + } + + private void parseAndSaveToken(String fqdn, String token) throws TokenParsingException { + // We do not need to verify the signature as this token is for accessing another system. + // That system will take care of ensuring its authenticity + Token parsedToken = new TokenParser() + .skippingSignatureVerification() + .verifyingExpiration() + .verifyingNotBefore() + .parse(token); + + // Verify if this token is for this system + String targetFqdn = parsedToken.getClaim("fqdn", String.class); + String hostname = ConfigDefaults.get().getHostname(); + + if (targetFqdn == null || !targetFqdn.equals(hostname)) { + throw new TokenParsingException("FQDN do not match. Expected %s got %s".formatted(hostname, targetFqdn)); + } + + hubFactory.saveToken(fqdn, token, TokenType.CONSUMED, parsedToken.getExpirationTime()); + } + + private IssServer createServer(IssRole role, String serverFqdn, String rootCA) { + return switch (role) { + case HUB -> { + IssHub hub = new IssHub(serverFqdn, rootCA); + hubFactory.save(hub); + yield hub; + } + case PERIPHERAL -> { + IssPeripheral peripheral = new IssPeripheral(serverFqdn, rootCA); + hubFactory.save(peripheral); + yield peripheral; + } + }; + } + + private void saveServer(IssServer server) { + if (server instanceof IssHub hub) { + hubFactory.save(hub); + } + else if (server instanceof IssPeripheral peripheral) { + hubFactory.save(peripheral); + } + else { + throw new IllegalArgumentException("Unknown server class " + server.getClass().getName()); + } + } + + private static void ensureSatAdmin(User user) { + if (!user.hasRole(RoleFactory.SAT_ADMIN)) { + throw new PermissionException(RoleFactory.SAT_ADMIN); + } + } + + private static void ensureValidToken(IssAccessToken accessToken) { + // Must be a valid not expired ISSUED token + // Actual verification of the JWT signature is not done: since the token was stored in the database we can + // consider it already verified + if (accessToken == null || accessToken.getType() != TokenType.ISSUED || + !accessToken.isValid() || accessToken.isExpired()) { + throw new PermissionException("You do not have permissions to perform this action. Invalid token provided"); + } + } + } diff --git a/java/code/src/com/suse/manager/hub/IssSparkHelper.java b/java/code/src/com/suse/manager/hub/IssSparkHelper.java index 6ab0ac88bdd5..8526fcadcdb5 100644 --- a/java/code/src/com/suse/manager/hub/IssSparkHelper.java +++ b/java/code/src/com/suse/manager/hub/IssSparkHelper.java @@ -22,7 +22,6 @@ import com.suse.manager.model.hub.IssRole; import com.suse.manager.webui.utils.gson.ResultJson; import com.suse.manager.webui.utils.token.Token; -import com.suse.manager.webui.utils.token.TokenParser; import com.suse.manager.webui.utils.token.TokenParsingException; import com.google.gson.reflect.TypeToken; @@ -62,21 +61,26 @@ public static Route usingTokenAuthentication(RouteWithIssToken route) { } String serializedToken = authorization.substring(7); - Token token = parseToken(serializedToken); IssAccessToken issuedToken = HUB_FACTORY.lookupIssuedToken(serializedToken); - if (issuedToken == null || token == null || issuedToken.isExpired() || !issuedToken.isValid()) { + if (issuedToken == null || issuedToken.isExpired() || !issuedToken.isValid()) { response.status(HttpServletResponse.SC_UNAUTHORIZED); return json(response, ResultJson.error("Invalid token provided"), new TypeToken<>() { }); } try { + Token token = issuedToken.getParsedToken(); String fqdn = token.getClaim("fqdn", String.class); - return route.handle(request, response, token, fqdn); + if (fqdn == null || !fqdn.equals(issuedToken.getServerFqdn())) { + response.status(HttpServletResponse.SC_UNAUTHORIZED); + return json(response, ResultJson.error("Invalid token provided"), new TypeToken<>() { }); + } + + return route.handle(request, response, issuedToken); } catch (TokenParsingException ex) { - response.status(HttpServletResponse.SC_BAD_REQUEST); - return json(response, ResultJson.error("Invalid token provided: missing claim"), new TypeToken<>() { }); + response.status(HttpServletResponse.SC_UNAUTHORIZED); + return json(response, ResultJson.error("Invalid token provided"), new TypeToken<>() { }); } finally { var authenticationService = AuthenticationServiceFactory.getInstance().getAuthenticationService(); @@ -113,7 +117,8 @@ public static RouteWithIssToken allowingOnlyUnregistered(RouteWithIssToken route } private static RouteWithIssToken allowingOnly(List allowedRoles, RouteWithIssToken route) { - return (request, response, token, fqdn) -> { + return (request, response, issAccessToken) -> { + String fqdn = issAccessToken.getServerFqdn(); Optional issHub = HUB_FACTORY.lookupIssHubByFqdn(fqdn); Optional issPeripheral = HUB_FACTORY.lookupIssPeripheralByFqdn(fqdn); @@ -124,7 +129,7 @@ private static RouteWithIssToken allowingOnly(List allowedRoles, RouteW } - return route.handle(request, response, token, fqdn); + return route.handle(request, response, issAccessToken); }; } @@ -146,18 +151,4 @@ private static boolean isRouteForbidden(List allowedRoles, boolean isHu return false; } - - private static Token parseToken(String serializedToken) { - try { - return new TokenParser() - .usingServerSecret() - .verifyingNotBefore() - .verifyingExpiration() - .parse(serializedToken); - } - catch (TokenParsingException ex) { - LOGGER.debug("Unable to parse token {}. Request will be rejected.", serializedToken, ex); - return null; - } - } } diff --git a/java/code/src/com/suse/manager/hub/RouteWithIssToken.java b/java/code/src/com/suse/manager/hub/RouteWithIssToken.java index 3a37e9a9011e..000dc542d44a 100644 --- a/java/code/src/com/suse/manager/hub/RouteWithIssToken.java +++ b/java/code/src/com/suse/manager/hub/RouteWithIssToken.java @@ -10,7 +10,7 @@ */ package com.suse.manager.hub; -import com.suse.manager.webui.utils.token.Token; +import com.suse.manager.model.hub.IssAccessToken; import spark.Request; import spark.Response; @@ -26,9 +26,8 @@ public interface RouteWithIssToken { * * @param request the request object * @param response the response object - * @param token the token with this request - * @param serverFqdn the FQDN of the remote server + * @param token the access token granting access and identifying the caller * @return the content to be set in the response */ - Object handle(Request request, Response response, Token token, String serverFqdn); + Object handle(Request request, Response response, IssAccessToken token); } diff --git a/java/code/src/com/suse/manager/hub/SyncController.java b/java/code/src/com/suse/manager/hub/SyncController.java index 5b40ed7601cb..643d78824c97 100644 --- a/java/code/src/com/suse/manager/hub/SyncController.java +++ b/java/code/src/com/suse/manager/hub/SyncController.java @@ -23,13 +23,11 @@ import com.redhat.rhn.domain.credentials.HubSCCCredentials; -import com.suse.manager.model.hub.IssHub; -import com.suse.manager.model.hub.IssPeripheral; +import com.suse.manager.model.hub.IssAccessToken; import com.suse.manager.model.hub.IssRole; import com.suse.manager.model.hub.RegisterJson; import com.suse.manager.model.hub.SCCCredentialsJson; import com.suse.manager.webui.controllers.ECMAScriptDateAdapter; -import com.suse.manager.webui.utils.token.Token; import com.suse.manager.webui.utils.token.TokenParsingException; import com.google.gson.Gson; @@ -82,52 +80,53 @@ public void initRoutes() { } // Basic ping to check if the system is up - private String ping(Request request, Response response, Token token, String fqdn) { - return message(response, "Pinged from %s".formatted(fqdn)); + private String ping(Request request, Response response, IssAccessToken token) { + return message(response, "Pinged from %s".formatted(token.getServerFqdn())); } - private String register(Request request, Response response, Token token, String fqdn) { + private String register(Request request, Response response, IssAccessToken token) { RegisterJson registerRequest = GSON.fromJson(request.body(), RegisterJson.class); String tokenToStore = registerRequest.getToken(); if (StringUtils.isEmpty(tokenToStore)) { - LOGGER.error("No token received in the request for server {}", fqdn); + LOGGER.error("No token received in the request for server {}", token.getServerFqdn()); return badRequest(response, "Required token is missing"); } try { - hubManager.storeAccessToken(fqdn, tokenToStore); - hubManager.saveNewServer(registerRequest.getRole(), fqdn, registerRequest.getRootCA()); + hubManager.storeAccessToken(token, tokenToStore); + hubManager.saveNewServer(token, registerRequest.getRole(), registerRequest.getRootCA()); return success(response); } catch (TokenParsingException ex) { - LOGGER.error("Unable to parse the received token for server {}", fqdn); + LOGGER.error("Unable to parse the received token for server {}", token.getServerFqdn()); return badRequest(response, "The specified token is not parseable"); } } - private String generateCredentials(Request request, Response response, Token token, String fqdn) { - IssPeripheral peripheral = (IssPeripheral) hubManager.findServer(IssRole.PERIPHERAL, fqdn); - if (peripheral == null) { - // This should never happen, fqdn guaranteed be a hub after calling allowingOnlyHub() on route init. + private String generateCredentials(Request request, Response response, IssAccessToken token) { + try { + HubSCCCredentials credentials = hubManager.generateSCCCredentials(token); + return success(response, new SCCCredentialsJson(credentials.getUsername(), credentials.getPassword())); + } + catch (IllegalArgumentException ex) { + // This should never happen, fqdn guaranteed be a peripheral after calling allowingOnlyPeripheral() when + // initializing the route. return badRequest(response, "Specified FQDN is not a known peripheral"); } - - HubSCCCredentials credentials = hubManager.generateSCCCredentials(peripheral); - return success(response, new SCCCredentialsJson(credentials.getUsername(), credentials.getPassword())); } - private String storeCredentials(Request request, Response response, Token token, String fqdn) { + private String storeCredentials(Request request, Response response, IssAccessToken token) { SCCCredentialsJson storeRequest = GSON.fromJson(request.body(), SCCCredentialsJson.class); - IssHub hub = (IssHub) hubManager.findServer(IssRole.HUB, fqdn); - if (hub == null) { + try { + hubManager.storeSCCCredentials(token, storeRequest.getUsername(), storeRequest.getPassword()); + return success(response); + } + catch (IllegalArgumentException ex) { // This should never happen, fqdn guaranteed be a hub after calling allowingOnlyHub() on route init. return badRequest(response, "Specified FQDN is not a known hub"); } - - hubManager.storeSCCCredentials(hub, storeRequest.getUsername(), storeRequest.getPassword()); - return success(response); } } diff --git a/java/code/src/com/suse/manager/hub/test/HubManagerTest.java b/java/code/src/com/suse/manager/hub/test/HubManagerTest.java index bd62bf52ec10..b030a1cdc683 100644 --- a/java/code/src/com/suse/manager/hub/test/HubManagerTest.java +++ b/java/code/src/com/suse/manager/hub/test/HubManagerTest.java @@ -18,14 +18,20 @@ import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; import com.redhat.rhn.common.conf.Config; import com.redhat.rhn.common.conf.ConfigDefaults; +import com.redhat.rhn.common.security.PermissionException; import com.redhat.rhn.domain.credentials.CredentialsFactory; import com.redhat.rhn.domain.credentials.HubSCCCredentials; import com.redhat.rhn.domain.credentials.SCCCredentials; +import com.redhat.rhn.domain.role.RoleFactory; +import com.redhat.rhn.domain.user.User; +import com.redhat.rhn.domain.user.UserFactory; import com.redhat.rhn.manager.setup.MirrorCredentialsManager; import com.redhat.rhn.testing.JMockBaseTestCaseWithUser; +import com.redhat.rhn.testing.UserTestUtils; import com.suse.manager.hub.HubManager; import com.suse.manager.hub.IssClientFactory; @@ -53,11 +59,16 @@ import org.junit.jupiter.api.Test; import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; import java.security.cert.CertificateException; import java.time.Instant; import java.time.temporal.ChronoUnit; +import java.util.Arrays; import java.util.List; import java.util.Optional; +import java.util.stream.Collectors; public class HubManagerTest extends JMockBaseTestCaseWithUser { @@ -65,6 +76,8 @@ public class HubManagerTest extends JMockBaseTestCaseWithUser { private static final String REMOTE_SERVER_FQDN = "remote-server.unit-test.local"; + private User satAdmin; + private HubFactory hubFactory; private HubManager hubManager; @@ -80,6 +93,10 @@ public class HubManagerTest extends JMockBaseTestCaseWithUser { public void setUp() throws Exception { super.setUp(); + satAdmin = UserTestUtils.createUser("satUser", user.getOrg().getId()); + satAdmin.addPermanentRole(RoleFactory.SAT_ADMIN); + UserFactory.save(satAdmin); + setImposteriser(ByteBuddyClassImposteriser.INSTANCE); // Setting a fake hostname for the token validation @@ -105,10 +122,83 @@ public void tearDown() throws Exception { Config.get().setString("server.secret_key", originalServerSecret); } + /** + * This test ensure the implementation does not have any public method that are not requiring either a user + * or a token to enforce authorization. + */ + @Test + public void implementationDoesNotHaveAnyPublicUnprotectedMethods() { + List publicClassMethods = Arrays.stream(hubManager.getClass().getMethods()) + // Exclude methods declared by Object + .filter(method -> !Object.class.equals(method.getDeclaringClass())) + // Exclude non-public methods + .filter(method -> Modifier.isPublic(method.getModifiers())) + .toList(); + + // First parameter of the method must be either a User or an IssAccessToken + List> allowedFirstParameters = List.of(User.class, IssAccessToken.class); + + // Extract all methods that don't have a valid first parameter + List unprotectedMethods = publicClassMethods.stream() + .filter(method -> !allowedFirstParameters.contains(method.getParameterTypes()[0])) + .map(Method::toGenericString) + .toList(); + + assertTrue(unprotectedMethods.isEmpty(), + "These methods seem to not enforce authorization, as the first parameter is not any of %s:%n\t%s" + .formatted(allowedFirstParameters, String.join("\n\t", unprotectedMethods)) + ); + + for (Method method : publicClassMethods) { + // Generate default values for all parameters except the first one. Since the method should fail due to + // validation the other parameters should be irrelevant + List params = Arrays.stream(method.getParameterTypes()) + .skip(1) + .map(paramType -> getDefaultValue(paramType)) + .collect(Collectors.toList()); + + Class firstParameterClass = method.getParameterTypes()[0]; + String expectedMessage = "You do not have permissions to perform this action. "; + + IssAccessToken expiredToken = new IssAccessToken( + TokenType.ISSUED, + "dummy", + "my.remote.server", + Instant.now().minus(30, ChronoUnit.DAYS) + ); + + if (User.class.equals(firstParameterClass)) { + params.add(0, user); + expectedMessage += "You need to have at least a SUSE Manager Administrator role to perform this action"; + } + else if (IssAccessToken.class.equals(firstParameterClass)) { + params.add(0, expiredToken); + expectedMessage += "Invalid token provided"; + } + else { + fail("Unable to identify a value for the first parameter type " + firstParameterClass); + // appeasing the compiler: this line will never be executed. + return; + } + + // Try to invoke the method. It should fail with a permission exception. Since we are using reflection + // it will be wrapped into an InvocationTargetException + InvocationTargetException wrapperException = assertThrows(InvocationTargetException.class, + () -> method.invoke(hubManager, params.toArray())); + + // Verify the actual exception is correct + assertInstanceOf(PermissionException.class, wrapperException.getCause(), + "Method " + method.toGenericString() + " is throwing an unexpected Exception"); + assertEquals(expectedMessage, wrapperException.getCause().getMessage(), + "Method " + method.toGenericString() + " is throwing an unexpected exception message"); + + } + } + @Test public void canIssueANewToken() throws Exception { Instant expectedExpiration = Instant.now().truncatedTo(ChronoUnit.SECONDS).plus(525_600L, ChronoUnit.MINUTES); - String token = hubManager.issueAccessToken(REMOTE_SERVER_FQDN); + String token = hubManager.issueAccessToken(satAdmin, REMOTE_SERVER_FQDN); // Ensure we get a token assertNotNull(token); @@ -153,7 +243,7 @@ public void canStoreThirdPartyToken() throws Exception { wifQ.6M9MOQvsiFr4EeyeAo36_2jUbV1Ju9ceCD\ kI-mykFms"""; - hubManager.storeAccessToken(REMOTE_SERVER_FQDN, token); + hubManager.storeAccessToken(satAdmin, REMOTE_SERVER_FQDN, token); // Ensure the token is correctly stored in the database IssAccessToken issAccessToken = hubFactory.lookupAccessTokenFor(REMOTE_SERVER_FQDN); @@ -185,7 +275,7 @@ public void rejectsTokenIfFqdnIsNotMatching() { var exception = assertThrows( TokenParsingException.class, - () -> hubManager.storeAccessToken(REMOTE_SERVER_FQDN, token) + () -> hubManager.storeAccessToken(satAdmin, REMOTE_SERVER_FQDN, token) ); assertEquals( @@ -216,7 +306,7 @@ public void rejectsTokenIfAlreadyExpired() { TokenParsingException exception = assertThrows( TokenParsingException.class, - () -> hubManager.storeAccessToken("external-server.dev.local", token) + () -> hubManager.storeAccessToken(satAdmin, "external-server.dev.local", token) ); InvalidJwtException cause = assertInstanceOf(InvalidJwtException.class, exception.getCause()); @@ -226,13 +316,13 @@ public void rejectsTokenIfAlreadyExpired() { @Test public void canSaveHubAndPeripheralServers() { - hubManager.saveNewServer(IssRole.HUB, "dummy.hub.fqdn", "dummy-certificate-data"); + hubManager.saveNewServer(getValidToken("dummy.hub.fqdn"), IssRole.HUB, "dummy-certificate-data"); Optional issHub = hubFactory.lookupIssHubByFqdn("dummy.hub.fqdn"); assertTrue(issHub.isPresent()); assertEquals("dummy-certificate-data", issHub.get().getRootCa()); - hubManager.saveNewServer(IssRole.PERIPHERAL, "dummy.peripheral.fqdn", null); + hubManager.saveNewServer(getValidToken("dummy.peripheral.fqdn"), IssRole.PERIPHERAL, null); Optional issPeripheral = hubFactory.lookupIssPeripheralByFqdn("dummy.peripheral.fqdn"); assertTrue(issPeripheral.isPresent()); assertNull(issPeripheral.get().getRootCa()); @@ -243,15 +333,15 @@ public void canRetrieveHubAndPeripheralServers() { hubFactory.save(new IssHub("dummy.hub.fqdn", null)); hubFactory.save(new IssPeripheral("dummy.peripheral.fqdn", null)); - IssServer result = hubManager.findServer(IssRole.PERIPHERAL, "dummy.peripheral.fqdn"); + IssServer result = hubManager.findServer(getValidToken("dummy.peripheral.fqdn"), IssRole.PERIPHERAL); assertNotNull(result); assertInstanceOf(IssPeripheral.class, result); - result = hubManager.findServer(IssRole.HUB, "dummy.hub.fqdn"); + result = hubManager.findServer(getValidToken("dummy.hub.fqdn"), IssRole.HUB); assertNotNull(result); assertInstanceOf(IssHub.class, result); - result = hubManager.findServer(IssRole.HUB, "dummy.unknown.fqdn"); + result = hubManager.findServer(getValidToken("dummy.unknown.fqdn"), IssRole.HUB); assertNull(result); } @@ -260,7 +350,7 @@ public void canUpdateServer() { hubFactory.save(new IssHub("dummy.hub.fqdn", null)); hubFactory.save(new IssPeripheral("dummy.peripheral.fqdn", null)); - IssHub hub = (IssHub) hubManager.findServer(IssRole.HUB, "dummy.hub.fqdn"); + IssHub hub = (IssHub) hubManager.findServer(getValidToken("dummy.hub.fqdn"), IssRole.HUB); assertNull(hub.getRootCa()); assertNull(hub.getMirrorCredentials()); @@ -282,14 +372,15 @@ public void canUpdateServer() { public void canGenerateSCCCredentials() { String peripheralFqdn = "dummy.peripheral.fqdn"; - var peripheral = (IssPeripheral) hubManager.saveNewServer(IssRole.PERIPHERAL, peripheralFqdn, null); + IssAccessToken peripheralToken = getValidToken(peripheralFqdn); + var peripheral = (IssPeripheral) hubManager.saveNewServer(peripheralToken, IssRole.PERIPHERAL, null); // Ensure no credentials exists assertEquals(0, CredentialsFactory.listCredentialsByType(HubSCCCredentials.class).stream() .filter(creds -> peripheralFqdn.equals(creds.getPeripheralUrl())) .count()); - HubSCCCredentials hubSCCCredentials = hubManager.generateSCCCredentials(peripheral); + HubSCCCredentials hubSCCCredentials = hubManager.generateSCCCredentials(peripheralToken); assertEquals("peripheral-%06d".formatted(peripheral.getId()), hubSCCCredentials.getUsername()); assertNotNull(hubSCCCredentials.getPassword()); assertEquals(peripheralFqdn, hubSCCCredentials.getPeripheralUrl()); @@ -297,15 +388,15 @@ public void canGenerateSCCCredentials() { @Test public void canStoreSCCCredentials() { - String hubFqdn = "dummy.hub.fqdn"; - var hub = (IssHub) hubManager.saveNewServer(IssRole.HUB, hubFqdn, null); + IssAccessToken hubToken = getValidToken("dummy.hub.fqdn"); + var hub = (IssHub) hubManager.saveNewServer(hubToken, IssRole.HUB, null); // Ensure no credentials exists assertEquals(0, CredentialsFactory.listSCCCredentials().stream() .filter(creds -> "https://dummy.hub.fqdn".equals(creds.getUrl())) .count()); - SCCCredentials sccCredentials = hubManager.storeSCCCredentials(hub, "dummy-username", "dummy-password"); + SCCCredentials sccCredentials = hubManager.storeSCCCredentials(hubToken, "dummy-username", "dummy-password"); assertEquals("dummy-username", sccCredentials.getUsername()); assertEquals("dummy-password", sccCredentials.getPassword()); assertEquals("https://dummy.hub.fqdn", sccCredentials.getUrl()); @@ -349,7 +440,7 @@ public void canRegisterPeripheralWithUserNameAndPassword() }}); // Register the remote server as PERIPHERAL for this local server - hubManager.register(REMOTE_SERVER_FQDN, IssRole.PERIPHERAL, "admin", "admin", null); + hubManager.register(satAdmin, REMOTE_SERVER_FQDN, IssRole.PERIPHERAL, "admin", "admin", null); // Verify the remote server is saved as peripheral Optional issPeripheral = hubFactory.lookupIssPeripheralByFqdn(REMOTE_SERVER_FQDN); @@ -403,7 +494,7 @@ public void canRegisterHubWithUserNameAndPassword() }}); // Register the remote server as HUB for this local server - hubManager.register(REMOTE_SERVER_FQDN, IssRole.HUB, "admin", "admin", null); + hubManager.register(satAdmin, REMOTE_SERVER_FQDN, IssRole.HUB, "admin", "admin", null); // Verify the remote server is saved as hub Optional issHub = hubFactory.lookupIssHubByFqdn(REMOTE_SERVER_FQDN); @@ -429,4 +520,37 @@ public void canRegisterHubWithUserNameAndPassword() sccCredentials.get(0).getUrl() ); } + + private static IssAccessToken getValidToken(String fdqn) { + return new IssAccessToken(TokenType.ISSUED, "dummy-token", fdqn); + } + + private static Object getDefaultValue(Class clazz) { + if (int.class.equals(clazz)) { + return 0; + } + else if (boolean.class.equals(clazz)) { + return false; + } + else if (double.class.equals(clazz)) { + return 0.0; + } + else if (float.class.equals(clazz)) { + return 0.0f; + } + else if (long.class.equals(clazz)) { + return 0L; + } + else if (short.class.equals(clazz)) { + return (short) 0; + } + else if (byte.class.equals(clazz)) { + return (byte) 0; + } + else if (char.class.equals(clazz)) { + return '\u0000'; + } + + return null; + } } diff --git a/java/code/src/com/suse/manager/model/hub/IssAccessToken.java b/java/code/src/com/suse/manager/model/hub/IssAccessToken.java index 4db3a64a2314..e43ab56ae688 100644 --- a/java/code/src/com/suse/manager/model/hub/IssAccessToken.java +++ b/java/code/src/com/suse/manager/model/hub/IssAccessToken.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024 SUSE LLC + * Copyright (c) 2024--2025 SUSE LLC * * This software is licensed to you under the GNU General Public License, * version 2 (GPLv2). There is NO WARRANTY for this software, express or @@ -11,6 +11,10 @@ package com.suse.manager.model.hub; +import com.suse.manager.webui.utils.token.Token; +import com.suse.manager.webui.utils.token.TokenParser; +import com.suse.manager.webui.utils.token.TokenParsingException; + import org.hibernate.annotations.Type; import java.time.Instant; @@ -158,6 +162,21 @@ public boolean isExpired() { return new Date().after(expirationDate); } + /** + * Retrieve the parsed token associated with this entity + * @return the parsed token + * @throws TokenParsingException if parsing the serialized value fails + */ + @Transient + public Token getParsedToken() throws TokenParsingException { + return new TokenParser() + .usingServerSecret() + .verifyingNotBefore() + .verifyingExpiration() + .parse(token); + + } + @Override public boolean equals(Object o) { if (this == o) { diff --git a/java/code/src/com/suse/manager/xmlrpc/iss/IssHandler.java b/java/code/src/com/suse/manager/xmlrpc/iss/IssHandler.java index 1d3c79b5013f..c5754f2492e2 100644 --- a/java/code/src/com/suse/manager/xmlrpc/iss/IssHandler.java +++ b/java/code/src/com/suse/manager/xmlrpc/iss/IssHandler.java @@ -82,7 +82,7 @@ public String generateAccessToken(User loggedInUser, String fqdn) { } try { - return hubManager.issueAccessToken(fqdn); + return hubManager.issueAccessToken(loggedInUser, fqdn); } catch (TokenException ex) { LOGGER.error("Unable to issue a token for {}", fqdn, ex); @@ -119,7 +119,7 @@ public int storeAccessToken(User loggedInUser, String fqdn, String token) { } try { - hubManager.storeAccessToken(fqdn, token); + hubManager.storeAccessToken(loggedInUser, fqdn, token); } catch (TokenParsingException ex) { LOGGER.error("Unable to process the token from {}", fqdn, ex); @@ -193,7 +193,7 @@ public int register(User loggedInUser, String fqdn, String role, String username } try { - hubManager.register(fqdn, remoteRole, username, password, rootCA); + hubManager.register(loggedInUser, fqdn, remoteRole, username, password, rootCA); } catch (CertificateException ex) { LOGGER.error("Unable to load the provided certificate", ex); @@ -267,7 +267,7 @@ public int registerWithToken(User loggedInUser, String fqdn, String role, String } try { - hubManager.register(fqdn, remoteRole, token, rootCA); + hubManager.register(loggedInUser, fqdn, remoteRole, token, rootCA); } catch (CertificateException ex) { LOGGER.error("Unable to load the provided certificate", ex); diff --git a/java/code/src/com/suse/manager/xmlrpc/iss/test/IssHandlerTest.java b/java/code/src/com/suse/manager/xmlrpc/iss/test/IssHandlerTest.java index ac0bf753fe46..62a4817b0a4a 100644 --- a/java/code/src/com/suse/manager/xmlrpc/iss/test/IssHandlerTest.java +++ b/java/code/src/com/suse/manager/xmlrpc/iss/test/IssHandlerTest.java @@ -16,9 +16,11 @@ import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertThrows; +import com.redhat.rhn.domain.iss.IssRole; import com.redhat.rhn.frontend.xmlrpc.InvalidTokenException; import com.redhat.rhn.frontend.xmlrpc.PermissionCheckFailureException; import com.redhat.rhn.frontend.xmlrpc.TokenCreationException; +import com.redhat.rhn.frontend.xmlrpc.TokenExchangeFailedException; import com.redhat.rhn.frontend.xmlrpc.test.BaseHandlerTestCase; import com.suse.manager.hub.HubManager; @@ -26,6 +28,7 @@ import com.suse.manager.webui.utils.token.TokenBuildingException; import com.suse.manager.webui.utils.token.TokenException; import com.suse.manager.webui.utils.token.TokenParsingException; +import com.suse.manager.xmlrpc.InvalidCertificateException; import com.suse.manager.xmlrpc.iss.IssHandler; import org.jmock.Expectations; @@ -37,6 +40,9 @@ import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.RegisterExtension; +import java.io.IOException; +import java.security.cert.CertificateException; + @ExtendWith(JUnit5Mockery.class) public class IssHandlerTest extends BaseHandlerTestCase { @@ -47,7 +53,7 @@ public class IssHandlerTest extends BaseHandlerTestCase { private HubManager hubManagerMock; - private IssHandler ISSHandler; + private IssHandler issHandler; @BeforeEach public void setup() { @@ -55,71 +61,199 @@ public void setup() { context.setImposteriser((ByteBuddyClassImposteriser.INSTANCE)); hubManagerMock = context.mock(HubManager.class); - ISSHandler = new IssHandler(hubManagerMock); + issHandler = new IssHandler(hubManagerMock); } @Test public void ensureOnlySatAdminCanAccessToTokenGeneration() throws Exception { Expectations expectations = new Expectations(); - expectations.allowing(hubManagerMock).issueAccessToken("uyuni-server.dev.local"); + expectations.allowing(hubManagerMock).issueAccessToken(satAdmin, "uyuni-server.dev.local"); expectations.will(returnValue("dummy-token")); context.checking(expectations); assertThrows( PermissionCheckFailureException.class, - () -> ISSHandler.generateAccessToken(regular, "uyuni-server.dev.local") + () -> issHandler.generateAccessToken(regular, "uyuni-server.dev.local") ); assertThrows( PermissionCheckFailureException.class, - () -> ISSHandler.generateAccessToken(admin, "uyuni-server.dev.local") + () -> issHandler.generateAccessToken(admin, "uyuni-server.dev.local") ); assertDoesNotThrow( - () -> ISSHandler.generateAccessToken(satAdmin, "uyuni-server.dev.local") + () -> issHandler.generateAccessToken(satAdmin, "uyuni-server.dev.local") ); } @Test public void throwsCorrectExceptionWhenIssuingFails() throws TokenException { Expectations expectations = new Expectations(); - expectations.allowing(hubManagerMock).issueAccessToken("uyuni-server.dev.local"); + expectations.allowing(hubManagerMock).issueAccessToken(satAdmin, "uyuni-server.dev.local"); expectations.will(throwException(new TokenBuildingException("unexpected error"))); context.checking(expectations); assertThrows(TokenCreationException.class, - () -> ISSHandler.generateAccessToken(satAdmin, "uyuni-server.dev.local")); + () -> issHandler.generateAccessToken(satAdmin, "uyuni-server.dev.local")); } @Test public void ensureOnlySatAdminCanAccessToTokenStorage() throws Exception { Expectations expectations = new Expectations(); - expectations.allowing(hubManagerMock).storeAccessToken("uyuni-server.dev.local", "dummy-token"); + expectations.allowing(hubManagerMock).storeAccessToken(satAdmin, "uyuni-server.dev.local", "dummy-token"); context.checking(expectations); assertThrows( PermissionCheckFailureException.class, - () -> ISSHandler.storeAccessToken(regular, "uyuni-server.dev.local", "dummy-token") + () -> issHandler.storeAccessToken(regular, "uyuni-server.dev.local", "dummy-token") ); assertThrows( PermissionCheckFailureException.class, - () -> ISSHandler.storeAccessToken(admin, "uyuni-server.dev.local", "dummy-token") + () -> issHandler.storeAccessToken(admin, "uyuni-server.dev.local", "dummy-token") ); assertDoesNotThrow( - () -> ISSHandler.storeAccessToken(satAdmin, "uyuni-server.dev.local", "dummy-token") + () -> issHandler.storeAccessToken(satAdmin, "uyuni-server.dev.local", "dummy-token") ); } @Test public void throwsCorrectExceptionWhenStoringFails() throws TokenParsingException { Expectations expectations = new Expectations(); - expectations.allowing(hubManagerMock).storeAccessToken("uyuni-server.dev.local", "dummy-token"); + expectations.allowing(hubManagerMock).storeAccessToken(satAdmin, "uyuni-server.dev.local", "dummy-token"); expectations.will(throwException(new TokenParsingException("Cannot parse"))); context.checking(expectations); assertThrows(InvalidTokenException.class, - () -> ISSHandler.storeAccessToken(satAdmin, "uyuni-server.dev.local", "dummy-token")); + () -> issHandler.storeAccessToken(satAdmin, "uyuni-server.dev.local", "dummy-token")); + } + + @Test + public void ensureOnlySatAdminCanRegister() throws Exception { + Expectations expectations = new Expectations(); + expectations.allowing(hubManagerMock) + .register(satAdmin, "remote-server.dev.local", IssRole.PERIPHERAL, "admin", "admin", null); + context.checking(expectations); + + assertThrows( + PermissionCheckFailureException.class, + () -> issHandler.register(regular, "remote-server.dev.local", "PERIPHERAL", "admin", "admin", null) + ); + + assertThrows( + PermissionCheckFailureException.class, + () -> issHandler.register(admin, "remote-server.dev.local", "PERIPHERAL", "admin", "admin", null) + ); + + assertDoesNotThrow( + () -> issHandler.register(satAdmin, "remote-server.dev.local", "PERIPHERAL", "admin", "admin", null) + ); + } + + @Test + public void throwsCorrectExceptionsWhenRegisteringFails() throws Exception { + Expectations expectations = new Expectations(); + expectations.allowing(hubManagerMock) + .register(satAdmin, "fails-certificate.dev.local", IssRole.PERIPHERAL, "admin", "admin", "dummy"); + expectations.will(throwException(new CertificateException("Unable to parse"))); + + expectations.allowing(hubManagerMock) + .register(satAdmin, "fails-parsing.dev.local", IssRole.PERIPHERAL, "admin", "admin", "dummy"); + expectations.will(throwException(new TokenParsingException("Unable to parse"))); + + expectations.allowing(hubManagerMock) + .register(satAdmin, "fails-building.dev.local", IssRole.PERIPHERAL, "admin", "admin", "dummy"); + expectations.will(throwException(new TokenBuildingException("Unable to build"))); + + expectations.allowing(hubManagerMock) + .register(satAdmin, "fails-connecting.dev.local", IssRole.PERIPHERAL, "admin", "admin", "dummy"); + expectations.will(throwException(new IOException("Unable to connect"))); + + context.checking(expectations); + + assertThrows( + InvalidCertificateException.class, + () -> issHandler.register(satAdmin, "fails-certificate.dev.local", "PERIPHERAL", "admin", "admin", "dummy") + ); + + assertThrows( + TokenExchangeFailedException.class, + () -> issHandler.register(satAdmin, "fails-parsing.dev.local", "PERIPHERAL", "admin", "admin", "dummy") + ); + + assertThrows( + TokenExchangeFailedException.class, + () -> issHandler.register(satAdmin, "fails-building.dev.local", "PERIPHERAL", "admin", "admin", "dummy") + ); + + assertThrows( + TokenExchangeFailedException.class, + () -> issHandler.register(satAdmin, "fails-connecting.dev.local", "PERIPHERAL", "admin", "admin", "dummy") + ); + } + + @Test + public void ensureOnlySatAdminCanRegisterWithToken() throws Exception { + Expectations expectations = new Expectations(); + expectations.allowing(hubManagerMock) + .register(satAdmin, "remote-server.dev.local", IssRole.PERIPHERAL, "token", null); + context.checking(expectations); + + assertThrows( + PermissionCheckFailureException.class, + () -> issHandler.registerWithToken(regular, "remote-server.dev.local", "PERIPHERAL", "token", null) + ); + + assertThrows( + PermissionCheckFailureException.class, + () -> issHandler.registerWithToken(admin, "remote-server.dev.local", "PERIPHERAL", "token", null) + ); + + assertDoesNotThrow( + () -> issHandler.registerWithToken(satAdmin, "remote-server.dev.local", "PERIPHERAL", "token", null) + ); + } + + @Test + public void throwsCorrectExceptionsWhenRegisteringWithTokenFails() throws Exception { + Expectations expectations = new Expectations(); + expectations.allowing(hubManagerMock) + .register(satAdmin, "fails-certificate.dev.local", IssRole.PERIPHERAL, "token", "dummy"); + expectations.will(throwException(new CertificateException("Unable to parse"))); + + expectations.allowing(hubManagerMock) + .register(satAdmin, "fails-parsing.dev.local", IssRole.PERIPHERAL, "token", "dummy"); + expectations.will(throwException(new TokenParsingException("Unable to parse"))); + + expectations.allowing(hubManagerMock) + .register(satAdmin, "fails-building.dev.local", IssRole.PERIPHERAL, "token", "dummy"); + expectations.will(throwException(new TokenBuildingException("Unable to build"))); + + expectations.allowing(hubManagerMock) + .register(satAdmin, "fails-connecting.dev.local", IssRole.PERIPHERAL, "token", "dummy"); + expectations.will(throwException(new IOException("Unable to connect"))); + + context.checking(expectations); + + assertThrows( + InvalidCertificateException.class, + () -> issHandler.registerWithToken(satAdmin, "fails-certificate.dev.local", "PERIPHERAL", "token", "dummy") + ); + + assertThrows( + TokenExchangeFailedException.class, + () -> issHandler.registerWithToken(satAdmin, "fails-parsing.dev.local", "PERIPHERAL", "token", "dummy") + ); + + assertThrows( + TokenExchangeFailedException.class, + () -> issHandler.registerWithToken(satAdmin, "fails-building.dev.local", "PERIPHERAL", "token", "dummy") + ); + + assertThrows( + TokenExchangeFailedException.class, + () -> issHandler.registerWithToken(satAdmin, "fails-connecting.dev.local", "PERIPHERAL", "token", "dummy") + ); } }